Перейти к содержимому
Поиск
Разработка

Apache ECharts: графики для интерфейсов — 9 сценариев и мастерская

Девять задач аналитики: от выручки и рейтингов до потоков и нагрузки. Настраивайте графики, проверяйте таблицу, скачивайте SVG, CSV и проект с PHP API.

Александр Некрасов
Apache ECharts: графики аналитики, тепловая карта и воронка

Хороший график помогает ответить на конкретный вопрос: почему выросла очередь, какой канал приносит заявки, где команда перегружена и что происходит после первого обращения клиента. Если для ответа всё равно приходится выгружать таблицу и строить новый отчёт, одной красивой анимации мало. Нужны понятные единицы измерения, фильтры, детализация и возможность проверить исходные значения.

Apache ECharts позволяет собрать такой интерфейс на обычном JavaScript. Разберём девять разных сценариев: от дневной выручки до потоков Sankey и загрузки данных с сервера. В мастерской ниже всё можно менять прямо на странице. Доступны исходники, настоящий PHP JSON API, команды для Windows и Linux и порядок развёртывания на VPS.

Что такое Apache ECharts и где он пригодится

Apache ECharts — библиотека визуализации данных. Вы передаёте ей описание графика и значения, а она рисует линии, столбцы, точки, тепловые карты, воронки, связи и другие представления. Библиотека обрабатывает масштабирование, подсказки, легенду, анимацию изменений и события пользователя. Собственный код связывает эти события с фильтрами, API и бизнес-правилами вашего приложения.

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

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

Как выбрать график под вопрос пользователя

Линия показывает изменение во времени, столбцы сравнивают категории, накопленные столбцы раскрывают состав целого, точки помогают исследовать связь двух показателей. Тепловая карта показывает распределение по двум измерениям, а Sankey — движение между этапами. Это разные способы чтения, поэтому не стоит использовать один вид на всей панели только ради единообразия.

Вопрос Представление Что проверить
Когда изменился результат? Линия и dataZoom Период, часовой пояс, пропуски
Из чего складывается поток? Накопленные столбцы Числа или доли
Кто лидирует? Горизонтальные столбцы Сортировка и единицы
Какие наблюдения отличаются? Scatter Масштаб и размер точки
Когда возникает пик? Heatmap Шкала цвета
Где теряется конверсия? Funnel Одна когорта
Куда переходят обращения? Sankey Баланс потоков
Насколько заполнен ресурс? Gauge Ёмкость и пороги
Что внутри итога? Drilldown Возврат, загрузка и ошибка

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

Версия, лицензии и состав проекта

В проекте зафиксирована Apache ECharts 6.1.0, сверенная с npm и официальным релизом на момент подготовки материала. Вместе с ней используется Bootstrap 5.3.8. package-lock.json фиксирует дерево npm-зависимостей, а готовая сборка уже находится в public. Для просмотра не нужно подключать внешний CDN или собирать React-приложение.

Apache ECharts распространяется под Apache License 2.0. Собственный код мастерской и вымышленные наборы данных доступны под MIT; зависимости сохраняют свои лицензии. В THIRD-PARTY-LICENSES.txt включены тексты лицензий и уведомления. При переносе проекта сохраняйте этот файл, а не переименовывайте всю сборку в «чистый MIT».

Интерфейс построен на HTML5, Bootstrap и обычном JavaScript. Графики рисует настоящая библиотека, без картинок вместо результата. Учебные данные воспроизводимы: значения не меняются случайным образом при обновлении страницы. Это удобно для сравнения вариантов и проверки собственных изменений.

Официальная документация: релиз Apache ECharts 6.1.0.

Официальная документация: установка и лицензия.

Мини-мастерская: девять живых сценариев

Выберите задачу в списке «Что пробуем». Появятся объяснение, область применения и действие для первого эксперимента. Затем изменяйте период, показатель, сегмент, канал или загрузку команды. График, таблица и готовая конфигурация обновляются вместе. На маленьком экране настройки располагаются вертикально, основные действия доступны обычными кнопками.

В детализации запрос действительно выполняется по HTTP. На сайте это статические JSON-файлы из пакета; в отдельно запущенном PHP-режиме — настоящий endpoint. Источник указан в верхней плашке. Учебная ошибка отправляет запрос к отсутствующему файлу или получает 503 от PHP. Старый график очищается, после чего доступен повтор без сбоя.

Встроенная мастерская прокручивается внутри страницы. На телефоне удобна полноэкранная ссылка. Данные вымышлены; PHP API запускается из архива.

CSV содержит таблицу после фильтров, SVG — изображение графика, option.json — конфигурацию со снимком значений. JavaScript показывает минимальное подключение этой конфигурации. Полный проект дополнительно содержит формы, HTTP-обработку и серверный пример. Снимок option не заменяет логику всего приложения.

Первый график: контейнер, init и setOption

У графика должен быть контейнер с ненулевыми шириной и высотой. Частая причина пустого места — div без высоты или инициализация внутри скрытой вкладки. Сначала задайте размеры, затем создайте экземпляр. После этого setOption передаёт библиотеке оси и серию. При подключённом сборщике минимальный пример выглядит так:

npm install --save-exact echarts@6.1.0
import * as echarts from 'echarts';
const element = document.querySelector('#chart');
const chart = echarts.init(element, null, { renderer: 'svg' });
chart.setOption({
  xAxis: { type: 'category', data: ['Пн', 'Вт', 'Ср'] },
  yAxis: { type: 'value', name: 'заявки' },
  series: [{ type: 'bar', data: [24, 38, 31] }]
});
<div id="chart" style="width:100%;height:440px"></div>

HTML не выполняет импорт npm-пакета сам по себе: исходник нужно собрать либо использовать локальную браузерную сборку. В архиве scripts/build.mjs выполняет сборку через esbuild и сохраняет public/app.js. На страницу подключается этот файл. Так пример использует фиксированные версии и не зависит от случайного содержимого CDN.

Option и dataset: данные отдельно от оформления

Объект option описывает оси, легенду, подсказки, серии и дополнительные компоненты. Значения можно разместить в series.data или передать через dataset. Второй вариант удобен, когда таблица используется несколькими сериями: dimensions и encode явно связывают поля с осями. Название показателя не приходится угадывать по позиции числа в длинном массиве.

const option = {
  dataset: { source: [
    ['Услуга', 'Выручка', 'Заказы'],
    ['Поддержка', 780, 94], ['Аудит', 460, 38]
  ] },
  xAxis: { type: 'value', name: 'тыс. ₽' },
  yAxis: { type: 'category' },
  series: [{ type: 'bar', encode: { x: 'Выручка', y: 'Услуга' } }]
};

Обычно setOption объединяет новые настройки с предыдущими. Это полезно для изменения существующей серии, но при переходе от Sankey к линии старые компоненты не нужны. В мастерской сценарий заменяется через notMerge: true. Если обновляете только данные одной диаграммы, используйте стабильные id серий и осмысленное частичное обновление вместо пересоздания экземпляра.

chart.setOption(nextOption, { notMerge: true });

Официальная документация: dataset, dimensions и encode.

1. Выручка по дням: линия, план и dataZoom

Первый пример показывает фактическую выручку и план за октябрь. Линия позволяет увидеть рост, провалы и отдельные дни отклонения. План оформлен пунктиром, поэтому различие сохраняется не только за счёт цвета. Ось подписана в тысячах рублей, а карточка и таблица используют ту же единицу.

Можно выбрать последние 7, 14 или все 30 дней. Ползунок dataZoom приближает часть периода, не удаляя остальные значения из приложения. Для клавиатуры есть кнопки приближения последних семи точек и возврата. CSV сохраняет весь выбранный набор: масштаб просмотра и фильтр данных — разные операции.

dataZoom: [
  { type: 'slider', start: 0, end: 100 },
  { type: 'inside', zoomOnMouseWheel: 'ctrl', moveOnMouseWheel: false }
]
// Кнопка приближает известный диапазон.
chart.dispatchAction({
  type: 'dataZoom',
  startValue: Date.parse('2026-10-24T00:00:00Z'),
  endValue: Date.parse('2026-10-30T00:00:00Z')
});

Не превращайте пропуски в нули автоматически. Ноль означает измеренное отсутствие результата, пропуск — отсутствие наблюдения. Временную зону агрегирования тоже выбирают явно: московский дневной отчёт и UTC-отчёт имеют разные границы суток. Здесь используются фиксированные UTC-даты, чтобы результат не зависел от настроек компьютера.

2. Состав заявок: накопленные столбцы и проценты

Каждая неделя содержит заявки из поиска, рекламы и рекомендаций. Высота столбца показывает общий поток, цветные части — вклад каналов. Это помогает понять, выросли ли все источники или результат изменился из-за одной кампании. Одинаковое значение stack объединяет серии в один столбец.

series: [
  { name: 'Поиск', type: 'bar', stack: 'requests', data: [70, 78, 86] },
  { name: 'Реклама', type: 'bar', stack: 'requests', data: [35, 52, 69] },
  { name: 'Рекомендации', type: 'bar', stack: 'requests', data: [24, 27, 30] }
]

Переключатель долей отвечает на вопрос об изменении структуры. Значение канала делится на сумму своей недели и умножается на сто. Для нулевой суммы нужен отдельный случай: деление на ноль не должно давать NaN. В учебных данных все недели имеют заявки, а таблица сохраняет исходные количества и итог.

Процентный график может скрывать падение объёма: сто процентов от десяти обращений выглядят так же, как сто процентов от тысячи. Поэтому рядом полезна числовая карточка. Расходы и заявки нельзя складывать в один stack: они имеют разные единицы и не образуют содержательного целого.

Официальная документация: накопленные столбцы.

3. Рейтинг услуг: сортировка и единицы

Горизонтальные столбцы удобны для названий услуг, филиалов и категорий. На вертикальном графике длинные подписи приходится поворачивать и уменьшать. В мастерской восемь услуг сравниваются по выручке или заказам. Смена показателя меняет порядок: услуги с большим чеком не обязательно лидируют по числу обращений.

Сортируйте целые записи, а не отдельно массив названий и массив чисел. Иначе аккуратный график подпишет чужие значения. Таблица и dataset здесь создаются из одного отсортированного массива. Порядок по названию полезен, когда пользователь ищет известную услугу, а не исследует рейтинг.

const ranked = [...services].sort((a, b) => b.revenue - a.revenue);
const source = [
  ['Услуга', 'Выручка'],
  ...ranked.map(item => [item.name, item.revenue])
];

Не обрезайте шкалу столбцов так, чтобы небольшое различие выглядело многократным. Для положительных величин естественна нулевая база. Если показываете отклонение от плана, назовите показатель отклонением и предусмотрите отрицательные значения. Подпись «выручка» без валюты и масштаба оставляет слишком много места для ошибки.

4. Scatter: трудозатраты и необычные проекты

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

series: [{
  type: 'scatter',
  data: [
    { name: 'Проект 01', value: [24, 72, 8], symbolSize: 16 },
    { name: 'Проект 02', value: [90, 155, 20], symbolSize: 22 }
  ],
  dimensions: ['Часы', 'Выручка, тыс. ₽', 'Заявки'],
  encode: { tooltip: [0, 1, 2] }
}]

Видимая связь не доказывает причину. На выручку влияют тип договора, состав услуги и срок проекта. Диаграмма помогает выбрать случаи для изучения, а не автоматически оценить сотрудника. В продукте полезно открывать карточку проекта по стабильному ID, сохраняя фильтр для возврата.

Если радиус пузырька растёт прямо пропорционально числу заявок, площадь растёт квадратично и усиливает различия. Здесь размер ограничен и использует корень из количества; точное число доступно в таблице. Для плотных наборов понадобятся прозрачность, агрегация или другой способ исследования.

5. Heatmap: дни, часы и нагрузка

Тепловая карта сводит два измерения в сетку. Семь дней и двенадцать часов дают 84 ячейки; цвет обозначает число обращений. Менеджер поддержки может найти пятничный пик или увидеть низкую нагрузку вечером, затем проверить значения в таблице. Переключение на учебную акцию усиливает только определённые часы пятницы.

xAxis: { type: 'category', data: ['08:00', '09:00', '10:00'] },
yAxis: { type: 'category', data: ['Пн', 'Вт'] },
visualMap: { min: 0, max: 50, calculable: false },
series: [{ type: 'heatmap', data: [[0, 0, 12], [1, 0, 28], [2, 1, 43]] }]

Тройка ячейки — индекс часа, индекс дня и измеренное число. Порядок легко перепутать, поэтому сверяйте известные координаты вручную. На узком экране подписи ячеек скрываются, чтобы не создать нечитаемую сетку; таблица сохраняется. Отсутствующее наблюдение нельзя смешивать с настоящим нулём, особенно в мониторинге.

В переключении на акцию меняется максимум шкалы. Поэтому одинаковый оттенок в разных режимах может означать разные значения. Для непосредственного сравнения недель используйте общую фиксированную шкалу. Автоматический диапазон удобен для изучения одного набора, а не для доказательства одинаковой нагрузки по цвету.

6. Funnel: конверсия одной когорты

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

const visitors = 4200;
const paid = 126;
const conversion = visitors > 0 ? paid / visitors * 100 : null;
// 3%; null обозначает отсутствие корректного знаменателя.

Если взять посещения за текущую неделю, заявки за прошлую и оплаты по всем старым договорам, фигура всё ещё нарисуется, но корректной воронки не получится. Определите когорту, окно наблюдения и правила перехода. Например, оплаты в течение тридцати дней после первого обращения, отдельно отмечая ещё не завершённые наблюдения.

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

7. Sankey: пути заявок и баланс потоков

Sankey показывает связи, ширина которых соответствует величине потока. Обращения приходят из поиска, рекламы и рекомендаций, распределяются по аудиту, поддержке и настройке, затем получают результат. Промежуточный узел сохраняет баланс: сколько заявок вошло, столько вышло в выполненные и отложенные.

series: [{
  type: 'sankey',
  data: [{ name: 'Поиск' }, { name: 'Аудит' }, { name: 'Выполнено' }],
  links: [
    { source: 'Поиск', target: 'Аудит', value: 70 },
    { source: 'Аудит', target: 'Выполнено', value: 70 }
  ]
}]

Фильтр услуги оставляет её входящие и исходящие связи. Это полезно для объяснения распределения нагрузки или движения пользователей по этапам. Но сотни пересекающихся связей быстро превращаются в спутанный рисунок. Выбирайте ограниченный уровень детализации и давайте переход к конкретному направлению.

Нельзя складывать все связи, чтобы получить число уникальных заявок: один поток проходит несколько этапов и учитывается повторно. Карточка мастерской суммирует только входы от каналов. В реальной системе также определитесь с возвратами и повторными обращениями: простая последовательность не описывает циклы автоматически.

8. Gauge: загрузка ресурса и смысл порогов

Круглая шкала подходит для одного показателя с понятными границами: занято из ста слотов, заполнение ёмкости или выполнение плана. Ползунок и числовое поле изменяют одну загрузку. Подпись сообщает оставшийся ресурс, а при высокой занятости меняются цвет и текст. Это альтернатива чтению положения стрелки.

series: [{
  type: 'gauge', min: 0, max: 100,
  detail: { formatter: '{value}%' },
  data: [{ name: 'Занято слотов', value: 72 }]
}]

Порог 85 процентов выбран для демонстрации, а не как универсальная норма. У поддержки, сервера и производственного оборудования разные ограничения. Поясните, что измеряется, как часто обновляется значение и какое действие ожидается при превышении. Цвет без такого контекста создаёт тревогу, но не помогает принять решение.

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

9. Drilldown: регион, филиал и HTTP-запрос

Сначала видны три региона; после выбора открываются пять филиалов одного региона. Нажатие столбца и выбор в обычном списке выполняют одно действие. Кнопка возврата восстанавливает обзор. Это подходит для продаж, мониторинга подразделений и любой панели с переходом от суммы к составляющим.

chart.on('click', event => {
  const region = event.data?.region;
  if (['north', 'center', 'south'].includes(region)) loadRegion(region);
});

В данные столбца помещается устойчивый ключ. Подпись можно перевести или переименовать, а ключ остаётся прежним. Сервер и браузер ограничивают допустимые регионы; ответ проверяется на соответствие выбранному уровню. Не используйте произвольное название из графика как готовый путь к файлу или URL.

Во время запроса старый график очищается, экспорт отключается и появляется загрузка. Иначе заголовок уже говорил бы «Север», а числа ещё относились бы ко всем регионам. При ошибке сохраняется выбранный уровень, показывается сообщение и повтор. Успех, ожидание и ошибка — части интерфейса, а не только записи в консоли.

Официальная документация: события и dispatchAction.

Запоздавшие ответы, отмена и обработка ошибок

Пользователь может выбрать несколько регионов подряд. Первый запрос иногда заканчивается позже второго и возвращает неактуальные данные. В проекте применяется AbortController и дополнительный номер поколения запроса. Только последний активный ответ может обновить диаграмму: отмена транспорта сама по себе не заменяет проверку актуальности результата.

let controller;
let sequence = 0;
async function loadRegion(region) {
  controller?.abort();
  controller = new AbortController();
  const current = ++sequence;
  const response = await fetch(`api.php?view=${encodeURIComponent(region)}`, {
    signal: controller.signal, cache: 'no-store'
  });
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const payload = await response.json();
  if (current !== sequence) return;
  // Проверить схему payload, затем обновить график.
}

Это сокращённый фрагмент. Полная мастерская ограничивает ожидание, проверяет форму JSON и показывает кнопку повтора. Она не превращает HTTP 500 в пустой успех. Пустой набор, отсутствие доступа и недоступный сервер требуют разных сообщений: «нет продаж» не должно означать «мы не смогли спросить базу».

В ECharts есть showLoading и hideLoading. Связывайте их с жизненным циклом конкретного запроса. Если старый запрос завершился после переключения, его finally не должен снимать индикатор нового. В архиве гонка ответов проверяется отдельно от вычислений графика.

Официальная документация: асинхронная загрузка.

PHP JSON API: рабочий пример только для чтения

Настоящий PHP-обработчик принимает view, выбирает один из четырёх известных файлов и возвращает JSON. Записей на диск, SQL-запросов и пользовательских команд в нём нет. Так можно изучить контракт и сетевое взаимодействие, затем заменить источник на свою базу, не меняя вид графика.

<?php
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
$view = $_GET['view'] ?? 'overview';
$allowed = ['overview', 'north', 'center', 'south'];
if (!is_string($view) || !in_array($view, $allowed, true)) {
    http_response_code(400);
    echo json_encode(['error' => 'UNKNOWN_VIEW']);
    exit;
}
// Полный обработчик в архиве проверяет метод, чтение и JSON.

Полный server/api.php проверяет метод GET, неизвестные параметры, длину запроса и флаг учебного сбоя. Он возвращает 400 для неверного ввода, 405 для другого метода и 503 при учебной или файловой ошибке. Ответ содержит Content-Type, no-store и nosniff. Это делает проблему диагностируемой, когда интерфейс не получает ожидаемые значения.

Статические JSON публичны, потому что содержат только учебные числа. При подключении клиентских данных нельзя оставлять рядом открытые копии. Авторизация и проверка организации выполняются до построения ответа. Переданный браузером ID клиента не подтверждает право читать статистику, даже если график расположен в административном меню.

Как подключить собственные данные

Сначала зафиксируйте контракт показателя: период, часовой пояс, валюту, округление и единицу строки. Для выручки решите, считаете ли вы оплаченные счета, закрытые акты или созданные заказы. Если панель смешивает разные определения, аккуратный интерфейс только ускоряет распространение неверного вывода. Подпись должна раскрывать смысл, а не просто повторять имя поля базы.

Серверу разумнее агрегировать данные до нужного уровня: день, услуга или филиал. Не передавайте все заказы ради тридцати точек. Параметры SQL связываются через подготовленный запрос, диапазон периода ограничивается. Тяжёлые агрегаты можно рассчитывать заранее или кешировать с понятным временем актуальности. Рядом с результатом полезны время формирования и признак неполного периода.

На сайте разобраны Redis и срок жизни кеша, а также Carbon и работа с датами. ECharts показывает результат, а кеш и временные правила помогают получить его быстро и правильно. Эти задачи связаны, но не должны смешиваться в одном обработчике графика.

Таблица, клавиатура и уменьшение движения

График удобен для обзора, но не заменяет точные значения. Под диаграммой находится таблица с заголовками, подписью и текущими строками. Сценарии переключаются стандартным select, детализация доступна без попадания пальцем в столбец, а числовой ввод заменяет ползунок. Табличная область прокручивается отдельно и выгружается в CSV.

ECharts поддерживает ARIA-описания. Мы задаём описание задачи и указываем, что значения доступны ниже. Это полезнее перечисления десятков точек одним длинным aria-label. В модульной сборке AriaComponent подключается явно; полная сборка содержит нужный компонент. Автоматическое описание не делает все графические действия доступными с клавиатуры, поэтому внешние контролы остаются важными.

aria: {
  enabled: true,
  label: {
    enabled: true,
    description: 'Выручка и план по дням. Точные значения — в таблице ниже.'
  }
}

Анимация учитывает prefers-reduced-motion, её можно отключить вручную. Движение должно помогать понять изменение, а не мешать читать цифры. Высокая загрузка сопровождается текстовым сообщением. Поддержку конкретного скринридера и физического устройства следует проверять в конечном продукте отдельно: наличие ARIA не является доказательством полной доступности.

Официальная документация: доступность и ARIA.

Экспорт SVG, CSV и текущего JavaScript

SVG удобно вставлять в документацию: линии и подписи остаются векторными. Мастерская выгружает текущий график через Blob, включая выбранный масштаб. CSV сохраняет значения для проверки и дальнейшей обработки. В нём есть UTF-8 BOM для распространённых табличных редакторов, кавычки и экранирование вложенных кавычек.

const svg = chart.renderToSVGString({ useViewBox: true });
const url = URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml' }));
const link = document.createElement('a');
link.href = url;
link.download = 'report.svg';
link.click();
setTimeout(() => URL.revokeObjectURL(url), 30000);

Строки, начинающиеся с символов формулы, требуют отдельной защиты при CSV-экспорте: одних кавычек недостаточно, если файл откроют в Excel. В проекте такие текстовые значения получают защитный префикс. Это полезно при замене учебных названий пользовательским вводом. Про импорт и серверный CSV есть отдельная практическая статья.

Option.json и JavaScript сохраняют выбранный сценарий, настройки внешних полей и текущий набор данных. Ручное приближение dataZoom и скрытие серий нажатием легенды в этот снимок не входят: после запуска скачанного кода масштаб и легенда будут исходными. Для изображения именно показанного состояния используйте SVG. CSV, как и таблица, содержит весь выбранный период, включая точки вне приближенной области.

Снимок конфигурации не содержит обработчики кнопок, авторизацию и асинхронную загрузку; для них скачайте полный проект. Если нужно сохранять состояние рабочего дашборда, отдельно считывайте события масштабирования и легенды в модель приложения. Не пытайтесь сериализовать произвольные функции через JSON.stringify и ожидать автоматического восстановления: функции при таком преобразовании теряются.

Размер контейнера, вкладки и освобождение ресурсов

Ширина меняется не только при изменении окна. Пользователь может свернуть боковое меню, открыть вкладку или развернуть панель. ResizeObserver отслеживает контейнер и вызывает chart.resize. Здесь дополнительно меняется плотность подписей: на узкой ширине часть подписей heatmap скрывается, но таблица сохраняет все значения.

const observer = new ResizeObserver(() => chart.resize());
observer.observe(element);
// Когда компонент окончательно удаляется:
observer.disconnect();
chart.dispose();

Если график создан внутри display:none, первый расчёт размеров может быть неверным. После показа вызовите resize, а при удалении узла освободите экземпляр. В одностраничном приложении отключайте обработчики и отменяйте незавершённые запросы. Иначе после нескольких переходов появляются повторные события и растёт потребление памяти.

Уход со страницы не всегда означает окончательное удаление. Браузер может сохранить её в back-forward cache для быстрого возврата кнопкой «Назад». В обработчике pagehide мастерская проверяет event.persisted: у сохранённой страницы экземпляр графика остаётся рабочим; dispose вызывается только при окончательном уходе. Иначе после возврата поля и таблица менялись бы, а уничтоженный график перестал бы обновляться.

Не задавайте графику фиксированную ширину, превышающую экран. Высота может оставаться постоянной для читаемости, а ширина обычно следует контейнеру. При этом сам факт отсутствия горизонтальной прокрутки страницы ещё не означает удобства: проверьте подписи воронки, легенду, таблицу и кнопки на реальной узкой области.

Официальная документация: размеры и жизненный цикл.

Официальная документация: pagehide и сохранение страницы в back-forward cache.

Canvas или SVG: выбор под объём данных

ECharts рисует через Canvas и SVG. Здесь используется SVG: наборы умеренные, важны чёткие подписи и векторный экспорт. При полном импорте доступны оба варианта. В сборке через echarts/core renderer и компоненты регистрируются отдельно, что позволяет уменьшить состав итогового файла и явно контролировать подключённые возможности.

Для больших массивов точек или специальных эффектов измерьте Canvas. Нельзя обещать, что один renderer всегда быстрее другого: результат зависит от числа элементов, обновлений, устройства и окна. Проверяйте сценарий пользователя, включая несколько диаграмм на странице, а не единственный статичный график на мощном компьютере.

До смены renderer проверьте объём и частоту обновлений. Иногда достаточно агрегировать секундные измерения по минутам или ограничить период. Десять тысяч подписей не становятся удобными только потому, что браузер смог их нарисовать. Производительность и читаемость связаны; оптимизация не должна уничтожать смысл данных.

Официальная документация: выбор Canvas и SVG.

Запуск под Windows

Распакуйте архив в отдельный каталог. Для статического просмотра нужен Node.js 24.x; JavaScript и CSS уже лежат в public, поэтому сначала можно запустить сервер без установки npm-зависимостей. Открытие через file:// не подходит для fetch JSON. В PowerShell перейдите в проект и запустите локальный сервер:

Set-Location C:\projects\echarts-project
node --version
node scripts/server.mjs
# Открыть http://127.0.0.1:8131
# Остановка: Ctrl+C

Редактируйте src/model.js, src/options.js и src/app.js. Сборка создаёт public/app.js заново, поэтому прямые изменения сжатого файла будут потеряны. npm ci использует lock-файл; затем можно собрать проект и выполнить тесты. Не обновляйте версии библиотек одновременно с первым знакомством с кодом: сначала получите воспроизводимый исходный результат.

npm ci
npm run build
npm test
# При установленном PHP 8.3+:
php -S 127.0.0.1:8136 -t public server/router.php

Последняя команда включает настоящий API. Встроенный PHP-сервер предназначен для локального знакомства, а не публикации в интернете. Если PHP не установлен, используйте Docker Desktop с Linux containers. Для curl в Windows при необходимости вызывайте curl.exe, чтобы не попасть в PowerShell-псевдоним с другим синтаксисом.

Linux и Docker: серверный запуск

На Linux статический режим запускается тем же Node.js-скриптом. Для PHP есть Dockerfile: первая стадия устанавливает зависимости, собирает интерфейс и запускает тесты, вторая содержит Apache и PHP. Node_modules не копируется в runtime. Порт открыт только на loopback, поэтому внешний доступ контролируется HTTPS-прокси.

cd ~/projects/echarts-project
docker compose up -d --build
docker compose ps
docker compose logs --tail=50 web
curl -f 'http://127.0.0.1:8136/api.php?view=north'
# Остановить только этот проект:
docker compose down

На Ubuntu или Debian предварительно устанавливаются Docker Engine и Compose plugin. Если пользователю не предоставлен доступ к daemon, используйте sudo docker. Не исправляйте ошибку доступа открытием сокета всем пользователям: права на Docker дают широкие права на хосте. Без Docker остаётся вариант с установленным PHP и локальным встроенным сервером.

Зафиксированы Node 24.22.2 и PHP 8.5.12. Чистая сборка требует registry образов и npm, но готовый интерфейс не обращается к CDN. При конфликте порта задайте PORT перед Compose. Устройство контейнерного проекта подробнее рассмотрено в материале про Docker Compose.

Официальная документация: Docker Engine для Ubuntu.

Linux VPS: HTTPS, доступ и журналы

Создайте отдельный каталог релиза, запустите контейнер на 127.0.0.1:8136 и проверьте API локально. Затем настройте домен и Caddy. Убедитесь, что порты 80 и 443 не заняты другим сервисом; конфигурацию существующего сайта нельзя заменять примером. В deploy/Caddyfile.example есть отдельный блок для подключения через import.

charts.example.com {
    basic_auth {
        demo REPLACE_WITH_CADDY_HASH
    }
    reverse_proxy 127.0.0.1:8136
    log {
        output file /var/log/caddy/echarts-access.json
        format json
    }
}

Хеш отдельного пароля получается через caddy hash-password. Открытый пароль не сохраняется в конфигурации или Git. Каталог журналов должен принадлежать caddy. После изменения выполняются validate и reload, затем проверка без пароля и с паролем. Без авторизации ожидается 401; успешный API-запрос возвращает JSON.

sudo install -d -o caddy -g caddy -m 0750 /var/log/caddy
caddy hash-password
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
curl -I https://charts.example.com
curl -u demo 'https://charts.example.com/api.php?view=north'
sudo journalctl -u caddy --since '30 minutes ago'
sudo docker compose -p echarts-demo logs --tail=100 web

BasicAuth закрывает стенд целиком, но не заменяет роли CRM. Публичный сертификат зависит от DNS и сетевой доступности. При проблеме проверяйте журнал и домен, а не отключайте проверку сертификатов. Устройство прокси разобрано в статье о Caddy.

Обновление, откат и контроль результата

Сохраните предыдущий архив и конфигурацию прокси. Новую версию распакуйте рядом, восстановите зависимости по lock-файлу и соберите. После проверки сценариев замените контейнер с тем же именем Compose-проекта. Так меняется конкретное приложение, а не весь Docker-хост или общий веб-сервер.

cd /srv/echarts/releases/2026-12-01
sudo docker compose -p echarts-demo build
sudo docker compose -p echarts-demo up -d
curl -f 'http://127.0.0.1:8136/api.php?view=overview'
# Откат: перейти в предыдущий каталог релиза и выполнить
# sudo docker compose -p echarts-demo up -d --build

Возможна короткая остановка; это не схема без перерыва обслуживания. В примере нет записываемой базы, поэтому откат контейнера не требует миграций. В рабочем приложении резервная копия и совместимость схемы рассматриваются отдельно. Старый ZIP не гарантирует возможность откатить уже изменённую базу.

После развёртывания проверьте JSON, CSS, JavaScript, типы содержимого и скачивание архива. В браузере выполните переход к филиалу, учебную ошибку и повтор. Это проверка пользовательского пути: HTTP 200 у пустой HTML-оболочки ещё не означает, что аналитика работает. Для проверки API в проект включён scripts/check-api.mjs.

Как собрать из примеров полезную панель

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

Связывайте диаграммы общими фильтрами там, где это имеет смысл. Если выбран филиал, период и валюта, человек ожидает эти условия во всех связанных карточках. Разные показатели могут обновляться с разной задержкой: показывайте актуальность, вместо ложного ощущения единого моментального снимка.

Расписание из мастерской FullCalendar показывает конкретные записи, ECharts — распределение занятости по дням и услугам. Полезный переход ведёт от необычного пика к отфильтрованному списку событий. Сохраняйте контекст возврата: период и выбранная услуга не должны исчезать при открытии детали.

Частые ошибки и диагностика

Если график не виден, проверьте размеры контейнера, загрузку app.js и консоль. Если неверны подписи, сравните encode с колонками dataset. Если старые серии остаются после смены типа, проверьте merge. Если интерфейс завис в ожидании, убедитесь, что загрузка снимается при ошибке, а не только в успешной ветке.

Ошибки данных важнее ошибок рисования. Рубли вместо тысяч рублей, разные временные зоны, повторное суммирование потоков Sankey и смешанные когорты дают убедительные, но неверные картинки. В проекте проверяются баланс потоков, агрегаты регионов, доли и соответствие таблицы фильтрам. Проверки привязаны к смыслу результата, а не к случайным CSS-классам.

Автоматические проверки используют настоящий SVG renderer ECharts. Браузерная сборка проходит переключения, HTTP-ошибку и повтор, отдельный скрипт обращается к PHP. Это дополняет чтение графика человеком: подписи, легенда и удобство кнопок оцениваются в самом интерфейсе, в том числе на узкой ширине.

Исходники и официальные материалы

В архиве находятся готовая public-сборка, JavaScript, JSON-наборы детализации, PHP-обработчик, Dockerfile, Compose, пример Caddy, README для Windows/Linux/VPS, тесты и лицензии. Используйте его как основу: замените учебный источник, сохраните контракт и постепенно уберите ненужные сценарии. Полный набор возможностей не обязательно должен попадать в первый релиз вашей панели.

Начните с вопроса пользователя, перенесите подходящий график и проверьте его на реальных данных. Затем добавьте таблицу, фильтры, ошибку загрузки и детализацию. Человек должен понимать происхождение цифр, иметь возможность проверить их и знать, что делать после обнаруженного отклонения. Именно это превращает визуализацию в рабочий инструмент.