Введение

Метки в столбчатых диаграммах часто сталкиваются с проблемами читаемости, когда диаграмма изменяет размер, отображается на разных разрешениях экрана или увеличивается/уменьшается с помощью элементов управления увеличением amCharts. По умолчанию метки в amCharts 4 имеют фиксированную ширину, что может привести к наложению, обрезанию или нечитаемому тексту на маленьких экранах.

Чтобы сделать метки действительно отзывчивыми, нам нужно:

  • Динамически рассчитывать максимальную ширину меток на основе размера диаграммы и ширины столбца.
  • Обрезать длинные метки, если это необходимо, чтобы предотвратить переполнение.
  • Использовать хуки событий, чтобы убедиться, что метки корректно подстраиваются, когда:
    • Диаграмма изменяет размер.
    • Происходит увеличение/панорамирование.
    • Данные обновляются динамически.

В этой статье мы рассмотрим, как динамически регулировать ширину меток и обрезать метки при необходимости с помощью событий amCharts 4.

Пример наложения меток и отсутствия отзывчивости в столбчатой диаграмме

Код реализации с проблемой:


am4core.ready(function () {
    // Создаем экземпляр диаграммы
    let chart = am4core.create("chartdiv", am4charts.XYChart);
    chart.hiddenState.properties.opacity = 0; // Начальное появление
    chart.logo.disabled = true;
    chart.responsive.enabled = true; // Включаем отзывчивое поведение

    // Данные диаграммы
    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 }
    ];

    // Создаем ось категорий (ось 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); // Устанавливаем все стороны в 0

    // Создаем ось значений (ось Y)
    let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
    valueAxis.min = 0;
    valueAxis.max = 24000;
    valueAxis.strictMinMax = true;
    valueAxis.renderer.minGridDistance = 30;

    // Создаем серию столбцов
    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;

    // Применяем разные цвета к каждому столбцу
    series.columns.template.adapter.add("fill", function (fill, target) {
      return chart.colors.getIndex(target.dataItem.index);
    });

    // Добавляем полосу прокрутки
    chart.scrollbarX = new am4core.Scrollbar();
    chart.scrollbarX.marginTop = 30;
    chart.scrollbarX.parent = chart.bottomAxesContainer;

    // Добавляем разрыв оси для больших значений
    let axisBreak = valueAxis.axisBreaks.create();
    axisBreak.startValue = 2100;
    axisBreak.endValue = 22900;

    // Регулируем размер разрыва оси
    let d = (axisBreak.endValue - axisBreak.startValue) / (valueAxis.max - valueAxis.min);
    axisBreak.breakSize = 0.05 * (1 - d) / d; // Увеличенный размер разрыва для лучшей видимости

    // Регулируем эффект наведения для лучшего взаимодействия
    let hoverState = axisBreak.states.create("hover");
    hoverState.properties.breakSize = 1; // Немного больший разрыв при наведении
    hoverState.properties.opacity = 0.1;
    hoverState.transitionDuration = 1500;

    axisBreak.defaultState.transitionDuration = 1000;

}); // конец am4core.ready()

Установка отзывчивой максимальной ширины для меток

В amCharts 4 метки для xAxis в столбчатой диаграмме имеют фиксированную maxWidth. Однако, поскольку эта ширина не изменяется динамически, мы должны рассчитывать ее на основе ширины столбца и размера диаграммы.

Для достижения этого мы подключаемся к событиям rangechangeended и sizechanged , проверяем ширину столбцов и устанавливаем maxWidth соответственно.

Реализация

// Функция для обновления максимальной ширины метки на основе самой широкой колонки
const updateLabelMaxWidth = () => {
  let maxColumnWidth = 0;

  // Проходим по каждой колонке и находим максимальную ширину
  series.columns.each(function (column) {
    if (column && column.pixelWidth > maxColumnWidth) {
      maxColumnWidth = column.pixelWidth;
    }
  });

  // Применяем максимальную ширину к меткам
  categoryAxis.renderer.labels.template.maxWidth = maxColumnWidth;
  console.log("Обновленная максимальная ширина метки: " + maxColumnWidth);

  // Принуждаем метку к повторной обработке
  categoryAxis.invalidateLabels();
}

// Запускаем обновление ширины метки при увеличении/панорамировании
categoryAxis.events.on("rangechangeended", function () {
  updateLabelMaxWidth();
});

// Запускаем обновление ширины метки при изменении размера экрана
chart.events.on("sizechanged", function () {
  updateLabelMaxWidth();
});

Объяснение

Функция updateLabelMaxWidth динамически регулирует ширину меток по оси X на основе самой широкой колонки в диаграмме. Она проходит по всем колонкам, находит максимальную ширину и применяет ее к меткам, чтобы предотвратить наложение.

Хуки событий:

  • rangechangeended → Обновляет ширину метки при увеличении/панорамировании.
  • sizechanged → Регулирует метки при изменении размера диаграммы.

Это гарантирует, что метки изменяются динамически в зависимости от доступного пространства.

Обрезка длинных меток для лучшей читаемости

Даже с динамической максимальной шириной некоторые метки могут быть слишком длинными и вызывать беспорядок. Чтобы с этим справиться, мы обрезаем метки, когда они превышают определенную длину.

Реализация

categoryAxis.renderer.labels.template.adapter.add("textOutput", function(text, target) {
    let maxLabelLength = 10; // Устанавливаем максимальное количество символов перед обрезкой

    if (text.length > maxLabelLength) {
        return text.substring(0, maxLabelLength) + "..."; // Обрезаем и добавляем многоточие
    }
    return text; // Возвращаем оригинальный текст, если он в пределах лимита
});

Объяснение

  • Эта функция проверяет длину каждой метки.
  • Если метка слишком длинная, она обрезает текст до первого слова и добавляет ".." в конце.
  • Метки, которые находятся в максимальном лимите символов, остаются без изменений.

Это гарантирует, что длинные метки не накладываются, при этом предоставляя достаточно информации и оставаясь отзывчивыми при изменении размера или увеличении/уменьшении.

Конечный результат после применения динамических настроек меток

Полный код реализации после применения решения


am4core.ready(function () {
  // Создаем экземпляр диаграммы
  let chart = am4core.create("chartdiv", am4charts.XYChart);
  chart.hiddenState.properties.opacity = 0; // Начальное появление
  chart.logo.disabled = true;
  chart.responsive.enabled = true; // Включаем отзывчивое поведение

  // Данные диаграммы
  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 }
  ];

  // Создаем ось категорий (ось 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); // Устанавливаем все стороны в 0

  // Создаем ось значений (ось Y)
  let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
  valueAxis.min = 0;
  valueAxis.max = 24000;
  valueAxis.strictMinMax = true;
  valueAxis.renderer.minGridDistance = 30;

  // Функция для обновления максимальной ширины метки на основе самой широкой колонки
  const updateLabelMaxWidth = () => {
    let maxColumnWidth = 0;

    // Проходим по каждой колонке и находим максимальную ширину
    series.columns.each(function (column) {
      if (column && column.pixelWidth > maxColumnWidth) {
        maxColumnWidth = column.pixelWidth;
      }
    });

    // Применяем максимальную ширину к меткам
    categoryAxis.renderer.labels.template.maxWidth = maxColumnWidth;
    console.log("Обновленная максимальная ширина метки: " + maxColumnWidth);

    // Принуждаем метку к повторной обработке
    categoryAxis.invalidateLabels();
  }

  // Запускаем обновление ширины метки при увеличении/панорамировании
  categoryAxis.events.on("rangechangeended", function () {
    updateLabelMaxWidth();
  });

  // Запускаем обновление ширины метки при изменении размера экрана
  chart.events.on("sizechanged", function () {
    updateLabelMaxWidth();
  });

  // Обрезаем метки в конце первого слова при необходимости
  categoryAxis.renderer.labels.template.adapter.add("textOutput", function (text, target) {
    let labelMaxWidth = target.maxWidth;

    if (labelMaxWidth < 60 && text) { // Проверяем, меньше ли ширина метки 60 пикселей
      let words = text.split(" "); // Разбиваем текст на слова
      return words.length > 1 ? words[0] + ".." : text; // Оставляем только первое слово с ".."
    }

    return text;
  });

  // Создаем серию столбцов
  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;

  // Применяем разные цвета к каждому столбцу
  series.columns.template.adapter.add("fill", function (fill, target) {
    return chart.colors.getIndex(target.dataItem.index);
  });

  // Добавляем полосу прокрутки
  chart.scrollbarX = new am4core.Scrollbar();
  chart.scrollbarX.marginTop = 30;
  chart.scrollbarX.parent = chart.bottomAxesContainer;

  // Добавляем разрыв оси для больших значений
  let axisBreak = valueAxis.axisBreaks.create();
  axisBreak.startValue = 2100;
  axisBreak.endValue = 22900;

  // Регулируем размер разрыва оси
  let d = (axisBreak.endValue - axisBreak.startValue) / (valueAxis.max - valueAxis.min);
  axisBreak.breakSize = 0.05 * (1 - d) / d; // Увеличенный размер разрыва для лучшей видимости

  // Регулируем эффект наведения для лучшего взаимодействия
  let hoverState = axisBreak.states.create("hover");
  hoverState.properties.breakSize = 1; // Немного больший разрыв при наведении
  hoverState.properties.opacity = 0.1;
  hoverState.transitionDuration = 1500;

  axisBreak.defaultState.transitionDuration = 1000;

}); // конец am4core.ready()

С этими доработками наша диаграмма теперь отлично адаптируется — метки остаются читаемыми, больше никаких некрасивых наложений, и все плавно изменяется в размере. Динамически регулируя ширину, обрезая длинные названия и подключаясь к событиям, мы убедились, что диаграмма выглядит отлично на любом экране. Теперь попробуйте это сами!