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

Несмотря на внушительные возможности, стартовать с ней просто:
-
Подключаем библиотеку в проект.
-
Описываем нужную конфигурацию в виде объекта option.
-
Передаем ее в качестве пропа в компонент 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, можно также добавлять:
-
notMerge — флаг, который определяет, нужно ли объединять с существующим option. По умолчанию false, новые опции заменяют старые.
-
onChartReady — колбэк вызывается, когда график полностью инициализирован.
-
onEvents { click: handleClick, mouseover: handleMouseOver } — список событий, на которые мы хотим подписаться.
-
opts — дополнительные опции для внутреннего рендеринга. Например, для смены режима отображения вместо canvas на svg используем свойство renderer со значением svg.
-
showLoading, loadingText, loadingColor — управление состоянием загрузки.
-
lazyUpdate — позволяет оптимизировать перерисовку при частых обновлениях.
-
autoResize — автоматически подгоняет график при изменении размеров контейнера.
Если нужно прямое управление графиком, используем метод getEchartsInstance(). Он возвращает текущий экземпляр объекта и позволяет напрямую работать с API ECharts, когда стандартных параметров уже недостаточно. С его помощью можно программно обновлять данные, менять конфигурацию или получать состояние диаграммы.
Основные сценарии использования:
-
Вызов методов:
-
setOption() — обновление конфигурации графика;
-
resize() — автоматическая подгонка размера графика;
-
getOption() — получение текущих настроек;
-
clear() — очистка графика;
-
dispatchAction() — выполнение определенных действий, например выделения или фильтрации;
-
convertToPixel() / convertFromPixel() — конвертация координат между графикой и DOM.
-
Гибкое управление графиком вне стандартных параметров, например, для динамической подгрузки данных.
-
Контроль поведения после инициализации: от обновления опций до кастомных взаимодействий.
Чтобы получить доступ к этому методу, нужно создать реф с помощью вызова 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:
-
Внутри formatter вызываем функцию create tooltop и создаем HTML-узел.
-
Заводим корень для отображения компонента React внутри узла DOM и синхронно рендерим JSX через flushSync.
-
Возвращаем полученную разметку как строку.
В результате 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/
Добавить комментарий