Состояние, синхронизация, экспорт
Состояние чарта
Зум и скрытые легендой серии — сериализуемое состояние:
const state = chart.getState();
// { zoom: { x: [0.25, 0.75] }, hiddenSeries: ['line-1', 'histogram-0#1'] }
localStorage.setItem('chart-state', JSON.stringify(state));
// восстановление — при создании или на живом инстансе
Charts.create({ ...options, initialState: JSON.parse(saved) });
await chart.setState(JSON.parse(saved));В hiddenSeries попадают и элементы серии, которая кладёт в легенду несколько пунктов, — секторы pie/donut и группы гистограммы, в виде 'seriesId#index'. Они восстанавливаются вместе со всем остальным, и, поскольку серии пересобираются на каждом update(), именно это не даёт фильтру легенды слетать при любой правке опций.
Синхронизация чартов
Чарты с одним sync.groupId разделяют подсветку узлов и окно зума:
Charts.create({ ...top, sync: { groupId: 'dashboard' } });
Charts.create({ ...bottom, sync: { groupId: 'dashboard' } });| Опция | По умолчанию | Описание |
|---|---|---|
groupId | 'default' | имя группы |
nodeInteraction | true | синхронизация подсветки (по индексу данных) |
zoom | true | синхронизация окна зума |
Модульная сборка
В сборке через grafit-charts/core синхронизация — отдельный модуль: register(syncModule).
Контекстное меню
contextMenu: { enabled: true } — правый клик открывает меню: «Скачать PNG», «Сбросить зум» (когда есть зум) и extraItems: [{ label, action }]. В сборке через grafit-charts/core — отдельный модуль: register(contextMenuModule).
Экспорт
chart.download({ fileName: 'report.png' });
const dataUrl = chart.getImageDataURL();Анимация
Появление серий анимируется по умолчанию (600 мс, ease-out). При update/updateDelta новые данные перетекают на место, а не подменяют нарисованное: строки сопоставляются, их числовые поля едут к новым значениям, оси едут вместе с ними. Открытая в этот момент подсказка остаётся на своём узле, и числа в ней не отстают.
Строки сопоставляются по позиции, поэтому изменение их количества рисуется сразу. Укажите key — поле, по которому строка остаётся той же строкой, — и оставшиеся строки продолжат перетекать, сколько бы ни пришло и ни ушло: приходящая вырастает от нуля своих полей значения и раскрывает собственную полосу, уходящая опускается и закрывает свою за собой — соседние категории расширяются плавно, а не рывком.
Ось значений едет к новым границам вместе с данными, а не пересчитывается по строкам в движении: её деления остаются круглыми числами конечной шкалы, и ничего на графике не прыгает в момент смены градации.
chart.update({ ...options, data: next, animation: { key: 'month', updateDuration: 300 } });Кнопка выдаёт новое показание — столбики едут к новым высотам, а выбывший сервис опускается, пока приходящий на его место вырастает:
import { getData, newServices, newValues, type Reading } from './data';
import type { ChartInstance, ChartOptions } from 'grafit-charts';
// Data replaced on demand: the bars walk to their new heights and the axis
// travels with them. `key` says what makes a bar the same bar between readings,
// so a service that drops out sinks away while the one taking its place grows in.
export function createOptions(): ChartOptions {
return {
data: getData(),
title: { text: 'Requests per minute' },
series: [{ type: 'bar', xField: 'service', yField: 'requests', name: 'Requests', cornerRadius: 4 }],
legend: { enabled: false },
tooltip: {},
animation: { key: 'service', updateDuration: 900 },
};
}
// Buttons under the demo. The data of the chart is the state of the demo:
// each reading is built from the one on screen, so the bars drift rather than jump.
export const actions = [
{
label: 'New values',
run: (chart: ChartInstance) => update(chart, newValues),
},
{
label: 'New services',
run: (chart: ChartInstance) => update(chart, newServices),
},
];
function update(chart: ChartInstance, next: (previous: Reading[]) => Reading[]): void {
const options = chart.getOptions();
void chart.update({ ...options, data: next((options.data as Reading[]) ?? getData()) });
}/** Services the dashboard watches; a reading shows a few of them at a time. */
const SERVICES = ['auth', 'search', 'checkout', 'catalog', 'billing', 'media', 'notify'];
/** A type rather than an interface: a chart takes rows as `Datum`, and only a type alias reads as one. */
export type Reading = {
service: string;
requests: number;
};
/** The starting frame, fixed — a demo that opens differently every time is hard to read. */
export function getData(): Reading[] {
return [
{ service: 'auth', requests: 820 },
{ service: 'search', requests: 1340 },
{ service: 'checkout', requests: 460 },
{ service: 'catalog', requests: 1180 },
{ service: 'billing', requests: 240 },
];
}
/** The same services, read again: every bar walks to a new height. */
export function newValues(previous: Reading[]): Reading[] {
return previous.map((row) => ({ service: row.service, requests: drift(row.requests) }));
}
/**
* A different handful of services. The ones that stayed keep the load they had
* and carry on from it; the rest arrive and leave — which is what `key` is for.
*/
export function newServices(previous: Reading[]): Reading[] {
return pickServices(previous.length).map((service) => ({
service,
requests: drift(previous.find((row) => row.service === service)?.requests ?? 600),
}));
}
/** A random handful, in a stable order — the axis should not reshuffle on its own. */
function pickServices(count: number): string[] {
const pool = [...SERVICES];
const picked: string[] = [];
while (picked.length < count && pool.length > 0) {
picked.push(...pool.splice(Math.floor(Math.random() * pool.length), 1));
}
return picked.sort((a, b) => SERVICES.indexOf(a) - SERVICES.indexOf(b));
}
/** Within ±35% of the previous value, clamped to a plausible range. */
function drift(value: number): number {
return Math.round(Math.min(1600, Math.max(120, value * (0.65 + Math.random() * 0.7))));
}animation: { enabled: false } выключает обе анимации. updateEnabled отвечает только за обновление и главнее там, где задан: график может появляться сразу, а дальше двигаться. update() разрешается, когда переход доехал, — как и waitForUpdate().
Опции
| Опция | Тип | По умолчанию | Описание |
|---|---|---|---|
animation.enabled | boolean | true | анимация входа и обновлений |
animation.duration | number | 600 | длительность появления, мс |
animation.updateEnabled | boolean | animation.enabled | переход при обновлении отдельно |
animation.updateDuration | number | duration, иначе 450 | длительность перехода, мс |
animation.key | string | (datum, index) => unknown | — | по чему строка — та же строка |
contextMenu.enabled | boolean | true | меню по правому клику |
contextMenu.extraItems | { label, action }[] | — | свои пункты после стандартных |
download(options) | { fileName?, fileFormat? } | chart.png | экспорт PNG/JPEG |
initialState | ChartState | — | стартовый зум и скрытые серии |