Показать данные красиво и понятно бывает сложнее, чем написать саму бизнес-логику. Нужно не просто вывести цифры, а сделать так, чтобы ими было удобно пользоваться: масштабировать, сравнивать, фильтровать. Можно ли совместить мощь, интерактивность и гибкость в одной библиотеке визуализации — и при этом без боли интегрировать ее в React? Спойлер: да, и это ECharts.

Привет, Хабр! Меня зовут Ольга Китова, я разработчик в IBS. Эта статья — про ECharts, один из самых сильных и гибких инструментов для визуализации данных. Я покажу, какие возможности дает эта библиотека, как она устроена «под капотом», в чем ее плюсы и минусы и как использовать ECharts в React-приложениях, на практике.

Что такое ECharts

ECharts — это библиотека для построения различных видов визуализаций с богатым набором диаграмм: от простых линейных и столбчатых графиков до интерактивных 3D-визуализаций с анимациями, масштабированием, фильтрацией и подсказками.

Несмотря на внушительные возможности, стартовать с ней просто:

  1. Подключаем библиотеку в проект.

  2. Описываем нужную конфигурацию в виде объекта option.

  3. Передаем ее в качестве пропа в компонент ECharts.

Формат «все-в-одном» на JSON упрощает хранение и передачу настроек: в одном объекте живут данные, стили и логика взаимодействия.

Функциональные возможности библиотеки:

  • поддержка десятков видов графиков и диаграмм: в демо можно пощелкать все варианты;

  • интерактивность: масштабирование, панорамирование, подсветка элементов, фильтры, кастомные события и динамическое изменение данных без перерисовки страницы;

  • анимации при загрузке и обновлении графиков;

  • гибкая настройка внешнего вида: цвета, шрифты, заливки, тени и прочее;

  • работа с большими массивами данных: оптимизация для отображения тысяч точек, поддержка различных форматов, обновление и редактирование данных на лету;

  • расширяемость: интеграция с другими библиотеками и фреймворками, такими как React, Vue и Angular;

  • возможность собирать комплексные дашборды.

Сравнение с другими библиотеками

ECharts 

Chart.js

Recharts

Plotly.js

Highcharts

Размер пакета

~1,2 МБ

~150 КБ

~150 КБ 

~2 МБ

~100–150 КБ 

Популярность

Высокая

Очень высокая

Высокая

Средняя

Очень высокая

Кол-во скачиваний в месяц (npm)

~2–3 млн

~4–5 млн

~1 млн

~0,5 млн

~10–15 млн

Интеграция с React (оболочки)

echarts-for-react

react-chartjs-2

react-plotty.js

highcharts react

Особенности

Мощная функциональность

Простая и легкая, отлично подходит для быстрых решений

«Реактовая» по духу, легко использовать в React

Поддержка научных расчетов и инженерных графиков, избыточна для простых задач

Минимальный вес, но наблюдается мутация исходных данных, переданных в series

Главный компромисс ECharts — размер пакета. При разработке крупных приложений каждый инструмент увеличивает тяжеловесность всего проекта. Но мне для работы важнее всего была функциональность, так что выбор пал именно на ECharts. Ну и немаловажно, что в отличие от Highcharts эта библиотека не мутирует исходные данные

Как устроен ECharts «под капотом»

ECharts работает поверх собственной графической библиотеки ZRender, которая обеспечивает 2D-рисование и поддерживает два режима рендеринга:

  • Canvas: по умолчанию быстрее всего использует HTML5 Canvas API для рисования графиков;

  • SVG: подходит для векторных графиков и адаптивной отрисовки.

ZRender хранит графику в древовидной структуре, которая похожа на дерево DOM. В ней есть два типа узлов: displayable (конечные элементы, которые реально отображаются на экране, — текст, изображения, фигуры, пути) и group (внутренние узлы, которые могут содержать другие группы или displayable-элементы). Каждый узел имеет свойства положения, масштаба и поворота, и все преобразования группы наследуются ее дочерними элементами. Это делает работу с графикой модульной и удобной: меняем один узел — обновляются все вложенные.

При рендеринге ZRender проходит дерево, вычисляет трансформации и отбирает те элементы, которые нужно отрисовать. Чтобы повысить производительность, используется проверка ограничивающей рамки, а сами объекты рисуются последовательно. И хотя Canvas по умолчанию не поддерживает события мыши, в ZRender реализована собственная событийная модель: клики, наведения, выделения. При движении мыши библиотека проверяет, попадает ли курсор в рамку или контур объекта, и при совпадении генерирует событие (click, mousemove, mouseover или mouseout). Таким образом, ZRender дает привычный для разработчика опыт работы с графикой, но без ограничений чистого Canvas.

Чтобы не блокировать интерфейс, ECharts использует инкрементальный рендеринг: большие объемы данных разбиваются на небольшие чанки. Каждый чанк проходит через пайплайн задач — фильтрацию, визуальное кодирование, создание графических элементов и т. д. В рамках одного кадра обрабатывается только ограниченное число задач, чтобы время выполнения было менее 16 мс, а оставшиеся откладываются до следующего вызова requestAnimationFrame. Если в процессе пользователь взаимодействует с графиком, старые задачи отменяются и формируются новые. Благодаря этому интерфейс остается отзывчивым, даже если данных очень много.

Для еще большей производительности библиотека может работать в многопоточном режиме с использованием Web Workers. В этом случае создается «фиктивный холст», который записывает команды отрисовки и передает их в основной поток. Там настоящий Canvas воспроизводит команды и выводит результат на экран, пока воркер продолжает обрабатывать следующие задачи. Такой подход позволяет разгрузить основной поток UI и ускорить работу с графикой без заметных задержек.

Плюсы и минусы ECharts

Преимущества:

  • огромный набор диаграмм и настроек;

  • активное сообщество, подробная документация и встроенный онлайн-редактор для экспериментов;

  • простая интеграция с фреймворками;

  • интерактивность и анимации «из коробки»;

  • одинаковое поведение графиков на разных платформах.

Недостатки:

  • вес библиотеки: полный пакет ~1,2 МБ;

  • крутая кривая обучения, если нужно глубоко кастомизировать;

  • специфическая стилизация: не через CSS, а через конфигурацию;

  • ограниченные возможности для 3D-сценариев и сложной геометрии.

Подключение в React

Для React чаще всего используют обертку echarts-for-react. Можно скачать через npm или yarn:

npm install echarts-for-react

или

yarn add echarts-for-react

Для работы с графиками больше никаких зависимостей не нужно. Импортируем компонент ReactECharts и используем его в приложении. Основой любой диаграммы в ECharts является объект option — именно в нем описываются данные и настройки визуализации. Достаточно передать этот объект в проп option компонента ReactECharts, и график появится на экране.

<ReactEcharts             

  ref={ref}   

  option={option} // Обязательно. Объект с параметрами конфигурации (тип EChartsOption)     
  
  notMerge={false} // Опционально. Флаг обновления данных (объединение данных с предыдущим option)   
  
  onChartReady={() => {}} // Опционально. Функция обратного вызова, когда диаграмма готова   
  
  onEvents={onEvents} // Опционально. Список событий, на которые идет подписка  

  opts={{ renderer: "svg" }} // Опционально. Дополнительные конфигурации диаграмм (renderer, devicePixelRatio)         

/> 

Из параметров, кроме option, можно также добавлять:

  1. notMerge — флаг, который определяет, нужно ли объединять с существующим option. По умолчанию false, новые опции заменяют старые.

  2. onChartReady — колбэк вызывается, когда график полностью инициализирован.

  3. onEvents { click: handleClick, mouseover: handleMouseOver } — список событий, на которые мы хотим подписаться.

  4. opts — дополнительные опции для внутреннего рендеринга. Например, для смены режима отображения вместо canvas на svg используем свойство renderer со значением svg.

  5. showLoading, loadingText, loadingColor — управление состоянием загрузки.

  6. lazyUpdate — позволяет оптимизировать перерисовку при частых обновлениях.

  7. autoResize — автоматически подгоняет график при изменении размеров контейнера.

Если нужно прямое управление графиком, используем метод getEchartsInstance(). Он возвращает текущий экземпляр объекта и позволяет напрямую работать с API ECharts, когда стандартных параметров уже недостаточно. С его помощью можно программно обновлять данные, менять конфигурацию или получать состояние диаграммы.

Основные сценарии использования:

  1. Вызов методов:

  • setOption() — обновление конфигурации графика;

  • resize() — автоматическая подгонка размера графика;

  • getOption() — получение текущих настроек;

  • clear() — очистка графика;

  • dispatchAction() — выполнение определенных действий, например выделения или фильтрации;

  • convertToPixel() / convertFromPixel() — конвертация координат между графикой и DOM.

  1. Гибкое управление графиком вне стандартных параметров, например, для динамической подгрузки данных.

  2. Контроль поведения после инициализации: от обновления опций до кастомных взаимодействий.

Чтобы получить доступ к этому методу, нужно создать реф с помощью вызова useRef и получить доступ через свойство current к API getEchartsInstance.

import React, { useRef, useEffect } from "react";

import ReactECharts from "echarts-for-react";

function Chart({ option }) {

  const echartsRef = useRef(null);

  useEffect(() => {

    chartInstance = echartsRef.current.getEchartsInstance();

    // Дальнейшее управление

    // Например,  chartInstance.resize()

  }, []);

  return (

    <ReactECharts

      ref={echartsRef}

      option={option}

    />

  );

}

Базовая конфигурация

Основные компоненты:

  • title — заголовок;

  • grid — сетка для выравнивания;

  • tooltip — подсказки;

  • legend — легенда с фильтрацией серий;

  • xAxis / yAxis — оси; поддерживаются типы value, category, time и log для числовых, дискретных, временных и логарифмических данных соответственно;

  • series — данные, которые нам нужно отобразить, и тип диаграммы.

Заглянем в код базовой конфигурации простого линейного графика:

// Заголовок графика

    title: {

      text: "Самые популярные технологии на ведущих веб-сайтах", // Текст заголовка

      subtext: "Обзор Cloudflare Radar 2024", // Подзаголовок

      left: "center", // Расположение: "left", "center", "right"

      top: "top", // Расположение по вертикали: "top", "middle", "bottom" или числовое значение

      padding: [20, 20], // Внутренние отступы (вертикальный и горизонтальный)

      itemGap: 10, // Расстояние между заголовком и подзаголовком

      textStyle: {

        fontSize: 20, // Размер шрифта

        fontWeight: "bold", // "normal", "bold", "bolder", "lighter", числовые значения

        color: "#ffffffff" // Цвет текста

      },

      subtextStyle: {

        fontSize: 16,

        color: "#485a8f",

      }

    },

    // Сетка (расположение графика)

    grid: {

      show: true,

      left: "10%",

      right: "10%",

      top: "80px",

      bottom: "20%",

      containLabel: true, // Учитывать метки

      backgroundColor: "rgba(0,0,0,0)",

      borderColor: "#c0dbff",

      borderWidth: 2

    },

    // Подсказка

    tooltip: {

      trigger: "axis", // "item", "axis", "none"

      triggerOn: "click", // "mousemove", "click", "none"

      alwaysShowContent: true, // показывать ли содержимое подсказки все время

      backgroundColor: "#333", // Цвет фона подсказки

      borderColor: "#333", // Цвет границы

      borderWidth: 1, // Ширина границы

      padding: 5, // Внутренние отступы

      formatter: "{a} <br/>{b} : {c}", // Формат отображения

      textStyle: {

        fontSize: 12,

        color: "#FFF"

      }

    },

    // Легенда

    legend: {

      data: ["Технологии"],

      orient: "horizontal", // "horizontal" или "vertical"

      left: "center", // "left", "center", "right" или числовое значение

      top: "bottom", // "top", "middle", "bottom" или числовое значение

      itemWidth: 25, // Размер иконки

      itemHeight: 14,

      itemGap: 10, // Расстояние между элементами

      show: true, // показывать/скрывать легенду

      selectedMode: true, // true, false, "multiple", "single"

      inactiveColor: "#ccc", // Цвет неактивных элементов

      textStyle: {

        color: "#FFF",

        fontSize: 12

      },

      padding: 0,

      formatter: function(name: string) { return name; } // Можно вернуть строку или функцию

    },

    // Оси

    xAxis: {

      type: "category", // "value", "category", "time", "log"

      name: "Категории", // Название оси

      nameLocation: "middle", // "start", "middle", "end"

      nameTextStyle: { //Стиль текста названия оси

        color: "#FFF",

        fontSize: 14,

        fontWeight: "bold"

      },

      nameGap: 35, // Пробел между названием оси и линией оси

      nameRotate: 0, // Вращение имени оси

      inverse: false, // Инвертировать ось

      boundaryGap: true, // Разрыв границы по обе стороны координатной оси

      data: [...data].map(({ name }) => name), // Метки

      axisLine: { // Настройки, связанные с осевой линией

        show: true,

        lineStyle: {

          color: "#FFF",

          width: 1,

          type: "solid" // "solid", "dashed", "dotted"

        }

      },

      axisTick: { // Настройки, связанные с отметкой оси (стили метки)

        show: true,

        alignWithLabel: true,

        length: 6,

        lineStyle: {

          color: "#c0dbff",

          width: 1,

          type: "solid"

        }

      },

      axisLabel: { // Настройки, связанные с меткой оси (стили текста)

        show: true,

        interval: "auto", // "auto" или числовое значение

        rotate: 0,

        margin: 8,

        formatter: null, // Функция или строка

        color: "#FFF",

        fontSize: 16,

        fontWeight: "normal"

      },

      splitLine: { // Разделительные линии

        show: true,

        lineStyle: {

          color: ["#eee", "#ccc"], // Цвет линий

          width: 1,

          type: "solid" // "solid", "dashed", "dotted"

        }

      }

    },

    // Серии данных

    series: [

      {

        name: "Технологии", // Название серии

        type: "line", // "line", "bar", "pie", "scatter", "effectScatter", "radar", "tree", и д.р

        data: [...data], // Массив данных

        smooth: true, // Плавная линия

        symbol: "circle", // "circle", "rect", "roundRect", "triangle", "diamond", "pin", "arrow", "none"

        symbolSize: 8, // Размер маркера

        showSymbol: true, // Показывать маркеры

        lineStyle: { // Стили для линии

          color: "#c0dbff",

          width: 2,

          type: "solid" // "solid", "dashed", "dotted"

        },

        areaStyle: { // Стили для заливки под линией

          color: "#c0dbff",

          opacity: 0.5

        },

        itemStyle: { // Стили для символов

          color: "#c0dbff",

          borderColor: "#c0dbff",

          borderWidth: 2,

        },

      },

      // Можно добавлять другие серии с разными типами

    ],

}

Практические примеры

Градиентная заливка

Рассмотрим график изменения температуры за неделю:

Чтобы добавить градиент, в ECharts используется класс graphic.LinearGradient. Мы задаем начальные и конечные точки, а также массив цветов с указанием смещений (offset). Все это передается в свойство areaStyle внутри series. LinearGradient(0, 0, 0, 1, …) означает вертикальный градиент сверху вниз.

areaStyle: {

    opacity: 0.8,

    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [

        {

            offset: 0,

            color: “#377AEF”,

        },

        {

            offset: 0.3,

            color: “#C5CFFF”,

        },

        {

            offset: 1,

            color: “#FFFFFF”,

        },

    ])

},

Кастомизация подписей осей (axisLabel)

Подписи на осях можно стилизовать. Например, у нас есть данные с текущими и прошлогодними значениями, и мы хотим отобразить дату вместе с отклонением.

Через свойство formatter можно собрать строку с нужными данными.

export const data = [

    { name: “01.06”, currentValue: 28, previousValue: 30 },

    { name: “02.06”, currentValue: 32, previousValue: 28 },

    { name: “03.06”, currentValue: 32, previousValue: 32 },

    { name: “04.06”, currentValue: 20, previousValue: 26 },

    { name: “05.06”, currentValue: 26, previousValue: 24 },

    { name: “06.06”, currentValue: 24, previousValue: 22 },

    { name: “07.06”, currentValue: 29, previousValue: 29 },

];

data: data.map((item) => {

          const { name, currentValue, previousValue } = item;

          const deviation = currentValue - previousValue;

          return {

            value: ${name}, ${deviation},

          };

        }),

Чтобы добавить иконки и стили, используется объект rich, где мы отдельно задаем оформление текста, иконок или подложки (substrate).

        axisLabel: {

          show: true,

          interval: 0,

          fontSize: 16,

          lineHeight: 20,

          fontWeight: 400,

          formatter: (params: string) => {

            const [date, deviation] = params.split(",");

            const dateWithStyles = {sun|}{text|${date}};

            const arrowIcon = +deviation >= 0 ? "{deviationUp|}" : "{deviationDown|}";

            const deviationWithStyles = ${arrowIcon}{text|${deviation}}{substrate|};

            return ${dateWithStyles} ${deviationWithStyles};

          },

          rich: {

            text: {

              color: "#FFFF",

              padding: [4, 2],

              align: "center",

            },

            sun: {

                backgroundColor: {

                  image: Sun,

                },

                height: 24,

                width: 24,

              },

              deviationUp: {

                backgroundColor: {

                  image: UpArrow,

                },

                height: 18,

                width: 18,

              },

              deviationDown: {

                backgroundColor: {

                  image: DownArrow,

                },

                height: 18,

                width: 18,

              },

              substrate: {

                height: 16,

                width: "40%",

                align: "right",

                borderRadius: [4],

                padding: [4, 4, 0, 4],

                backgroundColor: "#001C43",

              },

Кастомный tooltip

По умолчанию formatter в tooltip возвращает строку, но никто не мешает нам отрендерить полноценный React-компонент. Мы с коллегами нашли обходной путь, чтобы создать кастомный tooltip:

  1. Внутри formatter вызываем функцию create tooltop и создаем HTML-узел.

  2. Заводим корень для отображения компонента React внутри узла DOM и синхронно рендерим JSX через flushSync.

  3. Возвращаем полученную разметку как строку.

В результате tooltip может содержать что угодно: иконки, стили, React-компоненты. Это удобно для нестандартных сценариев.

 tooltip: {

      trigger: "axis",

      alwaysShowContent: true,

      backgroundColor: "#001C43",

      padding: 10,

      borderWidth: 5,

      borderColor: "#001C43",

      textStyle: {

        color: "#FFF",

      },

      formatter: (params) => createTooltip(params),

    },

  const createTooltip = useCallback((params: { seriesName: string; value: number; }[]) => {

    const temporaryElement = document.createElement("div");

    const root = createRoot(temporaryElement);

    flushSync(() => {

      root.render(<Tooltip values={params} />);

    });

    return temporaryElement.innerHTML;

  }, []);

Динамическое обновление данных

Если данные должны подгружаться «на лету» (например, при выборе чекбоксов), используется связка notMerge={true} и getEchartsInstance(). Через getEchartsInstance() можно вызвать setOption() с новым набором данных — график обновится без полной перерисовки. Такой подход хорош для интерактивных панелей и дашбордов.

Таким образом, ECharts позволяет не только быстро строить базовые графики, но и глубоко кастомизировать их: от стилизации осей до полностью кастомных тултипов и динамической подгрузки данных.

Итоги

Интеграция ECharts в React может значительно улучшить пользовательский опыт. Библиотека отлично подходит для приложений, которые требуют богатой, интерактивной и гибкой визуализации данных, будь то аналитика, отчеты, карты или мониторинг. Ее универсальность и расширяемость позволяют реализовать практически любые сценарии, связанные с отображением информации в графическом виде. Если вам важны гибкость и расширяемость, поддержка работы с большими массивами данных и богатый набор диаграмм «из коробки», то ECharts станет хорошим выбором. Однако стоит учитывать вес библиотеки и необходимость изучить конфигурацию, чтобы раскрыть ее возможности по максимуму.

Полезные ссылки:

Комментарии (0)