@pushwoosh/kit-charts 0.0.1 → 0.0.3
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 +19 -0
- package/core/Chart/Axes.js +38 -51
- package/core/Chart/Chart.js +45 -53
- package/core/Chart/Hinted.js +1 -2
- package/core/Chart/Legend.js +14 -15
- package/core/Chart/PlotDecorations.js +63 -36
- 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.d.ts +5 -0
- package/core/layout/measure-text.js +8 -3
- package/core/layout/metrics.d.ts +7 -0
- package/core/layout/metrics.js +7 -0
- package/core/layout/title-layout.js +9 -15
- package/core/model/build-model.js +44 -69
- package/core/model/palette.d.ts +8 -0
- package/core/model/palette.js +31 -0
- package/core/options/normalize.js +1 -2
- package/core/registry/registry.js +1 -1
- package/core/types/options.d.ts +4 -0
- package/package.json +2 -2
- package/series/area/area.js +2 -3
- 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
|
@@ -103,6 +103,25 @@ takes `value`; on a category axis both address a category by name and a band
|
|
|
103
103
|
covers the whole slot. Both accept a `label`, and a `zIndex` of 5 or more draws
|
|
104
104
|
them over the series instead of under it — the value Highcharts users reach for.
|
|
105
105
|
|
|
106
|
+
A label's `align` is horizontal and its `verticalAlign` vertical whichever way
|
|
107
|
+
the decoration runs, as in Highcharts: a line on a y axis takes its label to the
|
|
108
|
+
left edge of the plot (`align: 'left'`, the default there) and sits it on the
|
|
109
|
+
rule (`verticalAlign: 'middle'`). `x`/`y` shift it from there — a negative `x`
|
|
110
|
+
hangs it in the axis gutter.
|
|
111
|
+
|
|
112
|
+
`label.backgroundColor` draws the label on a pill instead of leaving it as loose
|
|
113
|
+
text, with `borderRadius` and `padding` to tune it. The text takes whichever of
|
|
114
|
+
white and `BRAND_BLACK` reads better on that fill — the greys a threshold is
|
|
115
|
+
painted in carry white at under 3:1 — and `style.color` overrides the choice.
|
|
116
|
+
That is what a threshold needs: a number sitting on a rule has no
|
|
117
|
+
ground of its own to read against. Highcharts has no such option — it takes
|
|
118
|
+
`useHTML` and a styled span, which this library has no equivalent of — so this is
|
|
119
|
+
an addition rather than a divergence: a Highcharts config still renders.
|
|
120
|
+
|
|
121
|
+
Whether a threshold is breached is the application's decision, not the chart's:
|
|
122
|
+
it passes the colour. Golden cases `area/threshold` and `area/threshold-breached`
|
|
123
|
+
are the same options with one colour changed.
|
|
124
|
+
|
|
106
125
|
## Interaction
|
|
107
126
|
|
|
108
127
|
`onPointClick(point, event)` reports the point nearest a click inside the plot,
|
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.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,18 @@ 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,
|
|
418
412
|
prev: previous ? {
|
|
419
413
|
x: previous.x,
|
|
420
414
|
y: previous.y,
|
|
@@ -428,34 +422,33 @@ function ChartBody({
|
|
|
428
422
|
};
|
|
429
423
|
});
|
|
430
424
|
const ctx = {
|
|
431
|
-
x:
|
|
425
|
+
x: points[0]?.x ?? 0,
|
|
432
426
|
points,
|
|
433
427
|
chart: {
|
|
434
|
-
title:
|
|
428
|
+
title: options.title?.text ?? undefined,
|
|
435
429
|
meta: options.meta
|
|
436
430
|
}
|
|
437
431
|
};
|
|
438
|
-
const formatter =
|
|
432
|
+
const formatter = options.tooltip?.formatter;
|
|
439
433
|
if (formatter) {
|
|
440
434
|
return formatter(ctx);
|
|
441
435
|
}
|
|
442
|
-
const table =
|
|
436
|
+
const table = options.tooltip?.table;
|
|
443
437
|
return table ? tableTooltip(ctx, table, formatX) : defaultTooltip(ctx, formatX, formatValue);
|
|
444
|
-
}, [hover, model,
|
|
445
|
-
const hasData =
|
|
446
|
-
const plotBorderWidth =
|
|
438
|
+
}, [hover, model, options.title?.text, options.meta, options.tooltip?.formatter, options.tooltip?.table, formatX, formatValue]);
|
|
439
|
+
const hasData = model?.series.some(series => series.points.some(point => point.y !== null)) ?? false;
|
|
440
|
+
const plotBorderWidth = options.chart?.plotBorderWidth ?? 0;
|
|
447
441
|
const scrolls = canvas.width > size.width || canvas.height > size.height;
|
|
448
442
|
// Only horizontal scrolling pins anything, and only a left-hand y axis: that is
|
|
449
443
|
// the one whose labels the plot would otherwise slide out from under. A right-hand
|
|
450
444
|
// axis and a vertically scrolling chart both stay in the scrolling canvas.
|
|
451
|
-
const pinsAxes = canvas.width > size.width && !(
|
|
445
|
+
const pinsAxes = canvas.width > size.width && !(model?.axisless ?? true);
|
|
452
446
|
const isPinned = axis => pinsAxes && axis.options.opposite !== true;
|
|
453
447
|
// Highcharts dims the series a hover is not on. Ours only does it when asked:
|
|
454
448
|
// switching it on by default would restyle every chart already drawn.
|
|
455
449
|
const inactiveOpacity = series => {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (opacity === undefined || !(hover !== null && hover !== void 0 && hover.hits.length)) {
|
|
450
|
+
const opacity = series.options.states?.inactive?.opacity;
|
|
451
|
+
if (opacity === undefined || !hover?.hits.length) {
|
|
459
452
|
return undefined;
|
|
460
453
|
}
|
|
461
454
|
return hover.hits.some(hit => hit.seriesIndex === series.index) ? undefined : opacity;
|
|
@@ -467,7 +460,7 @@ function ChartBody({
|
|
|
467
460
|
if (animation === false) {
|
|
468
461
|
return 0;
|
|
469
462
|
}
|
|
470
|
-
return
|
|
463
|
+
return animation?.duration ?? MOTION_MS;
|
|
471
464
|
};
|
|
472
465
|
const decorations = layer => {
|
|
473
466
|
if (!model || model.axisless) {
|
|
@@ -491,12 +484,12 @@ function ChartBody({
|
|
|
491
484
|
...containerProps,
|
|
492
485
|
ref: container,
|
|
493
486
|
className: className,
|
|
494
|
-
"$width": toCssSize(
|
|
495
|
-
"$height": toCssSize(
|
|
496
|
-
"$autoHeight":
|
|
497
|
-
"$background":
|
|
498
|
-
"$fontFamily":
|
|
499
|
-
"$fontSize":
|
|
487
|
+
"$width": toCssSize(options.chart?.width, '100%'),
|
|
488
|
+
"$height": toCssSize(options.chart?.height, '100%'),
|
|
489
|
+
"$autoHeight": options.chart?.height === undefined,
|
|
490
|
+
"$background": options.chart?.backgroundColor,
|
|
491
|
+
"$fontFamily": options.chart?.style?.fontFamily ?? DEFAULT_FONT_FAMILY,
|
|
492
|
+
"$fontSize": options.chart?.style?.fontSize ?? DEFAULT_FONT_SIZE,
|
|
500
493
|
children: [isLoading ? _jsx(Centred, {
|
|
501
494
|
children: loading ?? 'Loading…'
|
|
502
495
|
}) : null, !isLoading && model && !hasData ? _jsx(Centred, {
|
|
@@ -507,7 +500,7 @@ function ChartBody({
|
|
|
507
500
|
width: canvas.width,
|
|
508
501
|
height: canvas.height,
|
|
509
502
|
role: "img",
|
|
510
|
-
"aria-label":
|
|
503
|
+
"aria-label": options.title?.text ?? 'Chart',
|
|
511
504
|
onPointerMove: event => {
|
|
512
505
|
extendDrag(event);
|
|
513
506
|
handlePointerMove(event);
|
|
@@ -533,7 +526,7 @@ function ChartBody({
|
|
|
533
526
|
width: model.plot.width,
|
|
534
527
|
height: model.plot.height,
|
|
535
528
|
fill: "none",
|
|
536
|
-
stroke:
|
|
529
|
+
stroke: options.chart?.plotBorderColor ?? DEFAULT_GRID_COLOR,
|
|
537
530
|
strokeWidth: plotBorderWidth
|
|
538
531
|
}) : null, model.series.map(series => {
|
|
539
532
|
const ctx = contextFor(series);
|
|
@@ -563,8 +556,7 @@ function ChartBody({
|
|
|
563
556
|
spacing: model.spacing,
|
|
564
557
|
onToggle: toggleSeries ? toggle : undefined,
|
|
565
558
|
renderLabel: item => {
|
|
566
|
-
|
|
567
|
-
const formatter = (_options$legend = options.legend) === null || _options$legend === void 0 ? void 0 : _options$legend.labelFormatter;
|
|
559
|
+
const formatter = options.legend?.labelFormatter;
|
|
568
560
|
if (!formatter) {
|
|
569
561
|
return item.label;
|
|
570
562
|
}
|
|
@@ -573,7 +565,7 @@ function ChartBody({
|
|
|
573
565
|
name: item.label,
|
|
574
566
|
color: item.color,
|
|
575
567
|
visible: item.visible,
|
|
576
|
-
meta:
|
|
568
|
+
meta: model.series.find(s => s.index === item.seriesIndex)?.options.meta
|
|
577
569
|
});
|
|
578
570
|
}
|
|
579
571
|
})]
|
|
@@ -587,7 +579,7 @@ function ChartBody({
|
|
|
587
579
|
y: 0,
|
|
588
580
|
width: model.plot.x,
|
|
589
581
|
height: canvas.height,
|
|
590
|
-
fill:
|
|
582
|
+
fill: options.chart?.backgroundColor ?? DEFAULT_SURFACE_COLOR
|
|
591
583
|
}), model.yAxes.filter(isPinned).map(axis => _jsx(YAxis, {
|
|
592
584
|
plot: model.plot,
|
|
593
585
|
axis: axis
|
|
@@ -595,7 +587,7 @@ function ChartBody({
|
|
|
595
587
|
}) : null, zoom ? _jsx(ResetZoom, {
|
|
596
588
|
type: "button",
|
|
597
589
|
onClick: resetZoom,
|
|
598
|
-
children:
|
|
590
|
+
children: options.chart?.resetZoomText ?? 'Reset zoom'
|
|
599
591
|
}) : null, hover && tooltipContent ? _jsx(Tooltip, {
|
|
600
592
|
at: hover.anchor,
|
|
601
593
|
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
|
})]
|