basalt-ui 1.14.0 → 1.15.0
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/AGENTS.md +1 -1
- package/CHANGELOG.md +8 -0
- package/README.md +38 -2
- package/agent/rules/basalt-charts.md +260 -108
- package/agent/rules/basalt-mantine.md +4 -2
- package/agent/skills/basalt-charts/SKILL.md +114 -64
- package/agent/skills/basalt-design/SKILL.md +4 -3
- package/configs/oxlint-plugin.js +152 -0
- package/configs/oxlint-plugin.test.ts +107 -0
- package/configs/oxlint.json +2 -0
- package/dist/charts/cursor/resolve.d.ts +33 -0
- package/dist/charts/cursor/resolve.d.ts.map +1 -0
- package/dist/charts/cursor/resolve.js +61 -0
- package/dist/charts/cursor/resolve.test.d.ts +2 -0
- package/dist/charts/cursor/resolve.test.d.ts.map +1 -0
- package/dist/charts/cursor/scope.d.ts +17 -0
- package/dist/charts/cursor/scope.d.ts.map +1 -0
- package/dist/charts/cursor/scope.js +20 -0
- package/dist/charts/cursor/store.d.ts +31 -0
- package/dist/charts/cursor/store.d.ts.map +1 -0
- package/dist/charts/cursor/store.js +25 -0
- package/dist/charts/cursor/store.test.d.ts +2 -0
- package/dist/charts/cursor/store.test.d.ts.map +1 -0
- package/dist/charts/hooks/useChartCursor.d.ts +39 -0
- package/dist/charts/hooks/useChartCursor.d.ts.map +1 -0
- package/dist/charts/hooks/useChartCursor.js +104 -0
- package/dist/charts/hooks/useChartSize.d.ts +8 -4
- package/dist/charts/hooks/useChartSize.d.ts.map +1 -1
- package/dist/charts/index.d.ts +16 -13
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +38 -19
- package/dist/charts/kinds/Bars.d.ts +25 -47
- package/dist/charts/kinds/Bars.d.ts.map +1 -1
- package/dist/charts/kinds/Bars.js +230 -390
- package/dist/charts/kinds/Donut.d.ts +5 -4
- package/dist/charts/kinds/Donut.d.ts.map +1 -1
- package/dist/charts/kinds/Donut.js +17 -14
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/DualPanel.js +136 -90
- package/dist/charts/kinds/Heatmap.d.ts +15 -11
- package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
- package/dist/charts/kinds/Heatmap.js +38 -22
- package/dist/charts/kinds/MultiLine.d.ts +27 -35
- package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
- package/dist/charts/kinds/MultiLine.js +114 -237
- package/dist/charts/kinds/StackedArea.d.ts +21 -10
- package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
- package/dist/charts/kinds/StackedArea.js +74 -132
- package/dist/charts/kinds/ZonedLine.d.ts +24 -40
- package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
- package/dist/charts/kinds/ZonedLine.js +103 -184
- package/dist/charts/layout/auto-margin.d.ts +60 -0
- package/dist/charts/layout/auto-margin.d.ts.map +1 -0
- package/dist/charts/layout/auto-margin.js +37 -0
- package/dist/charts/layout/auto-margin.test.d.ts +2 -0
- package/dist/charts/layout/auto-margin.test.d.ts.map +1 -0
- package/dist/charts/primitives/CartesianChart.d.ts +143 -0
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -0
- package/dist/charts/primitives/CartesianChart.js +349 -0
- package/dist/charts/primitives/CartesianChart.test.d.ts +2 -0
- package/dist/charts/primitives/CartesianChart.test.d.ts.map +1 -0
- package/dist/charts/primitives/ChartFrame.d.ts +16 -5
- package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
- package/dist/charts/primitives/ChartFrame.js +17 -2
- package/dist/charts/primitives/ChartLegend.d.ts +12 -1
- package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
- package/dist/charts/primitives/ChartLegend.js +21 -3
- package/dist/charts/primitives/ChartLegend.test.d.ts +2 -0
- package/dist/charts/primitives/ChartLegend.test.d.ts.map +1 -0
- package/dist/charts/primitives/ChartTooltip.d.ts +16 -16
- package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
- package/dist/charts/primitives/ChartTooltip.js +45 -19
- package/dist/charts/primitives/HoverOverlay.d.ts +19 -4
- package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
- package/dist/charts/primitives/HoverOverlay.js +22 -1
- package/dist/charts/primitives/XZoneRects.d.ts +40 -0
- package/dist/charts/primitives/XZoneRects.d.ts.map +1 -0
- package/dist/charts/primitives/XZoneRects.js +28 -0
- package/dist/charts/primitives/XZoneRects.test.d.ts +2 -0
- package/dist/charts/primitives/XZoneRects.test.d.ts.map +1 -0
- package/dist/charts/series.d.ts +16 -1
- package/dist/charts/series.d.ts.map +1 -1
- package/dist/charts/series.js +3 -1
- package/dist/charts/series.test.d.ts +2 -0
- package/dist/charts/series.test.d.ts.map +1 -0
- package/dist/charts/utils/measure-text.d.ts +25 -0
- package/dist/charts/utils/measure-text.d.ts.map +1 -0
- package/dist/charts/utils/measure-text.js +64 -0
- package/dist/charts/utils/measure-text.test.d.ts +2 -0
- package/dist/charts/utils/measure-text.test.d.ts.map +1 -0
- package/dist/content/fence-block.d.ts.map +1 -1
- package/dist/surfaces.d.ts +1 -1
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +1 -1
- package/dist/tokens/chart-margin.test.d.ts +2 -0
- package/dist/tokens/chart-margin.test.d.ts.map +1 -0
- package/dist/tokens/index.d.ts +29 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +10 -0
- package/dist/tokens/palette.d.ts +4 -0
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +3 -0
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/src/charts/cursor/resolve.test.ts +61 -0
- package/src/charts/cursor/resolve.ts +102 -0
- package/src/charts/cursor/scope.tsx +32 -0
- package/src/charts/cursor/store.test.ts +46 -0
- package/src/charts/cursor/store.ts +53 -0
- package/src/charts/hooks/useChartCursor.ts +163 -0
- package/src/charts/hooks/useChartSize.ts +8 -4
- package/src/charts/index.ts +34 -14
- package/src/charts/kinds/Bars.tsx +327 -460
- package/src/charts/kinds/Donut.tsx +31 -23
- package/src/charts/kinds/DualPanel.tsx +228 -134
- package/src/charts/kinds/Heatmap.tsx +72 -47
- package/src/charts/kinds/MultiLine.tsx +158 -294
- package/src/charts/kinds/StackedArea.tsx +116 -175
- package/src/charts/kinds/ZonedLine.tsx +148 -255
- package/src/charts/layout/auto-margin.test.ts +42 -0
- package/src/charts/layout/auto-margin.ts +110 -0
- package/src/charts/primitives/CartesianChart.test.ts +99 -0
- package/src/charts/primitives/CartesianChart.tsx +566 -0
- package/src/charts/primitives/ChartFrame.test.tsx +33 -0
- package/src/charts/primitives/ChartFrame.tsx +40 -4
- package/src/charts/primitives/ChartLegend.test.tsx +48 -0
- package/src/charts/primitives/ChartLegend.tsx +45 -2
- package/src/charts/primitives/ChartTooltip.tsx +67 -37
- package/src/charts/primitives/HoverOverlay.tsx +39 -3
- package/src/charts/primitives/XZoneRects.test.tsx +63 -0
- package/src/charts/primitives/XZoneRects.tsx +67 -0
- package/src/charts/series.test.ts +61 -0
- package/src/charts/series.ts +18 -1
- package/src/charts/utils/measure-text.test.ts +36 -0
- package/src/charts/utils/measure-text.ts +109 -0
- package/src/content/fence-block.tsx +2 -3
- package/src/surfaces.ts +2 -1
- package/src/theme/spacing.test.ts +2 -0
- package/src/tokens/chart-margin.test.ts +39 -0
- package/src/tokens/density.test.ts +1 -0
- package/src/tokens/index.ts +36 -0
- package/src/tokens/palette.ts +3 -0
- package/dist/charts/hooks/useChartTooltip.d.ts +0 -13
- package/dist/charts/hooks/useChartTooltip.d.ts.map +0 -1
- package/dist/charts/hooks/useChartTooltip.js +0 -26
- package/dist/charts/hooks/useHoverSync.d.ts +0 -46
- package/dist/charts/hooks/useHoverSync.d.ts.map +0 -1
- package/dist/charts/hooks/useHoverSync.js +0 -75
- package/dist/charts/hover-context.d.ts +0 -21
- package/dist/charts/hover-context.d.ts.map +0 -1
- package/dist/charts/hover-context.js +0 -12
- package/dist/charts/hover-sync.d.ts +0 -14
- package/dist/charts/hover-sync.d.ts.map +0 -1
- package/dist/charts/hover-sync.js +0 -17
- package/dist/charts/primitives/ResponsiveChart.d.ts +0 -45
- package/dist/charts/primitives/ResponsiveChart.d.ts.map +0 -1
- package/dist/charts/primitives/ResponsiveChart.js +0 -24
- package/src/charts/hooks/useChartTooltip.ts +0 -31
- package/src/charts/hooks/useHoverSync.ts +0 -152
- package/src/charts/hover-context.ts +0 -28
- package/src/charts/hover-sync.tsx +0 -37
- package/src/charts/primitives/ResponsiveChart.tsx +0 -64
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useState } from "react";
|
|
2
3
|
import { useChartSize } from "../hooks/useChartSize.js";
|
|
3
4
|
import { deriveLegend } from "../series.js";
|
|
4
5
|
import { ChartLegend } from "./ChartLegend.js";
|
|
@@ -21,6 +22,7 @@ function resolveLegend(config, hover) {
|
|
|
21
22
|
placement: config?.placement ?? "bottom",
|
|
22
23
|
...config?.groups !== void 0 && { groups: config.groups },
|
|
23
24
|
...config?.maxRows !== void 0 && { maxRows: config.maxRows },
|
|
25
|
+
...config?.toggle !== void 0 && { toggle: config.toggle },
|
|
24
26
|
...hover !== void 0 && { highlighted: hover.highlighted, onHighlight: hover.onHighlight }
|
|
25
27
|
};
|
|
26
28
|
}
|
|
@@ -38,6 +40,15 @@ function ChartFrame({
|
|
|
38
40
|
}) {
|
|
39
41
|
const { ref: containerRef, width: containerW, height: containerH } = useChartSize();
|
|
40
42
|
const { ref: legendRef, width: legendW, height: legendH } = useChartSize();
|
|
43
|
+
const [hidden, setHidden] = useState(() => /* @__PURE__ */ new Set());
|
|
44
|
+
const toggleKey = useCallback((key) => {
|
|
45
|
+
setHidden((prev) => {
|
|
46
|
+
const next = new Set(prev);
|
|
47
|
+
if (next.has(key)) next.delete(key);
|
|
48
|
+
else next.add(key);
|
|
49
|
+
return next;
|
|
50
|
+
});
|
|
51
|
+
}, []);
|
|
41
52
|
const placement = legend === false ? "bottom" : legend.placement ?? "bottom";
|
|
42
53
|
const vertical = placement === "left" || placement === "right";
|
|
43
54
|
const legendVisible = legend !== false && !isPending;
|
|
@@ -48,11 +59,15 @@ function ChartFrame({
|
|
|
48
59
|
width: Math.max(containerW - sideLegendWidth, minWidth),
|
|
49
60
|
height: resolvedHeight - topBottomLegendHeight
|
|
50
61
|
};
|
|
62
|
+
const legendItems = legend === false ? [] : deriveLegend(series);
|
|
63
|
+
const togglable = legend !== false && (legend.toggle ?? legendItems.length > 1);
|
|
51
64
|
const legendNode = legend === false || isPending ? null : /* @__PURE__ */ jsx("div", { ref: legendRef, style: legendWrapperStyle(vertical), children: /* @__PURE__ */ jsx(
|
|
52
65
|
ChartLegend,
|
|
53
66
|
{
|
|
54
|
-
items:
|
|
67
|
+
items: legendItems,
|
|
55
68
|
placement,
|
|
69
|
+
hidden,
|
|
70
|
+
...togglable && { onToggle: toggleKey },
|
|
56
71
|
...chartId !== void 0 && { chartId },
|
|
57
72
|
...legend.groups !== void 0 && { groups: legend.groups },
|
|
58
73
|
...legend.maxRows !== void 0 && { maxRows: legend.maxRows },
|
|
@@ -69,7 +84,7 @@ function ChartFrame({
|
|
|
69
84
|
...isPending && { "aria-busy": "true" },
|
|
70
85
|
children: [
|
|
71
86
|
legendNode !== null && (placement === "top" || placement === "left") && legendNode,
|
|
72
|
-
plot.width > 0 && plot.height > 0 && (isPending ? /* @__PURE__ */ jsx(ChartPending, { width: plot.width, height: plot.height }) : children(plot)),
|
|
87
|
+
plot.width > 0 && plot.height > 0 && (isPending ? /* @__PURE__ */ jsx(ChartPending, { width: plot.width, height: plot.height }) : children({ ...plot, hidden })),
|
|
73
88
|
legendNode !== null && (placement === "bottom" || placement === "right") && legendNode
|
|
74
89
|
]
|
|
75
90
|
}
|
|
@@ -15,6 +15,9 @@ export type LegendEntry = {
|
|
|
15
15
|
/** Companions folded under this entry via `parent` (`deriveLegend`) — rendered as compact,
|
|
16
16
|
* subordinate sub-entries beside the parent's label instead of vanishing. */
|
|
17
17
|
children?: LegendEntry[];
|
|
18
|
+
/** Short qualifier rendered after the label in muted text — e.g. a flat-at-zero series that is
|
|
19
|
+
* invisible in the plot. */
|
|
20
|
+
note?: string;
|
|
18
21
|
};
|
|
19
22
|
/**
|
|
20
23
|
* Shared legend for all non-sparkline charts. `highlighted`/`onHighlight` are optional — omit for
|
|
@@ -23,11 +26,19 @@ export type LegendEntry = {
|
|
|
23
26
|
* reference bands instead of one flat smear. `maxRows` caps the number of rendered entries and
|
|
24
27
|
* rolls the remainder into a `+N more` chip (row-accurate wrap measurement is `ChartFrame`'s job —
|
|
25
28
|
* this is an entry-count cap, a deliberate simplification until that primitive lands).
|
|
29
|
+
*
|
|
30
|
+
* `onToggle` makes an entry a real toggle: clicking it hides the series in the plot, the tooltip,
|
|
31
|
+
* and the auto y-domain together (`ChartFrame` owns the `hidden` set — see `docs/CHARTS-SPEC.md`
|
|
32
|
+
* §5). Omit it for a static legend; a hidden entry is announced via `aria-pressed`.
|
|
26
33
|
*/
|
|
27
|
-
export declare function ChartLegend({ items, highlighted, onHighlight, chartId, placement, groups, maxRows, }: {
|
|
34
|
+
export declare function ChartLegend({ items, highlighted, onHighlight, hidden, onToggle, chartId, placement, groups, maxRows, }: {
|
|
28
35
|
items: LegendEntry[];
|
|
29
36
|
highlighted?: string | null;
|
|
30
37
|
onHighlight?: (key: string | null) => void;
|
|
38
|
+
/** Keys currently toggled off. */
|
|
39
|
+
hidden?: ReadonlySet<string>;
|
|
40
|
+
/** Present = entries are clickable toggles. */
|
|
41
|
+
onToggle?: (key: string) => void;
|
|
31
42
|
chartId?: string;
|
|
32
43
|
placement?: LegendPlacement;
|
|
33
44
|
groups?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartLegend.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartLegend.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAE5D,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,WAAW,CAAA;IAC9C,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,kGAAkG;IAClG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mFAAmF;IACnF,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB;iFAC6E;IAC7E,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"ChartLegend.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartLegend.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAE5D,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,WAAW,CAAA;IAC9C,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,kGAAkG;IAClG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mFAAmF;IACnF,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB;iFAC6E;IAC7E,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;IACxB;gCAC4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AA4ND;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,WAAkB,EAClB,WAAW,EACX,MAAkB,EAClB,QAAQ,EACR,OAAY,EACZ,SAAoB,EACpB,MAAc,EACd,OAAO,GACR,EAAE;IACD,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C,kCAAkC;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC5B,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,+BAmEA"}
|
|
@@ -19,6 +19,7 @@ const LEGEND_ITEM_BUTTON = {
|
|
|
19
19
|
textAlign: "inherit"
|
|
20
20
|
};
|
|
21
21
|
const GROUP_ORDER = ["series", "overlay", "reference"];
|
|
22
|
+
const NO_HIDDEN = /* @__PURE__ */ new Set();
|
|
22
23
|
const roleOf = (item) => item.role ?? "series";
|
|
23
24
|
function orderEntries(items, groups) {
|
|
24
25
|
if (!groups) return { entries: items, dividerAfter: /* @__PURE__ */ new Set() };
|
|
@@ -128,6 +129,9 @@ function LegendSwatch({ item, idPrefix }) {
|
|
|
128
129
|
}
|
|
129
130
|
) });
|
|
130
131
|
}
|
|
132
|
+
const LEGEND_NOTE_STYLE = {
|
|
133
|
+
opacity: 0.75
|
|
134
|
+
};
|
|
131
135
|
const LEGEND_CHILD_STYLE = {
|
|
132
136
|
display: "flex",
|
|
133
137
|
alignItems: "center",
|
|
@@ -169,13 +173,16 @@ function LegendChildSwatch({ item }) {
|
|
|
169
173
|
function LegendChild({ item }) {
|
|
170
174
|
return /* @__PURE__ */ jsxs("span", { style: LEGEND_CHILD_STYLE, children: [
|
|
171
175
|
/* @__PURE__ */ jsx(LegendChildSwatch, { item }),
|
|
172
|
-
/* @__PURE__ */ jsx("span", { children: item.label })
|
|
176
|
+
/* @__PURE__ */ jsx("span", { children: item.label }),
|
|
177
|
+
item.note ? /* @__PURE__ */ jsx("span", { children: item.note }) : null
|
|
173
178
|
] });
|
|
174
179
|
}
|
|
175
180
|
function ChartLegend({
|
|
176
181
|
items,
|
|
177
182
|
highlighted = null,
|
|
178
183
|
onHighlight,
|
|
184
|
+
hidden = NO_HIDDEN,
|
|
185
|
+
onToggle,
|
|
179
186
|
chartId = "",
|
|
180
187
|
placement = "bottom",
|
|
181
188
|
groups = false,
|
|
@@ -186,6 +193,10 @@ function ChartLegend({
|
|
|
186
193
|
if (key !== void 0) onHighlight?.(key);
|
|
187
194
|
};
|
|
188
195
|
const handleLeave = () => onHighlight?.(null);
|
|
196
|
+
const handleToggle = (e) => {
|
|
197
|
+
const key = e.currentTarget.dataset["legendKey"];
|
|
198
|
+
if (key !== void 0) onToggle?.(key);
|
|
199
|
+
};
|
|
189
200
|
const idPrefix = chartId ? `${chartId}-` : "";
|
|
190
201
|
const vertical = placement === "left" || placement === "right";
|
|
191
202
|
const { entries, dividerAfter } = orderEntries(items, groups);
|
|
@@ -199,11 +210,17 @@ function ChartLegend({
|
|
|
199
210
|
{
|
|
200
211
|
type: "button",
|
|
201
212
|
"data-legend-key": item.key,
|
|
202
|
-
"aria-label": item.label,
|
|
213
|
+
"aria-label": item.note ? `${item.label} \u2014 ${item.note}` : item.label,
|
|
214
|
+
...onToggle !== void 0 && { "aria-pressed": !hidden.has(item.key) },
|
|
203
215
|
style: {
|
|
204
216
|
...LEGEND_ITEM_BUTTON,
|
|
205
|
-
|
|
217
|
+
cursor: onToggle === void 0 ? "default" : "pointer",
|
|
218
|
+
// Toggled-off wins over hover-dimming: a hidden series must read as hidden even while
|
|
219
|
+
// it is the one being hovered.
|
|
220
|
+
opacity: hidden.has(item.key) ? 0.35 : highlighted === null || highlighted === item.key ? 1 : 0.3,
|
|
221
|
+
textDecoration: hidden.has(item.key) ? "line-through" : "none"
|
|
206
222
|
},
|
|
223
|
+
...onToggle !== void 0 && { onClick: handleToggle },
|
|
207
224
|
onMouseEnter: handleEnter,
|
|
208
225
|
onMouseLeave: handleLeave,
|
|
209
226
|
onFocus: handleEnter,
|
|
@@ -211,6 +228,7 @@ function ChartLegend({
|
|
|
211
228
|
children: [
|
|
212
229
|
/* @__PURE__ */ jsx(LegendSwatch, { item, idPrefix }),
|
|
213
230
|
/* @__PURE__ */ jsx("span", { children: item.label }),
|
|
231
|
+
item.note ? /* @__PURE__ */ jsx("span", { style: LEGEND_NOTE_STYLE, children: item.note }) : null,
|
|
214
232
|
item.children?.map((child) => /* @__PURE__ */ jsx(LegendChild, { item: child }, child.key))
|
|
215
233
|
]
|
|
216
234
|
},
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChartLegend.test.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartLegend.test.tsx"],"names":[],"mappings":""}
|
|
@@ -1,19 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
/** Theme-aware tooltip container styles — use with useChartTooltip(). */
|
|
3
|
-
export declare function useTooltipStyles(): CSSProperties;
|
|
4
|
-
/**
|
|
5
|
-
* Outer tooltip shell. Renders nothing when tip is null. Renders to `document.body` via a
|
|
6
|
-
* portal, so it may be authored anywhere in the tree, including inside `<svg>`.
|
|
7
|
-
*/
|
|
8
|
-
export declare function ChartTooltip({ tip, tooltipRef, styles, children, }: {
|
|
9
|
-
tip: {
|
|
10
|
-
x: number;
|
|
11
|
-
y: number;
|
|
12
|
-
} | null;
|
|
13
|
-
tooltipRef?: RefObject<HTMLDivElement | null>;
|
|
14
|
-
styles: CSSProperties;
|
|
15
|
-
children: ReactNode;
|
|
16
|
-
}): import("react").ReactPortal | null;
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
17
2
|
/** Tooltip header — shows formatted date + optional right-aligned label with color. */
|
|
18
3
|
export declare function TooltipHeader({ date, label, labelColor, }: {
|
|
19
4
|
date: string;
|
|
@@ -34,4 +19,19 @@ export declare function TooltipRow({ color, label, value, valueColor, shape, str
|
|
|
34
19
|
export declare function TooltipBody({ children }: {
|
|
35
20
|
children: ReactNode;
|
|
36
21
|
}): import("react").JSX.Element;
|
|
22
|
+
/**
|
|
23
|
+
* Floating tooltip positioned against a viewport-space anchor, with flip + clamp handled once for
|
|
24
|
+
* every chart instead of per call site (this used to be recomputed inside each chart's own `useChartTooltip`, and only for
|
|
25
|
+
* charts that remembered to use it).
|
|
26
|
+
*
|
|
27
|
+
* The box is measured after mount via `useLayoutEffect`, so the first paint of a given tooltip is
|
|
28
|
+
* hidden rather than misplaced — a tooltip that flashes at the wrong corner reads as a bug.
|
|
29
|
+
*/
|
|
30
|
+
export declare function ChartTooltipFloat({ anchor, children, }: {
|
|
31
|
+
anchor: {
|
|
32
|
+
x: number;
|
|
33
|
+
y: number;
|
|
34
|
+
} | null;
|
|
35
|
+
children: ReactNode;
|
|
36
|
+
}): import("react").ReactPortal | null;
|
|
37
37
|
//# sourceMappingURL=ChartTooltip.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartTooltip.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartTooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"ChartTooltip.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartTooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAuBrD,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,KAAK,EACL,UAAU,GACX,EAAE;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,+BAoCA;AAED,4CAA4C;AAC5C,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,KAAK,EACL,KAAK,EACL,UAAU,EACV,KAAK,EACL,WAAW,EACX,MAAM,GACP,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,CAAA;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uEAAuE;IACvE,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,+BA0CA;AAED,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAEhE;AAOD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,QAAQ,GACT,EAAE;IACD,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACvC,QAAQ,EAAE,SAAS,CAAA;CACpB,sCA4CA"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useRef, useState } from "react";
|
|
2
3
|
import { createPortal } from "react-dom";
|
|
3
4
|
import { VX } from "../../tokens/index.js";
|
|
4
5
|
import { fmtTooltipDate } from "../utils/format.js";
|
|
@@ -15,22 +16,6 @@ const TOOLTIP_STYLES = {
|
|
|
15
16
|
boxShadow: VX.shadowCard,
|
|
16
17
|
minWidth: 140
|
|
17
18
|
};
|
|
18
|
-
function useTooltipStyles() {
|
|
19
|
-
return TOOLTIP_STYLES;
|
|
20
|
-
}
|
|
21
|
-
function ChartTooltip({
|
|
22
|
-
tip,
|
|
23
|
-
tooltipRef,
|
|
24
|
-
styles,
|
|
25
|
-
children
|
|
26
|
-
}) {
|
|
27
|
-
if (!tip) return null;
|
|
28
|
-
if (typeof document === "undefined") return null;
|
|
29
|
-
return createPortal(
|
|
30
|
-
/* @__PURE__ */ jsx("div", { ref: tooltipRef, style: { ...styles, left: tip.x, top: tip.y }, children }),
|
|
31
|
-
document.body
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
19
|
function TooltipHeader({
|
|
35
20
|
date,
|
|
36
21
|
label,
|
|
@@ -131,10 +116,51 @@ function TooltipRow({
|
|
|
131
116
|
function TooltipBody({ children }) {
|
|
132
117
|
return /* @__PURE__ */ jsx("div", { style: { padding: "5px 0" }, children });
|
|
133
118
|
}
|
|
119
|
+
const TOOLTIP_GAP = 12;
|
|
120
|
+
const VIEWPORT_MARGIN = 8;
|
|
121
|
+
function ChartTooltipFloat({
|
|
122
|
+
anchor,
|
|
123
|
+
children
|
|
124
|
+
}) {
|
|
125
|
+
const ref = useRef(null);
|
|
126
|
+
const [box, setBox] = useState(null);
|
|
127
|
+
useLayoutEffect(() => {
|
|
128
|
+
const el = ref.current;
|
|
129
|
+
if (el === null) return;
|
|
130
|
+
const { offsetWidth, offsetHeight } = el;
|
|
131
|
+
setBox(
|
|
132
|
+
(prev) => prev !== null && prev.width === offsetWidth && prev.height === offsetHeight ? prev : { width: offsetWidth, height: offsetHeight }
|
|
133
|
+
);
|
|
134
|
+
}, [children, anchor]);
|
|
135
|
+
if (anchor === null) return null;
|
|
136
|
+
if (typeof document === "undefined") return null;
|
|
137
|
+
const width = box?.width ?? 0;
|
|
138
|
+
const height = box?.height ?? 0;
|
|
139
|
+
const vw = window.innerWidth;
|
|
140
|
+
const vh = window.innerHeight;
|
|
141
|
+
const flipsLeft = anchor.x + TOOLTIP_GAP + width + VIEWPORT_MARGIN > vw;
|
|
142
|
+
const left = flipsLeft ? Math.max(VIEWPORT_MARGIN, anchor.x - TOOLTIP_GAP - width) : anchor.x + TOOLTIP_GAP;
|
|
143
|
+
const top = Math.min(
|
|
144
|
+
Math.max(VIEWPORT_MARGIN, anchor.y - TOOLTIP_GAP),
|
|
145
|
+
Math.max(VIEWPORT_MARGIN, vh - height - VIEWPORT_MARGIN)
|
|
146
|
+
);
|
|
147
|
+
return createPortal(
|
|
148
|
+
/* @__PURE__ */ jsx(
|
|
149
|
+
"div",
|
|
150
|
+
{
|
|
151
|
+
ref,
|
|
152
|
+
role: "tooltip",
|
|
153
|
+
"aria-live": "polite",
|
|
154
|
+
style: { ...TOOLTIP_STYLES, left, top, visibility: box === null ? "hidden" : "visible" },
|
|
155
|
+
children
|
|
156
|
+
}
|
|
157
|
+
),
|
|
158
|
+
document.body
|
|
159
|
+
);
|
|
160
|
+
}
|
|
134
161
|
export {
|
|
135
|
-
|
|
162
|
+
ChartTooltipFloat,
|
|
136
163
|
TooltipBody,
|
|
137
164
|
TooltipHeader,
|
|
138
|
-
TooltipRow
|
|
139
|
-
useTooltipStyles
|
|
165
|
+
TooltipRow
|
|
140
166
|
};
|
|
@@ -1,10 +1,25 @@
|
|
|
1
|
-
import type { PointerEventHandler } from 'react';
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
|
|
1
|
+
import type { FocusEventHandler, KeyboardEventHandler, PointerEventHandler } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Transparent <rect> that captures pointer events (mouse + touch + pen) for tooltip + crosshair
|
|
4
|
+
* sync.
|
|
5
|
+
*
|
|
6
|
+
* With `onKeyDown` it also becomes the chart's keyboard affordance: the rect takes focus and
|
|
7
|
+
* ←/→ scrub the cursor, so a chart's values are reachable without a pointer.
|
|
8
|
+
*/
|
|
9
|
+
export declare function HoverOverlay({ width, height, onMove, onLeave, onKeyDown, onBlur, ariaLabel, valueNow, valueMax, valueText, }: {
|
|
5
10
|
width: number;
|
|
6
11
|
height: number;
|
|
7
12
|
onMove: PointerEventHandler<SVGRectElement>;
|
|
8
13
|
onLeave: PointerEventHandler<SVGRectElement>;
|
|
14
|
+
/** Present = the overlay is focusable and scrubs on ←/→. */
|
|
15
|
+
onKeyDown?: KeyboardEventHandler<SVGRectElement>;
|
|
16
|
+
onBlur?: FocusEventHandler<SVGRectElement>;
|
|
17
|
+
ariaLabel?: string;
|
|
18
|
+
/** Index of the focused point — announced as the slider position. */
|
|
19
|
+
valueNow?: number;
|
|
20
|
+
/** Last index of the domain. */
|
|
21
|
+
valueMax?: number;
|
|
22
|
+
/** Human-readable label for the focused point (the formatted x key). */
|
|
23
|
+
valueText?: string;
|
|
9
24
|
}): import("react").JSX.Element;
|
|
10
25
|
//# sourceMappingURL=HoverOverlay.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HoverOverlay.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/HoverOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"HoverOverlay.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/HoverOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAA;AAEzF;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,MAAM,EACN,MAAM,EACN,OAAO,EACP,SAAS,EACT,MAAM,EACN,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,SAAS,GACV,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,mBAAmB,CAAC,cAAc,CAAC,CAAA;IAC3C,OAAO,EAAE,mBAAmB,CAAC,cAAc,CAAC,CAAA;IAC5C,4DAA4D;IAC5D,SAAS,CAAC,EAAE,oBAAoB,CAAC,cAAc,CAAC,CAAA;IAChD,MAAM,CAAC,EAAE,iBAAiB,CAAC,cAAc,CAAC,CAAA;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,+BA8BA"}
|
|
@@ -3,7 +3,13 @@ function HoverOverlay({
|
|
|
3
3
|
width,
|
|
4
4
|
height,
|
|
5
5
|
onMove,
|
|
6
|
-
onLeave
|
|
6
|
+
onLeave,
|
|
7
|
+
onKeyDown,
|
|
8
|
+
onBlur,
|
|
9
|
+
ariaLabel,
|
|
10
|
+
valueNow,
|
|
11
|
+
valueMax,
|
|
12
|
+
valueText
|
|
7
13
|
}) {
|
|
8
14
|
return /* @__PURE__ */ jsx(
|
|
9
15
|
"rect",
|
|
@@ -11,6 +17,21 @@ function HoverOverlay({
|
|
|
11
17
|
width,
|
|
12
18
|
height,
|
|
13
19
|
fill: "transparent",
|
|
20
|
+
...onKeyDown !== void 0 && {
|
|
21
|
+
tabIndex: 0,
|
|
22
|
+
// `slider`, not `application`: `application` would drop this node out of the screen
|
|
23
|
+
// reader's normal browse mode entirely, which is a far bigger hammer than an arrow-key
|
|
24
|
+
// scrub needs. A slider announces both the affordance and the focused point.
|
|
25
|
+
role: "slider",
|
|
26
|
+
"aria-label": ariaLabel ?? "Chart data \u2014 use arrow keys to scrub",
|
|
27
|
+
"aria-orientation": "horizontal",
|
|
28
|
+
"aria-valuemin": 0,
|
|
29
|
+
...valueMax !== void 0 && { "aria-valuemax": valueMax },
|
|
30
|
+
...valueNow !== void 0 && { "aria-valuenow": valueNow },
|
|
31
|
+
...valueText !== void 0 && { "aria-valuetext": valueText },
|
|
32
|
+
onKeyDown,
|
|
33
|
+
...onBlur !== void 0 && { onBlur }
|
|
34
|
+
},
|
|
14
35
|
style: { touchAction: "pan-y" },
|
|
15
36
|
onPointerMove: onMove,
|
|
16
37
|
onPointerLeave: onLeave,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { scalePoint } from '@visx/scale';
|
|
2
|
+
/**
|
|
3
|
+
* The point-scale instance type, derived from `@visx/scale`'s own `scalePoint` return type.
|
|
4
|
+
* Equivalent to d3's `ScalePoint<string>` but resolved through `@visx/scale` (an installed dep) so
|
|
5
|
+
* it needs no `@types/d3-scale` and stays inside the visx-only-in-charts boundary.
|
|
6
|
+
*/
|
|
7
|
+
type PointScale = ReturnType<typeof scalePoint<string>>;
|
|
8
|
+
export type XZoneSpec = {
|
|
9
|
+
/** Left edge — a domain key produced by the kind's `getX`. Omit for "start of the plot area". */
|
|
10
|
+
from?: string;
|
|
11
|
+
/** Right edge — a domain key produced by the kind's `getX`. Omit for "end of the plot area". */
|
|
12
|
+
to?: string;
|
|
13
|
+
fill: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Vertical zone backgrounds (x-range overlays — twilight bands, a shaded time window) — one
|
|
17
|
+
* <rect> per zone, spanning y=[0, height] and x=[scale(from), scale(to)]. The x analog of
|
|
18
|
+
* `ZoneRects`, for a `scalePoint` x-axis instead of a `scaleLinear` y-axis.
|
|
19
|
+
*
|
|
20
|
+
* An omitted `from` resolves to `0` (start of the plot area); an omitted `to` resolves to
|
|
21
|
+
* `xScale.range()[1]` (the plot's right edge). A present key resolves to `xScale(key)` — the
|
|
22
|
+
* POINT CENTER, so a band reads as "from this sample to that sample", not edge-to-edge.
|
|
23
|
+
*
|
|
24
|
+
* A key that is not in the scale's domain skips that band entirely (renders nothing for it) —
|
|
25
|
+
* it is NEVER clamped to a plot edge. Clamping would silently paint a band across the whole
|
|
26
|
+
* chart for a typo'd or stale key, which is the exact class of quiet lie the repo's "nothing to
|
|
27
|
+
* draw is two different states" doctrine rejects: an unknown key must read as "not drawn", not
|
|
28
|
+
* as "the whole plot".
|
|
29
|
+
*
|
|
30
|
+
* A degenerate resolved range (right edge <= left edge, e.g. an inverted `from`/`to` pair) is
|
|
31
|
+
* also skipped — no negative-width rect.
|
|
32
|
+
*/
|
|
33
|
+
export declare function XZoneRects({ zones, height, xScale, }: {
|
|
34
|
+
zones: XZoneSpec[];
|
|
35
|
+
/** Plot-area height (yMax); rects span from 0 to this. */
|
|
36
|
+
height: number;
|
|
37
|
+
xScale: PointScale;
|
|
38
|
+
}): import("react").JSX.Element;
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=XZoneRects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"XZoneRects.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/XZoneRects.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C;;;;GAIG;AACH,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAA;AAEvD,MAAM,MAAM,SAAS,GAAG;IACtB,iGAAiG;IACjG,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,gGAAgG;IAChG,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,MAAM,EACN,MAAM,GACP,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,0DAA0D;IAC1D,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,UAAU,CAAA;CACnB,+BAsBA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
2
|
+
function XZoneRects({
|
|
3
|
+
zones,
|
|
4
|
+
height,
|
|
5
|
+
xScale
|
|
6
|
+
}) {
|
|
7
|
+
return /* @__PURE__ */ jsx(Fragment, { children: zones.map((z, i) => {
|
|
8
|
+
const [rangeStart, rangeEnd] = xScale.range();
|
|
9
|
+
const xFrom = z.from === void 0 ? rangeStart : xScale(z.from);
|
|
10
|
+
const xTo = z.to === void 0 ? rangeEnd : xScale(z.to);
|
|
11
|
+
if (xFrom === void 0 || xTo === void 0) return null;
|
|
12
|
+
if (xTo <= xFrom) return null;
|
|
13
|
+
return /* @__PURE__ */ jsx(
|
|
14
|
+
"rect",
|
|
15
|
+
{
|
|
16
|
+
x: xFrom,
|
|
17
|
+
y: 0,
|
|
18
|
+
width: xTo - xFrom,
|
|
19
|
+
height,
|
|
20
|
+
fill: z.fill
|
|
21
|
+
},
|
|
22
|
+
`xzone-${i}`
|
|
23
|
+
);
|
|
24
|
+
}) });
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
XZoneRects
|
|
28
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"XZoneRects.test.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/XZoneRects.test.tsx"],"names":[],"mappings":""}
|
package/dist/charts/series.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ export type ChartLegendConfig = {
|
|
|
36
36
|
groups?: boolean;
|
|
37
37
|
/** Wrap cap → "+N more" rollup at high cardinality. */
|
|
38
38
|
maxRows?: number;
|
|
39
|
+
/** Clicking an entry hides that series. Default: on whenever there is more than one entry. */
|
|
40
|
+
toggle?: boolean;
|
|
39
41
|
};
|
|
40
42
|
/** Visual identity of a series — everything the legend + tooltip swatch need. No accessors, no `T`. */
|
|
41
43
|
export type SeriesStyle = {
|
|
@@ -50,12 +52,24 @@ export type SeriesStyle = {
|
|
|
50
52
|
strokeWidth?: number;
|
|
51
53
|
/** bar/area: the swatch honors this so it cannot lie. */
|
|
52
54
|
fillOpacity?: number;
|
|
55
|
+
/** Which y-axis this series is measured against. Default 'left'. `'right'` is what makes a
|
|
56
|
+
* chart dual-axis — `CartesianChart` reads it for the crosshair dots and the tooltip. */
|
|
57
|
+
axis?: 'left' | 'right';
|
|
53
58
|
/** Default 'series'. */
|
|
54
59
|
role?: SeriesRole;
|
|
55
60
|
/** Default true; false = companion folded under `parent`. */
|
|
56
61
|
legend?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Default true; false = drawn and legended, but NEVER listed as a tooltip row. The replacement
|
|
64
|
+
* for the per-kind `hideBarTooltipRows` escape the old layer had: it belongs to the series, not to the kind, so
|
|
65
|
+
* one series can opt out without the kind growing a prop for it.
|
|
66
|
+
*/
|
|
67
|
+
tooltip?: boolean;
|
|
57
68
|
/** e.g. an MA line names its parent so hover-dimming keeps the pair. */
|
|
58
69
|
parent?: string;
|
|
70
|
+
/** Short qualifier rendered after the label in muted text — e.g. a flat-at-zero series that is
|
|
71
|
+
* invisible in the plot. */
|
|
72
|
+
note?: string;
|
|
59
73
|
};
|
|
60
74
|
/** Full descriptor = visual identity + data accessors. Generic over the point type. */
|
|
61
75
|
export type ChartSeries<T> = SeriesStyle & {
|
|
@@ -92,7 +106,8 @@ export type TooltipRowData = {
|
|
|
92
106
|
export declare function deriveLegend(series: readonly SeriesStyle[]): LegendEntry[];
|
|
93
107
|
/**
|
|
94
108
|
* Derive tooltip rows from a series descriptor array + the hovered datum. Skips rows where
|
|
95
|
-
* `getValue(datum) === null`; honors per-series `formatValue` over the
|
|
109
|
+
* `getValue(datum) === null` or `tooltip === false`; honors per-series `formatValue` over the
|
|
110
|
+
* shared `fallbackFormat`.
|
|
96
111
|
* Kinds pass `series.toReversed()` for stacked charts so the tooltip stack matches the visual stack.
|
|
97
112
|
*/
|
|
98
113
|
export declare function deriveTooltipRows<T>(series: readonly ChartSeries<T>[], datum: T, fallbackFormat: (v: number) => string): TooltipRowData[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"series.d.ts","sourceRoot":"","sources":["../../src/charts/series.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAE3D;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAE5C,iEAAiE;AACjE,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAA;AAEhD,0CAA0C;AAC1C,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAA;AAE3C,uCAAuC;AACvC,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAA;AAE3D,6DAA6D;AAC7D,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAA;AAEjE;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,wBAAwB;IACxB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,4DAA4D;IAC5D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,uGAAuG;AACvG,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,UAAU,CAAA;IAChB,uBAAuB;IACvB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,wBAAwB;IACxB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"series.d.ts","sourceRoot":"","sources":["../../src/charts/series.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAE3D;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAE5C,iEAAiE;AACjE,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,CAAA;AAEhD,0CAA0C;AAC1C,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAA;AAE3C,uCAAuC;AACvC,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAA;AAE3D,6DAA6D;AAC7D,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAA;AAEjE;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,wBAAwB;IACxB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,4DAA4D;IAC5D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,8FAA8F;IAC9F,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,uGAAuG;AACvG,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,UAAU,CAAA;IAChB,uBAAuB;IACvB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;6FACyF;IACzF,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB,wBAAwB;IACxB,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;gCAC4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,uFAAuF;AACvF,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,WAAW,GAAG;IACzC,6CAA6C;IAC7C,QAAQ,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,IAAI,CAAA;IACjC,iEAAiE;IACjE,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IACnC,qEAAqE;IACrE,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAC5D,CAAA;AAED,4DAA4D;AAC5D,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,GAAG,KAAK,CAAA;IACrB,MAAM,EAAE,OAAO,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAkBD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,EAAE,CAoB1E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,MAAM,EAAE,SAAS,WAAW,CAAC,CAAC,CAAC,EAAE,EACjC,KAAK,EAAE,CAAC,EACR,cAAc,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GACpC,cAAc,EAAE,CAkBlB"}
|
package/dist/charts/series.js
CHANGED
|
@@ -9,7 +9,8 @@ function legendEntryFor(s) {
|
|
|
9
9
|
dashed: s.dash === "dashed",
|
|
10
10
|
...s.strokeWidth !== void 0 && { strokeWidth: s.strokeWidth },
|
|
11
11
|
...s.fillOpacity !== void 0 && { fillOpacity: s.fillOpacity },
|
|
12
|
-
...s.role !== void 0 && { role: s.role }
|
|
12
|
+
...s.role !== void 0 && { role: s.role },
|
|
13
|
+
...s.note !== void 0 && { note: s.note }
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
function deriveLegend(series) {
|
|
@@ -33,6 +34,7 @@ function deriveLegend(series) {
|
|
|
33
34
|
function deriveTooltipRows(series, datum, fallbackFormat) {
|
|
34
35
|
const rows = [];
|
|
35
36
|
for (const s of series) {
|
|
37
|
+
if (s.tooltip === false) continue;
|
|
36
38
|
const value = s.getValue(datum);
|
|
37
39
|
if (value === null) continue;
|
|
38
40
|
const format = s.formatValue ?? fallbackFormat;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"series.test.d.ts","sourceRoot":"","sources":["../../src/charts/series.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text measurement for chart layout — the input to {@link autoMargin}.
|
|
3
|
+
*
|
|
4
|
+
* A static margin token cannot know how wide a tick label is, which is why charts used to have to be
|
|
5
|
+
* hand-nudged whenever a label grew (`docs/CHARTS-SPEC.md` §1). This measures the strings that
|
|
6
|
+
* will actually be painted, via an OFFSCREEN canvas 2D context: no element is attached, so there
|
|
7
|
+
* is no layout thrash and no reflow — unlike an SVG `getBBox()` probe.
|
|
8
|
+
*
|
|
9
|
+
* Mantine-free and dependency-free by construction (`charts/**` boundary).
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* The resolved value of `--basalt-font-mono` — canvas needs a concrete family list, it cannot
|
|
13
|
+
* resolve a CSS custom property. Read once from the document element.
|
|
14
|
+
*/
|
|
15
|
+
export declare function monoFontFamily(): string;
|
|
16
|
+
/**
|
|
17
|
+
* Width in px of `text` at `fontPx` in the chart tick font. Memoized per `font|text` — a chart
|
|
18
|
+
* re-measures the same ~10 tick labels on every resize, and the cache turns that into a Map hit.
|
|
19
|
+
*/
|
|
20
|
+
export declare function measureText(text: string, fontPx: number, fontFamily?: string): number;
|
|
21
|
+
/** Widest of `texts` at `fontPx`. Returns 0 for an empty list. */
|
|
22
|
+
export declare function maxTextWidth(texts: readonly string[], fontPx: number, fontFamily?: string): number;
|
|
23
|
+
/** Test seam — drops the memo table (and the resolved family) so a test can change fonts. */
|
|
24
|
+
export declare function resetTextMetrics(): void;
|
|
25
|
+
//# sourceMappingURL=measure-text.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measure-text.d.ts","sourceRoot":"","sources":["../../../src/charts/utils/measure-text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAuCH;;;GAGG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAWvC;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAkBrF;AAED,kEAAkE;AAClE,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CAOR;AAED,6FAA6F;AAC7F,wBAAgB,gBAAgB,IAAI,IAAI,CAIvC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const FALLBACK_CHAR_RATIO = 0.6;
|
|
2
|
+
const FALLBACK_MONO = "ui-monospace, SFMono-Regular, Menlo, monospace";
|
|
3
|
+
const CACHE_LIMIT = 2e3;
|
|
4
|
+
const cache = /* @__PURE__ */ new Map();
|
|
5
|
+
let ctx;
|
|
6
|
+
let monoFamily;
|
|
7
|
+
function canvasContext() {
|
|
8
|
+
if (ctx !== void 0) return ctx;
|
|
9
|
+
if (typeof document === "undefined") {
|
|
10
|
+
ctx = null;
|
|
11
|
+
return ctx;
|
|
12
|
+
}
|
|
13
|
+
ctx = document.createElement("canvas").getContext("2d");
|
|
14
|
+
document.fonts?.ready?.then?.(() => cache.clear()).catch(() => {
|
|
15
|
+
});
|
|
16
|
+
return ctx;
|
|
17
|
+
}
|
|
18
|
+
function monoFontFamily() {
|
|
19
|
+
if (monoFamily !== void 0) return monoFamily;
|
|
20
|
+
if (typeof document === "undefined" || typeof getComputedStyle !== "function") {
|
|
21
|
+
monoFamily = FALLBACK_MONO;
|
|
22
|
+
return monoFamily;
|
|
23
|
+
}
|
|
24
|
+
const value = getComputedStyle(document.documentElement).getPropertyValue("--basalt-font-mono").trim();
|
|
25
|
+
monoFamily = value.length > 0 ? value : FALLBACK_MONO;
|
|
26
|
+
return monoFamily;
|
|
27
|
+
}
|
|
28
|
+
function measureText(text, fontPx, fontFamily) {
|
|
29
|
+
const family = fontFamily ?? monoFontFamily();
|
|
30
|
+
const font = `${fontPx}px ${family}`;
|
|
31
|
+
const key = `${font}|${text}`;
|
|
32
|
+
const hit = cache.get(key);
|
|
33
|
+
if (hit !== void 0) return hit;
|
|
34
|
+
const c = canvasContext();
|
|
35
|
+
let width;
|
|
36
|
+
if (c === null) {
|
|
37
|
+
width = text.length * fontPx * FALLBACK_CHAR_RATIO;
|
|
38
|
+
} else {
|
|
39
|
+
c.font = font;
|
|
40
|
+
width = c.measureText(text).width;
|
|
41
|
+
}
|
|
42
|
+
if (cache.size >= CACHE_LIMIT) cache.clear();
|
|
43
|
+
cache.set(key, width);
|
|
44
|
+
return width;
|
|
45
|
+
}
|
|
46
|
+
function maxTextWidth(texts, fontPx, fontFamily) {
|
|
47
|
+
let max = 0;
|
|
48
|
+
for (const text of texts) {
|
|
49
|
+
const width = measureText(text, fontPx, fontFamily);
|
|
50
|
+
if (width > max) max = width;
|
|
51
|
+
}
|
|
52
|
+
return max;
|
|
53
|
+
}
|
|
54
|
+
function resetTextMetrics() {
|
|
55
|
+
cache.clear();
|
|
56
|
+
ctx = void 0;
|
|
57
|
+
monoFamily = void 0;
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
maxTextWidth,
|
|
61
|
+
measureText,
|
|
62
|
+
monoFontFamily,
|
|
63
|
+
resetTextMetrics
|
|
64
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measure-text.test.d.ts","sourceRoot":"","sources":["../../../src/charts/utils/measure-text.test.ts"],"names":[],"mappings":""}
|