@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.
- package/ai-documentation.md +22 -1
- package/core/Chart/Axes.js +38 -51
- package/core/Chart/Chart.d.ts +4 -4
- package/core/Chart/Chart.js +46 -53
- package/core/Chart/Hinted.js +1 -2
- package/core/Chart/Legend.js +14 -15
- package/core/Chart/PlotDecorations.js +5 -5
- package/core/Chart/StackLabels.js +6 -7
- package/core/Chart/Titles.js +14 -17
- package/core/Chart/Tooltip.js +12 -13
- package/core/data/points.js +1 -1
- package/core/geometry/path.js +1 -2
- package/core/layout/legend-layout.js +12 -12
- package/core/layout/measure-text.js +3 -3
- package/core/layout/title-layout.js +9 -15
- package/core/model/build-model.js +44 -69
- package/core/options/normalize.js +1 -2
- package/core/registry/registry.js +1 -1
- package/core/types/formatter.d.ts +7 -5
- package/core/types/options.d.ts +12 -10
- package/package.json +2 -2
- package/presets/AreaChart.d.ts +1 -1
- package/presets/ColumnChart.d.ts +1 -1
- package/presets/LineChart.d.ts +1 -1
- package/presets/PieChart.d.ts +1 -1
- package/presets/StackedColumnChart.d.ts +2 -2
- package/series/area/area.js +3 -4
- package/series/column/column.js +4 -8
- package/series/heatmap/heatmap.js +5 -8
- package/series/line/line.js +2 -3
- package/series/pie/pie.js +6 -8
- package/series/shared/DataLabels.js +8 -8
- package/series/shared/Markers.js +8 -8
package/ai-documentation.md
CHANGED
|
@@ -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
|
package/core/Chart/Axes.js
CHANGED
|
@@ -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:
|
|
8
|
-
fontFamily: style
|
|
9
|
-
fontSize:
|
|
10
|
-
fontWeight: style
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
122
|
-
const labelY = baseline + direction * (tickLength + (onTop ? AXIS_LABEL_GAP : X_AXIS_LABEL_DROP)) + (
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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 * ((
|
|
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 =
|
|
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 = (
|
|
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 + (
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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),
|
package/core/Chart/Chart.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/core/Chart/Chart.js
CHANGED
|
@@ -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
|
|
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 =
|
|
151
|
+
const scrollable = options.chart?.scrollablePlotArea;
|
|
153
152
|
const canvas = useMemo(() => ({
|
|
154
|
-
width: Math.max(size.width,
|
|
155
|
-
height: Math.max(size.height,
|
|
156
|
-
}), [size, scrollable
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
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:
|
|
373
|
-
meta: series
|
|
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 =
|
|
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
|
-
}, [
|
|
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 `${
|
|
391
|
-
}, [
|
|
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 =
|
|
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:
|
|
412
|
-
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
|
|
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:
|
|
426
|
+
x: points[0]?.x ?? 0,
|
|
432
427
|
points,
|
|
433
428
|
chart: {
|
|
434
|
-
title:
|
|
429
|
+
title: options.title?.text ?? undefined,
|
|
435
430
|
meta: options.meta
|
|
436
431
|
}
|
|
437
432
|
};
|
|
438
|
-
const formatter =
|
|
433
|
+
const formatter = options.tooltip?.formatter;
|
|
439
434
|
if (formatter) {
|
|
440
435
|
return formatter(ctx);
|
|
441
436
|
}
|
|
442
|
-
const table =
|
|
437
|
+
const table = options.tooltip?.table;
|
|
443
438
|
return table ? tableTooltip(ctx, table, formatX) : defaultTooltip(ctx, formatX, formatValue);
|
|
444
|
-
}, [hover, model,
|
|
445
|
-
const hasData =
|
|
446
|
-
const plotBorderWidth =
|
|
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 && !(
|
|
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
|
-
|
|
457
|
-
|
|
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
|
|
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(
|
|
495
|
-
"$height": toCssSize(
|
|
496
|
-
"$autoHeight":
|
|
497
|
-
"$background":
|
|
498
|
-
"$fontFamily":
|
|
499
|
-
"$fontSize":
|
|
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":
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
591
|
+
children: options.chart?.resetZoomText ?? 'Reset zoom'
|
|
599
592
|
}) : null, hover && tooltipContent ? _jsx(Tooltip, {
|
|
600
593
|
at: hover.anchor,
|
|
601
594
|
pointer: hover.pointer,
|
package/core/Chart/Hinted.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
package/core/Chart/Legend.js
CHANGED
|
@@ -15,29 +15,28 @@ export function Legend({
|
|
|
15
15
|
renderLabel,
|
|
16
16
|
onToggle
|
|
17
17
|
}) {
|
|
18
|
-
if (!items.length ||
|
|
18
|
+
if (!items.length || options?.enabled === false) {
|
|
19
19
|
return null;
|
|
20
20
|
}
|
|
21
|
-
const style = options
|
|
22
|
-
const symbolSize =
|
|
23
|
-
const symbolRadius =
|
|
24
|
-
const symbolPadding =
|
|
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
|
|
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
|
|
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
|
|
51
|
-
"$hoverWeight": options
|
|
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:
|
|
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:
|
|
83
|
-
fontFamily: style
|
|
84
|
-
fontSize:
|
|
85
|
-
fontWeight: style
|
|
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:
|
|
33
|
-
fontFamily: style
|
|
34
|
-
fontSize:
|
|
35
|
-
fontWeight: style
|
|
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 (!
|
|
52
|
+
if (!label?.text) {
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
55
|
const align = label.align ?? (along === 'x' ? 'center' : 'left');
|