2 votes

amcharts stacked barchart - mise en évidence de la valeur (changement d'alpha) en réponse à un événement

J'ai un diagramme à barres construit avec amcharts . J'ai construit le graphique à barres empilées en suivant cet exemple .

J'essaie de modifier l'alpha (ou la couleur) d'une boîte si je passe au-dessus d'un autre élément de ma page (par exemple un tableau). Par exemple, je voudrais changer l'alpha (ou la couleur) de la boîte pour (2003, Europe) si un utilisateur survole la cellule correspondante du tableau quelque part sur ma page.

J'ai placé mon graphique dans un composant angulaire 6 mais je suppose que cela n'a pas vraiment d'importance tant que je capture correctement les événements de changement d'entrée.


EDIT 1.

J'ai encore du mal à naviguer dans les objets. J'ai construit un diagramme à barres et il semble correct.

  createSeries(field, name) {
    var series = this.chart.series.push(new am4charts.ColumnSeries());
    series.name = name;
    series.dataFields.valueY = field;
    series.dataFields.categoryX = "country";
    series.sequencedInterpolation = true;
    series.columns.template.fillOpacity = 0.5

    // Make it stacked
    series.stacked = true;

    // Configure columns
    series.columns.template.width = am4core.percent(60);
    series.columns.template.tooltipText = "[bold]{name}[/]\n[font-size:14px]{categoryX}: {valueY}";
    console.log(series)
    return series
  }

J'ai ensuite des séries de quatre colonnes, une pour chaque champ de mes données. Je peux granuler les séries par champ.

  grabBar(country, field) {
    // If you don't have access to the chart code in your angular scope,
    // use am4core global.
    // no reason to loop through ALL series/columns, just whipped this up quick
    let foundBar: am4charts.XYSeries | null;
    this.chart.series.each(series => {
      if (series.dataFields.categoryY == field) {
        console.log("yay")
        series.dataItems.values
      }
    });
    return foundBar;

mais je n'arrive pas à trouver un moyen d'accéder à l'élément qui correspond à un pays et à un champ spécifique. Ce que je vois, ce sont quatre éléments, un pour chaque champ, ColumnSeries qui n'ont pas de columns propriété. Mon site am4core.registry.baseSprites[0] n'a pas series là-dedans.

Voici mon code complet :

import { Component, OnInit, NgZone, Input } from '@angular/core';
import * as am4core from "@amcharts/amcharts4/core";
import * as am4charts from "@amcharts/amcharts4/charts";
import am4themes_animated from "@amcharts/amcharts4/themes/animated";
import { CountryItinerary, Team } from "../model/classes"

am4core.useTheme(am4themes_animated);

@Component({
  selector: 'barchart-distance',
  templateUrl: './barchart-distance.component.html',
  styleUrls: ['./barchart-distance.component.scss']
})
export class BarchartDistanceComponent implements OnInit {

  constructor(private zone: NgZone) { }
  _selectedTeam: Team | null

  @Input() 
  set selectedTeam(team: Team | null) {
    this._selectedTeam = team;
    if (team) {
      this.changeColorToBar(team.isocode, "week_1", 1.0)
    }
  }

  chart: am4charts.XYChart
  categoryAxis: any
  valueAxis: any

  ngOnInit() {

  }

  grabBar(country, weekId) {
    // If you don't have access to the chart code in your angular scope,
    // use am4core global.
    // no reason to loop through ALL series/columns, just whipped this up quick
    let foundBar: am4charts.XYSeries | null;
    this.chart.series.each(series => {
      if (series.name == "Week_2") {
        console.log(series.dataItem)
      }

    });
    return foundBar;
  }

  changeColorToBar(country, weekId, opacity) {
    let bar = this.grabBar(country, weekId)
    if (bar) {
      bar.background.fillOpacity = opacity
    }
  }

  createSeries(field, name) {
    var series = this.chart.series.push(new am4charts.ColumnSeries());
    series.name = name;
    series.dataFields.valueY = field;
    series.dataFields.categoryX = "country";
    series.sequencedInterpolation = true;
    series.columns.template.fillOpacity = 0.5

    // Make it stacked
    series.stacked = true;

    // Configure columns
    series.columns.template.width = am4core.percent(60);
    series.columns.template.tooltipText = "[bold]{name}[/]\n[font-size:14px]{categoryX}: {valueY}";
    console.log(series)
    return series
  }

  drawWithData() {
    this.zone.runOutsideAngular(() => {
      this.chart = am4core.create("chartdiv2", am4charts.XYChart);
      let data = [
        new CountryItinerary("Italy", "IT", [10, 15, 15, 22]),
        new CountryItinerary("Germany", "DE", [20, 30, 40, 24]),
        new CountryItinerary("Greece", "GR",  [13, 10, 20, 15])
      ]

      this.chart.data = data.map ((el) => {
        let newEl = { "country": el.isocode }
        el.distances.forEach((item, index) => {
          newEl["week_" + index] = item
        })
        return newEl   
      })
      console.log(this.chart.data)

      this.categoryAxis = this.chart.xAxes.push(new am4charts.CategoryAxis());
      this.categoryAxis.dataFields.category = "country";
      this.categoryAxis.renderer.grid.template.location = 0;

      this.valueAxis = this.chart.yAxes.push(new am4charts.ValueAxis());
      this.valueAxis.renderer.inside = true;
      this.valueAxis.renderer.labels.template.disabled = true;
      this.valueAxis.min = 0;

      let numberOfTrips = data[0].distances.length
      for (let i = 0; i < numberOfTrips; i++) {
        this.createSeries("week_" + i, "Week_" + i)
     } 
     console.log(this.chart.series)

    })
  }

  ngAfterViewInit() {
    this.drawWithData()
  }

}

Là-haut, series.dataItem.dataContext est indéfinie.

2voto

notacouch Points 909

Vous voudrez obtenir des informations qui pourront être utilisées pour identifier Column dans le tableau. Par exemple, les en-têtes de ligne/colonne du graphique ou les données de votre application. Ensuite, vous pouvez itérer à travers les séries de votre graphique pour correspondre à leurs données, par exemple, le nom du pays, et itérer à travers leur columns ' dataItem.dataContext.year pour correspondre à l'année.

Avec l'année et le pays, nous pouvons sélectionner une colonne dans une série, par exemple :

function grabColumn(year, country) {
  // If you don't have access to the chart code in your angular scope,
  // use am4core global.
  // no reason to loop through ALL series/columns, just whipped this up quick
  let foundColumn;
  am4core.registry.baseSprites[0].series.each(series => {
    if (series.name === country) {
      series.columns.each(column => {
        if (column.dataItem.dataContext.year === year) {
          foundColumn = column;
        }
      });
    }
  });
  return foundColumn;
}

Pour changer couleurs tandis que vous pouvez régler le fill de l column directement, je recommande d'utiliser États par exemple en ajoutant ceci à la fin de createSeries méthode :

  const highlightState = series.columns.template.states.create('highlight');
  highlightState.properties.fill = am4core.color("#f00"); // These are fine, too: // am4core.color("red"); // "red";

Donc, étant donné une table :

  <table>
    <thead><tr><td></td><th>Europe</th><th>North America</th><th>Asia</th><th>Latin America</th><th>Middle East</th><th>Africa</th></tr></thead>
    <tbody>
      <tr><th>2003</th><td>2.5</td><td>2.5</td><td>2.1</td><td>1.2</td><td>0.2</td><td>0.1</td></tr>
      <tr><th>2004</th><td>2.6</td><td>2.7</td><td>2.2</td><td>1.3</td><td>0.3</td><td>0.1</td></tr>
      <tr><th>2005</th><td>2.8</td><td>2.9</td><td>2.4</td><td>1.4</td><td>0.3</td><td>0.1</td></tr>
    </tbody>
  </table>

En passant la souris sur une cellule de données, le premier enfant de sa ligne contient l'année, et le même élément est affiché. nième enfant de la ligne d'en-tête contient le pays. Nous pouvons utiliser cela pour saisir la colonne appropriée et changer son état, au lieu de querySelector / addEventListener c'est votre truc angulaire :

const $headerRow = document.querySelector('thead tr');
let highlightedColumn;
document.querySelector('tbody').addEventListener('mouseover', event => {
  if (event.target.matches('td')) {
    const year = event.target.parentElement.getElementsByTagName('th')[0].childNodes[0].nodeValue;
    const siblings = event.target.parentNode.children;
    const totalSiblings = siblings.length;
    let country;
    for(let i = 1; i < totalSiblings; ++i) {
      if (siblings[i] === event.target) {
        country = $headerRow.children[i].childNodes[0].nodeValue;
        break;
      }
    }

    const column = grabColumn(year, country);
    if (highlightedColumn !== column) {              
      if (highlightedColumn) {
        highlightedColumn.setState('default');
      }
      column.setState('highlight');
      highlightedColumn = column;
    }
  }
});

Vous voudriez aussi vous assurer qu'il y a un bon mouseout pour réinitialiser la colonne en surbrillance.

Voici une démo :

https://codepen.io/team/amcharts/pen/36fae5caa2538b22fc2111666749c0b3

2voto

notacouch Points 909

(En ajoutant une réponse différente après qu'il ait fourni du code angulaire réel et où les choses se précipitent pour lui, ma réponse précédente semble beaucoup moins pertinente).

J'ai passé ma propre méthode en tant que Input() au composant tableau, mais en veillant à utiliser une fonction flèche pour qu'elle conserve la portée de ce composant graphique :

  selectTeam = (team, weekN) => {
    this._selectedTeam = team;
    if (team) {
      this.changeColorToBar(team.isocode, weekN, 1.0);
    }
  }

En ce qui concerne la colonne elle-même, étant donné que la valeur de la propriété que vous cherchez à modifier peut également changer, il est peut-être moins judicieux d'utiliser les États et d'ajuster les propriétés directement sur l'objet. Dans ce cas, vous changeriez fillOpacity directement, et non pas sur le background :

  changeColorToBar(country, weekId, opacity) {
    let bar = this.grabBar(country, weekId);
    if (bar && bar !== this.currentBar) {
      if (this.currentBar) this.currentBar.fillOpacity = 0.5; // Default
      bar.fillOpacity = opacity;
      this.currentBar = bar;
    }
  }

Je n'ai eu aucun problème pour trouver le graphique, ses séries et ses colonnes. Le timing peut bien sûr poser problème, c'est-à-dire que le graphique et les séries doivent être effectivement chargés, mais cela se sera probablement produit au moment où l'utilisateur passera la souris :

  grabBar(country, weekId = 0) {
    const totalSeries = this.chart.series.length;
    for (let i = 0; i < totalSeries; ++i) {
      const series = this.chart.series.getIndex(i);
      if (series.name === "Week_" + weekId) {
        const totalColumns = series.columns.length;
        for (let c = 0; c < totalColumns; ++c) {
          const column = series.columns.getIndex(c);
          if (
            column.dataItem.dataContext.country === country
          ) {
            return column;
          }
        }
      }
    }
  }

Voici une démo :

https://stackblitz.com/edit/so-55597531-table-input-chart

Si vous n'arrivez toujours pas à trouver les colonnes dans les séries et autres, vous pouvez peut-être mettre un échantillon de votre projet sur StackBlitz pour que nous puissions essayer de voir où les choses ne vont pas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X