@galaxy-io/dls 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/charts/BarChart.d.ts +3 -3
  2. package/dist/charts/BarChart.js +68 -104
  3. package/dist/charts/ChartCategoryTargets.d.ts +27 -0
  4. package/dist/charts/ChartCategoryTargets.js +75 -0
  5. package/dist/charts/ChartFrame.d.ts +34 -6
  6. package/dist/charts/ChartFrame.js +72 -15
  7. package/dist/charts/ChartPrimitives.d.ts +35 -0
  8. package/dist/charts/ChartPrimitives.js +75 -38
  9. package/dist/charts/ChartTooltip.d.ts +3 -3
  10. package/dist/charts/ChartTooltip.js +2 -2
  11. package/dist/charts/LineChart.js +47 -62
  12. package/dist/charts/PieChart.js +48 -26
  13. package/dist/charts/barChartGeometry.d.ts +66 -33
  14. package/dist/charts/barChartGeometry.js +7 -15
  15. package/dist/charts/barChartLegend.d.ts +25 -0
  16. package/dist/charts/barChartLegend.js +23 -0
  17. package/dist/charts/chartFormat.d.ts +9 -1
  18. package/dist/charts/chartFormat.js +13 -1
  19. package/dist/charts/chartScales.d.ts +58 -22
  20. package/dist/charts/chartScales.js +92 -38
  21. package/dist/charts/constants.d.ts +22 -6
  22. package/dist/charts/constants.js +22 -1
  23. package/dist/charts/lineChartGeometry.d.ts +53 -15
  24. package/dist/charts/lineChartGeometry.js +6 -15
  25. package/dist/charts/pieChartGeometry.d.ts +20 -8
  26. package/dist/charts/pieChartGeometry.js +3 -13
  27. package/dist/charts/types.d.ts +51 -145
  28. package/dist/charts/types.js +22 -3
  29. package/dist/charts/useCartesianChartLayout.d.ts +70 -0
  30. package/dist/charts/useCartesianChartLayout.js +56 -0
  31. package/dist/charts/useChartDimensions.d.ts +10 -3
  32. package/dist/charts/useChartDimensions.js +14 -4
  33. package/dist/charts/useChartInteraction.d.ts +13 -8
  34. package/dist/charts/useChartInteraction.js +7 -11
  35. package/dist/charts/useSeriesColorResolver.d.ts +37 -0
  36. package/dist/charts/useSeriesColorResolver.js +27 -0
  37. package/dist/inputs/DateInput.js +3 -3
  38. package/dist/inputs/MultiSelectInput.d.ts +3 -2
  39. package/dist/inputs/MultiSelectInput.js +8 -8
  40. package/dist/inputs/SelectInput.d.ts +4 -8
  41. package/dist/inputs/SelectInput.js +14 -20
  42. package/dist/styles.css +13 -11
  43. package/package.json +1 -1
@@ -123,6 +123,30 @@ var ChartArcPath = /*#__PURE__*/ styled("path")({
123
123
  propsAsIs: true
124
124
  });
125
125
  /**
126
+ * Wrapper for the category target layer.
127
+ *
128
+ * Exists to carry the focus-ring colour: the targets themselves are plain
129
+ * `styled.x` because they receive refs (see the module note above), so they
130
+ * cannot read the theme directly. Setting `color` here lets them use
131
+ * `currentColor` and still take the token rather than inheriting whatever the
132
+ * consumer's page happens to set.
133
+ */
134
+ var _exp11 = () => ({ theme }) => theme.color.border.selected;
135
+ var ChartCategoryTargetLayer = withTheme(/*#__PURE__*/ styled("g")({
136
+ name: "ChartCategoryTargetLayer",
137
+ class: "c1wvzvu4",
138
+ propsAsIs: true,
139
+ vars: { "c1wvzvu4-0": [_exp11()] }
140
+ }));
141
+ /**
142
+ * Shared by both target shapes.
143
+ *
144
+ * The ring is a `stroke`, not an `outline`: `outline` on SVG elements is a late
145
+ * arrival in WebKit and `outline-offset` behaves inconsistently across engines,
146
+ * while a stroke on a transparent shape is universally supported — and it
147
+ * traces a pie arc, which a rectangular outline could not.
148
+ */
149
+ /**
126
150
  * Per-category hit target, rendered above the marks.
127
151
  *
128
152
  * Hit testing the full column rather than the bars themselves means a two-pixel
@@ -132,13 +156,26 @@ var ChartArcPath = /*#__PURE__*/ styled("path")({
132
156
  */
133
157
  var ChartHitTarget = /*#__PURE__*/ styled("rect")({
134
158
  name: "ChartHitTarget",
135
- class: "c1wvzvu4",
159
+ class: "c17guewt",
160
+ propsAsIs: true
161
+ });
162
+ /**
163
+ * The radial equivalent, reusing the arc's own `d`.
164
+ *
165
+ * A pie has no category axis to band, so its targets are the arc shapes
166
+ * themselves — drawn again, transparent, above the marks. That keeps hit
167
+ * testing and focus identical across all three charts instead of the pie
168
+ * hit-testing its visible arcs directly.
169
+ */
170
+ var ChartHitTargetPath = /*#__PURE__*/ styled("path")({
171
+ name: "ChartHitTargetPath",
172
+ class: "cblgiyf",
136
173
  propsAsIs: true
137
174
  });
138
175
  /** Full-plot backdrop; clicking it clears a pinned category. */
139
176
  var ChartBackdrop = /*#__PURE__*/ styled("rect")({
140
177
  name: "ChartBackdrop",
141
- class: "c17guewt",
178
+ class: "c1lr03cc",
142
179
  propsAsIs: true
143
180
  });
144
181
  /**
@@ -146,24 +183,24 @@ var ChartBackdrop = /*#__PURE__*/ styled("rect")({
146
183
  * zero — the line under the bars is what makes them read as resting on a
147
184
  * baseline.
148
185
  */
149
- var _exp11 = () => ({ theme }) => theme.color.border.primary;
186
+ var _exp14 = () => ({ theme }) => theme.color.border.primary;
150
187
  var ChartGridLine = withTheme(/*#__PURE__*/ styled("line")({
151
188
  name: "ChartGridLine",
152
- class: "cblgiyf",
189
+ class: "cizy1s8",
153
190
  propsAsIs: true,
154
- vars: { "cblgiyf-0": [_exp11()] }
191
+ vars: { "cizy1s8-0": [_exp14()] }
155
192
  }));
156
193
  /** Vertical rule marking the hovered category on a line chart. */
157
- var _exp12 = () => ({ theme }) => theme.color.border.secondary;
194
+ var _exp15 = () => ({ theme }) => theme.color.border.secondary;
158
195
  var ChartCursorLine = withTheme(/*#__PURE__*/ styled("line")({
159
196
  name: "ChartCursorLine",
160
- class: "c1lr03cc",
197
+ class: "c1ukq11d",
161
198
  propsAsIs: true,
162
- vars: { "c1lr03cc-0": [_exp12()] }
199
+ vars: { "c1ukq11d-0": [_exp15()] }
163
200
  }));
164
201
  var ChartAxisLabelBox = /*#__PURE__*/ styled("foreignObject")({
165
202
  name: "ChartAxisLabelBox",
166
- class: "cizy1s8",
203
+ class: "cwcko2m",
167
204
  propsAsIs: true
168
205
  });
169
206
  /**
@@ -173,21 +210,21 @@ var ChartAxisLabelBox = /*#__PURE__*/ styled("foreignObject")({
173
210
  * than teleporting, and it stays mounted while hidden so the fade can actually
174
211
  * run — unmounting on hide is why a fade-out never appears.
175
212
  */
176
- var _exp13 = () => ({ $x }) => `${$x}px`;
177
- var _exp14 = () => ({ $y }) => `${$y}px`;
178
- var _exp17 = () => ({ theme }) => theme.color.border.primary;
179
- var _exp18 = () => ({ theme }) => theme.color.background.primary;
180
- var _exp19 = () => ({ $isVisible }) => $isVisible ? 1 : 0;
213
+ var _exp16 = () => ({ $x }) => `${$x}px`;
214
+ var _exp17 = () => ({ $y }) => `${$y}px`;
215
+ var _exp20 = () => ({ theme }) => theme.color.border.primary;
216
+ var _exp21 = () => ({ theme }) => theme.color.background.primary;
217
+ var _exp22 = () => ({ $isVisible }) => $isVisible ? 1 : 0;
181
218
  var ChartTooltipPositioner = withTheme(/*#__PURE__*/ styled("div")({
182
219
  name: "ChartTooltipPositioner",
183
- class: "c1ukq11d",
220
+ class: "cqwhvyz",
184
221
  propsAsIs: false,
185
222
  vars: {
186
- "c1ukq11d-0": [_exp13()],
187
- "c1ukq11d-1": [_exp14()],
188
- "c1ukq11d-2": [_exp17()],
189
- "c1ukq11d-3": [_exp18()],
190
- "c1ukq11d-4": [_exp19()]
223
+ "cqwhvyz-0": [_exp16()],
224
+ "cqwhvyz-1": [_exp17()],
225
+ "cqwhvyz-2": [_exp20()],
226
+ "cqwhvyz-3": [_exp21()],
227
+ "cqwhvyz-4": [_exp22()]
191
228
  }
192
229
  }));
193
230
  /**
@@ -198,32 +235,32 @@ var ChartTooltipPositioner = withTheme(/*#__PURE__*/ styled("div")({
198
235
  * of the DLS. `pointer-events: none` keeps it from stealing hover from the arc
199
236
  * behind it.
200
237
  */
201
- var _exp23 = () => ({ $centerX }) => `${$centerX}px`;
202
- var _exp24 = () => ({ $centerY }) => `${$centerY}px`;
203
- var _exp26 = () => ({ $diameter }) => `${$diameter}px`;
238
+ var _exp26 = () => ({ $centerX }) => `${$centerX}px`;
239
+ var _exp27 = () => ({ $centerY }) => `${$centerY}px`;
240
+ var _exp29 = () => ({ $diameter }) => `${$diameter}px`;
204
241
  var ChartCenterBox = /*#__PURE__*/ styled("div")({
205
242
  name: "ChartCenterBox",
206
- class: "cwcko2m",
243
+ class: "c1iaray",
207
244
  propsAsIs: false,
208
245
  vars: {
209
- "cwcko2m-0": [_exp23()],
210
- "cwcko2m-1": [_exp24()],
211
- "cwcko2m-2": [_exp26()]
246
+ "c1iaray-0": [_exp26()],
247
+ "c1iaray-1": [_exp27()],
248
+ "c1iaray-2": [_exp29()]
212
249
  }
213
250
  });
214
251
  /**
215
252
  * Sits slightly above centre, over where the bars would be, so the axes stay
216
253
  * legible underneath.
217
254
  */
218
- var _exp27 = () => ({ theme }) => theme.color.border.primary;
219
- var _exp28 = () => ({ theme }) => theme.color.background.tertiary;
255
+ var _exp30 = () => ({ theme }) => theme.color.border.primary;
256
+ var _exp31 = () => ({ theme }) => theme.color.background.tertiary;
220
257
  var ChartEmptyOverlay = withTheme(/*#__PURE__*/ styled("div")({
221
258
  name: "ChartEmptyOverlay",
222
- class: "cqwhvyz",
259
+ class: "cz6jod1",
223
260
  propsAsIs: false,
224
261
  vars: {
225
- "cqwhvyz-0": [_exp27()],
226
- "cqwhvyz-1": [_exp28()]
262
+ "cz6jod1-0": [_exp30()],
263
+ "cz6jod1-1": [_exp31()]
227
264
  }
228
265
  }));
229
266
  /**
@@ -235,12 +272,12 @@ var ChartEmptyOverlay = withTheme(/*#__PURE__*/ styled("div")({
235
272
  * the marks dim instantly while their legend entries sit through the full
236
273
  * {@link CHART_RESTORE_DELAY_MS}, and the two visibly fall out of step.
237
274
  */
238
- var _exp29 = () => ({ $inset }) => `${$inset}px`;
275
+ var _exp32 = () => ({ $inset }) => `${$inset}px`;
239
276
  var ChartLegendRow = /*#__PURE__*/ styled("div")({
240
277
  name: "ChartLegendRow",
241
- class: "c1iaray",
278
+ class: "c1de5mqk",
242
279
  propsAsIs: false,
243
- vars: { "c1iaray-0": [_exp29()] }
280
+ vars: { "c1de5mqk-0": [_exp32()] }
244
281
  });
245
282
  /**
246
283
  * One legend entry, when the legend is interactive.
@@ -255,14 +292,14 @@ var ChartLegendRow = /*#__PURE__*/ styled("div")({
255
292
  */
256
293
  var ChartLegendItemButton = /*#__PURE__*/ styled("button")({
257
294
  name: "ChartLegendItemButton",
258
- class: "cz6jod1",
295
+ class: "cjirv6a",
259
296
  propsAsIs: false
260
297
  });
261
298
  /** The same row, when the legend is inert — no button semantics to announce. */
262
299
  var ChartLegendItemStatic = /*#__PURE__*/ styled("div")({
263
300
  name: "ChartLegendItemStatic",
264
- class: "c1de5mqk",
301
+ class: "c1armna5",
265
302
  propsAsIs: false
266
303
  });
267
304
  //#endregion
268
- export { ChartArcPath, ChartAreaPath, ChartAxisLabelBox, ChartBackdrop, ChartBarPath, ChartCenterBox, ChartCursorLine, ChartEmptyOverlay, ChartGridLine, ChartHitTarget, ChartLegendItemButton, ChartLegendItemStatic, ChartLegendRow, ChartLinePath, ChartPlotBox, ChartPlotGroup, ChartPointCircle, ChartRoot, ChartSvg, ChartTooltipPositioner };
305
+ export { ChartArcPath, ChartAreaPath, ChartAxisLabelBox, ChartBackdrop, ChartBarPath, ChartCategoryTargetLayer, ChartCenterBox, ChartCursorLine, ChartEmptyOverlay, ChartGridLine, ChartHitTarget, ChartHitTargetPath, ChartLegendItemButton, ChartLegendItemStatic, ChartLegendRow, ChartLinePath, ChartPlotBox, ChartPlotGroup, ChartPointCircle, ChartRoot, ChartSvg, ChartTooltipPositioner };
@@ -7,7 +7,7 @@ import { ChartTooltipVerticalAlign } from "./useChartTooltipPosition";
7
7
  * still gets collision handling — in V1 the custom path received no positioning
8
8
  * at all and had to reimplement it.
9
9
  */
10
- interface ChartHoverTooltipProps {
10
+ interface ChartTooltipProps {
11
11
  /**
12
12
  * The model to display, or `null` when nothing is active.
13
13
  *
@@ -24,5 +24,5 @@ interface ChartHoverTooltipProps {
24
24
  verticalAlign?: ChartTooltipVerticalAlign;
25
25
  renderer?: ChartTooltipRenderer;
26
26
  }
27
- declare const ChartHoverTooltip: ({ model, containerWidth, containerHeight, anchorHalfWidth, placeAwayFromX, verticalAlign, renderer, }: ChartHoverTooltipProps) => import("react").JSX.Element;
28
- export default ChartHoverTooltip;
27
+ declare const ChartTooltip: ({ model, containerWidth, containerHeight, anchorHalfWidth, placeAwayFromX, verticalAlign, renderer, }: ChartTooltipProps) => import("react").JSX.Element;
28
+ export default ChartTooltip;
@@ -7,7 +7,7 @@ import { ChartTooltipPositioner } from "./ChartPrimitives.js";
7
7
  import { useState } from "react";
8
8
  import { jsx, jsxs } from "react/jsx-runtime";
9
9
  //#region src/charts/ChartTooltip.tsx
10
- var ChartHoverTooltip = ({ model, containerWidth, containerHeight, anchorHalfWidth, placeAwayFromX, verticalAlign, renderer }) => {
10
+ var ChartTooltip = ({ model, containerWidth, containerHeight, anchorHalfWidth, placeAwayFromX, verticalAlign, renderer }) => {
11
11
  const [retained, setRetained] = useState(model);
12
12
  if (model !== null && model !== retained) setRetained(model);
13
13
  const { tooltipRef, x, y, isMeasured } = useChartTooltipPosition({
@@ -69,4 +69,4 @@ var ChartHoverTooltip = ({ model, containerWidth, containerHeight, anchorHalfWid
69
69
  });
70
70
  };
71
71
  //#endregion
72
- export { ChartHoverTooltip as default };
72
+ export { ChartTooltip as default };
@@ -1,13 +1,10 @@
1
- import { useGalaxyTheme } from "../theme/GalaxyTheme.js";
2
- import { ChartMarkState, LineChartCurve } from "./types.js";
1
+ import { ChartKind, ChartTargetShape, LineChartCurve } from "./types.js";
3
2
  import { LINE_AREA_OPACITY, LINE_POINT_RADIUS } from "./constants.js";
4
- import { addAxisTitleSpace, getValueTicks } from "./chartScales.js";
3
+ import { useSeriesColorResolver } from "./useSeriesColorResolver.js";
5
4
  import { useChartInteraction } from "./useChartInteraction.js";
6
- import { useChartFormatters } from "./useChartFormatters.js";
7
- import { useChartDimensions } from "./useChartDimensions.js";
8
- import { buildLineChartGeometry } from "./lineChartGeometry.js";
9
- import { resolveSeriesColor } from "./chartTheme.js";
10
- import { ChartAreaPath, ChartCursorLine, ChartHitTarget, ChartLinePath, ChartPointCircle } from "./ChartPrimitives.js";
5
+ import { useCartesianChartLayout } from "./useCartesianChartLayout.js";
6
+ import { buildLineChartGeometry, getLineValueDomain } from "./lineChartGeometry.js";
7
+ import { ChartAreaPath, ChartCursorLine, ChartLinePath, ChartPointCircle } from "./ChartPrimitives.js";
11
8
  import ChartFrame from "./ChartFrame.js";
12
9
  import { ChartCategoryAxis, ChartValueAxis } from "./ChartAxis.js";
13
10
  import { useCallback, useMemo, useRef } from "react";
@@ -22,39 +19,33 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
22
19
  * nearest-point-to-cursor model needs.
23
20
  */
24
21
  var LineChart = ({ series, lines, categories, width, height, fillWidth, fillHeight, aspectRatio, margin, curve = LineChartCurve.SMOOTH, strokeWidth, showCursor = true, valueUnit, valueFormatter, labelFormatter, valueDomain, categoryLabelMode, categoryAxisTitle, valueAxisTitle, showGrid = true, showLegend = true, noTooltip = false, tooltipRenderer, isLoading = false, contentWhenEmpty, ariaLabel, ariaDescription, onPinnedChange }) => {
25
- const { theme } = useGalaxyTheme();
26
22
  const containerRef = useRef(null);
27
- const dimensions = useChartDimensions({
23
+ const colors = useSeriesColorResolver(series);
24
+ const { dimensions, domain, valueTicks, formatValue, formatLabel } = useCartesianChartLayout({
28
25
  containerRef,
26
+ rawDomain: useMemo(() => getLineValueDomain(lines), [lines]),
27
+ valueDomain,
29
28
  width,
30
29
  height,
31
30
  fillWidth,
32
31
  fillHeight,
33
32
  aspectRatio,
34
- margin: addAxisTitleSpace(margin, {
35
- hasValueTitle: Boolean(valueAxisTitle),
36
- hasCategoryTitle: Boolean(categoryAxisTitle)
37
- })
38
- });
39
- const { plotWidth, plotHeight } = dimensions;
40
- const { formatValue, formatLabel } = useChartFormatters({
41
- valueFormatter,
33
+ margin,
42
34
  valueUnit,
43
- labelFormatter
35
+ valueFormatter,
36
+ labelFormatter,
37
+ valueAxisTitle,
38
+ categoryAxisTitle
44
39
  });
45
- const metricKeys = useMemo(() => Object.keys(series), [series]);
46
- const resolveColor = useCallback(({ metric }) => resolveSeriesColor(theme, metricKeys.indexOf(metric), series[metric]?.color), [
47
- theme,
48
- metricKeys,
49
- series
50
- ]);
40
+ const { plotWidth, plotHeight } = dimensions;
41
+ const resolveColor = useCallback(({ metric }) => colors.resolve(metric), [colors]);
51
42
  const geometry = useMemo(() => buildLineChartGeometry({
52
43
  lines,
53
44
  categories,
54
45
  plotWidth,
55
46
  plotHeight,
56
47
  curve,
57
- valueDomain,
48
+ domain,
58
49
  strokeWidth,
59
50
  resolveColor
60
51
  }), [
@@ -63,35 +54,29 @@ var LineChart = ({ series, lines, categories, width, height, fillWidth, fillHeig
63
54
  plotWidth,
64
55
  plotHeight,
65
56
  curve,
66
- valueDomain,
57
+ domain,
67
58
  strokeWidth,
68
59
  resolveColor
69
60
  ]);
70
- const valueTicks = useMemo(() => getValueTicks(geometry.valueScale, valueDomain), [geometry.valueScale, valueDomain]);
71
61
  const isEmpty = geometry.categories.length === 0 || lines.length === 0;
72
- const legendItems = useMemo(() => metricKeys.map((metric, lineIndex) => ({
62
+ const legendItems = useMemo(() => colors.keys.map((metric) => ({
73
63
  key: metric,
74
64
  label: series[metric].label,
75
- color: resolveColor({
76
- metric,
77
- lineIndex
78
- })
79
- })), [
80
- metricKeys,
81
- series,
82
- resolveColor
83
- ]);
65
+ color: colors.resolve(metric)
66
+ })), [colors, series]);
84
67
  const legendKeys = useMemo(() => legendItems.map((item) => item.key), [legendItems]);
85
68
  const interaction = useChartInteraction({
86
69
  categoryCount: geometry.columns.length,
87
70
  seriesKeys: legendKeys,
88
71
  disabled: noTooltip || isLoading || isEmpty,
72
+ isLoading,
89
73
  onPinnedChange
90
74
  });
91
75
  const activeColumn = interaction.activeIndex === null ? null : geometry.columns[interaction.activeIndex];
92
- const tooltipModel = useMemo(() => {
93
- if (noTooltip || !activeColumn) return null;
94
- const rows = [...activeColumn.points].sort((a, b) => b.value - a.value).map((point) => ({
76
+ const buildCategoryTooltip = useCallback((index) => {
77
+ const column = geometry.columns[index];
78
+ if (!column) return null;
79
+ const rows = [...column.points].sort((a, b) => b.value - a.value).map((point) => ({
95
80
  key: point.id,
96
81
  label: series[point.metric]?.label ?? point.metric,
97
82
  value: point.value,
@@ -99,26 +84,35 @@ var LineChart = ({ series, lines, categories, width, height, fillWidth, fillHeig
99
84
  color: point.color
100
85
  }));
101
86
  return {
102
- label: formatLabel(activeColumn.label),
87
+ label: formatLabel(column.label),
103
88
  rows,
104
- anchorX: activeColumn.x,
105
- anchorY: activeColumn.anchorY
89
+ anchorX: column.x,
90
+ anchorY: column.anchorY
106
91
  };
107
92
  }, [
108
- noTooltip,
109
- activeColumn,
93
+ geometry.columns,
110
94
  series,
111
95
  formatValue,
112
96
  formatLabel
113
97
  ]);
114
98
  const categoryPositions = useMemo(() => geometry.columns.map((column) => column.x - geometry.step / 2), [geometry.columns, geometry.step]);
99
+ const categoryTargets = useMemo(() => geometry.columns.map((column) => ({
100
+ key: String(column.categoryIndex),
101
+ shape: ChartTargetShape.RECT,
102
+ x: column.bandX,
103
+ y: 0,
104
+ width: column.bandWidth,
105
+ height: plotHeight
106
+ })), [geometry.columns, plotHeight]);
115
107
  return /* @__PURE__ */ jsxs(ChartFrame, {
116
108
  containerRef,
117
109
  dimensions,
118
110
  fillWidth,
119
111
  fillHeight,
120
112
  interaction,
121
- ariaLabel: ariaLabel ?? `Line chart with ${geometry.categories.length} categories and ${lines.length} series`,
113
+ kind: ChartKind.LINE,
114
+ categoryCount: geometry.categories.length,
115
+ ariaLabel,
122
116
  ariaDescription,
123
117
  axes: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ChartValueAxis, {
124
118
  ticks: valueTicks,
@@ -140,8 +134,9 @@ var LineChart = ({ series, lines, categories, width, height, fillWidth, fillHeig
140
134
  title: categoryAxisTitle
141
135
  })] }),
142
136
  noTooltip,
143
- tooltipModel,
137
+ buildCategoryTooltip,
144
138
  tooltipRenderer,
139
+ categoryTargets,
145
140
  anchorHalfWidth: geometry.step / 2,
146
141
  isEmpty,
147
142
  isLoading,
@@ -153,7 +148,7 @@ var LineChart = ({ series, lines, categories, width, height, fillWidth, fillHeig
153
148
  d: linePath.areaPath,
154
149
  fill: linePath.color,
155
150
  fillOpacity: LINE_AREA_OPACITY,
156
- "data-state": isLoading ? ChartMarkState.MUTED : interaction.getMarkState({ seriesKey: linePath.metric })
151
+ "data-state": interaction.getMarkState({ seriesKey: linePath.metric })
157
152
  }, `${linePath.id}:area`) : null),
158
153
  showCursor && activeColumn && /* @__PURE__ */ jsx(ChartCursorLine, {
159
154
  x1: activeColumn.x,
@@ -166,14 +161,14 @@ var LineChart = ({ series, lines, categories, width, height, fillWidth, fillHeig
166
161
  stroke: linePath.color,
167
162
  strokeWidth: linePath.strokeWidth,
168
163
  strokeDasharray: linePath.dashArray,
169
- "data-state": isLoading ? ChartMarkState.MUTED : interaction.getMarkState({ seriesKey: linePath.metric })
164
+ "data-state": interaction.getMarkState({ seriesKey: linePath.metric })
170
165
  }, linePath.id)),
171
166
  geometry.isolatedPoints.map((point) => /* @__PURE__ */ jsx(ChartPointCircle, {
172
167
  cx: point.x,
173
168
  cy: point.y,
174
169
  r: LINE_POINT_RADIUS,
175
170
  fill: point.color,
176
- "data-state": isLoading ? ChartMarkState.MUTED : interaction.getMarkState({ seriesKey: point.metric })
171
+ "data-state": interaction.getMarkState({ seriesKey: point.metric })
177
172
  }, `${point.id}:isolated`)),
178
173
  activeColumn?.points.map((point) => /* @__PURE__ */ jsx(ChartPointCircle, {
179
174
  cx: point.x,
@@ -181,17 +176,7 @@ var LineChart = ({ series, lines, categories, width, height, fillWidth, fillHeig
181
176
  r: LINE_POINT_RADIUS,
182
177
  fill: point.color,
183
178
  "data-state": interaction.getMarkState({ seriesKey: point.metric })
184
- }, point.id)),
185
- !noTooltip && !isLoading && geometry.columns.map((column) => /* @__PURE__ */ jsx(ChartHitTarget, {
186
- x: column.bandX,
187
- y: 0,
188
- width: column.bandWidth,
189
- height: plotHeight,
190
- "data-interactive": "true",
191
- onMouseEnter: () => interaction.onCategoryEnter(column.categoryIndex),
192
- onMouseLeave: interaction.onCategoryLeave,
193
- onClick: () => interaction.onCategoryClick(column.categoryIndex)
194
- }, column.categoryIndex))
179
+ }, point.id))
195
180
  ]
196
181
  });
197
182
  };
@@ -1,13 +1,12 @@
1
- import { useGalaxyTheme } from "../theme/GalaxyTheme.js";
2
1
  import FlexWrapper, { AlignItems, FlexDirection, JustifyContent } from "../containers/FlexWrapper.js";
3
- import { ChartMarkState } from "./types.js";
2
+ import { ChartKind, ChartTargetShape } from "./types.js";
4
3
  import { PIE_CHART_MARGIN, PIE_MIN_SLICE_SHARE } from "./constants.js";
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 { resolveChartPaletteColor, resolveSeriesColor } from "./chartTheme.js";
11
10
  import { ChartArcPath, ChartCenterBox } from "./ChartPrimitives.js";
12
11
  import ChartFrame from "./ChartFrame.js";
13
12
  import { useCallback, useMemo, useRef } from "react";
@@ -21,8 +20,8 @@ import { jsx } from "react/jsx-runtime";
21
20
  * default, which keeps every remaining arc large enough to actually hit.
22
21
  */
23
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, onPinnedChange }) => {
24
- const { theme } = useGalaxyTheme();
25
23
  const containerRef = useRef(null);
24
+ const colors = useSeriesColorResolver(series);
26
25
  const dimensions = useChartDimensions({
27
26
  containerRef,
28
27
  width,
@@ -30,7 +29,8 @@ var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRa
30
29
  fillWidth,
31
30
  fillHeight,
32
31
  aspectRatio,
33
- margin: margin ?? PIE_CHART_MARGIN
32
+ margin,
33
+ defaultMargin: PIE_CHART_MARGIN
34
34
  });
35
35
  const { plotWidth, plotHeight } = dimensions;
36
36
  const { formatValue, formatLabel } = useChartFormatters({
@@ -38,15 +38,9 @@ var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRa
38
38
  valueUnit,
39
39
  labelFormatter
40
40
  });
41
- const sliceKeys = useMemo(() => Object.keys(series), [series]);
42
41
  const getSliceLabel = useCallback((sliceKey) => series[sliceKey]?.label ?? sliceKey, [series]);
43
42
  const getSliceColor = useCallback((sliceKey) => series[sliceKey]?.color, [series]);
44
- const resolveColor = useCallback(({ sliceKey, index, explicit }) => {
45
- if (explicit) return resolveChartPaletteColor(theme, explicit);
46
- if (sliceKey === null) return theme.color.text.tertiary;
47
- const slotIndex = sliceKeys.indexOf(sliceKey);
48
- return resolveSeriesColor(theme, slotIndex >= 0 ? slotIndex : index);
49
- }, [theme, sliceKeys]);
43
+ const resolveColor = useCallback(({ sliceKey, explicit }) => sliceKey === null ? colors.neutral : colors.resolve(sliceKey, explicit), [colors]);
50
44
  const geometry = useMemo(() => buildPieChartGeometry({
51
45
  slices,
52
46
  plotWidth,
@@ -83,11 +77,11 @@ var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRa
83
77
  categoryCount: geometry.arcs.length,
84
78
  seriesKeys: legendKeys,
85
79
  disabled: noTooltip || isLoading || isEmpty,
80
+ isLoading,
86
81
  onPinnedChange
87
82
  });
88
- const tooltipModel = useMemo(() => {
89
- if (noTooltip || interaction.activeIndex === null) return null;
90
- const chartArc = geometry.arcs[interaction.activeIndex];
83
+ const buildCategoryTooltip = useCallback((index) => {
84
+ const chartArc = geometry.arcs[index];
91
85
  if (!chartArc) return null;
92
86
  return {
93
87
  label: formatLabel(chartArc.label),
@@ -102,12 +96,39 @@ var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRa
102
96
  anchorY: chartArc.anchorY
103
97
  };
104
98
  }, [
105
- noTooltip,
106
- interaction.activeIndex,
107
99
  geometry.arcs,
108
100
  formatValue,
109
101
  formatLabel
110
102
  ]);
103
+ /**
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
+ * Targets reuse each arc's own `d`, drawn again transparent above the marks.
121
+ *
122
+ * The arcs themselves stop being interactive: hit testing, focus and the
123
+ * focus ring then work identically on all three charts, instead of the pie
124
+ * attaching handlers permanently and toggling `data-interactive` while the
125
+ * cartesian charts rendered their targets away.
126
+ */
127
+ const categoryTargets = useMemo(() => geometry.arcs.map((chartArc) => ({
128
+ key: chartArc.id,
129
+ shape: ChartTargetShape.PATH,
130
+ d: chartArc.path
131
+ })), [geometry.arcs]);
111
132
  const showCenter = centerContent && (innerRadiusRatio ?? .62) > 0;
112
133
  return /* @__PURE__ */ jsx(ChartFrame, {
113
134
  containerRef,
@@ -115,11 +136,17 @@ var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRa
115
136
  fillWidth,
116
137
  fillHeight,
117
138
  interaction,
118
- ariaLabel: ariaLabel ?? `Pie chart with ${geometry.arcs.length} slices`,
139
+ kind: ChartKind.PIE,
140
+ categoryCount: geometry.arcs.length,
141
+ ariaLabel,
119
142
  ariaDescription,
120
143
  noTooltip,
121
- tooltipModel,
144
+ buildCategoryTooltip,
122
145
  tooltipRenderer,
146
+ categoryTargets,
147
+ categoryTargetsTransform: `translate(${geometry.centerX}, ${geometry.centerY})`,
148
+ wrapCategoryNavigation: true,
149
+ describeCategory,
123
150
  placeAwayFromX: geometry.centerX,
124
151
  verticalAlign: ChartTooltipVerticalAlign.CENTERED,
125
152
  isEmpty,
@@ -139,15 +166,10 @@ var PieChart = ({ series, slices, width, height, fillWidth, fillHeight, aspectRa
139
166
  children: geometry.arcs.map((chartArc, index) => /* @__PURE__ */ jsx(ChartArcPath, {
140
167
  d: chartArc.path,
141
168
  fill: chartArc.color,
142
- "data-state": isLoading ? ChartMarkState.MUTED : interaction.getMarkState({
169
+ "data-state": interaction.getMarkState({
143
170
  categoryIndex: index,
144
171
  seriesKey: chartArc.id
145
- }),
146
- "data-interactive": noTooltip || isLoading ? "false" : "true",
147
- onMouseEnter: () => interaction.onCategoryEnter(index),
148
- onMouseLeave: interaction.onCategoryLeave,
149
- onClick: () => interaction.onCategoryClick(index),
150
- "aria-label": `${chartArc.label}: ${formatValue(chartArc.value)}`
172
+ })
151
173
  }, chartArc.id))
152
174
  })
153
175
  });