@pushwoosh/kit-charts 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/LICENSE +21 -0
  2. package/ai-documentation.md +378 -0
  3. package/core/Chart/Axes.d.ts +19 -0
  4. package/core/Chart/Axes.js +204 -0
  5. package/core/Chart/Chart.d.ts +33 -0
  6. package/core/Chart/Chart.js +616 -0
  7. package/core/Chart/Chart.styles.d.ts +109 -0
  8. package/core/Chart/Chart.styles.js +227 -0
  9. package/core/Chart/ChartErrorBoundary.d.ts +15 -0
  10. package/core/Chart/ChartErrorBoundary.js +27 -0
  11. package/core/Chart/Hinted.d.ts +11 -0
  12. package/core/Chart/Hinted.js +63 -0
  13. package/core/Chart/Legend.d.ts +15 -0
  14. package/core/Chart/Legend.js +92 -0
  15. package/core/Chart/PlotDecorations.d.ts +17 -0
  16. package/core/Chart/PlotDecorations.js +172 -0
  17. package/core/Chart/StackLabels.d.ts +6 -0
  18. package/core/Chart/StackLabels.js +85 -0
  19. package/core/Chart/Titles.d.ts +6 -0
  20. package/core/Chart/Titles.js +67 -0
  21. package/core/Chart/Tooltip.d.ts +13 -0
  22. package/core/Chart/Tooltip.js +87 -0
  23. package/core/Chart/useSize.d.ts +5 -0
  24. package/core/Chart/useSize.js +45 -0
  25. package/core/animation/motion.d.ts +20 -0
  26. package/core/animation/motion.js +20 -0
  27. package/core/animation/useTweenedNumbers.d.ts +1 -0
  28. package/core/animation/useTweenedNumbers.js +51 -0
  29. package/core/data/points.d.ts +14 -0
  30. package/core/data/points.js +73 -0
  31. package/core/format/time.d.ts +2 -0
  32. package/core/format/time.js +38 -0
  33. package/core/geometry/path.d.ts +19 -0
  34. package/core/geometry/path.js +76 -0
  35. package/core/layout/legend-layout.d.ts +21 -0
  36. package/core/layout/legend-layout.js +103 -0
  37. package/core/layout/measure-text.d.ts +9 -0
  38. package/core/layout/measure-text.js +57 -0
  39. package/core/layout/metrics.d.ts +63 -0
  40. package/core/layout/metrics.js +66 -0
  41. package/core/layout/point-width.d.ts +11 -0
  42. package/core/layout/point-width.js +86 -0
  43. package/core/layout/title-layout.d.ts +30 -0
  44. package/core/layout/title-layout.js +99 -0
  45. package/core/model/build-model.d.ts +69 -0
  46. package/core/model/build-model.js +556 -0
  47. package/core/model/palette.d.ts +15 -0
  48. package/core/model/palette.js +17 -0
  49. package/core/options/normalize.d.ts +8 -0
  50. package/core/options/normalize.js +44 -0
  51. package/core/registry/registry.d.ts +5 -0
  52. package/core/registry/registry.js +32 -0
  53. package/core/scale/band.d.ts +10 -0
  54. package/core/scale/band.js +33 -0
  55. package/core/scale/color.d.ts +9 -0
  56. package/core/scale/color.js +69 -0
  57. package/core/scale/linear.d.ts +17 -0
  58. package/core/scale/linear.js +64 -0
  59. package/core/scale/time.d.ts +19 -0
  60. package/core/scale/time.js +187 -0
  61. package/core/stack/stack.d.ts +9 -0
  62. package/core/stack/stack.js +76 -0
  63. package/core/types/formatter.d.ts +59 -0
  64. package/core/types/formatter.js +1 -0
  65. package/core/types/geometry.d.ts +16 -0
  66. package/core/types/geometry.js +1 -0
  67. package/core/types/options.d.ts +331 -0
  68. package/core/types/options.js +2 -0
  69. package/core/types/renderer.d.ts +101 -0
  70. package/core/types/renderer.js +1 -0
  71. package/core/types/scale.d.ts +34 -0
  72. package/core/types/scale.js +1 -0
  73. package/index.d.ts +16 -0
  74. package/index.js +10 -0
  75. package/package.json +64 -0
  76. package/presets/AreaChart.d.ts +3 -0
  77. package/presets/AreaChart.js +13 -0
  78. package/presets/ColumnChart.d.ts +3 -0
  79. package/presets/ColumnChart.js +13 -0
  80. package/presets/LineChart.d.ts +3 -0
  81. package/presets/LineChart.js +13 -0
  82. package/presets/PieChart.d.ts +3 -0
  83. package/presets/PieChart.js +13 -0
  84. package/presets/StackedColumnChart.d.ts +7 -0
  85. package/presets/StackedColumnChart.js +31 -0
  86. package/presets/index.d.ts +5 -0
  87. package/presets/index.js +5 -0
  88. package/series/area/area.d.ts +2 -0
  89. package/series/area/area.js +57 -0
  90. package/series/area/index.d.ts +1 -0
  91. package/series/area/index.js +1 -0
  92. package/series/column/column.d.ts +2 -0
  93. package/series/column/column.js +163 -0
  94. package/series/column/index.d.ts +1 -0
  95. package/series/column/index.js +1 -0
  96. package/series/heatmap/heatmap.d.ts +2 -0
  97. package/series/heatmap/heatmap.js +172 -0
  98. package/series/heatmap/index.d.ts +1 -0
  99. package/series/heatmap/index.js +1 -0
  100. package/series/line/index.d.ts +1 -0
  101. package/series/line/index.js +1 -0
  102. package/series/line/line.d.ts +2 -0
  103. package/series/line/line.js +49 -0
  104. package/series/pie/index.d.ts +1 -0
  105. package/series/pie/index.js +1 -0
  106. package/series/pie/pie.d.ts +2 -0
  107. package/series/pie/pie.js +224 -0
  108. package/series/shared/DataLabels.d.ts +14 -0
  109. package/series/shared/DataLabels.js +124 -0
  110. package/series/shared/Markers.d.ts +9 -0
  111. package/series/shared/Markers.js +34 -0
  112. package/series/shared/color.d.ts +1 -0
  113. package/series/shared/color.js +19 -0
  114. package/series/shared/domain.d.ts +3 -0
  115. package/series/shared/domain.js +14 -0
  116. package/series/shared/hit.d.ts +3 -0
  117. package/series/shared/hit.js +33 -0
  118. package/series/shared/legend.d.ts +3 -0
  119. package/series/shared/legend.js +12 -0
@@ -0,0 +1,616 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { Grid, XAxis, YAxis } from './Axes';
4
+ import { Canvas, Centred, Container, PinnedAxes, ResetZoom, ScrollBox, SeriesLayer, TooltipCell, TooltipDivider, TooltipHeadCell, TooltipName, TooltipRow, TooltipSeriesCell, TooltipSeriesInner, TooltipSwatch, TooltipTable, TooltipTitle, TooltipValue, ZoomBand } from './Chart.styles';
5
+ import { ChartErrorBoundary } from './ChartErrorBoundary';
6
+ import { Legend } from './Legend';
7
+ import { PlotDecorations } from './PlotDecorations';
8
+ import { StackLabels } from './StackLabels';
9
+ import { Titles } from './Titles';
10
+ import { Tooltip } from './Tooltip';
11
+ import { useSize } from './useSize';
12
+ import { MOTION_MS } from '../animation/motion';
13
+ import { formatTime } from '../format/time';
14
+ import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE } from '../layout/measure-text';
15
+ import { buildChartModel, hiddenKey, isContinuous } from '../model/build-model';
16
+ import { DEFAULT_GRID_COLOR, DEFAULT_SURFACE_COLOR, DEFAULT_TEXT_COLOR } from '../model/palette';
17
+ import { resolveRenderers } from '../registry/registry';
18
+ /** Series switched off through their options; the legend takes over from there. */
19
+ const hiddenByOptions = options => {
20
+ const off = new Set();
21
+ (options.series ?? []).forEach((series, index) => {
22
+ if (series.visible === false) {
23
+ off.add(hiddenKey(index));
24
+ }
25
+ });
26
+ return off;
27
+ };
28
+ const seriesIdentity = options => (options.series ?? []).map(series => `${series.type}:${series.name ?? ''}`).join('|');
29
+ const toCssSize = (value, fallback) => {
30
+ if (value === null || value === undefined) {
31
+ return fallback;
32
+ }
33
+ return typeof value === 'number' ? `${value}px` : value;
34
+ };
35
+ function defaultTooltip(ctx, formatX, formatValue) {
36
+ return _jsxs("div", {
37
+ children: [_jsx(TooltipTitle, {
38
+ children: formatX(ctx.x)
39
+ }), ctx.points.map(point => _jsxs(TooltipRow, {
40
+ children: [_jsx(TooltipSwatch, {
41
+ "$color": point.color
42
+ }), _jsx(TooltipName, {
43
+ children: point.seriesName
44
+ }), _jsx(TooltipValue, {
45
+ children: formatValue(point.value ?? point.y)
46
+ })]
47
+ }, `${point.seriesIndex}-${point.index}`))]
48
+ });
49
+ }
50
+ function tableTooltip(ctx, table, formatX) {
51
+ const heading = table.header ? table.header(ctx) : formatX(ctx.x);
52
+ const columns = table.columns.map(column => ({
53
+ column,
54
+ title: typeof column.title === 'function' ? column.title(ctx) : column.title
55
+ })).filter(({
56
+ title
57
+ }) => title !== null);
58
+ const headed = table.seriesTitle !== undefined || columns.some(({
59
+ title
60
+ }) => title !== undefined);
61
+ return _jsxs("div", {
62
+ children: [heading === null || heading === undefined || heading === '' ? null : _jsxs(_Fragment, {
63
+ children: [_jsx(TooltipTitle, {
64
+ children: heading
65
+ }), _jsx(TooltipDivider, {
66
+ "$color": DEFAULT_GRID_COLOR
67
+ })]
68
+ }), _jsxs(TooltipTable, {
69
+ children: [headed ? _jsx("thead", {
70
+ children: _jsxs("tr", {
71
+ children: [_jsx(TooltipHeadCell, {
72
+ "$align": "left",
73
+ children: table.seriesTitle ?? ''
74
+ }), columns.map(({
75
+ column,
76
+ title
77
+ }, index) => _jsx(TooltipHeadCell
78
+ // eslint-disable-next-line react/no-array-index-key
79
+ , {
80
+ "$align": column.align ?? 'right',
81
+ children: title ?? ''
82
+ }, `head-${index}`))]
83
+ })
84
+ }) : null, _jsx("tbody", {
85
+ children: ctx.points.map(point => _jsxs("tr", {
86
+ children: [_jsx(TooltipSeriesCell, {
87
+ children: _jsxs(TooltipSeriesInner, {
88
+ children: [_jsx(TooltipSwatch, {
89
+ "$color": point.color,
90
+ "$round": true
91
+ }), point.seriesName]
92
+ })
93
+ }), columns.map(({
94
+ column
95
+ }, index) => _jsx(TooltipCell
96
+ // eslint-disable-next-line react/no-array-index-key
97
+ , {
98
+ "$align": column.align ?? 'right',
99
+ "$muted": index > 0,
100
+ children: column.cell(point)
101
+ }, `cell-${index}`))]
102
+ }, `${point.seriesIndex}-${point.index}`))
103
+ })]
104
+ })]
105
+ });
106
+ }
107
+ /** Shorter than this and the drag was a click, not a selection. */
108
+ const ZOOM_MIN_DRAG = 6;
109
+ function ChartBody({
110
+ options,
111
+ renderers,
112
+ isLoading = false,
113
+ empty,
114
+ loading,
115
+ className,
116
+ containerProps,
117
+ toggleSeries = true,
118
+ onSeriesToggle,
119
+ onPointClick
120
+ }) {
121
+ var _options$chart, _options$chart2, _options$tooltip4, _options$tooltip7, _options$tooltip8, _options$title2, _options$tooltip1, _options$tooltip10, _options$chart3, _options$chart4, _options$chart5, _options$chart6, _options$chart7, _options$chart8, _options$chart9, _options$title3, _options$chart0, _options$chart1, _options$chart10;
122
+ const [container, size] = useSize();
123
+ const [hover, setHover] = useState(null);
124
+ const [zoom, setZoom] = useState(null);
125
+ const [drag, setDrag] = useState(null);
126
+ const [hidden, setHidden] = useState(() => hiddenByOptions(options));
127
+ const identity = seriesIdentity(options);
128
+ // A different set of series is a different chart: start from its own options
129
+ // again rather than carrying the previous chart's switched-off indices over.
130
+ useEffect(() => {
131
+ setHidden(hiddenByOptions(options));
132
+ setZoom(null);
133
+ // eslint-disable-next-line react-hooks/exhaustive-deps
134
+ }, [identity]);
135
+ const toggle = useCallback(item => {
136
+ // A tooltip captured before the toggle would keep quoting what just went away.
137
+ setHover(null);
138
+ const key = hiddenKey(item.seriesIndex, item.pointIndex);
139
+ setHidden(current => {
140
+ const next = new Set(current);
141
+ if (next.has(key)) {
142
+ next.delete(key);
143
+ } else {
144
+ next.add(key);
145
+ }
146
+ onSeriesToggle === null || onSeriesToggle === void 0 || onSeriesToggle(item.seriesIndex, !next.has(key), item.pointIndex);
147
+ return next;
148
+ });
149
+ }, [onSeriesToggle]);
150
+ const registry = useMemo(() => resolveRenderers(renderers), [renderers]);
151
+ // A scrollable plot area draws at its minimum size and scrolls inside the box.
152
+ const scrollable = (_options$chart = options.chart) === null || _options$chart === void 0 ? void 0 : _options$chart.scrollablePlotArea;
153
+ const canvas = useMemo(() => ({
154
+ width: Math.max(size.width, (scrollable === null || scrollable === void 0 ? void 0 : scrollable.minWidth) ?? 0),
155
+ height: Math.max(size.height, (scrollable === null || scrollable === void 0 ? void 0 : scrollable.minHeight) ?? 0)
156
+ }), [size, scrollable === null || scrollable === void 0 ? void 0 : scrollable.minWidth, scrollable === null || scrollable === void 0 ? void 0 : scrollable.minHeight]);
157
+ // A zoom is an override on the first x axis, not a second source of truth: the
158
+ // model is still built from the options, so everything downstream is unaware.
159
+ const zoomed = useMemo(() => {
160
+ if (!zoom) {
161
+ return options;
162
+ }
163
+ const axes = options.xAxis;
164
+ const [first, ...rest] = Array.isArray(axes) ? axes : [axes ?? {}];
165
+ return {
166
+ ...options,
167
+ xAxis: [{
168
+ ...first,
169
+ min: zoom[0],
170
+ max: zoom[1]
171
+ }, ...rest]
172
+ };
173
+ }, [options, zoom]);
174
+ const built = useMemo(() => canvas.width && canvas.height ? buildChartModel({
175
+ options: zoomed,
176
+ size: canvas,
177
+ renderers: registry,
178
+ hidden
179
+ }) : null, [zoomed, canvas, registry, hidden]);
180
+ const anyShown = (built === null || built === void 0 ? void 0 : built.series.some(series => !series.hidden)) ?? false;
181
+ const lastShown = useRef(null);
182
+ useEffect(() => {
183
+ if (built && anyShown) {
184
+ lastShown.current = built;
185
+ }
186
+ }, [built, anyShown]);
187
+ // Switching off the last series must not reshape the axes under it: the plot
188
+ // holds the geometry of the last state that had something on show, so the series
189
+ // fades out where it stood instead of jumping to a rescaled axis first.
190
+ const model = useMemo(() => {
191
+ if (anyShown || !built) {
192
+ return built;
193
+ }
194
+ const frozen = lastShown.current;
195
+ if (!frozen) {
196
+ return built;
197
+ }
198
+ return {
199
+ ...frozen,
200
+ series: frozen.series.map(series => ({
201
+ ...series,
202
+ hidden: true
203
+ })),
204
+ legend: built.legend,
205
+ titles: built.titles
206
+ };
207
+ }, [built, anyShown]);
208
+ const contextFor = useCallback(series => {
209
+ if (!model) {
210
+ return null;
211
+ }
212
+ const yModel = model.yAxes[Math.min(series.options.yAxis ?? 0, model.yAxes.length - 1)];
213
+ if (!yModel || yModel.scale.kind === 'color') {
214
+ return null;
215
+ }
216
+ const hit = hover === null || hover === void 0 ? void 0 : hover.hits.find(candidate => candidate.seriesIndex === series.index);
217
+ return {
218
+ series: series.options,
219
+ seriesIndex: series.index,
220
+ points: series.points,
221
+ plot: model.plot,
222
+ scales: {
223
+ x: model.xAxis.scale,
224
+ y: yModel.scale,
225
+ color: model.colorScale
226
+ },
227
+ color: series.color,
228
+ colors: model.colors,
229
+ stacked: model.stacks.get(series.index),
230
+ slot: model.slots.get(series.index),
231
+ hovered: hit ? {
232
+ seriesIndex: series.index,
233
+ pointIndex: hit.pointIndex
234
+ } : undefined,
235
+ hidden: series.hidden,
236
+ hiddenPoints: series.hiddenPoints,
237
+ animate: true,
238
+ numberFormat: model.numberFormat
239
+ };
240
+ }, [model, hover]);
241
+ const hitsAt = useCallback(event => {
242
+ if (!model) {
243
+ return null;
244
+ }
245
+ const bounds = event.currentTarget.getBoundingClientRect();
246
+ const local = {
247
+ x: event.clientX - bounds.left,
248
+ y: event.clientY - bounds.top
249
+ };
250
+ const hits = model.series.flatMap(series => {
251
+ if (series.hidden) {
252
+ return [];
253
+ }
254
+ const ctx = contextFor(series);
255
+ return ctx ? series.renderer.hitTest(ctx, local) ?? [] : [];
256
+ });
257
+ return {
258
+ hits,
259
+ bounds
260
+ };
261
+ }, [model, contextFor]);
262
+ const handlePointerMove = useCallback(event => {
263
+ var _options$tooltip, _options$tooltip2;
264
+ if (!model || ((_options$tooltip = options.tooltip) === null || _options$tooltip === void 0 ? void 0 : _options$tooltip.enabled) === false) {
265
+ return;
266
+ }
267
+ // While a zoom is being dragged out, a tooltip is in the way of the thing
268
+ // the reader is actually doing.
269
+ if (drag) {
270
+ setHover(null);
271
+ return;
272
+ }
273
+ const found = hitsAt(event);
274
+ if (!found) {
275
+ return;
276
+ }
277
+ const {
278
+ hits,
279
+ bounds
280
+ } = found;
281
+ if (!hits.length) {
282
+ setHover(null);
283
+ return;
284
+ }
285
+ const shared = ((_options$tooltip2 = options.tooltip) === null || _options$tooltip2 === void 0 ? void 0 : _options$tooltip2.shared) ?? false;
286
+ const nearest = hits.reduce((best, hit) => hit.distance < best.distance ? hit : best);
287
+ const selected = shared ? hits.filter(hit => String(hit.x) === String(nearest.x)) : [nearest];
288
+ const highest = selected.reduce((best, hit) => hit.anchor.y < best.anchor.y ? hit : best, selected[0]);
289
+ setHover({
290
+ hits: selected,
291
+ pointer: {
292
+ x: event.clientX,
293
+ y: event.clientY
294
+ },
295
+ anchor: {
296
+ x: bounds.left + highest.anchor.x,
297
+ y: bounds.top + highest.anchor.y
298
+ }
299
+ });
300
+ }, [model, hitsAt, options.tooltip, drag]);
301
+ const handlePointerLeave = useCallback(() => setHover(null), []);
302
+ const zooms = ((_options$chart2 = options.chart) === null || _options$chart2 === void 0 ? void 0 : _options$chart2.zoomType) === 'x' && model !== null && isContinuous(model.xAxis.scale);
303
+ const localX = event => event.clientX - event.currentTarget.getBoundingClientRect().left;
304
+ const startDrag = useCallback(event => {
305
+ if (!zooms || !model) {
306
+ return;
307
+ }
308
+ const x = localX(event);
309
+ if (x < model.plot.x || x > model.plot.x + model.plot.width) {
310
+ return;
311
+ }
312
+ setDrag({
313
+ from: x,
314
+ to: x
315
+ });
316
+ }, [zooms, model]);
317
+ const extendDrag = useCallback(event => {
318
+ if (!drag || !model) {
319
+ return;
320
+ }
321
+ const x = Math.min(Math.max(localX(event), model.plot.x), model.plot.x + model.plot.width);
322
+ setDrag({
323
+ from: drag.from,
324
+ to: x
325
+ });
326
+ }, [drag, model]);
327
+ const endDrag = useCallback(() => {
328
+ if (!drag || !model || !isContinuous(model.xAxis.scale)) {
329
+ setDrag(null);
330
+ return;
331
+ }
332
+ const [low, high] = drag.from <= drag.to ? [drag.from, drag.to] : [drag.to, drag.from];
333
+ setDrag(null);
334
+ // A click is a drag of nothing; zooming to a point would empty the chart.
335
+ if (high - low < ZOOM_MIN_DRAG) {
336
+ return;
337
+ }
338
+ const scale = model.xAxis.scale;
339
+ setZoom([scale.invert(low), scale.invert(high)]);
340
+ }, [drag, model]);
341
+ // Released outside the chart, the drag still ends: bound to the window rather
342
+ // than the canvas, or letting go past the edge leaves the band stuck on screen.
343
+ useEffect(() => {
344
+ if (!drag) {
345
+ return undefined;
346
+ }
347
+ window.addEventListener('pointerup', endDrag);
348
+ return () => window.removeEventListener('pointerup', endDrag);
349
+ }, [drag, endDrag]);
350
+ const resetZoom = useCallback(() => {
351
+ setZoom(null);
352
+ setHover(null);
353
+ }, []);
354
+ const handleClick = useCallback(event => {
355
+ var _hitsAt, _series$points$neares;
356
+ if (!onPointClick || !model) {
357
+ return;
358
+ }
359
+ const hits = ((_hitsAt = hitsAt(event)) === null || _hitsAt === void 0 ? void 0 : _hitsAt.hits) ?? [];
360
+ if (!hits.length) {
361
+ return;
362
+ }
363
+ const nearest = hits.reduce((best, hit) => hit.distance < best.distance ? hit : best);
364
+ const series = model.series.find(item => item.index === nearest.seriesIndex);
365
+ onPointClick({
366
+ seriesIndex: nearest.seriesIndex,
367
+ seriesName: (series === null || series === void 0 ? void 0 : series.options.name) ?? `Series ${nearest.seriesIndex + 1}`,
368
+ pointIndex: nearest.pointIndex,
369
+ x: nearest.x,
370
+ y: nearest.y,
371
+ value: nearest.value,
372
+ color: (series === null || series === void 0 ? void 0 : series.color) ?? DEFAULT_TEXT_COLOR,
373
+ meta: series === null || series === void 0 || (_series$points$neares = series.points[nearest.pointIndex]) === null || _series$points$neares === void 0 ? void 0 : _series$points$neares.meta
374
+ }, event);
375
+ }, [onPointClick, model, hitsAt]);
376
+ const formatX = useCallback(value => {
377
+ var _options$tooltip3;
378
+ if (typeof value !== 'number') {
379
+ return String(value);
380
+ }
381
+ const pattern = ((_options$tooltip3 = options.tooltip) === null || _options$tooltip3 === void 0 ? void 0 : _options$tooltip3.dateFormat) ?? (model && isContinuous(model.xAxis.scale) && model.xAxis.scale.kind === 'time' ? '%b %d, %Y' : undefined);
382
+ return pattern ? formatTime(pattern, value) : String(value);
383
+ }, [(_options$tooltip4 = options.tooltip) === null || _options$tooltip4 === void 0 ? void 0 : _options$tooltip4.dateFormat, model]);
384
+ const formatValue = useCallback(value => {
385
+ var _options$tooltip5, _options$tooltip6;
386
+ if (value === null) {
387
+ return '—';
388
+ }
389
+ const text = model ? model.numberFormat(value) : String(value);
390
+ return `${((_options$tooltip5 = options.tooltip) === null || _options$tooltip5 === void 0 ? void 0 : _options$tooltip5.valuePrefix) ?? ''}${text}${((_options$tooltip6 = options.tooltip) === null || _options$tooltip6 === void 0 ? void 0 : _options$tooltip6.valueSuffix) ?? ''}`;
391
+ }, [(_options$tooltip7 = options.tooltip) === null || _options$tooltip7 === void 0 ? void 0 : _options$tooltip7.valuePrefix, (_options$tooltip8 = options.tooltip) === null || _options$tooltip8 === void 0 ? void 0 : _options$tooltip8.valueSuffix, model]);
392
+ const tooltipContent = useMemo(() => {
393
+ var _points$, _options$title, _options$tooltip9, _options$tooltip0;
394
+ if (!hover || !model) {
395
+ return null;
396
+ }
397
+ const shown = hover.hits.filter(hit => {
398
+ const series = model.series.find(item => item.index === hit.seriesIndex);
399
+ return series ? !series.hidden : false;
400
+ });
401
+ if (!shown.length) {
402
+ return null;
403
+ }
404
+ const points = shown.map(hit => {
405
+ const series = model.series.find(item => item.index === hit.seriesIndex);
406
+ const neighbours = (series === null || series === void 0 ? void 0 : series.points) ?? [];
407
+ const previous = neighbours[hit.pointIndex - 1];
408
+ const next = neighbours[hit.pointIndex + 1];
409
+ return {
410
+ seriesIndex: hit.seriesIndex,
411
+ seriesName: (series === null || series === void 0 ? void 0 : series.options.name) ?? `Series ${hit.seriesIndex + 1}`,
412
+ color: (series === null || series === void 0 ? void 0 : series.color) ?? DEFAULT_TEXT_COLOR,
413
+ x: hit.x,
414
+ y: hit.y,
415
+ value: hit.value,
416
+ index: hit.pointIndex,
417
+ meta: series === null || series === void 0 ? void 0 : series.options.meta,
418
+ prev: previous ? {
419
+ x: previous.x,
420
+ y: previous.y,
421
+ index: previous.index
422
+ } : undefined,
423
+ next: next ? {
424
+ x: next.x,
425
+ y: next.y,
426
+ index: next.index
427
+ } : undefined
428
+ };
429
+ });
430
+ const ctx = {
431
+ x: ((_points$ = points[0]) === null || _points$ === void 0 ? void 0 : _points$.x) ?? 0,
432
+ points,
433
+ chart: {
434
+ title: ((_options$title = options.title) === null || _options$title === void 0 ? void 0 : _options$title.text) ?? undefined,
435
+ meta: options.meta
436
+ }
437
+ };
438
+ const formatter = (_options$tooltip9 = options.tooltip) === null || _options$tooltip9 === void 0 ? void 0 : _options$tooltip9.formatter;
439
+ if (formatter) {
440
+ return formatter(ctx);
441
+ }
442
+ const table = (_options$tooltip0 = options.tooltip) === null || _options$tooltip0 === void 0 ? void 0 : _options$tooltip0.table;
443
+ return table ? tableTooltip(ctx, table, formatX) : defaultTooltip(ctx, formatX, formatValue);
444
+ }, [hover, model, (_options$title2 = options.title) === null || _options$title2 === void 0 ? void 0 : _options$title2.text, options.meta, (_options$tooltip1 = options.tooltip) === null || _options$tooltip1 === void 0 ? void 0 : _options$tooltip1.formatter, (_options$tooltip10 = options.tooltip) === null || _options$tooltip10 === void 0 ? void 0 : _options$tooltip10.table, formatX, formatValue]);
445
+ const hasData = (model === null || model === void 0 ? void 0 : model.series.some(series => series.points.some(point => point.y !== null))) ?? false;
446
+ const plotBorderWidth = ((_options$chart3 = options.chart) === null || _options$chart3 === void 0 ? void 0 : _options$chart3.plotBorderWidth) ?? 0;
447
+ const scrolls = canvas.width > size.width || canvas.height > size.height;
448
+ // Only horizontal scrolling pins anything, and only a left-hand y axis: that is
449
+ // the one whose labels the plot would otherwise slide out from under. A right-hand
450
+ // axis and a vertically scrolling chart both stay in the scrolling canvas.
451
+ const pinsAxes = canvas.width > size.width && !((model === null || model === void 0 ? void 0 : model.axisless) ?? true);
452
+ const isPinned = axis => pinsAxes && axis.options.opposite !== true;
453
+ // Highcharts dims the series a hover is not on. Ours only does it when asked:
454
+ // switching it on by default would restyle every chart already drawn.
455
+ const inactiveOpacity = series => {
456
+ var _series$options$state;
457
+ const opacity = (_series$options$state = series.options.states) === null || _series$options$state === void 0 || (_series$options$state = _series$options$state.inactive) === null || _series$options$state === void 0 ? void 0 : _series$options$state.opacity;
458
+ if (opacity === undefined || !(hover !== null && hover !== void 0 && hover.hits.length)) {
459
+ return undefined;
460
+ }
461
+ return hover.hits.some(hit => hit.seriesIndex === series.index) ? undefined : opacity;
462
+ };
463
+ // `animation: false` is a zero-length transition rather than a separate code
464
+ // path, so nothing downstream has to know a series opted out.
465
+ const motionOf = series => {
466
+ const animation = series.options.animation;
467
+ if (animation === false) {
468
+ return 0;
469
+ }
470
+ return (animation === null || animation === void 0 ? void 0 : animation.duration) ?? MOTION_MS;
471
+ };
472
+ const decorations = layer => {
473
+ if (!model || model.axisless) {
474
+ return null;
475
+ }
476
+ return _jsxs(_Fragment, {
477
+ children: [_jsx(PlotDecorations, {
478
+ plot: model.plot,
479
+ axis: model.xAxis,
480
+ along: "x",
481
+ layer: layer
482
+ }), model.yAxes.map(axis => _jsx(PlotDecorations, {
483
+ plot: model.plot,
484
+ axis: axis,
485
+ along: "y",
486
+ layer: layer
487
+ }, `${axis.id}-${layer}`))]
488
+ });
489
+ };
490
+ return _jsxs(Container, {
491
+ ...containerProps,
492
+ ref: container,
493
+ className: className,
494
+ "$width": toCssSize((_options$chart4 = options.chart) === null || _options$chart4 === void 0 ? void 0 : _options$chart4.width, '100%'),
495
+ "$height": toCssSize((_options$chart5 = options.chart) === null || _options$chart5 === void 0 ? void 0 : _options$chart5.height, '100%'),
496
+ "$autoHeight": ((_options$chart6 = options.chart) === null || _options$chart6 === void 0 ? void 0 : _options$chart6.height) === undefined,
497
+ "$background": (_options$chart7 = options.chart) === null || _options$chart7 === void 0 ? void 0 : _options$chart7.backgroundColor,
498
+ "$fontFamily": ((_options$chart8 = options.chart) === null || _options$chart8 === void 0 || (_options$chart8 = _options$chart8.style) === null || _options$chart8 === void 0 ? void 0 : _options$chart8.fontFamily) ?? DEFAULT_FONT_FAMILY,
499
+ "$fontSize": ((_options$chart9 = options.chart) === null || _options$chart9 === void 0 || (_options$chart9 = _options$chart9.style) === null || _options$chart9 === void 0 ? void 0 : _options$chart9.fontSize) ?? DEFAULT_FONT_SIZE,
500
+ children: [isLoading ? _jsx(Centred, {
501
+ children: loading ?? 'Loading…'
502
+ }) : null, !isLoading && model && !hasData ? _jsx(Centred, {
503
+ children: empty ?? 'No data'
504
+ }) : null, !isLoading && model && hasData ? _jsx(ScrollBox, {
505
+ "$scrolls": scrolls,
506
+ children: _jsxs(Canvas, {
507
+ width: canvas.width,
508
+ height: canvas.height,
509
+ role: "img",
510
+ "aria-label": ((_options$title3 = options.title) === null || _options$title3 === void 0 ? void 0 : _options$title3.text) ?? 'Chart',
511
+ onPointerMove: event => {
512
+ extendDrag(event);
513
+ handlePointerMove(event);
514
+ },
515
+ onPointerLeave: handlePointerLeave,
516
+ onPointerDown: zooms ? startDrag : undefined,
517
+ onClick: onPointClick ? handleClick : undefined,
518
+ "$clickable": Boolean(onPointClick),
519
+ children: [_jsx(Titles, {
520
+ entries: model.titles
521
+ }), model.axisless ? null : _jsx(Grid, {
522
+ plot: model.plot,
523
+ yAxis: model.yAxes[0]
524
+ }), model.axisless ? null : model.yAxes.filter(axis => !isPinned(axis)).map(axis => _jsx(YAxis, {
525
+ plot: model.plot,
526
+ axis: axis
527
+ }, axis.id)), model.axisless ? null : _jsx(XAxis, {
528
+ plot: model.plot,
529
+ axis: model.xAxis
530
+ }), decorations('under'), plotBorderWidth > 0 ? _jsx("rect", {
531
+ x: model.plot.x,
532
+ y: model.plot.y,
533
+ width: model.plot.width,
534
+ height: model.plot.height,
535
+ fill: "none",
536
+ stroke: ((_options$chart0 = options.chart) === null || _options$chart0 === void 0 ? void 0 : _options$chart0.plotBorderColor) ?? DEFAULT_GRID_COLOR,
537
+ strokeWidth: plotBorderWidth
538
+ }) : null, model.series.map(series => {
539
+ const ctx = contextFor(series);
540
+ return ctx ? _jsx(SeriesLayer, {
541
+ "data-series-index": series.index,
542
+ "$hidden": series.hidden,
543
+ "$collapse": series.renderer.exit === 'collapse',
544
+ "$selfAnimated": series.renderer.animatesGeometry === true,
545
+ "$inactive": inactiveOpacity(series),
546
+ "$motionMs": motionOf(series),
547
+ "$cursor": series.options.cursor ?? 'default',
548
+ "aria-hidden": series.hidden,
549
+ children: series.renderer.render(ctx)
550
+ }, `series-${series.index}`) : null;
551
+ }), decorations('over'), drag ? _jsx(ZoomBand, {
552
+ x: Math.min(drag.from, drag.to),
553
+ y: model.plot.y,
554
+ width: Math.abs(drag.to - drag.from),
555
+ height: model.plot.height
556
+ }) : null, _jsx(StackLabels, {
557
+ model: model
558
+ }), _jsx(Legend, {
559
+ items: model.legend,
560
+ options: options.legend,
561
+ plot: model.plot,
562
+ size: canvas,
563
+ spacing: model.spacing,
564
+ onToggle: toggleSeries ? toggle : undefined,
565
+ renderLabel: item => {
566
+ var _options$legend, _model$series$find;
567
+ const formatter = (_options$legend = options.legend) === null || _options$legend === void 0 ? void 0 : _options$legend.labelFormatter;
568
+ if (!formatter) {
569
+ return item.label;
570
+ }
571
+ return formatter({
572
+ seriesIndex: item.seriesIndex,
573
+ name: item.label,
574
+ color: item.color,
575
+ visible: item.visible,
576
+ meta: (_model$series$find = model.series.find(s => s.index === item.seriesIndex)) === null || _model$series$find === void 0 ? void 0 : _model$series$find.options.meta
577
+ });
578
+ }
579
+ })]
580
+ })
581
+ }) : null, !isLoading && model && hasData && pinsAxes ? _jsxs(PinnedAxes, {
582
+ width: model.plot.x + 1,
583
+ height: canvas.height,
584
+ "aria-hidden": true,
585
+ children: [_jsx("rect", {
586
+ x: 0,
587
+ y: 0,
588
+ width: model.plot.x,
589
+ height: canvas.height,
590
+ fill: ((_options$chart1 = options.chart) === null || _options$chart1 === void 0 ? void 0 : _options$chart1.backgroundColor) ?? DEFAULT_SURFACE_COLOR
591
+ }), model.yAxes.filter(isPinned).map(axis => _jsx(YAxis, {
592
+ plot: model.plot,
593
+ axis: axis
594
+ }, axis.id))]
595
+ }) : null, zoom ? _jsx(ResetZoom, {
596
+ type: "button",
597
+ onClick: resetZoom,
598
+ children: ((_options$chart10 = options.chart) === null || _options$chart10 === void 0 ? void 0 : _options$chart10.resetZoomText) ?? 'Reset zoom'
599
+ }) : null, hover && tooltipContent ? _jsx(Tooltip, {
600
+ at: hover.anchor,
601
+ pointer: hover.pointer,
602
+ options: options.tooltip,
603
+ children: tooltipContent
604
+ }) : null]
605
+ });
606
+ }
607
+ export function Chart(props) {
608
+ return _jsx(ChartErrorBoundary, {
609
+ fallback: _jsx(Centred, {
610
+ children: "Chart failed to render"
611
+ }),
612
+ children: _jsx(ChartBody, {
613
+ ...props
614
+ })
615
+ });
616
+ }