Introdução

Os rótulos em gráficos de barras frequentemente enfrentam problemas de legibilidade quando o gráfico é redimensionado, exibido em diferentes resoluções de tela ou ampliado/reduzido usando os controles de zoom do amCharts. Por padrão, os rótulos em amCharts 4 têm uma largura fixa, o que pode levar a sobreposição, truncamento ou texto ilegível em telas pequenas.

Para tornar os rótulos realmente responsivos, precisamos:

  • Calcular dinamicamente a largura máxima dos rótulos com base no tamanho do gráfico e na largura da coluna.
  • Truncar rótulos longos se necessário para evitar transbordamento.
  • Usar ganchos de evento para garantir que os rótulos se ajustem corretamente quando:
    • O gráfico é redimensionado.
    • O zoom/pan ocorre.
    • Os dados são atualizados dinamicamente.

Neste artigo, vamos explorar como ajustar a largura dos rótulos dinamicamente e truncar rótulos quando necessário usando eventos do amCharts 4.

Um exemplo de rótulos sobrepostos e falta de responsividade em um gráfico de colunas

Código de implementação com o problema:


am4core.ready(function () {
    // Criar a instância do gráfico
    let chart = am4core.create("chartdiv", am4charts.XYChart);
    chart.hiddenState.properties.opacity = 0; // Fade-in inicial
    chart.logo.disabled = true;
    chart.responsive.enabled = true; // Habilitar comportamento responsivo

    // Dados do 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 }
    ];

    // Criar Eixo de Categoria (Eixo 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); // Definir todos os lados como 0

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

    // Criar Série de Colunas
    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 cores diferentes a cada coluna
    series.columns.template.adapter.add("fill", function (fill, target) {
      return chart.colors.getIndex(target.dataItem.index);
    });

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

    // Adicionar Quebra de Eixo para valores grandes
    let axisBreak = valueAxis.axisBreaks.create();
    axisBreak.startValue = 2100;
    axisBreak.endValue = 22900;

    // Ajustar Tamanho da Quebra de Eixo
    let d = (axisBreak.endValue - axisBreak.startValue) / (valueAxis.max - valueAxis.min);
    axisBreak.breakSize = 0.05 * (1 - d) / d; // Aumentar tamanho da quebra para melhor visibilidade

    // Ajustar efeito de hover para melhor interação
    let hoverState = axisBreak.states.create("hover");
    hoverState.properties.breakSize = 1; // Quebra ligeiramente maior quando sobreposta
    hoverState.properties.opacity = 0.1;
    hoverState.transitionDuration = 1500;

    axisBreak.defaultState.transitionDuration = 1000;

}); // fim am4core.ready()

Definindo uma Largura Máxima Responsiva para Rótulos

Em amCharts 4, os rótulos para o xAxis em um gráfico de colunas têm uma largura máxima fixa. No entanto, como essa largura não se ajusta dinamicamente, precisamos calculá-la com base na largura da coluna e no tamanho do gráfico.

Para conseguir isso, conectamos aos eventos rangechangeendedsizechanged , verificamos a largura das colunas e definimos a largura máxima de acordo.

Implementação

// Função para atualizar a largura máxima do rótulo com base na coluna mais larga
const updateLabelMaxWidth = () => {
  let maxColumnWidth = 0;

  // Loop através de cada coluna e encontrar a largura máxima
  series.columns.each(function (column) {
    if (column && column.pixelWidth > maxColumnWidth) {
      maxColumnWidth = column.pixelWidth;
    }
  });

  // Aplicar largura máxima aos rótulos
  categoryAxis.renderer.labels.template.maxWidth = maxColumnWidth;
  console.log("Largura Máxima do Rótulo Atualizada: " + maxColumnWidth);

  // Forçar rótulo a ser reprocessado
  categoryAxis.invalidateLabels();
}

// Disparar atualização da largura do rótulo no zoom/pan
categoryAxis.events.on("rangechangeended", function () {
  updateLabelMaxWidth();
});

// Disparar atualização da largura do rótulo ao redimensionar a tela
chart.events.on("sizechanged", function () {
  updateLabelMaxWidth();
});

Explicação

A função updateLabelMaxWidth ajusta dinamicamente a largura dos rótulos do eixo x com base na coluna mais larga do gráfico. Ela percorre todas as colunas, encontra a largura máxima e a aplica aos rótulos para evitar sobreposição.

Ganchos de Evento:

  • rangechangeended → Atualiza a largura do rótulo ao fazer zoom/pan.
  • sizechanged → Ajusta os rótulos quando o gráfico é redimensionado.

Isso garante que os rótulos redimensionem dinamicamente com base no espaço disponível.

Truncando Rótulos Longos para Melhor Legibilidade

Mesmo com uma largura máxima dinâmica, alguns rótulos ainda podem ser muito longos e causar desordem. Para lidar com isso, truncamos rótulos quando eles excedem um certo comprimento.

Implementação

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

    if (text.length > maxLabelLength) {
        return text.substring(0, maxLabelLength) + "..."; // Truncar e adicionar reticências
    }
    return text; // Retornar texto original se estiver dentro do limite
});

Explicação

  • Essa função verifica o comprimento de cada rótulo.
  • Se o rótulo for muito longo, ele trunca o texto para a primeira palavra e adiciona ".." no final.
  • Rótulos dentro do limite máximo de caracteres permanecem inalterados.

Isso garante que rótulos longos não se sobreponham, enquanto ainda fornecem informações suficientes e permanecem responsivos quando redimensionados ou ampliados/reduzidos.

Resultado final após aplicar ajustes dinâmicos de rótulos

Código de Implementação Completo após aplicar a solução


am4core.ready(function () {
  // Criar a instância do gráfico
  let chart = am4core.create("chartdiv", am4charts.XYChart);
  chart.hiddenState.properties.opacity = 0; // Fade-in inicial
  chart.logo.disabled = true;
  chart.responsive.enabled = true; // Habilitar comportamento responsivo

  // Dados do 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 }
  ];

  // Criar Eixo de Categoria (Eixo 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); // Definir todos os lados como 0

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

  // Função para atualizar a largura máxima do rótulo com base na coluna mais larga
  const updateLabelMaxWidth = () => {
    let maxColumnWidth = 0;

    // Loop através de cada coluna e encontrar a largura máxima
    series.columns.each(function (column) {
      if (column && column.pixelWidth > maxColumnWidth) {
        maxColumnWidth = column.pixelWidth;
      }
    });

    // Aplicar largura máxima aos rótulos
    categoryAxis.renderer.labels.template.maxWidth = maxColumnWidth;
    console.log("Largura Máxima do Rótulo Atualizada: " + maxColumnWidth);

    // Forçar rótulo a ser reprocessado
    categoryAxis.invalidateLabels();
  }

  // Disparar atualização da largura do rótulo no zoom/pan
  categoryAxis.events.on("rangechangeended", function () {
    updateLabelMaxWidth();
  });

  // Disparar atualização da largura do rótulo ao redimensionar a tela
  chart.events.on("sizechanged", function () {
    updateLabelMaxWidth();
  });

  // Truncar rótulos no final da primeira palavra quando necessário
  categoryAxis.renderer.labels.template.adapter.add("textOutput", function (text, target) {
    let labelMaxWidth = target.maxWidth;

    if (labelMaxWidth < 60 && text) { // Verificar se a largura do rótulo é menor que 60 pixels
      let words = text.split(" "); // Dividir texto em palavras
      return words.length > 1 ? words[0] + ".." : text; // Manter apenas a primeira palavra com ".."
    }

    return text;
  });

  // Criar Série de Colunas
  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 cores diferentes a cada coluna
  series.columns.template.adapter.add("fill", function (fill, target) {
    return chart.colors.getIndex(target.dataItem.index);
  });

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

  // Adicionar Quebra de Eixo para valores grandes
  let axisBreak = valueAxis.axisBreaks.create();
  axisBreak.startValue = 2100;
  axisBreak.endValue = 22900;

  // Ajustar Tamanho da Quebra de Eixo
  let d = (axisBreak.endValue - axisBreak.startValue) / (valueAxis.max - valueAxis.min);
  axisBreak.breakSize = 0.05 * (1 - d) / d; // Aumentar tamanho da quebra para melhor visibilidade

  // Ajustar efeito de hover para melhor interação
  let hoverState = axisBreak.states.create("hover");
  hoverState.properties.breakSize = 1; // Quebra ligeiramente maior quando sobreposta
  hoverState.properties.opacity = 0.1;
  hoverState.transitionDuration = 1500;

  axisBreak.defaultState.transitionDuration = 1000;

}); // fim am4core.ready()

Com essas melhorias, nosso gráfico agora se adapta perfeitamente—os rótulos permanecem legíveis, sem mais sobreposições feias, e tudo redimensiona suavemente. Ao ajustar dinamicamente as larguras, truncar nomes longos e conectar-se a eventos, garantimos que o gráfico fique ótimo em qualquer tela. Agora, vá em frente e experimente!