@galaxy-io/dls 1.3.4 → 1.3.6
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/dist/accordion/Accordion.d.ts +1 -2
- package/dist/accordion/Accordion.js +18 -26
- package/dist/charts/BarChart.d.ts +1 -7
- package/dist/charts/BarChart.js +42 -20
- package/dist/charts/ChartAxis.d.ts +11 -2
- package/dist/charts/ChartAxis.js +5 -4
- package/dist/charts/ChartCategoryTargets.d.ts +16 -16
- package/dist/charts/ChartCategoryTargets.js +19 -70
- package/dist/charts/ChartFrame.d.ts +14 -12
- package/dist/charts/ChartFrame.js +36 -46
- package/dist/charts/ChartLegend.d.ts +12 -5
- package/dist/charts/ChartLegend.js +31 -6
- package/dist/charts/ChartPrimitives.d.ts +31 -31
- package/dist/charts/ChartPrimitives.js +97 -79
- package/dist/charts/ChartTooltip.js +9 -4
- package/dist/charts/LineChart.d.ts +1 -7
- package/dist/charts/LineChart.js +68 -48
- package/dist/charts/PieChart.d.ts +1 -8
- package/dist/charts/PieChart.js +38 -45
- package/dist/charts/chartFormat.d.ts +1 -9
- package/dist/charts/chartFormat.js +1 -13
- package/dist/charts/constants.d.ts +23 -7
- package/dist/charts/constants.js +82 -8
- package/dist/charts/types.d.ts +14 -4
- package/dist/charts/types.js +15 -4
- package/dist/charts/useChartInteraction.d.ts +15 -32
- package/dist/charts/useChartInteraction.js +9 -89
- package/dist/charts/useSeriesColorResolver.d.ts +7 -0
- package/dist/charts/useSeriesColorResolver.js +2 -1
- package/dist/styles.css +25 -26
- package/dist/switcher/Switcher.js +2 -2
- package/package.json +1 -1
package/dist/charts/PieChart.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import FlexWrapper, { AlignItems, FlexDirection, JustifyContent } from "../containers/FlexWrapper.js";
|
|
2
|
-
import { ChartKind, ChartTargetShape } from "./types.js";
|
|
3
|
-
import { PIE_CHART_MARGIN, PIE_MIN_SLICE_SHARE } from "./constants.js";
|
|
2
|
+
import { ChartKind, ChartLegendPlacement, ChartTargetShape } from "./types.js";
|
|
3
|
+
import { PIE_CHART_MARGIN, PIE_MIN_SLICE_SHARE, SKELETON_PIE_VALUES } from "./constants.js";
|
|
4
4
|
import { useSeriesColorResolver } from "./useSeriesColorResolver.js";
|
|
5
5
|
import { ChartTooltipVerticalAlign } from "./useChartTooltipPosition.js";
|
|
6
6
|
import { useChartInteraction } from "./useChartInteraction.js";
|
|
7
7
|
import { useChartFormatters } from "./useChartFormatters.js";
|
|
8
8
|
import { useChartDimensions } from "./useChartDimensions.js";
|
|
9
9
|
import { buildPieChartGeometry } from "./pieChartGeometry.js";
|
|
10
|
-
import { ChartArcPath, ChartCenterBox } from "./ChartPrimitives.js";
|
|
10
|
+
import { ChartArcPath, ChartCenterBox, ChartSkeletonGroup } from "./ChartPrimitives.js";
|
|
11
11
|
import ChartFrame from "./ChartFrame.js";
|
|
12
12
|
import { useCallback, useMemo, useRef } from "react";
|
|
13
13
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -19,7 +19,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
19
19
|
* each arc is its own target. Small slices are folded into an "Other" bucket by
|
|
20
20
|
* default, which keeps every remaining arc large enough to actually hit.
|
|
21
21
|
*/
|
|
22
|
-
var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRatio, margin, innerRadiusRatio, padAngle, cornerRadius, minSliceShare = PIE_MIN_SLICE_SHARE, aggregatedLabel, centerContent, valueUnit, valueFormatter, labelFormatter, showLegend = true, noTooltip = false, tooltipRenderer, isLoading = false, contentWhenEmpty, ariaLabel, ariaDescription
|
|
22
|
+
var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRatio, margin, innerRadiusRatio, padAngle, cornerRadius, minSliceShare = PIE_MIN_SLICE_SHARE, aggregatedLabel, centerContent, valueUnit, valueFormatter, labelFormatter, showLegend = true, noTooltip = false, tooltipRenderer, isLoading = false, contentWhenEmpty, ariaLabel, ariaDescription }) => {
|
|
23
23
|
const containerRef = useRef(null);
|
|
24
24
|
const colors = useSeriesColorResolver(series);
|
|
25
25
|
const dimensions = useChartDimensions({
|
|
@@ -40,9 +40,23 @@ var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRa
|
|
|
40
40
|
});
|
|
41
41
|
const getSliceLabel = useCallback((sliceKey) => series[sliceKey]?.label ?? sliceKey, [series]);
|
|
42
42
|
const getSliceColor = useCallback((sliceKey) => series[sliceKey]?.color, [series]);
|
|
43
|
-
|
|
43
|
+
/**
|
|
44
|
+
* The slices actually rendered: the caller's, or three ghost arcs while
|
|
45
|
+
* loading, pushed through the real geometry so the skeleton donut gets the
|
|
46
|
+
* true pad angles and corner radii. The cast is safe because nothing derived
|
|
47
|
+
* from skeleton data ever surfaces: targets are off, the legend shows stubs,
|
|
48
|
+
* and the accessible name says "loading" instead of counting slices.
|
|
49
|
+
*/
|
|
50
|
+
const displaySlices = useMemo(() => isLoading ? SKELETON_PIE_VALUES.map((value, index) => ({
|
|
51
|
+
key: `skeleton-${index}`,
|
|
52
|
+
value
|
|
53
|
+
})) : slices, [isLoading, slices]);
|
|
54
|
+
const resolveColor = useCallback(({ sliceKey, explicit }) => {
|
|
55
|
+
if (isLoading) return colors.placeholder;
|
|
56
|
+
return sliceKey === null ? colors.neutral : colors.resolve(sliceKey, explicit);
|
|
57
|
+
}, [colors, isLoading]);
|
|
44
58
|
const geometry = useMemo(() => buildPieChartGeometry({
|
|
45
|
-
slices,
|
|
59
|
+
slices: displaySlices,
|
|
46
60
|
plotWidth,
|
|
47
61
|
plotHeight,
|
|
48
62
|
innerRadiusRatio,
|
|
@@ -54,7 +68,7 @@ var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRa
|
|
|
54
68
|
getSliceColor,
|
|
55
69
|
getSliceLabel
|
|
56
70
|
}), [
|
|
57
|
-
|
|
71
|
+
displaySlices,
|
|
58
72
|
plotWidth,
|
|
59
73
|
plotHeight,
|
|
60
74
|
innerRadiusRatio,
|
|
@@ -76,9 +90,7 @@ var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRa
|
|
|
76
90
|
const interaction = useChartInteraction({
|
|
77
91
|
categoryCount: geometry.arcs.length,
|
|
78
92
|
seriesKeys: legendKeys,
|
|
79
|
-
disabled: noTooltip || isLoading || isEmpty
|
|
80
|
-
isLoading,
|
|
81
|
-
onPinnedChange
|
|
93
|
+
disabled: noTooltip || isLoading || isEmpty
|
|
82
94
|
});
|
|
83
95
|
const buildCategoryTooltip = useCallback((index) => {
|
|
84
96
|
const chartArc = geometry.arcs[index];
|
|
@@ -101,35 +113,28 @@ var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRa
|
|
|
101
113
|
formatLabel
|
|
102
114
|
]);
|
|
103
115
|
/**
|
|
104
|
-
* A slice reads better than the generic "label: row, row" sentence.
|
|
105
|
-
*
|
|
106
|
-
* The frame's default would render "Enterprise: 42.1% of total 1,240", which
|
|
107
|
-
* puts the share where a reader expects the value.
|
|
108
|
-
*/
|
|
109
|
-
const describeCategory = useCallback((index) => {
|
|
110
|
-
const chartArc = geometry.arcs[index];
|
|
111
|
-
if (!chartArc) return "";
|
|
112
|
-
const share = `${Math.round(chartArc.share * 1e3) / 10}% of total`;
|
|
113
|
-
return `${formatLabel(chartArc.label)}, ${formatValue(chartArc.value)}, ${share}`;
|
|
114
|
-
}, [
|
|
115
|
-
geometry.arcs,
|
|
116
|
-
formatValue,
|
|
117
|
-
formatLabel
|
|
118
|
-
]);
|
|
119
|
-
/**
|
|
120
116
|
* Targets reuse each arc's own `d`, drawn again transparent above the marks.
|
|
121
117
|
*
|
|
122
|
-
* The arcs themselves
|
|
123
|
-
*
|
|
124
|
-
* attaching handlers permanently and toggling `data-interactive` while the
|
|
125
|
-
* cartesian charts rendered their targets away.
|
|
118
|
+
* The arcs themselves stay pure marks: hit testing then works identically on
|
|
119
|
+
* all three charts, instead of the pie hit-testing its visible arcs directly.
|
|
126
120
|
*/
|
|
127
121
|
const categoryTargets = useMemo(() => geometry.arcs.map((chartArc) => ({
|
|
128
122
|
key: chartArc.id,
|
|
129
123
|
shape: ChartTargetShape.PATH,
|
|
130
124
|
d: chartArc.path
|
|
131
125
|
})), [geometry.arcs]);
|
|
132
|
-
const showCenter = centerContent && (innerRadiusRatio ?? .62) > 0;
|
|
126
|
+
const showCenter = centerContent && !isLoading && (innerRadiusRatio ?? .62) > 0;
|
|
127
|
+
const marks = /* @__PURE__ */ jsx("g", {
|
|
128
|
+
transform: `translate(${geometry.centerX}, ${geometry.centerY})`,
|
|
129
|
+
children: geometry.arcs.map((chartArc, index) => /* @__PURE__ */ jsx(ChartArcPath, {
|
|
130
|
+
d: chartArc.path,
|
|
131
|
+
fill: chartArc.color,
|
|
132
|
+
"data-state": interaction.getMarkState({
|
|
133
|
+
categoryIndex: index,
|
|
134
|
+
seriesKey: chartArc.id
|
|
135
|
+
})
|
|
136
|
+
}, chartArc.id))
|
|
137
|
+
});
|
|
133
138
|
return /* @__PURE__ */ jsx(ChartFrame, {
|
|
134
139
|
containerRef,
|
|
135
140
|
dimensions,
|
|
@@ -145,8 +150,6 @@ var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRa
|
|
|
145
150
|
tooltipRenderer,
|
|
146
151
|
categoryTargets,
|
|
147
152
|
categoryTargetsTransform: `translate(${geometry.centerX}, ${geometry.centerY})`,
|
|
148
|
-
wrapCategoryNavigation: true,
|
|
149
|
-
describeCategory,
|
|
150
153
|
placeAwayFromX: geometry.centerX,
|
|
151
154
|
verticalAlign: ChartTooltipVerticalAlign.CENTERED,
|
|
152
155
|
isEmpty,
|
|
@@ -154,24 +157,14 @@ var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRa
|
|
|
154
157
|
contentWhenEmpty,
|
|
155
158
|
showLegend,
|
|
156
159
|
legendItems,
|
|
157
|
-
|
|
160
|
+
legendPlacement: ChartLegendPlacement.RIGHT,
|
|
158
161
|
plotOverlay: showCenter && !isEmpty ? /* @__PURE__ */ jsx(ChartDonutCenter, {
|
|
159
162
|
centerX: dimensions.margin.left + geometry.centerX,
|
|
160
163
|
centerY: dimensions.margin.top + geometry.centerY,
|
|
161
164
|
diameter: geometry.innerRadius * 2,
|
|
162
165
|
children: centerContent
|
|
163
166
|
}) : null,
|
|
164
|
-
children: /* @__PURE__ */ jsx(
|
|
165
|
-
transform: `translate(${geometry.centerX}, ${geometry.centerY})`,
|
|
166
|
-
children: geometry.arcs.map((chartArc, index) => /* @__PURE__ */ jsx(ChartArcPath, {
|
|
167
|
-
d: chartArc.path,
|
|
168
|
-
fill: chartArc.color,
|
|
169
|
-
"data-state": interaction.getMarkState({
|
|
170
|
-
categoryIndex: index,
|
|
171
|
-
seriesKey: chartArc.id
|
|
172
|
-
})
|
|
173
|
-
}, chartArc.id))
|
|
174
|
-
})
|
|
167
|
+
children: isLoading ? /* @__PURE__ */ jsx(ChartSkeletonGroup, { children: marks }) : marks
|
|
175
168
|
});
|
|
176
169
|
};
|
|
177
170
|
/** Positions arbitrary content inside the donut hole. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChartValueUnit } from "./types";
|
|
2
|
-
import type { ChartLabelFormatter,
|
|
2
|
+
import type { ChartLabelFormatter, ChartValueFormatter } from "./types";
|
|
3
3
|
/**
|
|
4
4
|
* Format a value with a built-in unit.
|
|
5
5
|
*
|
|
@@ -17,11 +17,3 @@ export declare function formatChartValue(value: number, unit: ChartValueUnit): s
|
|
|
17
17
|
export declare function resolveValueFormatter(valueFormatter?: ChartValueFormatter, unit?: ChartValueUnit): ChartValueFormatter;
|
|
18
18
|
/** Pick the effective label formatter, defaulting to identity. */
|
|
19
19
|
export declare function resolveLabelFormatter(labelFormatter?: ChartLabelFormatter): ChartLabelFormatter;
|
|
20
|
-
/**
|
|
21
|
-
* Flatten a tooltip model into one sentence, for a screen reader.
|
|
22
|
-
*
|
|
23
|
-
* Derived from the tooltip rather than written separately so the two cannot
|
|
24
|
-
* drift: whatever a sighted reader sees on hover is what a keyboard reader
|
|
25
|
-
* hears on focus, by construction.
|
|
26
|
-
*/
|
|
27
|
-
export declare function describeTooltipModel(model: ChartPlotTooltipModel): string;
|
|
@@ -55,17 +55,5 @@ function resolveValueFormatter(valueFormatter, unit) {
|
|
|
55
55
|
function resolveLabelFormatter(labelFormatter) {
|
|
56
56
|
return labelFormatter ?? ((label) => label);
|
|
57
57
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Flatten a tooltip model into one sentence, for a screen reader.
|
|
60
|
-
*
|
|
61
|
-
* Derived from the tooltip rather than written separately so the two cannot
|
|
62
|
-
* drift: whatever a sighted reader sees on hover is what a keyboard reader
|
|
63
|
-
* hears on focus, by construction.
|
|
64
|
-
*/
|
|
65
|
-
function describeTooltipModel(model) {
|
|
66
|
-
if (model.rows.length === 0) return model.label;
|
|
67
|
-
const rows = model.rows.map((row) => `${row.label} ${row.formattedValue}`).join(", ");
|
|
68
|
-
return `${model.label}: ${rows}`;
|
|
69
|
-
}
|
|
70
58
|
//#endregion
|
|
71
|
-
export {
|
|
59
|
+
export { formatChartValue, resolveLabelFormatter, resolveValueFormatter };
|
|
@@ -185,27 +185,43 @@ export declare const CHART_RESTORE_DELAY_MS = 200;
|
|
|
185
185
|
*
|
|
186
186
|
* The grammar is inherited from the reference charts and is the opposite of the
|
|
187
187
|
* obvious one: hovering does not brighten the hovered mark, it *softens*
|
|
188
|
-
* everything else. `MUTED` applies to marks outside the hovered category
|
|
189
|
-
*
|
|
188
|
+
* everything else. `MUTED` applies to marks outside the hovered category or
|
|
189
|
+
* series.
|
|
190
190
|
*
|
|
191
191
|
* The step down from ACTIVE is deliberately steep. These are large filled areas
|
|
192
192
|
* — bars and arcs — and a filled shape still reads as present at an opacity
|
|
193
193
|
* where a hairline would have vanished, so a timid step barely registers as a
|
|
194
194
|
* highlight at all.
|
|
195
|
-
*
|
|
196
|
-
* Keep `FADED` below `MUTED`, or pinning becomes visually indistinguishable
|
|
197
|
-
* from hovering — the pin exists to say "I am holding this one", which only
|
|
198
|
-
* reads if the rest recede further than they do on hover.
|
|
199
195
|
*/
|
|
200
196
|
export declare const CHART_MARK_OPACITY_ACTIVE = 1;
|
|
201
197
|
export declare const CHART_MARK_OPACITY_MUTED = 0.1;
|
|
202
|
-
export declare const CHART_MARK_OPACITY_FADED = 0.04;
|
|
203
198
|
/** Distance in px between the tooltip and its anchor. */
|
|
204
199
|
export declare const CHART_TOOLTIP_OFFSET = 10;
|
|
205
200
|
/** Minimum width of the tooltip, in px. */
|
|
206
201
|
export declare const CHART_TOOLTIP_MIN_WIDTH = 168;
|
|
207
202
|
/** Maximum width of the tooltip, in px. */
|
|
208
203
|
export declare const CHART_TOOLTIP_MAX_WIDTH = 320;
|
|
204
|
+
/** Gap in px between the plot box and a side-placed legend. */
|
|
205
|
+
export declare const CHART_LEGEND_SIDE_GAP = 16;
|
|
206
|
+
/**
|
|
207
|
+
* Ghost bar heights, one inner array per group — three bars each, so the
|
|
208
|
+
* skeleton reads as the grouped chart it usually stands in for rather than a
|
|
209
|
+
* sparse row of lone bars.
|
|
210
|
+
*/
|
|
211
|
+
export declare const SKELETON_BAR_GROUPS: number[][];
|
|
212
|
+
/**
|
|
213
|
+
* Ghost lines, one inner array per line, offset from each other and drawn with
|
|
214
|
+
* their area fills so the skeleton has the layered depth of a real multi-series
|
|
215
|
+
* chart instead of a single bare stroke.
|
|
216
|
+
*/
|
|
217
|
+
export declare const SKELETON_LINE_SERIES: number[][];
|
|
218
|
+
/** Ghost donut shares — three arcs, halving. */
|
|
219
|
+
export declare const SKELETON_PIE_VALUES: number[];
|
|
220
|
+
/**
|
|
221
|
+
* Skeleton pulse period, matching the cadence of `TextShimmer` and
|
|
222
|
+
* `AvatarShimmer` so charts load in step with everything else on the page.
|
|
223
|
+
*/
|
|
224
|
+
export declare const CHART_SKELETON_PULSE_MS = 2500;
|
|
209
225
|
/**
|
|
210
226
|
* Approximate width of one CAPTION-size character.
|
|
211
227
|
*
|
package/dist/charts/constants.js
CHANGED
|
@@ -195,27 +195,101 @@ var CHART_RESTORE_DELAY_MS = 200;
|
|
|
195
195
|
*
|
|
196
196
|
* The grammar is inherited from the reference charts and is the opposite of the
|
|
197
197
|
* obvious one: hovering does not brighten the hovered mark, it *softens*
|
|
198
|
-
* everything else. `MUTED` applies to marks outside the hovered category
|
|
199
|
-
*
|
|
198
|
+
* everything else. `MUTED` applies to marks outside the hovered category or
|
|
199
|
+
* series.
|
|
200
200
|
*
|
|
201
201
|
* The step down from ACTIVE is deliberately steep. These are large filled areas
|
|
202
202
|
* — bars and arcs — and a filled shape still reads as present at an opacity
|
|
203
203
|
* where a hairline would have vanished, so a timid step barely registers as a
|
|
204
204
|
* highlight at all.
|
|
205
|
-
*
|
|
206
|
-
* Keep `FADED` below `MUTED`, or pinning becomes visually indistinguishable
|
|
207
|
-
* from hovering — the pin exists to say "I am holding this one", which only
|
|
208
|
-
* reads if the rest recede further than they do on hover.
|
|
209
205
|
*/
|
|
210
206
|
var CHART_MARK_OPACITY_ACTIVE = 1;
|
|
211
207
|
var CHART_MARK_OPACITY_MUTED = .1;
|
|
212
|
-
var CHART_MARK_OPACITY_FADED = .04;
|
|
213
208
|
/** Distance in px between the tooltip and its anchor. */
|
|
214
209
|
var CHART_TOOLTIP_OFFSET = 10;
|
|
215
210
|
/** Minimum width of the tooltip, in px. */
|
|
216
211
|
var CHART_TOOLTIP_MIN_WIDTH = 168;
|
|
217
212
|
/** Maximum width of the tooltip, in px. */
|
|
218
213
|
var CHART_TOOLTIP_MAX_WIDTH = 320;
|
|
214
|
+
/** Gap in px between the plot box and a side-placed legend. */
|
|
215
|
+
var CHART_LEGEND_SIDE_GAP = 16;
|
|
216
|
+
/**
|
|
217
|
+
* Ghost bar heights, one inner array per group — three bars each, so the
|
|
218
|
+
* skeleton reads as the grouped chart it usually stands in for rather than a
|
|
219
|
+
* sparse row of lone bars.
|
|
220
|
+
*/
|
|
221
|
+
var SKELETON_BAR_GROUPS = [
|
|
222
|
+
[
|
|
223
|
+
65,
|
|
224
|
+
45,
|
|
225
|
+
25
|
|
226
|
+
],
|
|
227
|
+
[
|
|
228
|
+
80,
|
|
229
|
+
55,
|
|
230
|
+
30
|
|
231
|
+
],
|
|
232
|
+
[
|
|
233
|
+
45,
|
|
234
|
+
70,
|
|
235
|
+
35
|
|
236
|
+
],
|
|
237
|
+
[
|
|
238
|
+
70,
|
|
239
|
+
50,
|
|
240
|
+
40
|
|
241
|
+
],
|
|
242
|
+
[
|
|
243
|
+
55,
|
|
244
|
+
75,
|
|
245
|
+
30
|
|
246
|
+
]
|
|
247
|
+
];
|
|
248
|
+
/**
|
|
249
|
+
* Ghost lines, one inner array per line, offset from each other and drawn with
|
|
250
|
+
* their area fills so the skeleton has the layered depth of a real multi-series
|
|
251
|
+
* chart instead of a single bare stroke.
|
|
252
|
+
*/
|
|
253
|
+
var SKELETON_LINE_SERIES = [
|
|
254
|
+
[
|
|
255
|
+
30,
|
|
256
|
+
55,
|
|
257
|
+
45,
|
|
258
|
+
70,
|
|
259
|
+
60,
|
|
260
|
+
85,
|
|
261
|
+
75
|
|
262
|
+
],
|
|
263
|
+
[
|
|
264
|
+
20,
|
|
265
|
+
35,
|
|
266
|
+
50,
|
|
267
|
+
40,
|
|
268
|
+
60,
|
|
269
|
+
55,
|
|
270
|
+
70
|
|
271
|
+
],
|
|
272
|
+
[
|
|
273
|
+
10,
|
|
274
|
+
25,
|
|
275
|
+
20,
|
|
276
|
+
35,
|
|
277
|
+
30,
|
|
278
|
+
45,
|
|
279
|
+
40
|
|
280
|
+
]
|
|
281
|
+
];
|
|
282
|
+
/** Ghost donut shares — three arcs, halving. */
|
|
283
|
+
var SKELETON_PIE_VALUES = [
|
|
284
|
+
5,
|
|
285
|
+
3,
|
|
286
|
+
2
|
|
287
|
+
];
|
|
288
|
+
/**
|
|
289
|
+
* Skeleton pulse period, matching the cadence of `TextShimmer` and
|
|
290
|
+
* `AvatarShimmer` so charts load in step with everything else on the page.
|
|
291
|
+
*/
|
|
292
|
+
var CHART_SKELETON_PULSE_MS = 2500;
|
|
219
293
|
/**
|
|
220
294
|
* Approximate width of one CAPTION-size character.
|
|
221
295
|
*
|
|
@@ -264,4 +338,4 @@ var CHART_PALETTE_ORDER = [
|
|
|
264
338
|
ChartPalette.RED
|
|
265
339
|
];
|
|
266
340
|
//#endregion
|
|
267
|
-
export { AGGREGATED_SLICE_KEY, AUTO_MARGIN_LEFT_STEP, AXIS_TITLE_BAND, AXIS_TITLE_GAP, BAR_GAP_COMFORTABLE, BAR_GAP_MEDIUM, BAR_GAP_TIGHT, BAR_RADIUS, BAR_RADIUS_NARROW, BAR_WIDTH_RATIO, CAPTION_CHAR_WIDTH_PX, CATEGORY_AXIS_LABEL_GAP, CATEGORY_AXIS_LABEL_HEIGHT,
|
|
341
|
+
export { AGGREGATED_SLICE_KEY, AUTO_MARGIN_LEFT_STEP, AXIS_TITLE_BAND, AXIS_TITLE_GAP, BAR_GAP_COMFORTABLE, BAR_GAP_MEDIUM, BAR_GAP_TIGHT, BAR_RADIUS, BAR_RADIUS_NARROW, BAR_WIDTH_RATIO, CAPTION_CHAR_WIDTH_PX, CATEGORY_AXIS_LABEL_GAP, CATEGORY_AXIS_LABEL_HEIGHT, CHART_LEGEND_SIDE_GAP, CHART_MARK_OPACITY_ACTIVE, CHART_MARK_OPACITY_MUTED, CHART_PALETTE_ORDER, CHART_RESTORE_DELAY_MS, CHART_SKELETON_PULSE_MS, CHART_TOOLTIP_MAX_WIDTH, CHART_TOOLTIP_MIN_WIDTH, CHART_TOOLTIP_OFFSET, CHART_TRANSITION_MS, DEFAULT_CHART_MARGIN, DEFAULT_LINE_STROKE_WIDTH, DEFAULT_VALUE_TICK_COUNT, GRID_LINE_BLEED, GROUP_GAP_COMFORTABLE, GROUP_GAP_MEDIUM, GROUP_GAP_TIGHT, LINE_AREA_OPACITY, LINE_POINT_RADIUS, MAX_AUTO_MARGIN_LEFT, MAX_BAR_WIDTH, MAX_GAP_SHARE, MIN_AUTO_MARGIN_LEFT, MIN_BARS_FOR_TIGHT_SPACING, MIN_BAR_LENGTH, MIN_CATEGORY_LABEL_GAP, NARROW_BAR_WIDTH, PIE_CHART_MARGIN, PIE_CORNER_RADIUS, PIE_DONUT_INNER_RATIO, PIE_MAX_AUTO_INNER_RATIO, PIE_MAX_INSET_SHARE, PIE_MIN_SLICE_SHARE, PIE_PAD_ANGLE, PIE_TOOLTIP_ANCHOR_INSET, SKELETON_BAR_GROUPS, SKELETON_LINE_SERIES, SKELETON_PIE_VALUES, STACK_SEGMENT_GAP, VALUE_AXIS_LABEL_HEIGHT, VALUE_AXIS_TICK_GAP, WIDTH_FOR_MEDIUM_SPACING, WIDTH_FOR_TIGHT_SPACING };
|
package/dist/charts/types.d.ts
CHANGED
|
@@ -120,6 +120,18 @@ export declare enum ChartKind {
|
|
|
120
120
|
LINE = "LINE",
|
|
121
121
|
PIE = "PIE"
|
|
122
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Where the legend sits relative to the plot.
|
|
125
|
+
*
|
|
126
|
+
* Its own enum rather than a branch on {@link ChartKind}, which promises to be
|
|
127
|
+
* nothing but a label for the accessible name.
|
|
128
|
+
*/
|
|
129
|
+
export declare enum ChartLegendPlacement {
|
|
130
|
+
/** Beneath the plot, wrapping — the cartesian layout. */
|
|
131
|
+
BOTTOM = "BOTTOM",
|
|
132
|
+
/** Beside the plot, stacked, vertically centred on it — the pie layout. */
|
|
133
|
+
RIGHT = "RIGHT"
|
|
134
|
+
}
|
|
123
135
|
/** How the category axis copes when labels outgrow their slots. */
|
|
124
136
|
export declare enum ChartCategoryLabelMode {
|
|
125
137
|
/** Render every label that fits; otherwise show every nth. The default. */
|
|
@@ -139,12 +151,10 @@ export declare enum ChartCategoryLabelMode {
|
|
|
139
151
|
* DOM and trigger a React warning.
|
|
140
152
|
*/
|
|
141
153
|
export declare enum ChartMarkState {
|
|
142
|
-
/** Full strength — the default, and the hovered
|
|
154
|
+
/** Full strength — the default, and the hovered category. */
|
|
143
155
|
ACTIVE = "active",
|
|
144
156
|
/** Softened, for categories other than the hovered one. */
|
|
145
|
-
MUTED = "muted"
|
|
146
|
-
/** Furthest back, for categories outside a pinned one. */
|
|
147
|
-
FADED = "faded"
|
|
157
|
+
MUTED = "muted"
|
|
148
158
|
}
|
|
149
159
|
/** Which pair of a rectangle's corners carries the radius. */
|
|
150
160
|
export declare enum BarCorners {
|
package/dist/charts/types.js
CHANGED
|
@@ -80,6 +80,19 @@ var ChartKind = /* @__PURE__ */ function(ChartKind) {
|
|
|
80
80
|
ChartKind["PIE"] = "PIE";
|
|
81
81
|
return ChartKind;
|
|
82
82
|
}({});
|
|
83
|
+
/**
|
|
84
|
+
* Where the legend sits relative to the plot.
|
|
85
|
+
*
|
|
86
|
+
* Its own enum rather than a branch on {@link ChartKind}, which promises to be
|
|
87
|
+
* nothing but a label for the accessible name.
|
|
88
|
+
*/
|
|
89
|
+
var ChartLegendPlacement = /* @__PURE__ */ function(ChartLegendPlacement) {
|
|
90
|
+
/** Beneath the plot, wrapping — the cartesian layout. */
|
|
91
|
+
ChartLegendPlacement["BOTTOM"] = "BOTTOM";
|
|
92
|
+
/** Beside the plot, stacked, vertically centred on it — the pie layout. */
|
|
93
|
+
ChartLegendPlacement["RIGHT"] = "RIGHT";
|
|
94
|
+
return ChartLegendPlacement;
|
|
95
|
+
}({});
|
|
83
96
|
/** How the category axis copes when labels outgrow their slots. */
|
|
84
97
|
var ChartCategoryLabelMode = /* @__PURE__ */ function(ChartCategoryLabelMode) {
|
|
85
98
|
/** Render every label that fits; otherwise show every nth. The default. */
|
|
@@ -100,12 +113,10 @@ var ChartCategoryLabelMode = /* @__PURE__ */ function(ChartCategoryLabelMode) {
|
|
|
100
113
|
* DOM and trigger a React warning.
|
|
101
114
|
*/
|
|
102
115
|
var ChartMarkState = /* @__PURE__ */ function(ChartMarkState) {
|
|
103
|
-
/** Full strength — the default, and the hovered
|
|
116
|
+
/** Full strength — the default, and the hovered category. */
|
|
104
117
|
ChartMarkState["ACTIVE"] = "active";
|
|
105
118
|
/** Softened, for categories other than the hovered one. */
|
|
106
119
|
ChartMarkState["MUTED"] = "muted";
|
|
107
|
-
/** Furthest back, for categories outside a pinned one. */
|
|
108
|
-
ChartMarkState["FADED"] = "faded";
|
|
109
120
|
return ChartMarkState;
|
|
110
121
|
}({});
|
|
111
122
|
/** Which pair of a rectangle's corners carries the radius. */
|
|
@@ -152,4 +163,4 @@ var ChartTargetShape = /* @__PURE__ */ function(ChartTargetShape) {
|
|
|
152
163
|
return ChartTargetShape;
|
|
153
164
|
}({});
|
|
154
165
|
//#endregion
|
|
155
|
-
export { BarChartNormalization, BarCorners, ChartCategoryLabelMode, ChartKind, ChartMarkState, ChartPalette, ChartTargetShape, ChartValueUnit, LineChartCurve };
|
|
166
|
+
export { BarChartNormalization, BarCorners, ChartCategoryLabelMode, ChartKind, ChartLegendPlacement, ChartMarkState, ChartPalette, ChartTargetShape, ChartValueUnit, LineChartCurve };
|
|
@@ -1,40 +1,30 @@
|
|
|
1
1
|
import { ChartMarkState } from "./types";
|
|
2
2
|
/**
|
|
3
|
-
* Hover
|
|
3
|
+
* Hover interaction shared by every chart.
|
|
4
4
|
*
|
|
5
|
-
* Two independent axes:
|
|
5
|
+
* Two independent axes, both hover-only:
|
|
6
6
|
*
|
|
7
|
-
* - **Category** — a numeric index along the plot. Hovering previews
|
|
8
|
-
*
|
|
9
|
-
* - **Series** — a legend key. Hovering
|
|
10
|
-
*
|
|
7
|
+
* - **Category** — a numeric index along the plot. Hovering previews it: the
|
|
8
|
+
* tooltip opens and everything outside the category recedes.
|
|
9
|
+
* - **Series** — a legend key. Hovering (or focusing) a legend entry isolates
|
|
10
|
+
* that series.
|
|
11
11
|
*
|
|
12
12
|
* Both live here rather than one of them in the charts, so there is a single
|
|
13
13
|
* `isEngaged` boolean. That matters more than it looks: `ChartPlotGroup` uses it
|
|
14
14
|
* to cancel the mark restore delay, and a second hover source that didn't feed
|
|
15
15
|
* it would sit through that delay before muting anything and feel broken.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
17
|
+
* Deliberately no click layer. Clicking used to pin the hover so it survived
|
|
18
|
+
* the pointer leaving, and it read as the chart getting stuck — the emphasis
|
|
19
|
+
* now always follows the pointer and releases with it.
|
|
18
20
|
*/
|
|
19
21
|
interface UseChartInteractionOptions {
|
|
20
22
|
/** Number of hoverable categories. A shrinking count clears stale indices. */
|
|
21
23
|
categoryCount: number;
|
|
22
|
-
/** Legend keys currently rendered. A key that disappears clears
|
|
24
|
+
/** Legend keys currently rendered. A key that disappears clears stale hover. */
|
|
23
25
|
seriesKeys?: string[];
|
|
24
26
|
/** Suppress all interaction — used while loading and when the tooltip is off. */
|
|
25
27
|
disabled?: boolean;
|
|
26
|
-
/**
|
|
27
|
-
* Render every mark muted, whatever the two axes say.
|
|
28
|
-
*
|
|
29
|
-
* Distinct from {@link disabled}, which suppresses *input*. Loading suppresses
|
|
30
|
-
* *emphasis*: there is nothing yet worth drawing attention to. Folded in here
|
|
31
|
-
* rather than left to each mark, because the alternative is the same
|
|
32
|
-
* `isLoading ? MUTED : getMarkState(…)` ternary hand-copied at every mark —
|
|
33
|
-
* which is exactly how one of them came to be missing it.
|
|
34
|
-
*/
|
|
35
|
-
isLoading?: boolean;
|
|
36
|
-
/** Notified when the pinned category changes. */
|
|
37
|
-
onPinnedChange?: (index: number | null) => void;
|
|
38
28
|
}
|
|
39
29
|
interface MarkStateArgs {
|
|
40
30
|
/** Omit for marks that belong to no particular category, such as a line. */
|
|
@@ -43,11 +33,9 @@ interface MarkStateArgs {
|
|
|
43
33
|
seriesKey?: string;
|
|
44
34
|
}
|
|
45
35
|
export interface ChartInteraction {
|
|
46
|
-
|
|
47
|
-
/** Pinned wins over hovered — this is what the tooltip follows. */
|
|
36
|
+
/** The hovered category — what the tooltip follows. */
|
|
48
37
|
activeIndex: number | null;
|
|
49
38
|
hoveredSeriesKey: string | null;
|
|
50
|
-
pinnedSeriesKey: string | null;
|
|
51
39
|
/** True while *either* axis is engaged; cancels the mark restore delay. */
|
|
52
40
|
isEngaged: boolean;
|
|
53
41
|
/**
|
|
@@ -55,22 +43,17 @@ export interface ChartInteraction {
|
|
|
55
43
|
*
|
|
56
44
|
* The grammar is inherited from the reference and is the opposite of the
|
|
57
45
|
* obvious one: engaging something does not brighten it, it softens everything
|
|
58
|
-
* else.
|
|
59
|
-
* the pinned mark and the one under the pointer stay legible against them.
|
|
46
|
+
* else.
|
|
60
47
|
*
|
|
61
48
|
* A mark has to survive *both* filters to stay active — the two axes
|
|
62
|
-
* intersect rather than override, so
|
|
63
|
-
*
|
|
49
|
+
* intersect rather than override, so hovering a legend entry and a category
|
|
50
|
+
* at once leaves only their overlap lit.
|
|
64
51
|
*/
|
|
65
52
|
getMarkState: (args: MarkStateArgs) => ChartMarkState;
|
|
66
53
|
onCategoryEnter: (categoryIndex: number) => void;
|
|
67
54
|
onCategoryLeave: () => void;
|
|
68
|
-
onCategoryClick: (categoryIndex: number) => void;
|
|
69
55
|
onSeriesEnter: (seriesKey: string) => void;
|
|
70
56
|
onSeriesLeave: () => void;
|
|
71
|
-
onSeriesClick: (seriesKey: string) => void;
|
|
72
|
-
/** Attach to the plot backdrop; clears both pins. */
|
|
73
|
-
onBackdropClick: () => void;
|
|
74
57
|
}
|
|
75
|
-
export declare function useChartInteraction({ categoryCount, seriesKeys, disabled,
|
|
58
|
+
export declare function useChartInteraction({ categoryCount, seriesKeys, disabled, }: UseChartInteractionOptions): ChartInteraction;
|
|
76
59
|
export {};
|
|
@@ -1,43 +1,11 @@
|
|
|
1
1
|
import { ChartMarkState } from "./types.js";
|
|
2
|
-
import {
|
|
3
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { useCallback, useMemo, useState } from "react";
|
|
4
3
|
//#region src/charts/useChartInteraction.ts
|
|
5
|
-
|
|
6
|
-
function severity(state) {
|
|
7
|
-
return match(state).with(ChartMarkState.ACTIVE, () => 0).with(ChartMarkState.MUTED, () => 1).with(ChartMarkState.FADED, () => 2).exhaustive();
|
|
8
|
-
}
|
|
9
|
-
function useChartInteraction({ categoryCount, seriesKeys, disabled = false, isLoading = false, onPinnedChange }) {
|
|
4
|
+
function useChartInteraction({ categoryCount, seriesKeys, disabled = false }) {
|
|
10
5
|
const [hoveredIndex, setHoveredIndex] = useState(null);
|
|
11
|
-
const [pinnedIndex, setPinnedIndex] = useState(null);
|
|
12
6
|
const [hoveredSeriesKey, setHoveredSeriesKey] = useState(null);
|
|
13
|
-
const [pinnedSeriesKey, setPinnedSeriesKey] = useState(null);
|
|
14
|
-
const onPinnedChangeRef = useRef(onPinnedChange);
|
|
15
|
-
useEffect(() => {
|
|
16
|
-
onPinnedChangeRef.current = onPinnedChange;
|
|
17
|
-
});
|
|
18
7
|
const safeHovered = hoveredIndex !== null && hoveredIndex < categoryCount ? hoveredIndex : null;
|
|
19
|
-
const
|
|
20
|
-
const isKnownSeries = (key) => key !== null && (seriesKeys === void 0 || seriesKeys.includes(key));
|
|
21
|
-
const safeHoveredSeries = isKnownSeries(hoveredSeriesKey) ? hoveredSeriesKey : null;
|
|
22
|
-
const safePinnedSeries = isKnownSeries(pinnedSeriesKey) ? pinnedSeriesKey : null;
|
|
23
|
-
const clearPins = useCallback(() => {
|
|
24
|
-
setPinnedIndex(null);
|
|
25
|
-
setPinnedSeriesKey(null);
|
|
26
|
-
onPinnedChangeRef.current?.(null);
|
|
27
|
-
}, []);
|
|
28
|
-
const hasPin = safePinned !== null || safePinnedSeries !== null;
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (disabled || !hasPin) return;
|
|
31
|
-
const onKeyDown = (event) => {
|
|
32
|
-
if (event.key === "Escape") clearPins();
|
|
33
|
-
};
|
|
34
|
-
window.addEventListener("keydown", onKeyDown);
|
|
35
|
-
return () => window.removeEventListener("keydown", onKeyDown);
|
|
36
|
-
}, [
|
|
37
|
-
disabled,
|
|
38
|
-
hasPin,
|
|
39
|
-
clearPins
|
|
40
|
-
]);
|
|
8
|
+
const safeHoveredSeries = hoveredSeriesKey !== null && (seriesKeys === void 0 || seriesKeys.includes(hoveredSeriesKey)) ? hoveredSeriesKey : null;
|
|
41
9
|
const onCategoryEnter = useCallback((categoryIndex) => {
|
|
42
10
|
if (disabled) return;
|
|
43
11
|
setHoveredIndex(categoryIndex);
|
|
@@ -46,80 +14,32 @@ function useChartInteraction({ categoryCount, seriesKeys, disabled = false, isLo
|
|
|
46
14
|
if (disabled) return;
|
|
47
15
|
setHoveredIndex(null);
|
|
48
16
|
}, [disabled]);
|
|
49
|
-
const onCategoryClick = useCallback((categoryIndex) => {
|
|
50
|
-
if (disabled) return;
|
|
51
|
-
const next = pinnedIndex === categoryIndex ? null : categoryIndex;
|
|
52
|
-
setPinnedIndex(next);
|
|
53
|
-
onPinnedChangeRef.current?.(next);
|
|
54
|
-
}, [disabled, pinnedIndex]);
|
|
55
17
|
const onSeriesEnter = useCallback((seriesKey) => {
|
|
56
18
|
setHoveredSeriesKey(seriesKey);
|
|
57
19
|
}, []);
|
|
58
20
|
const onSeriesLeave = useCallback(() => {
|
|
59
21
|
setHoveredSeriesKey(null);
|
|
60
22
|
}, []);
|
|
61
|
-
const onSeriesClick = useCallback((seriesKey) => {
|
|
62
|
-
const next = pinnedSeriesKey === seriesKey ? null : seriesKey;
|
|
63
|
-
setPinnedSeriesKey(next);
|
|
64
|
-
if (next === null) setHoveredSeriesKey(null);
|
|
65
|
-
}, [pinnedSeriesKey]);
|
|
66
23
|
const getMarkState = useCallback(({ categoryIndex, seriesKey }) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (categoryIndex === void 0) return ChartMarkState.ACTIVE;
|
|
70
|
-
if (safePinned !== null) {
|
|
71
|
-
if (categoryIndex === safePinned) return ChartMarkState.ACTIVE;
|
|
72
|
-
if (categoryIndex === safeHovered) return ChartMarkState.MUTED;
|
|
73
|
-
return ChartMarkState.FADED;
|
|
74
|
-
}
|
|
75
|
-
if (safeHovered !== null) return categoryIndex === safeHovered ? ChartMarkState.ACTIVE : ChartMarkState.MUTED;
|
|
76
|
-
return ChartMarkState.ACTIVE;
|
|
77
|
-
})();
|
|
78
|
-
const seriesState = (() => {
|
|
79
|
-
if (seriesKey === void 0) return ChartMarkState.ACTIVE;
|
|
80
|
-
if (safePinnedSeries !== null) {
|
|
81
|
-
if (seriesKey === safePinnedSeries) return ChartMarkState.ACTIVE;
|
|
82
|
-
if (seriesKey === safeHoveredSeries) return ChartMarkState.MUTED;
|
|
83
|
-
return ChartMarkState.FADED;
|
|
84
|
-
}
|
|
85
|
-
if (safeHoveredSeries !== null) return seriesKey === safeHoveredSeries ? ChartMarkState.ACTIVE : ChartMarkState.MUTED;
|
|
86
|
-
return ChartMarkState.ACTIVE;
|
|
87
|
-
})();
|
|
88
|
-
return severity(categoryState) >= severity(seriesState) ? categoryState : seriesState;
|
|
89
|
-
}, [
|
|
90
|
-
isLoading,
|
|
91
|
-
safePinned,
|
|
92
|
-
safeHovered,
|
|
93
|
-
safePinnedSeries,
|
|
94
|
-
safeHoveredSeries
|
|
95
|
-
]);
|
|
24
|
+
return categoryIndex !== void 0 && safeHovered !== null && categoryIndex !== safeHovered || seriesKey !== void 0 && safeHoveredSeries !== null && seriesKey !== safeHoveredSeries ? ChartMarkState.MUTED : ChartMarkState.ACTIVE;
|
|
25
|
+
}, [safeHovered, safeHoveredSeries]);
|
|
96
26
|
return useMemo(() => ({
|
|
97
|
-
|
|
98
|
-
activeIndex: safePinned ?? safeHovered,
|
|
27
|
+
activeIndex: safeHovered,
|
|
99
28
|
hoveredSeriesKey: safeHoveredSeries,
|
|
100
|
-
|
|
101
|
-
isEngaged: safePinned !== null || safeHovered !== null || safePinnedSeries !== null || safeHoveredSeries !== null,
|
|
29
|
+
isEngaged: safeHovered !== null || safeHoveredSeries !== null,
|
|
102
30
|
getMarkState,
|
|
103
31
|
onCategoryEnter,
|
|
104
32
|
onCategoryLeave,
|
|
105
|
-
onCategoryClick,
|
|
106
33
|
onSeriesEnter,
|
|
107
|
-
onSeriesLeave
|
|
108
|
-
onSeriesClick,
|
|
109
|
-
onBackdropClick: clearPins
|
|
34
|
+
onSeriesLeave
|
|
110
35
|
}), [
|
|
111
36
|
safeHovered,
|
|
112
|
-
safePinned,
|
|
113
37
|
safeHoveredSeries,
|
|
114
|
-
safePinnedSeries,
|
|
115
38
|
getMarkState,
|
|
116
39
|
onCategoryEnter,
|
|
117
40
|
onCategoryLeave,
|
|
118
|
-
onCategoryClick,
|
|
119
41
|
onSeriesEnter,
|
|
120
|
-
onSeriesLeave
|
|
121
|
-
onSeriesClick,
|
|
122
|
-
clearPins
|
|
42
|
+
onSeriesLeave
|
|
123
43
|
]);
|
|
124
44
|
}
|
|
125
45
|
//#endregion
|