@pushwoosh/kit-charts 0.0.2 → 0.0.4

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.
@@ -62,7 +62,8 @@ Deliberate differences from Highcharts:
62
62
  `ChartOptions<TChartMeta, TSeriesMeta, TPointMeta>`.
63
63
  2. Formatters take a typed context **argument**; there is no `this`.
64
64
  3. Formatters return a `ReactNode`, never an HTML string. There is no `useHTML`.
65
- 4. `TooltipPoint` exposes `prev` and `next` neighbours directly.
65
+ 4. `TooltipPoint` exposes `prev` and `next` neighbours directly, and carries the
66
+ point's own `meta` as `pointMeta` beside the series' `meta`.
66
67
  5. Formatters see `ctx.chart.title` and `ctx.chart.meta`, not the option tree.
67
68
  6. No `credits` option.
68
69
  7. No `reflow`; resize is observed automatically.
@@ -338,6 +339,14 @@ the value wrapped in `valuePrefix`/`valueSuffix`. `formatter` replaces the body
338
339
  returns a `ReactNode`; `ctx.points[].prev`/`.next` carry the neighbouring points, so
339
340
  a delta needs no extra lookup. `shared: true` collects every series at the hovered x.
340
341
 
342
+ `ctx.points[].pointMeta` is the hovered point's own `meta` — the third type
343
+ parameter of `ChartOptions<TChartMeta, TSeriesMeta, TPointMeta>`, the same object
344
+ `onPointClick` reports. `meta` beside it stays the series'. A bar whose height is a
345
+ rate and whose tooltip has to name the two counts behind it needs the per-point
346
+ one; nothing else reaches it, because a formatter sees no option tree.
347
+
348
+ Golden case: `tooltip/point-meta`.
349
+
341
350
  Golden cases: `tooltip/callout`, `tooltip/shared-formatter`.
342
351
 
343
352
  ## Styling
@@ -362,6 +371,18 @@ A data label whose text carries `\n` is drawn as stacked lines, styled by
362
371
  `style` for the first and `secondaryStyle` for the rest. It is one label: it
363
372
  moves, measures and hides as one.
364
373
 
374
+ ## Areas
375
+
376
+ `fillColor` paints the body under the line, where the fill is a token of its own
377
+ rather than a wash of the series colour — `color` still keys the legend, the
378
+ markers and the tooltip swatch. Left unset the body is the series colour at
379
+ `fillOpacity`. Golden case: `area/fill-color`.
380
+
381
+ Both live on `SeriesOptionsBase`, as every series option does, but a renderer
382
+ reads only what it draws with: `fillColor` is read by `area` alone, `fillOpacity`
383
+ by `area` and `column`. On a pie or a heatmap they are typed and inert — the same
384
+ deal as `innerSize` on a line.
385
+
365
386
  ## The device pixel grid
366
387
 
367
388
  `useSize` floors the measured box, so the canvas is always a whole number of
@@ -4,10 +4,10 @@ import { DEFAULT_FONT_SIZE, measureText } from '../layout/measure-text';
4
4
  import { AXIS_LABEL_GAP, AXIS_TITLE_GAP, crisp, TEXT_BASELINE_SHIFT, X_AXIS_LABEL_DROP, X_AXIS_TITLE_GAP } from '../layout/metrics';
5
5
  import { DEFAULT_AXIS_COLOR, DEFAULT_GRID_COLOR, DEFAULT_MUTED_TEXT_COLOR } from '../model/palette';
6
6
  const textProps = style => ({
7
- fill: (style === null || style === void 0 ? void 0 : style.color) ?? DEFAULT_MUTED_TEXT_COLOR,
8
- fontFamily: style === null || style === void 0 ? void 0 : style.fontFamily,
9
- fontSize: (style === null || style === void 0 ? void 0 : style.fontSize) ?? DEFAULT_FONT_SIZE,
10
- fontWeight: style === null || style === void 0 ? void 0 : style.fontWeight
7
+ fill: style?.color ?? DEFAULT_MUTED_TEXT_COLOR,
8
+ fontFamily: style?.fontFamily,
9
+ fontSize: style?.fontSize ?? DEFAULT_FONT_SIZE,
10
+ fontWeight: style?.fontWeight
11
11
  });
12
12
  const ANCHOR = {
13
13
  left: 'start',
@@ -71,8 +71,7 @@ function AxisTitle({
71
71
  anchor,
72
72
  defaultRotation
73
73
  }) {
74
- var _title$style;
75
- if (!(title !== null && title !== void 0 && title.text) || ((_title$style = title.style) === null || _title$style === void 0 ? void 0 : _title$style.display) === 'none') {
74
+ if (!title?.text || title.style?.display === 'none') {
76
75
  return null;
77
76
  }
78
77
  const rotation = title.rotation ?? defaultRotation;
@@ -90,8 +89,7 @@ function AxisTitle({
90
89
  });
91
90
  }
92
91
  const titleY = (options, plot) => {
93
- var _options$title;
94
- const align = ((_options$title = options.title) === null || _options$title === void 0 ? void 0 : _options$title.align) ?? 'middle';
92
+ const align = options.title?.align ?? 'middle';
95
93
  if (align === 'high') {
96
94
  return plot.y;
97
95
  }
@@ -104,7 +102,6 @@ export function XAxis({
104
102
  plot,
105
103
  axis
106
104
  }) {
107
- var _options$labels, _options$labels2, _options$title2;
108
105
  const {
109
106
  options
110
107
  } = axis;
@@ -118,8 +115,8 @@ export function XAxis({
118
115
  const lineColor = options.lineColor ?? DEFAULT_AXIS_COLOR;
119
116
  const tickLength = options.tickLength ?? 0;
120
117
  const tickWidth = options.tickWidth ?? 1;
121
- const labelsEnabled = ((_options$labels = options.labels) === null || _options$labels === void 0 ? void 0 : _options$labels.enabled) !== false;
122
- const labelY = baseline + direction * (tickLength + (onTop ? AXIS_LABEL_GAP : X_AXIS_LABEL_DROP)) + (((_options$labels2 = options.labels) === null || _options$labels2 === void 0 ? void 0 : _options$labels2.y) ?? 0);
118
+ const labelsEnabled = options.labels?.enabled !== false;
119
+ const labelY = baseline + direction * (tickLength + (onTop ? AXIS_LABEL_GAP : X_AXIS_LABEL_DROP)) + (options.labels?.y ?? 0);
123
120
  return _jsxs("g", {
124
121
  children: [options.lineWidth === 0 ? null : _jsx("line", {
125
122
  x1: plot.x,
@@ -128,29 +125,26 @@ export function XAxis({
128
125
  y2: crisp(baseline, options.lineWidth ?? 1),
129
126
  stroke: lineColor,
130
127
  strokeWidth: options.lineWidth ?? 1
131
- }), axis.ticks.map(tick => {
132
- var _options$labels3, _options$labels4;
133
- return _jsxs("g", {
134
- children: [tickLength > 0 && tickWidth > 0 ? _jsx("line", {
135
- x1: crisp(tick.position, tickWidth),
136
- x2: crisp(tick.position, tickWidth),
137
- y1: crisp(baseline, tickWidth),
138
- y2: crisp(baseline + direction * tickLength, tickWidth),
139
- stroke: options.tickColor ?? lineColor,
140
- strokeWidth: tickWidth
141
- }) : null, labelsEnabled && tick.label ? _jsx(TickLabel, {
142
- hint: tick.hint,
143
- x: tick.position + (((_options$labels3 = options.labels) === null || _options$labels3 === void 0 ? void 0 : _options$labels3.x) ?? 0),
144
- y: labelY,
145
- anchor: tick.anchor,
146
- style: (_options$labels4 = options.labels) === null || _options$labels4 === void 0 ? void 0 : _options$labels4.style,
147
- children: tick.label
148
- }) : null]
149
- }, `x-${tick.value}`);
150
- }), _jsx(AxisTitle, {
128
+ }), axis.ticks.map(tick => _jsxs("g", {
129
+ children: [tickLength > 0 && tickWidth > 0 ? _jsx("line", {
130
+ x1: crisp(tick.position, tickWidth),
131
+ x2: crisp(tick.position, tickWidth),
132
+ y1: crisp(baseline, tickWidth),
133
+ y2: crisp(baseline + direction * tickLength, tickWidth),
134
+ stroke: options.tickColor ?? lineColor,
135
+ strokeWidth: tickWidth
136
+ }) : null, labelsEnabled && tick.label ? _jsx(TickLabel, {
137
+ hint: tick.hint,
138
+ x: tick.position + (options.labels?.x ?? 0),
139
+ y: labelY,
140
+ anchor: tick.anchor,
141
+ style: options.labels?.style,
142
+ children: tick.label
143
+ }) : null]
144
+ }, `x-${tick.value}`)), _jsx(AxisTitle, {
151
145
  title: options.title,
152
146
  x: plot.x + plot.width / 2,
153
- y: labelY + direction * ((((_options$title2 = options.title) === null || _options$title2 === void 0 ? void 0 : _options$title2.offset) ?? 0) + X_AXIS_TITLE_GAP),
147
+ y: labelY + direction * ((options.title?.offset ?? 0) + X_AXIS_TITLE_GAP),
154
148
  anchor: "middle",
155
149
  defaultRotation: 0
156
150
  })]
@@ -160,40 +154,33 @@ export function YAxis({
160
154
  plot,
161
155
  axis
162
156
  }) {
163
- var _options$labels5, _options$labels6, _options$title3, _options$title4;
164
157
  const {
165
158
  options
166
159
  } = axis;
167
160
  if (options.visible === false) {
168
161
  return null;
169
162
  }
170
- const labelsEnabled = ((_options$labels5 = options.labels) === null || _options$labels5 === void 0 ? void 0 : _options$labels5.enabled) !== false;
163
+ const labelsEnabled = options.labels?.enabled !== false;
171
164
  const onRight = options.opposite === true;
172
165
  const offset = options.offset ?? 0;
173
166
  // `offset` pushes the whole axis — labels and title — away from the plot.
174
167
  const edge = onRight ? plot.x + plot.width + AXIS_LABEL_GAP + offset : plot.x - AXIS_LABEL_GAP - offset;
175
- const anchor = (((_options$labels6 = options.labels) === null || _options$labels6 === void 0 ? void 0 : _options$labels6.align) && ANCHOR[options.labels.align]) ?? (onRight ? 'start' : 'end');
176
- const columnWidth = labelsEnabled ? axis.ticks.reduce((widest, tick) => {
177
- var _options$labels7;
178
- return Math.max(widest, measureText(tick.label, (_options$labels7 = options.labels) === null || _options$labels7 === void 0 ? void 0 : _options$labels7.style));
179
- }, 0) : 0;
168
+ const anchor = (options.labels?.align && ANCHOR[options.labels.align]) ?? (onRight ? 'start' : 'end');
169
+ const columnWidth = labelsEnabled ? axis.ticks.reduce((widest, tick) => Math.max(widest, measureText(tick.label, options.labels?.style)), 0) : 0;
180
170
  // Where the labels grow away from the plot, the title has to clear the whole
181
171
  // column of them or it lands on top of the numbers.
182
172
  const outwards = onRight ? anchor !== 'end' : anchor !== 'start';
183
173
  const titleShift = outwards ? Math.ceil(columnWidth * (anchor === 'middle' ? 0.5 : 1)) + AXIS_TITLE_GAP : 0;
184
- const titleX = onRight ? edge + titleShift + (((_options$title3 = options.title) === null || _options$title3 === void 0 ? void 0 : _options$title3.offset) ?? 0) : edge - titleShift - (((_options$title4 = options.title) === null || _options$title4 === void 0 ? void 0 : _options$title4.offset) ?? 0);
174
+ const titleX = onRight ? edge + titleShift + (options.title?.offset ?? 0) : edge - titleShift - (options.title?.offset ?? 0);
185
175
  return _jsxs("g", {
186
- children: [labelsEnabled ? axis.ticks.map(tick => {
187
- var _options$labels8, _options$labels9;
188
- return tick.label ? _jsx(TickLabel, {
189
- x: edge + (((_options$labels8 = options.labels) === null || _options$labels8 === void 0 ? void 0 : _options$labels8.x) ?? 0),
190
- y: tick.position + TEXT_BASELINE_SHIFT,
191
- anchor: anchor,
192
- hint: tick.hint,
193
- style: (_options$labels9 = options.labels) === null || _options$labels9 === void 0 ? void 0 : _options$labels9.style,
194
- children: tick.label
195
- }, `y-${tick.value}`) : null;
196
- }) : null, _jsx(AxisTitle, {
176
+ children: [labelsEnabled ? axis.ticks.map(tick => tick.label ? _jsx(TickLabel, {
177
+ x: edge + (options.labels?.x ?? 0),
178
+ y: tick.position + TEXT_BASELINE_SHIFT,
179
+ anchor: anchor,
180
+ hint: tick.hint,
181
+ style: options.labels?.style,
182
+ children: tick.label
183
+ }, `y-${tick.value}`) : null) : null, _jsx(AxisTitle, {
197
184
  title: options.title,
198
185
  x: titleX,
199
186
  y: titleY(options, plot),
@@ -12,8 +12,8 @@ export interface ClickedPoint<TPointMeta = unknown> {
12
12
  color: string;
13
13
  meta: TPointMeta;
14
14
  }
15
- export interface ChartProps<TChartMeta = unknown, TSeriesMeta = unknown> {
16
- options: ChartOptions<TChartMeta, TSeriesMeta>;
15
+ export interface ChartProps<TChartMeta = unknown, TSeriesMeta = unknown, TPointMeta = unknown> {
16
+ options: ChartOptions<TChartMeta, TSeriesMeta, TPointMeta>;
17
17
  renderers?: readonly AnySeriesRenderer[];
18
18
  isLoading?: boolean;
19
19
  empty?: ReactNode;
@@ -28,6 +28,6 @@ export interface ChartProps<TChartMeta = unknown, TSeriesMeta = unknown> {
28
28
  */
29
29
  onSeriesToggle?: (seriesIndex: number, visible: boolean, pointIndex?: number) => void;
30
30
  /** Called with the point nearest a click inside the plot; drill-down hangs off this. */
31
- onPointClick?: (point: ClickedPoint, event: ReactMouseEvent<SVGSVGElement>) => void;
31
+ onPointClick?: (point: ClickedPoint<TPointMeta>, event: ReactMouseEvent<SVGSVGElement>) => void;
32
32
  }
33
- export declare function Chart<TChartMeta = unknown, TSeriesMeta = unknown>(props: ChartProps<TChartMeta, TSeriesMeta>): ReactElement;
33
+ export declare function Chart<TChartMeta = unknown, TSeriesMeta = unknown, TPointMeta = unknown>(props: ChartProps<TChartMeta, TSeriesMeta, TPointMeta>): ReactElement;
@@ -118,7 +118,6 @@ function ChartBody({
118
118
  onSeriesToggle,
119
119
  onPointClick
120
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
121
  const [container, size] = useSize();
123
122
  const [hover, setHover] = useState(null);
124
123
  const [zoom, setZoom] = useState(null);
@@ -143,17 +142,17 @@ function ChartBody({
143
142
  } else {
144
143
  next.add(key);
145
144
  }
146
- onSeriesToggle === null || onSeriesToggle === void 0 || onSeriesToggle(item.seriesIndex, !next.has(key), item.pointIndex);
145
+ onSeriesToggle?.(item.seriesIndex, !next.has(key), item.pointIndex);
147
146
  return next;
148
147
  });
149
148
  }, [onSeriesToggle]);
150
149
  const registry = useMemo(() => resolveRenderers(renderers), [renderers]);
151
150
  // 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;
151
+ const scrollable = options.chart?.scrollablePlotArea;
153
152
  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]);
153
+ width: Math.max(size.width, scrollable?.minWidth ?? 0),
154
+ height: Math.max(size.height, scrollable?.minHeight ?? 0)
155
+ }), [size, scrollable?.minWidth, scrollable?.minHeight]);
157
156
  // A zoom is an override on the first x axis, not a second source of truth: the
158
157
  // model is still built from the options, so everything downstream is unaware.
159
158
  const zoomed = useMemo(() => {
@@ -177,7 +176,7 @@ function ChartBody({
177
176
  renderers: registry,
178
177
  hidden
179
178
  }) : null, [zoomed, canvas, registry, hidden]);
180
- const anyShown = (built === null || built === void 0 ? void 0 : built.series.some(series => !series.hidden)) ?? false;
179
+ const anyShown = built?.series.some(series => !series.hidden) ?? false;
181
180
  const lastShown = useRef(null);
182
181
  useEffect(() => {
183
182
  if (built && anyShown) {
@@ -213,7 +212,7 @@ function ChartBody({
213
212
  if (!yModel || yModel.scale.kind === 'color') {
214
213
  return null;
215
214
  }
216
- const hit = hover === null || hover === void 0 ? void 0 : hover.hits.find(candidate => candidate.seriesIndex === series.index);
215
+ const hit = hover?.hits.find(candidate => candidate.seriesIndex === series.index);
217
216
  return {
218
217
  series: series.options,
219
218
  seriesIndex: series.index,
@@ -260,8 +259,7 @@ function ChartBody({
260
259
  };
261
260
  }, [model, contextFor]);
262
261
  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) {
262
+ if (!model || options.tooltip?.enabled === false) {
265
263
  return;
266
264
  }
267
265
  // While a zoom is being dragged out, a tooltip is in the way of the thing
@@ -282,7 +280,7 @@ function ChartBody({
282
280
  setHover(null);
283
281
  return;
284
282
  }
285
- const shared = ((_options$tooltip2 = options.tooltip) === null || _options$tooltip2 === void 0 ? void 0 : _options$tooltip2.shared) ?? false;
283
+ const shared = options.tooltip?.shared ?? false;
286
284
  const nearest = hits.reduce((best, hit) => hit.distance < best.distance ? hit : best);
287
285
  const selected = shared ? hits.filter(hit => String(hit.x) === String(nearest.x)) : [nearest];
288
286
  const highest = selected.reduce((best, hit) => hit.anchor.y < best.anchor.y ? hit : best, selected[0]);
@@ -299,7 +297,7 @@ function ChartBody({
299
297
  });
300
298
  }, [model, hitsAt, options.tooltip, drag]);
301
299
  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);
300
+ const zooms = options.chart?.zoomType === 'x' && model !== null && isContinuous(model.xAxis.scale);
303
301
  const localX = event => event.clientX - event.currentTarget.getBoundingClientRect().left;
304
302
  const startDrag = useCallback(event => {
305
303
  if (!zooms || !model) {
@@ -352,11 +350,10 @@ function ChartBody({
352
350
  setHover(null);
353
351
  }, []);
354
352
  const handleClick = useCallback(event => {
355
- var _hitsAt, _series$points$neares;
356
353
  if (!onPointClick || !model) {
357
354
  return;
358
355
  }
359
- const hits = ((_hitsAt = hitsAt(event)) === null || _hitsAt === void 0 ? void 0 : _hitsAt.hits) ?? [];
356
+ const hits = hitsAt(event)?.hits ?? [];
360
357
  if (!hits.length) {
361
358
  return;
362
359
  }
@@ -364,33 +361,30 @@ function ChartBody({
364
361
  const series = model.series.find(item => item.index === nearest.seriesIndex);
365
362
  onPointClick({
366
363
  seriesIndex: nearest.seriesIndex,
367
- seriesName: (series === null || series === void 0 ? void 0 : series.options.name) ?? `Series ${nearest.seriesIndex + 1}`,
364
+ seriesName: series?.options.name ?? `Series ${nearest.seriesIndex + 1}`,
368
365
  pointIndex: nearest.pointIndex,
369
366
  x: nearest.x,
370
367
  y: nearest.y,
371
368
  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
369
+ color: series?.color ?? DEFAULT_TEXT_COLOR,
370
+ meta: series?.points[nearest.pointIndex]?.meta
374
371
  }, event);
375
372
  }, [onPointClick, model, hitsAt]);
376
373
  const formatX = useCallback(value => {
377
- var _options$tooltip3;
378
374
  if (typeof value !== 'number') {
379
375
  return String(value);
380
376
  }
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);
377
+ const pattern = options.tooltip?.dateFormat ?? (model && isContinuous(model.xAxis.scale) && model.xAxis.scale.kind === 'time' ? '%b %d, %Y' : undefined);
382
378
  return pattern ? formatTime(pattern, value) : String(value);
383
- }, [(_options$tooltip4 = options.tooltip) === null || _options$tooltip4 === void 0 ? void 0 : _options$tooltip4.dateFormat, model]);
379
+ }, [options.tooltip?.dateFormat, model]);
384
380
  const formatValue = useCallback(value => {
385
- var _options$tooltip5, _options$tooltip6;
386
381
  if (value === null) {
387
382
  return '—';
388
383
  }
389
384
  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]);
385
+ return `${options.tooltip?.valuePrefix ?? ''}${text}${options.tooltip?.valueSuffix ?? ''}`;
386
+ }, [options.tooltip?.valuePrefix, options.tooltip?.valueSuffix, model]);
392
387
  const tooltipContent = useMemo(() => {
393
- var _points$, _options$title, _options$tooltip9, _options$tooltip0;
394
388
  if (!hover || !model) {
395
389
  return null;
396
390
  }
@@ -403,18 +397,19 @@ function ChartBody({
403
397
  }
404
398
  const points = shown.map(hit => {
405
399
  const series = model.series.find(item => item.index === hit.seriesIndex);
406
- const neighbours = (series === null || series === void 0 ? void 0 : series.points) ?? [];
400
+ const neighbours = series?.points ?? [];
407
401
  const previous = neighbours[hit.pointIndex - 1];
408
402
  const next = neighbours[hit.pointIndex + 1];
409
403
  return {
410
404
  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,
405
+ seriesName: series?.options.name ?? `Series ${hit.seriesIndex + 1}`,
406
+ color: series?.color ?? DEFAULT_TEXT_COLOR,
413
407
  x: hit.x,
414
408
  y: hit.y,
415
409
  value: hit.value,
416
410
  index: hit.pointIndex,
417
- meta: series === null || series === void 0 ? void 0 : series.options.meta,
411
+ meta: series?.options.meta,
412
+ pointMeta: neighbours[hit.pointIndex]?.meta,
418
413
  prev: previous ? {
419
414
  x: previous.x,
420
415
  y: previous.y,
@@ -428,34 +423,33 @@ function ChartBody({
428
423
  };
429
424
  });
430
425
  const ctx = {
431
- x: ((_points$ = points[0]) === null || _points$ === void 0 ? void 0 : _points$.x) ?? 0,
426
+ x: points[0]?.x ?? 0,
432
427
  points,
433
428
  chart: {
434
- title: ((_options$title = options.title) === null || _options$title === void 0 ? void 0 : _options$title.text) ?? undefined,
429
+ title: options.title?.text ?? undefined,
435
430
  meta: options.meta
436
431
  }
437
432
  };
438
- const formatter = (_options$tooltip9 = options.tooltip) === null || _options$tooltip9 === void 0 ? void 0 : _options$tooltip9.formatter;
433
+ const formatter = options.tooltip?.formatter;
439
434
  if (formatter) {
440
435
  return formatter(ctx);
441
436
  }
442
- const table = (_options$tooltip0 = options.tooltip) === null || _options$tooltip0 === void 0 ? void 0 : _options$tooltip0.table;
437
+ const table = options.tooltip?.table;
443
438
  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;
439
+ }, [hover, model, options.title?.text, options.meta, options.tooltip?.formatter, options.tooltip?.table, formatX, formatValue]);
440
+ const hasData = model?.series.some(series => series.points.some(point => point.y !== null)) ?? false;
441
+ const plotBorderWidth = options.chart?.plotBorderWidth ?? 0;
447
442
  const scrolls = canvas.width > size.width || canvas.height > size.height;
448
443
  // Only horizontal scrolling pins anything, and only a left-hand y axis: that is
449
444
  // the one whose labels the plot would otherwise slide out from under. A right-hand
450
445
  // 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);
446
+ const pinsAxes = canvas.width > size.width && !(model?.axisless ?? true);
452
447
  const isPinned = axis => pinsAxes && axis.options.opposite !== true;
453
448
  // Highcharts dims the series a hover is not on. Ours only does it when asked:
454
449
  // switching it on by default would restyle every chart already drawn.
455
450
  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)) {
451
+ const opacity = series.options.states?.inactive?.opacity;
452
+ if (opacity === undefined || !hover?.hits.length) {
459
453
  return undefined;
460
454
  }
461
455
  return hover.hits.some(hit => hit.seriesIndex === series.index) ? undefined : opacity;
@@ -467,7 +461,7 @@ function ChartBody({
467
461
  if (animation === false) {
468
462
  return 0;
469
463
  }
470
- return (animation === null || animation === void 0 ? void 0 : animation.duration) ?? MOTION_MS;
464
+ return animation?.duration ?? MOTION_MS;
471
465
  };
472
466
  const decorations = layer => {
473
467
  if (!model || model.axisless) {
@@ -491,12 +485,12 @@ function ChartBody({
491
485
  ...containerProps,
492
486
  ref: container,
493
487
  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,
488
+ "$width": toCssSize(options.chart?.width, '100%'),
489
+ "$height": toCssSize(options.chart?.height, '100%'),
490
+ "$autoHeight": options.chart?.height === undefined,
491
+ "$background": options.chart?.backgroundColor,
492
+ "$fontFamily": options.chart?.style?.fontFamily ?? DEFAULT_FONT_FAMILY,
493
+ "$fontSize": options.chart?.style?.fontSize ?? DEFAULT_FONT_SIZE,
500
494
  children: [isLoading ? _jsx(Centred, {
501
495
  children: loading ?? 'Loading…'
502
496
  }) : null, !isLoading && model && !hasData ? _jsx(Centred, {
@@ -507,7 +501,7 @@ function ChartBody({
507
501
  width: canvas.width,
508
502
  height: canvas.height,
509
503
  role: "img",
510
- "aria-label": ((_options$title3 = options.title) === null || _options$title3 === void 0 ? void 0 : _options$title3.text) ?? 'Chart',
504
+ "aria-label": options.title?.text ?? 'Chart',
511
505
  onPointerMove: event => {
512
506
  extendDrag(event);
513
507
  handlePointerMove(event);
@@ -533,7 +527,7 @@ function ChartBody({
533
527
  width: model.plot.width,
534
528
  height: model.plot.height,
535
529
  fill: "none",
536
- stroke: ((_options$chart0 = options.chart) === null || _options$chart0 === void 0 ? void 0 : _options$chart0.plotBorderColor) ?? DEFAULT_GRID_COLOR,
530
+ stroke: options.chart?.plotBorderColor ?? DEFAULT_GRID_COLOR,
537
531
  strokeWidth: plotBorderWidth
538
532
  }) : null, model.series.map(series => {
539
533
  const ctx = contextFor(series);
@@ -563,8 +557,7 @@ function ChartBody({
563
557
  spacing: model.spacing,
564
558
  onToggle: toggleSeries ? toggle : undefined,
565
559
  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;
560
+ const formatter = options.legend?.labelFormatter;
568
561
  if (!formatter) {
569
562
  return item.label;
570
563
  }
@@ -573,7 +566,7 @@ function ChartBody({
573
566
  name: item.label,
574
567
  color: item.color,
575
568
  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
569
+ meta: model.series.find(s => s.index === item.seriesIndex)?.options.meta
577
570
  });
578
571
  }
579
572
  })]
@@ -587,7 +580,7 @@ function ChartBody({
587
580
  y: 0,
588
581
  width: model.plot.x,
589
582
  height: canvas.height,
590
- fill: ((_options$chart1 = options.chart) === null || _options$chart1 === void 0 ? void 0 : _options$chart1.backgroundColor) ?? DEFAULT_SURFACE_COLOR
583
+ fill: options.chart?.backgroundColor ?? DEFAULT_SURFACE_COLOR
591
584
  }), model.yAxes.filter(isPinned).map(axis => _jsx(YAxis, {
592
585
  plot: model.plot,
593
586
  axis: axis
@@ -595,7 +588,7 @@ function ChartBody({
595
588
  }) : null, zoom ? _jsx(ResetZoom, {
596
589
  type: "button",
597
590
  onClick: resetZoom,
598
- children: ((_options$chart10 = options.chart) === null || _options$chart10 === void 0 ? void 0 : _options$chart10.resetZoomText) ?? 'Reset zoom'
591
+ children: options.chart?.resetZoomText ?? 'Reset zoom'
599
592
  }) : null, hover && tooltipContent ? _jsx(Tooltip, {
600
593
  at: hover.anchor,
601
594
  pointer: hover.pointer,
@@ -15,8 +15,7 @@ export function Hinted({
15
15
  const ref = useRef(null);
16
16
  const [at, setAt] = useState(null);
17
17
  const open = useCallback(() => {
18
- var _ref$current;
19
- const box = (_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : _ref$current.getBoundingClientRect();
18
+ const box = ref.current?.getBoundingClientRect();
20
19
  if (box) {
21
20
  setAt({
22
21
  x: box.left + box.width / 2,
@@ -15,29 +15,28 @@ export function Legend({
15
15
  renderLabel,
16
16
  onToggle
17
17
  }) {
18
- if (!items.length || (options === null || options === void 0 ? void 0 : options.enabled) === false) {
18
+ if (!items.length || options?.enabled === false) {
19
19
  return null;
20
20
  }
21
- const style = options === null || options === void 0 ? void 0 : options.itemStyle;
22
- const symbolSize = (options === null || options === void 0 ? void 0 : options.symbolHeight) ?? 8;
23
- const symbolRadius = (options === null || options === void 0 ? void 0 : options.symbolRadius) ?? 2;
24
- const symbolPadding = (options === null || options === void 0 ? void 0 : options.symbolPadding) ?? 6;
21
+ const style = options?.itemStyle;
22
+ const symbolSize = options?.symbolHeight ?? 8;
23
+ const symbolRadius = options?.symbolRadius ?? 2;
24
+ const symbolPadding = options?.symbolPadding ?? 6;
25
25
  const {
26
26
  positions,
27
27
  metrics
28
28
  } = layoutLegend(items, options, plot, size, spacing);
29
29
  const interactive = Boolean(onToggle);
30
- const activate = item => () => onToggle === null || onToggle === void 0 ? void 0 : onToggle(item);
30
+ const activate = item => () => onToggle?.(item);
31
31
  const onKeyDown = item => event => {
32
32
  if (event.key !== 'Enter' && event.key !== ' ') {
33
33
  return;
34
34
  }
35
35
  event.preventDefault();
36
- onToggle === null || onToggle === void 0 || onToggle(item);
36
+ onToggle?.(item);
37
37
  };
38
38
  return _jsx("g", {
39
39
  children: items.map((item, index) => {
40
- var _options$itemHoverSty, _options$itemHoverSty2;
41
40
  const {
42
41
  x,
43
42
  y
@@ -47,8 +46,8 @@ export function Legend({
47
46
  return _jsxs(LegendItemBox, {
48
47
  "$off": !item.visible,
49
48
  "$interactive": interactive,
50
- "$hoverColor": options === null || options === void 0 || (_options$itemHoverSty = options.itemHoverStyle) === null || _options$itemHoverSty === void 0 ? void 0 : _options$itemHoverSty.color,
51
- "$hoverWeight": options === null || options === void 0 || (_options$itemHoverSty2 = options.itemHoverStyle) === null || _options$itemHoverSty2 === void 0 ? void 0 : _options$itemHoverSty2.fontWeight,
49
+ "$hoverColor": options?.itemHoverStyle?.color,
50
+ "$hoverWeight": options?.itemHoverStyle?.fontWeight,
52
51
  role: interactive ? 'button' : undefined,
53
52
  tabIndex: interactive ? 0 : undefined,
54
53
  "aria-pressed": interactive ? item.visible : undefined,
@@ -72,17 +71,17 @@ export function Legend({
72
71
  }) : _jsx("rect", {
73
72
  x: x,
74
73
  y: y - symbolSize / 2,
75
- width: (options === null || options === void 0 ? void 0 : options.symbolWidth) ?? symbolSize,
74
+ width: options?.symbolWidth ?? symbolSize,
76
75
  height: symbolSize,
77
76
  rx: item.symbol === 'circle' ? symbolSize / 2 : symbolRadius,
78
77
  fill: item.color
79
78
  }), _jsx("text", {
80
79
  x: labelX,
81
80
  y: y + TEXT_BASELINE_SHIFT,
82
- fill: (style === null || style === void 0 ? void 0 : style.color) ?? DEFAULT_TEXT_COLOR,
83
- fontFamily: style === null || style === void 0 ? void 0 : style.fontFamily,
84
- fontSize: (style === null || style === void 0 ? void 0 : style.fontSize) ?? DEFAULT_FONT_SIZE,
85
- fontWeight: style === null || style === void 0 ? void 0 : style.fontWeight,
81
+ fill: style?.color ?? DEFAULT_TEXT_COLOR,
82
+ fontFamily: style?.fontFamily,
83
+ fontSize: style?.fontSize ?? DEFAULT_FONT_SIZE,
84
+ fontWeight: style?.fontWeight,
86
85
  textDecoration: item.visible ? undefined : 'line-through',
87
86
  children: renderLabel(item)
88
87
  })]
@@ -29,10 +29,10 @@ function project(scale, value, edge) {
29
29
  return typeof at === 'number' ? at : null;
30
30
  }
31
31
  const textProps = (style, onFill) => ({
32
- fill: (style === null || style === void 0 ? void 0 : style.color) ?? (onFill ? textColorOn(onFill) : DEFAULT_MUTED_TEXT_COLOR),
33
- fontFamily: style === null || style === void 0 ? void 0 : style.fontFamily,
34
- fontSize: (style === null || style === void 0 ? void 0 : style.fontSize) ?? DEFAULT_FONT_SIZE,
35
- fontWeight: style === null || style === void 0 ? void 0 : style.fontWeight
32
+ fill: style?.color ?? (onFill ? textColorOn(onFill) : DEFAULT_MUTED_TEXT_COLOR),
33
+ fontFamily: style?.fontFamily,
34
+ fontSize: style?.fontSize ?? DEFAULT_FONT_SIZE,
35
+ fontWeight: style?.fontWeight
36
36
  });
37
37
  const ANCHOR = {
38
38
  left: 'start',
@@ -49,7 +49,7 @@ function PlotLabel({
49
49
  box,
50
50
  along
51
51
  }) {
52
- if (!(label !== null && label !== void 0 && label.text)) {
52
+ if (!label?.text) {
53
53
  return null;
54
54
  }
55
55
  const align = label.align ?? (along === 'x' ? 'center' : 'left');