Introduction

Les étiquettes dans les graphiques à barres rencontrent souvent des problèmes de lisibilité lorsque le graphique est redimensionné, affiché sur différentes résolutions d'écran ou zoomé à l'aide des contrôles de zoom d'amCharts. Par défaut, les étiquettes dans amCharts 4 ont une largeur fixe, ce qui peut entraîner des chevauchements, des coupures ou un texte illisible sur les petits écrans.

Pour rendre les étiquettes véritablement réactives, nous devons :

  • Calculer dynamiquement la largeur maximale des étiquettes en fonction de la taille du graphique et de la largeur des colonnes.
  • Couper les longues étiquettes si nécessaire pour éviter le débordement.
  • Utiliser des hooks d'événements pour s'assurer que les étiquettes s'ajustent correctement lorsque :
    • Le graphique est redimensionné.
    • Un zoom/défilement se produit.
    • Les données se mettent à jour dynamiquement.

Dans cet article, nous allons explorer comment ajuster dynamiquement la largeur des étiquettes et couper les étiquettes si nécessaire en utilisant les événements d'amCharts 4.

Un exemple d'étiquettes se chevauchant et d'un manque de réactivité dans un graphique en colonnes

Code d'implémentation avec le problème :


am4core.ready(function () {
    // Créer l'instance du graphique
    let chart = am4core.create("chartdiv", am4charts.XYChart);
    chart.hiddenState.properties.opacity = 0; // Effet d'apparition initial
    chart.logo.disabled = true;
    chart.responsive.enabled = true; // Activer le comportement réactif

    // Données du graphique
    chart.data = [
      { company: "Apple Inc.", visits: 23725 },
      { company: "Microsoft Corporation", visits: 1882 },
      { company: "Alphabet Inc. (Google)", visits: 1809 },
      { company: "Amazon.com, Inc.", visits: 1322 },
      { company: "Tesla, Inc.", visits: 1122 },
      { company: "Meta Platforms, Inc. (Facebook)", visits: 1114 },
      { company: "Berkshire Hathaway Inc.", visits: 711 },
      { company: "PepsiCo, Inc.", visits: 443 },
      { company: "Johnson & Johnson Services, Inc.", visits: 350 },
      { company: "McDonald's Corporation", visits: 298 },
      { company: "NVIDIA Corporation", visits: 150 },
      { company: "Netflix, Inc.", visits: 100 }
    ];

    // Créer l'Axe Catégorie (Axe X)
    let categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
    categoryAxis.dataFields.category = "company";
    categoryAxis.renderer.grid.template.location = 0;
    categoryAxis.renderer.minGridDistance = 40;
    categoryAxis.fontSize = 11;
    categoryAxis.renderer.labels.template.wrap = true;
    categoryAxis.renderer.labels.template.maxWidth = 80;
    categoryAxis.renderer.labels.template.padding(10, 0, 0, 0); // Définir tous les côtés à 0

    // Créer l'Axe Valeur (Axe Y)
    let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
    valueAxis.min = 0;
    valueAxis.max = 24000;
    valueAxis.strictMinMax = true;
    valueAxis.renderer.minGridDistance = 30;

    // Créer la Série de Colonnes
    let series = chart.series.push(new am4charts.ColumnSeries());
    series.dataFields.categoryX = "company";
    series.dataFields.valueY = "visits";
    series.columns.template.tooltipText = "{valueY.value}";
    series.columns.template.tooltipY = 0;
    series.columns.template.strokeOpacity = 0;

    // Appliquer des couleurs différentes à chaque colonne
    series.columns.template.adapter.add("fill", function (fill, target) {
      return chart.colors.getIndex(target.dataItem.index);
    });

    // Ajouter une barre de défilement
    chart.scrollbarX = new am4core.Scrollbar();
    chart.scrollbarX.marginTop = 30;
    chart.scrollbarX.parent = chart.bottomAxesContainer;

    // Ajouter une coupure d'axe pour les grandes valeurs
    let axisBreak = valueAxis.axisBreaks.create();
    axisBreak.startValue = 2100;
    axisBreak.endValue = 22900;

    // Ajuster la taille de la coupure d'axe
    let d = (axisBreak.endValue - axisBreak.startValue) / (valueAxis.max - valueAxis.min);
    axisBreak.breakSize = 0.05 * (1 - d) / d; // Augmenter la taille de la coupure pour une meilleure visibilité

    // Ajuster l'effet de survol pour une meilleure interaction
    let hoverState = axisBreak.states.create("hover");
    hoverState.properties.breakSize = 1; // Coupure légèrement plus grande lorsqu'elle est survolée
    hoverState.properties.opacity = 0.1;
    hoverState.transitionDuration = 1500;

    axisBreak.defaultState.transitionDuration = 1000;

}); // fin am4core.ready()

Définir une largeur maximale réactive pour les étiquettes

Dans amCharts 4, les étiquettes pour l'xAxis dans un graphique en colonnes ont une largeur maximale fixe. Cependant, comme cette largeur ne s'ajuste pas dynamiquement, nous devons la calculer en fonction de la largeur des colonnes et de la taille du graphique.

Pour y parvenir, nous nous accrochons aux événements rangechangeended et sizechanged, vérifions la largeur des colonnes et définissons la largeur maximale en conséquence.

Implémentation

// Fonction pour mettre à jour la largeur maximale des étiquettes en fonction de la colonne la plus large
const updateLabelMaxWidth = () => {
  let maxColumnWidth = 0;

  // Boucle à travers chaque colonne et trouve la largeur maximale
  series.columns.each(function (column) {
    if (column && column.pixelWidth > maxColumnWidth) {
      maxColumnWidth = column.pixelWidth;
    }
  });

  // Appliquer la largeur maximale aux étiquettes
  categoryAxis.renderer.labels.template.maxWidth = maxColumnWidth;
  console.log("Largeur maximale des étiquettes mise à jour : " + maxColumnWidth);

  // Forcer le traitement des étiquettes
  categoryAxis.invalidateLabels();
}

// Déclencher la mise à jour de la largeur des étiquettes lors du zoom/défilement
categoryAxis.events.on("rangechangeended", function () {
  updateLabelMaxWidth();
});

// Déclencher la mise à jour de la largeur des étiquettes lors du redimensionnement de l'écran
chart.events.on("sizechanged", function () {
  updateLabelMaxWidth();
});

Explication

La fonction updateLabelMaxWidth ajuste dynamiquement la largeur des étiquettes de l'axe x en fonction de la colonne la plus large dans le graphique. Elle parcourt toutes les colonnes, trouve la largeur maximale et l'applique aux étiquettes pour éviter les chevauchements.

Hooks d'événements :

  • rangechangeended → Met à jour la largeur des étiquettes lors du zoom/défilement.
  • sizechanged → Ajuste les étiquettes lorsque le graphique est redimensionné.

Cela garantit que les étiquettes se redimensionnent dynamiquement en fonction de l'espace disponible.

Couper les longues étiquettes pour une meilleure lisibilité

Même avec une largeur maximale dynamique, certaines étiquettes peuvent encore être trop longues et causer un encombrement. Pour y remédier, nous coupons les étiquettes lorsqu'elles dépassent une certaine longueur.

Implémentation

categoryAxis.renderer.labels.template.adapter.add("textOutput", function(text, target) {
    let maxLabelLength = 10; // Définir le nombre maximum de caractères avant la coupure

    if (text.length > maxLabelLength) {
        return text.substring(0, maxLabelLength) + "..."; // Couper et ajouter des points de suspension
    }
    return text; // Retourner le texte original s'il est dans la limite
});

Explication

  • Cette fonction vérifie la longueur de chaque étiquette.
  • Si l'étiquette est trop longue, elle coupe le texte au premier mot et ajoute ".." à la fin.
  • Les étiquettes dans la limite de caractères maximale restent inchangées.

Cela garantit que les longues étiquettes ne se chevauchent pas tout en fournissant suffisamment d'informations et en restant réactives lorsqu'elles sont redimensionnées ou zoomées.

Résultat final après application des ajustements dynamiques des étiquettes

Code d'implémentation complet après application de la solution


am4core.ready(function () {
  // Créer l'instance du graphique
  let chart = am4core.create("chartdiv", am4charts.XYChart);
  chart.hiddenState.properties.opacity = 0; // Effet d'apparition initial
  chart.logo.disabled = true;
  chart.responsive.enabled = true; // Activer le comportement réactif

  // Données du graphique
  chart.data = [
    { company: "Apple Inc.", visits: 23725 },
    { company: "Microsoft Corporation", visits: 1882 },
    { company: "Alphabet Inc. (Google)", visits: 1809 },
    { company: "Amazon.com, Inc.", visits: 1322 },
    { company: "Tesla, Inc.", visits: 1122 },
    { company: "Meta Platforms, Inc. (Facebook)", visits: 1114 },
    { company: "Berkshire Hathaway Inc.", visits: 711 },
    { company: "PepsiCo, Inc.", visits: 443 },
    { company: "Johnson & Johnson Services, Inc.", visits: 350 },
    { company: "McDonald's Corporation", visits: 298 },
    { company: "NVIDIA Corporation", visits: 150 },
    { company: "Netflix, Inc.", visits: 100 }
  ];

  // Créer l'Axe Catégorie (Axe X)
  let categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
  categoryAxis.dataFields.category = "company";
  categoryAxis.renderer.grid.template.location = 0;
  categoryAxis.renderer.minGridDistance = 40;
  categoryAxis.fontSize = 11;
  categoryAxis.renderer.labels.template.wrap = true;
  categoryAxis.renderer.labels.template.maxWidth = 80;
  categoryAxis.renderer.labels.template.padding(10, 0, 0, 0); // Définir tous les côtés à 0

  // Créer l'Axe Valeur (Axe Y)
  let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
  valueAxis.min = 0;
  valueAxis.max = 24000;
  valueAxis.strictMinMax = true;
  valueAxis.renderer.minGridDistance = 30;

  // Fonction pour mettre à jour la largeur maximale des étiquettes en fonction de la colonne la plus large
  const updateLabelMaxWidth = () => {
    let maxColumnWidth = 0;

    // Boucle à travers chaque colonne et trouve la largeur maximale
    series.columns.each(function (column) {
      if (column && column.pixelWidth > maxColumnWidth) {
        maxColumnWidth = column.pixelWidth;
      }
    });

    // Appliquer la largeur maximale aux étiquettes
    categoryAxis.renderer.labels.template.maxWidth = maxColumnWidth;
    console.log("Largeur maximale des étiquettes mise à jour : " + maxColumnWidth);

    // Forcer le traitement des étiquettes
    categoryAxis.invalidateLabels();
  }

  // Déclencher la mise à jour de la largeur des étiquettes lors du zoom/défilement
  categoryAxis.events.on("rangechangeended", function () {
    updateLabelMaxWidth();
  });

  // Déclencher la mise à jour de la largeur des étiquettes lors du redimensionnement de l'écran
  chart.events.on("sizechanged", function () {
    updateLabelMaxWidth();
  });

  // Couper les étiquettes à la fin du premier mot si nécessaire
  categoryAxis.renderer.labels.template.adapter.add("textOutput", function (text, target) {
    let labelMaxWidth = target.maxWidth;

    if (labelMaxWidth < 60 && text) { // Vérifier si la largeur de l'étiquette est inférieure à 60 pixels
      let words = text.split(" "); // Diviser le texte en mots
      return words.length > 1 ? words[0] + ".." : text; // Garder seulement le premier mot avec ".."
    }

    return text;
  });

  // Créer la Série de Colonnes
  let series = chart.series.push(new am4charts.ColumnSeries());
  series.dataFields.categoryX = "company";
  series.dataFields.valueY = "visits";
  series.columns.template.tooltipText = "{valueY.value}";
  series.columns.template.tooltipY = 0;
  series.columns.template.strokeOpacity = 0;

  // Appliquer des couleurs différentes à chaque colonne
  series.columns.template.adapter.add("fill", function (fill, target) {
    return chart.colors.getIndex(target.dataItem.index);
  });

  // Ajouter une barre de défilement
  chart.scrollbarX = new am4core.Scrollbar();
  chart.scrollbarX.marginTop = 30;
  chart.scrollbarX.parent = chart.bottomAxesContainer;

  // Ajouter une coupure d'axe pour les grandes valeurs
  let axisBreak = valueAxis.axisBreaks.create();
  axisBreak.startValue = 2100;
  axisBreak.endValue = 22900;

  // Ajuster la taille de la coupure d'axe
  let d = (axisBreak.endValue - axisBreak.startValue) / (valueAxis.max - valueAxis.min);
  axisBreak.breakSize = 0.05 * (1 - d) / d; // Augmenter la taille de la coupure pour une meilleure visibilité

  // Ajuster l'effet de survol pour une meilleure interaction
  let hoverState = axisBreak.states.create("hover");
  hoverState.properties.breakSize = 1; // Coupure légèrement plus grande lorsqu'elle est survolée
  hoverState.properties.opacity = 0.1;
  hoverState.transitionDuration = 1500;

  axisBreak.defaultState.transitionDuration = 1000;

}); // fin am4core.ready()

Avec ces ajustements, notre graphique s'adapte parfaitement—les étiquettes restent lisibles, plus de chevauchements inesthétiques, et tout se redimensionne en douceur. En ajustant dynamiquement les largeurs, en coupant les longs noms et en utilisant des événements, nous avons veillé à ce que le graphique soit superbe sur n'importe quel écran. Maintenant, allez-y et essayez-le !