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