Introducción

Las etiquetas en gráficos de barras a menudo enfrentan problemas de legibilidad cuando el gráfico se redimensiona, se muestra en diferentes resoluciones de pantalla o se hace zoom utilizando los controles de zoom de amCharts. Por defecto, las etiquetas en amCharts 4 tienen un ancho fijo, lo que puede llevar a superposición, truncamiento o texto ilegible en pantallas pequeñas.

Para hacer que las etiquetas sean realmente responsivas, necesitamos:

  • Calcular dinámicamente el ancho máximo de las etiquetas basado en el tamaño del gráfico y el ancho de la columna.
  • Truncar etiquetas largas si es necesario para evitar desbordamientos.
  • Usar ganchos de eventos para asegurar que las etiquetas se ajusten correctamente cuando:
    • El gráfico se redimensiona.
    • Se produce zoom/desplazamiento.
    • Los datos se actualizan dinámicamente.

En este artículo, exploraremos cómo ajustar dinámicamente el ancho de las etiquetas y truncar etiquetas cuando sea necesario utilizando eventos de amCharts 4.

Un ejemplo de etiquetas superpuestas y falta de capacidad de respuesta en un gráfico de columnas

Código de implementación con el problema:


am4core.ready(function () {
    // Crear la instancia del gráfico
    let chart = am4core.create("chartdiv", am4charts.XYChart);
    chart.hiddenState.properties.opacity = 0; // Desvanecimiento inicial
    chart.logo.disabled = true;
    chart.responsive.enabled = true; // Habilitar comportamiento responsivo

    // Datos del gráfico
    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 }
    ];

    // Crear Eje de Categoría (Eje 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); // Establecer todos los lados a 0

    // Crear Eje de Valor (Eje Y)
    let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
    valueAxis.min = 0;
    valueAxis.max = 24000;
    valueAxis.strictMinMax = true;
    valueAxis.renderer.minGridDistance = 30;

    // Crear Serie de Columnas
    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;

    // Aplicar diferentes colores a cada columna
    series.columns.template.adapter.add("fill", function (fill, target) {
      return chart.colors.getIndex(target.dataItem.index);
    });

    // Agregar Barra de Desplazamiento
    chart.scrollbarX = new am4core.Scrollbar();
    chart.scrollbarX.marginTop = 30;
    chart.scrollbarX.parent = chart.bottomAxesContainer;

    // Agregar Ruptura de Eje para valores grandes
    let axisBreak = valueAxis.axisBreaks.create();
    axisBreak.startValue = 2100;
    axisBreak.endValue = 22900;

    // Ajustar Tamaño de Ruptura de Eje
    let d = (axisBreak.endValue - axisBreak.startValue) / (valueAxis.max - valueAxis.min);
    axisBreak.breakSize = 0.05 * (1 - d) / d; // Aumentar tamaño de ruptura para mejor visibilidad

    // Ajustar efecto de hover para mejor interacción
    let hoverState = axisBreak.states.create("hover");
    hoverState.properties.breakSize = 1; // Ruptura ligeramente más grande al pasar el mouse
    hoverState.properties.opacity = 0.1;
    hoverState.transitionDuration = 1500;

    axisBreak.defaultState.transitionDuration = 1000;

}); // fin am4core.ready()

Estableciendo un Ancho Máximo Responsivo para las Etiquetas

En amCharts 4, las etiquetas para el xAxis en un gráfico de columnas tienen un maxWidth fijo. Sin embargo, dado que este ancho no se ajusta dinámicamente, debemos calcularlo en función del ancho de la columna y el tamaño del gráfico.

Para lograr esto, nos enganchamos en el evento rangechangeendedsizechanged , verificamos el ancho de las columnas y establecemos el maxWidth en consecuencia.

Implementación

// Función para actualizar el ancho máximo de la etiqueta basado en la columna más ancha
const updateLabelMaxWidth = () => {
  let maxColumnWidth = 0;

  // Recorrer cada columna y encontrar el ancho máximo
  series.columns.each(function (column) {
    if (column && column.pixelWidth > maxColumnWidth) {
      maxColumnWidth = column.pixelWidth;
    }
  });

  // Aplicar ancho máximo a las etiquetas
  categoryAxis.renderer.labels.template.maxWidth = maxColumnWidth;
  console.log("Ancho máximo de etiqueta actualizado: " + maxColumnWidth);

  // Forzar a que la etiqueta se reprocesa
  categoryAxis.invalidateLabels();
}

// Disparar actualización del ancho de la etiqueta en zoom/desplazamiento
categoryAxis.events.on("rangechangeended", function () {
  updateLabelMaxWidth();
});

// Disparar actualización del ancho de la etiqueta en redimensionamiento de pantalla
chart.events.on("sizechanged", function () {
  updateLabelMaxWidth();
});

Explicación

La función updateLabelMaxWidth ajusta dinámicamente el ancho de las etiquetas del eje x basado en la columna más ancha en el gráfico. Recorre todas las columnas, encuentra el ancho máximo y lo aplica a las etiquetas para prevenir superposición.

Ganchos de Eventos:

  • rangechangeended → Actualiza el ancho de la etiqueta al hacer zoom/desplazamiento.
  • sizechanged → Ajusta las etiquetas cuando el gráfico se redimensiona.

Esto asegura que las etiquetas se redimensionen dinámicamente según el espacio disponible.

Truncando Etiquetas Largas para Mejor Legibilidad

Incluso con un ancho máximo dinámico, algunas etiquetas pueden seguir siendo demasiado largas y causar desorden. Para manejar esto, truncamos etiquetas cuando exceden cierta longitud.

Implementación

categoryAxis.renderer.labels.template.adapter.add("textOutput", function(text, target) {
    let maxLabelLength = 10; // Establecer el número máximo de caracteres antes de truncar

    if (text.length > maxLabelLength) {
        return text.substring(0, maxLabelLength) + "..."; // Truncar y agregar puntos suspensivos
    }
    return text; // Devolver el texto original si está dentro del límite
});

Explicación

  • Esta función verifica la longitud de cada etiqueta.
  • Si la etiqueta es demasiado larga, trunca el texto al primer palabra y agrega ".." al final.
  • Las etiquetas dentro del límite máximo de caracteres permanecen sin cambios.

Esto asegura que las etiquetas largas no se superpongan mientras aún proporcionan suficiente información y se mantienen responsivas al ser redimensionadas o al hacer zoom.

Resultado final después de aplicar ajustes dinámicos de etiquetas

Código de Implementación Completo después de aplicar la solución


am4core.ready(function () {
  // Crear la instancia del gráfico
  let chart = am4core.create("chartdiv", am4charts.XYChart);
  chart.hiddenState.properties.opacity = 0; // Desvanecimiento inicial
  chart.logo.disabled = true;
  chart.responsive.enabled = true; // Habilitar comportamiento responsivo

  // Datos del gráfico
  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 }
  ];

  // Crear Eje de Categoría (Eje 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); // Establecer todos los lados a 0

  // Crear Eje de Valor (Eje Y)
  let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
  valueAxis.min = 0;
  valueAxis.max = 24000;
  valueAxis.strictMinMax = true;
  valueAxis.renderer.minGridDistance = 30;

  // Función para actualizar el ancho máximo de la etiqueta basado en la columna más ancha
  const updateLabelMaxWidth = () => {
    let maxColumnWidth = 0;

    // Recorrer cada columna y encontrar el ancho máximo
    series.columns.each(function (column) {
      if (column && column.pixelWidth > maxColumnWidth) {
        maxColumnWidth = column.pixelWidth;
      }
    });

    // Aplicar ancho máximo a las etiquetas
    categoryAxis.renderer.labels.template.maxWidth = maxColumnWidth;
    console.log("Ancho máximo de etiqueta actualizado: " + maxColumnWidth);

    // Forzar a que la etiqueta se reprocesa
    categoryAxis.invalidateLabels();
  }

  // Disparar actualización del ancho de la etiqueta en zoom/desplazamiento
  categoryAxis.events.on("rangechangeended", function () {
    updateLabelMaxWidth();
  });

  // Disparar actualización del ancho de la etiqueta en redimensionamiento de pantalla
  chart.events.on("sizechanged", function () {
    updateLabelMaxWidth();
  });

  // Truncar etiquetas al final de la primera palabra cuando sea necesario
  categoryAxis.renderer.labels.template.adapter.add("textOutput", function (text, target) {
    let labelMaxWidth = target.maxWidth;

    if (labelMaxWidth < 60 && text) { // Verificar si el ancho de la etiqueta es menor a 60 píxeles
      let words = text.split(" "); // Dividir el texto en palabras
      return words.length > 1 ? words[0] + ".." : text; // Mantener solo la primera palabra con ".."
    }

    return text;
  });

  // Crear Serie de Columnas
  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;

  // Aplicar diferentes colores a cada columna
  series.columns.template.adapter.add("fill", function (fill, target) {
    return chart.colors.getIndex(target.dataItem.index);
  });

  // Agregar Barra de Desplazamiento
  chart.scrollbarX = new am4core.Scrollbar();
  chart.scrollbarX.marginTop = 30;
  chart.scrollbarX.parent = chart.bottomAxesContainer;

  // Agregar Ruptura de Eje para valores grandes
  let axisBreak = valueAxis.axisBreaks.create();
  axisBreak.startValue = 2100;
  axisBreak.endValue = 22900;

  // Ajustar Tamaño de Ruptura de Eje
  let d = (axisBreak.endValue - axisBreak.startValue) / (valueAxis.max - valueAxis.min);
  axisBreak.breakSize = 0.05 * (1 - d) / d; // Aumentar tamaño de ruptura para mejor visibilidad

  // Ajustar efecto de hover para mejor interacción
  let hoverState = axisBreak.states.create("hover");
  hoverState.properties.breakSize = 1; // Ruptura ligeramente más grande al pasar el mouse
  hoverState.properties.opacity = 0.1;
  hoverState.transitionDuration = 1500;

  axisBreak.defaultState.transitionDuration = 1000;

}); // fin am4core.ready()

Con estos ajustes, nuestro gráfico ahora se adapta perfectamente—las etiquetas se mantienen legibles, no más superposiciones feas, y todo se redimensiona suavemente. Al ajustar dinámicamente los anchos, truncar nombres largos y engancharse a eventos, hemos asegurado que el gráfico se vea genial en cualquier pantalla. ¡Ahora, adelante y pruébalo!