Как работать с ECharts в React: от простого графика до интерактивного дашборда

от автора

Показать данные красиво и понятно бывает сложнее, чем написать саму бизнес-логику. Нужно не просто вывести цифры, а сделать так, чтобы ими было удобно пользоваться: масштабировать, сравнивать, фильтровать. Можно ли совместить мощь, интерактивность и гибкость в одной библиотеке визуализации — и при этом без боли интегрировать ее в 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 станет хорошим выбором. Однако стоит учитывать вес библиотеки и необходимость изучить конфигурацию, чтобы раскрыть ее возможности по максимуму.

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


ссылка на оригинал статьи https://habr.com/ru/articles/944318/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *