basalt-ui 1.13.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 +15 -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/shell/app-sidebar.d.ts +19 -1
- package/dist/shell/app-sidebar.d.ts.map +1 -1
- package/dist/shell/app-sidebar.js +30 -26
- package/dist/shell/app-sidebar.module.css +11 -0
- package/dist/shell/app-sidebar.module.css.d.ts +1 -0
- package/dist/shell/app-sidebar.test.d.ts +2 -0
- package/dist/shell/app-sidebar.test.d.ts.map +1 -0
- package/dist/shell/index.d.ts +10 -1
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +2 -0
- package/dist/shell/index.test.d.ts +2 -0
- package/dist/shell/index.test.d.ts.map +1 -0
- 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/shell/app-sidebar.module.css +11 -0
- package/src/shell/app-sidebar.module.css.d.ts +1 -0
- package/src/shell/app-sidebar.test.tsx +98 -0
- package/src/shell/app-sidebar.tsx +22 -0
- package/src/shell/index.test.tsx +35 -0
- package/src/shell/index.tsx +11 -0
- 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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plot-rect margins derived from the labels that will actually be painted.
|
|
3
|
+
*
|
|
4
|
+
* `VX.margin` is a static token, so charts used to clip (or get hand-nudged) whenever a tick label
|
|
5
|
+
* outgrew it — the "I always push the charts around" failure mode. Here the token becomes a
|
|
6
|
+
* FLOOR: a measured side may only ever grow past it, never shrink below it, so no chart gets
|
|
7
|
+
* tighter than it was, and a wide label widens its own gutter automatically.
|
|
8
|
+
*
|
|
9
|
+
* See `docs/CHARTS-SPEC.md` §1.
|
|
10
|
+
*/
|
|
11
|
+
import type { ChartMargin } from '../../tokens/index.js';
|
|
12
|
+
export type AutoMarginInput = {
|
|
13
|
+
/** Left-axis tick labels, already formatted. */
|
|
14
|
+
left?: readonly string[];
|
|
15
|
+
/** Right-axis tick labels, already formatted. Presence (not a flag) is what widens `right`. */
|
|
16
|
+
right?: readonly string[];
|
|
17
|
+
/** X-axis tick labels, already formatted. */
|
|
18
|
+
bottom?: readonly string[];
|
|
19
|
+
/** Degrees of counter-clockwise rotation applied to the x tick labels. Default 0. */
|
|
20
|
+
rotate?: number;
|
|
21
|
+
/** Tick-label font size. Default `VX.axisFont`. */
|
|
22
|
+
fontPx?: number;
|
|
23
|
+
/** Explicit per-side overrides — applied LAST, so an escape hatch always wins. */
|
|
24
|
+
override?: Partial<ChartMargin>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the four margins from measured labels.
|
|
28
|
+
*
|
|
29
|
+
* Law, per side:
|
|
30
|
+
* - `left` — widest left label + gap, floored at `VX.margin.left`.
|
|
31
|
+
* - `right` — with a right axis: widest right label + gap. Without one: half the widest x label,
|
|
32
|
+
* so the LAST x tick (centered on the plot's right edge) cannot clip. Both floored at
|
|
33
|
+
* `VX.margin.right`.
|
|
34
|
+
* - `bottom` — one label line (or the rotated bounding height) + gap, floored at
|
|
35
|
+
* `VX.margin.bottom`.
|
|
36
|
+
* - `top` — `VX.margin.top`. Nothing measures into it; a chart that draws into the top gutter
|
|
37
|
+
* passes an override.
|
|
38
|
+
*/
|
|
39
|
+
export declare function autoMargin(input?: AutoMarginInput): ChartMargin;
|
|
40
|
+
/**
|
|
41
|
+
* The tick labels a linear axis WILL paint, derived before the plot rect exists.
|
|
42
|
+
*
|
|
43
|
+
* This is what breaks the margin/scale circularity: a linear scale's tick VALUES depend only on
|
|
44
|
+
* its domain, so a throwaway-range probe yields the exact label strings {@link autoMargin} needs
|
|
45
|
+
* to measure — and returning the resolved `format` alongside them guarantees the measured string
|
|
46
|
+
* and the painted string are the same one. Shared by `CartesianChart` and by hand-composed kinds
|
|
47
|
+
* (`DualPanel`) so the two can't drift.
|
|
48
|
+
*/
|
|
49
|
+
export declare function probeAxisLabels(opts: {
|
|
50
|
+
domain: [number, number];
|
|
51
|
+
ticks: number;
|
|
52
|
+
/** Overrides d3's own tick format. */
|
|
53
|
+
format?: (v: number) => string;
|
|
54
|
+
/** Must match the real scale's `nice` — niceing changes which ticks exist. */
|
|
55
|
+
nice?: boolean;
|
|
56
|
+
}): {
|
|
57
|
+
labels: string[];
|
|
58
|
+
format: (v: number) => string;
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=auto-margin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-margin.d.ts","sourceRoot":"","sources":["../../../src/charts/layout/auto-margin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAa/C,MAAM,MAAM,eAAe,GAAG;IAC5B,gDAAgD;IAChD,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxB,+FAA+F;IAC/F,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1B,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kFAAkF;IAClF,QAAQ,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;CAChC,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,KAAK,GAAE,eAAoB,GAAG,WAAW,CA6BnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE;IACpC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,sCAAsC;IACtC,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IAC9B,8EAA8E;IAC9E,IAAI,CAAC,EAAE,OAAO,CAAA;CACf,GAAG;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;CAAE,CAQtD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { scaleLinear } from "@visx/scale";
|
|
2
|
+
import { VX } from "../../tokens/index.js";
|
|
3
|
+
import { maxTextWidth } from "../utils/measure-text.js";
|
|
4
|
+
const AXIS_TICK_GAP = 12;
|
|
5
|
+
const lineHeight = (fontPx) => Math.ceil(fontPx * 1.35);
|
|
6
|
+
function autoMargin(input = {}) {
|
|
7
|
+
const fontPx = input.fontPx ?? VX.axisFont;
|
|
8
|
+
const { left = [], right = [], bottom = [], rotate = 0, override } = input;
|
|
9
|
+
const leftWidth = maxTextWidth(left, fontPx);
|
|
10
|
+
const rightWidth = maxTextWidth(right, fontPx);
|
|
11
|
+
const bottomWidth = maxTextWidth(bottom, fontPx);
|
|
12
|
+
const radians = rotate * Math.PI / 180;
|
|
13
|
+
const bottomExtent = rotate === 0 ? lineHeight(fontPx) : Math.ceil(
|
|
14
|
+
Math.abs(bottomWidth * Math.sin(radians)) + Math.abs(lineHeight(fontPx) * Math.cos(radians))
|
|
15
|
+
);
|
|
16
|
+
return {
|
|
17
|
+
top: override?.top ?? VX.margin.top,
|
|
18
|
+
right: override?.right ?? Math.ceil(
|
|
19
|
+
Math.max(VX.margin.right, right.length > 0 ? rightWidth + AXIS_TICK_GAP : bottomWidth / 2)
|
|
20
|
+
),
|
|
21
|
+
bottom: override?.bottom ?? Math.ceil(Math.max(VX.margin.bottom, bottomExtent + AXIS_TICK_GAP)),
|
|
22
|
+
left: override?.left ?? Math.ceil(Math.max(VX.margin.left, left.length > 0 ? leftWidth + AXIS_TICK_GAP : 0))
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function probeAxisLabels(opts) {
|
|
26
|
+
const probe = scaleLinear({
|
|
27
|
+
domain: opts.domain,
|
|
28
|
+
range: [1, 0],
|
|
29
|
+
...opts.nice === true && { nice: true }
|
|
30
|
+
});
|
|
31
|
+
const format = opts.format ?? probe.tickFormat(opts.ticks);
|
|
32
|
+
return { labels: probe.ticks(opts.ticks).map(format), format };
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
autoMargin,
|
|
36
|
+
probeAxisLabels
|
|
37
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-margin.test.d.ts","sourceRoot":"","sources":["../../../src/charts/layout/auto-margin.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { scaleLinear, scalePoint } from '@visx/scale';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
import type { ChartMargin } from '../../tokens/index.js';
|
|
4
|
+
import type { ChartLegendConfig, ChartSeries } from '../series.js';
|
|
5
|
+
import type { XZoneSpec } from './XZoneRects.js';
|
|
6
|
+
import type { ZoneSpec } from './ZoneRects.js';
|
|
7
|
+
type LinearScale = ReturnType<typeof scaleLinear<number>>;
|
|
8
|
+
type PointScale = ReturnType<typeof scalePoint<string>>;
|
|
9
|
+
/** One y-axis, fully described. Collapses the removed `yDomain` / `yAutoMaxFloor` / `yAutoMinCeil` /
|
|
10
|
+
* `yAutoPad` / `numTicksY` / `formatYTick` prop soup into a single object per axis. */
|
|
11
|
+
export type AxisConfig<T> = {
|
|
12
|
+
/**
|
|
13
|
+
* `'auto'` (default) derives the domain from the VISIBLE series; a tuple fixes it; a function
|
|
14
|
+
* computes it from the data — the seam a stacked chart uses to sum its bands. The function also
|
|
15
|
+
* receives the visible series, so a stacked domain SHRINKS when the legend toggles a band off
|
|
16
|
+
* (summing `props.series` there would leave a permanent gap above the stack).
|
|
17
|
+
*/
|
|
18
|
+
domain?: [number, number] | 'auto' | ((data: readonly T[], visible: readonly ChartSeries<T>[]) => [number, number]);
|
|
19
|
+
/** When 'auto': the upper bound is at least this. */
|
|
20
|
+
autoMaxFloor?: number;
|
|
21
|
+
/** When 'auto': the lower bound is at most this. Default 0 (forced zero baseline). `Infinity`
|
|
22
|
+
* pads down from the raw data minimum instead. */
|
|
23
|
+
autoMinCeil?: number;
|
|
24
|
+
/** When 'auto': padding multiplier applied away from the data. Default 1.1. */
|
|
25
|
+
autoPad?: number;
|
|
26
|
+
ticks?: number;
|
|
27
|
+
/** Tick + tooltip formatter for this axis. Defaults to d3's own tick format. */
|
|
28
|
+
format?: (v: number) => string;
|
|
29
|
+
/** Horizontal grid rules. Default: on for the left axis, off for the right. */
|
|
30
|
+
grid?: boolean;
|
|
31
|
+
};
|
|
32
|
+
export type CartesianTooltipConfig<T> = {
|
|
33
|
+
/**
|
|
34
|
+
* Default true — the tooltip tracks the pointer. Set false to ANCHOR it to the crosshair at the
|
|
35
|
+
* plot's top edge instead, which reads better across a column of charts sharing one cursor
|
|
36
|
+
* (every tooltip then lines up on the same x). Anchoring costs one `getBoundingClientRect` per
|
|
37
|
+
* hovered frame, which is why following stays the default.
|
|
38
|
+
*/
|
|
39
|
+
follow?: boolean;
|
|
40
|
+
/** Right-aligned badge in the tooltip header (e.g. a status label). */
|
|
41
|
+
label?: (d: T) => {
|
|
42
|
+
text: string;
|
|
43
|
+
color: string;
|
|
44
|
+
} | null;
|
|
45
|
+
/** Rows rendered BEFORE the derived per-series rows (a total, a context line). */
|
|
46
|
+
prependRows?: (d: T) => ReactNode;
|
|
47
|
+
/** Rows appended after the derived per-series rows. */
|
|
48
|
+
extraRows?: (d: T) => ReactNode;
|
|
49
|
+
};
|
|
50
|
+
/** Everything a mark renderer needs. Handed to `CartesianChart`'s child on every render. */
|
|
51
|
+
export type PlotContext<T> = {
|
|
52
|
+
data: readonly T[];
|
|
53
|
+
/** Series minus the ones the legend has toggled off — draw THESE, not `props.series`. */
|
|
54
|
+
visible: ChartSeries<T>[];
|
|
55
|
+
hidden: ReadonlySet<string>;
|
|
56
|
+
xScale: PointScale;
|
|
57
|
+
yScale: LinearScale;
|
|
58
|
+
/** Present only when a `y2` axis is configured. */
|
|
59
|
+
y2Scale: LinearScale | null;
|
|
60
|
+
xMax: number;
|
|
61
|
+
yMax: number;
|
|
62
|
+
margin: ChartMargin;
|
|
63
|
+
/** The point the shared cursor currently rests on, or null. */
|
|
64
|
+
cursorPoint: T | null;
|
|
65
|
+
/** Series key the legend is hovering, for mark dimming. */
|
|
66
|
+
highlighted: string | null;
|
|
67
|
+
};
|
|
68
|
+
export type CartesianChartProps<T> = {
|
|
69
|
+
data: T[];
|
|
70
|
+
/** Stable id — identifies this chart as the cursor's source. */
|
|
71
|
+
chartId: string;
|
|
72
|
+
/** Extracts the x-domain key (a date or category string) from a point. */
|
|
73
|
+
getX: (d: T) => string;
|
|
74
|
+
/** The single source of truth for marks, legend entries, and tooltip rows. */
|
|
75
|
+
series: ChartSeries<T>[];
|
|
76
|
+
y?: AxisConfig<T>;
|
|
77
|
+
/** Passing `y2` is what makes the chart dual-axis: it draws the right axis and widens the
|
|
78
|
+
* right margin by measurement. Series opt in with `axis: 'right'`. */
|
|
79
|
+
y2?: AxisConfig<T>;
|
|
80
|
+
/** Exact number of x ticks. Default: as many as fit (`smartTicks`). */
|
|
81
|
+
xTicks?: number;
|
|
82
|
+
/** X tick label formatter. Default `fmtAxisDate` (DD.MM). */
|
|
83
|
+
formatX?: (key: string) => string;
|
|
84
|
+
/** Value-range bands on the left scale, drawn behind the marks. */
|
|
85
|
+
zones?: ZoneSpec[];
|
|
86
|
+
/** X-range bands (time windows), drawn behind the marks. */
|
|
87
|
+
xZones?: XZoneSpec[];
|
|
88
|
+
refLines?: {
|
|
89
|
+
value: number;
|
|
90
|
+
color: string;
|
|
91
|
+
dashed?: boolean;
|
|
92
|
+
axis?: 'left' | 'right';
|
|
93
|
+
}[];
|
|
94
|
+
height?: number;
|
|
95
|
+
aspectRatio?: number;
|
|
96
|
+
fill?: boolean;
|
|
97
|
+
legend?: ChartLegendConfig | false;
|
|
98
|
+
/** `false` disables the tooltip entirely (and with it the crosshair dots). */
|
|
99
|
+
tooltip?: CartesianTooltipConfig<T> | false;
|
|
100
|
+
/** Per-side overrides of the measured margins — the escape hatch, applied last. */
|
|
101
|
+
margin?: Partial<ChartMargin>;
|
|
102
|
+
/**
|
|
103
|
+
* Where this series' crosshair dot sits, as a value in the axis domain. Defaults to
|
|
104
|
+
* `series.getValue`, which is correct for every unstacked chart. A STACKED chart must override
|
|
105
|
+
* it with the cumulative band top — otherwise its dots sit at each band's raw value, off the
|
|
106
|
+
* line the reader is actually looking at.
|
|
107
|
+
*/
|
|
108
|
+
cursorValue?: (point: T, series: ChartSeries<T>, visible: readonly ChartSeries<T>[]) => number | null;
|
|
109
|
+
ariaLabel?: string;
|
|
110
|
+
isPending?: boolean;
|
|
111
|
+
/** Draw ONLY the marks. Everything around them is the primitive's job. */
|
|
112
|
+
children: (ctx: PlotContext<T>) => ReactNode;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Resolve one axis' `[min, max]` from its {@link AxisConfig} and the VISIBLE series. Exported for
|
|
116
|
+
* testing and for a hand-composed kind that needs the same auto-domain law `CartesianChart`
|
|
117
|
+
* applies (rather than reinventing the padding rules per chart).
|
|
118
|
+
*/
|
|
119
|
+
export declare function resolveAxisDomain<T>(cfg: AxisConfig<T> | undefined, data: readonly T[], series: readonly ChartSeries<T>[],
|
|
120
|
+
/**
|
|
121
|
+
* Values that must stay inside the plot even though no series reports them — the bounds of the
|
|
122
|
+
* zones and reference lines pinned to THIS axis. Without them a target zone or a stretch-goal
|
|
123
|
+
* ref line sitting past the data silently clips, which is precisely the case those overlays
|
|
124
|
+
* exist for. Infinite bounds ("top/bottom of axis") are ignored: they clamp to the domain by
|
|
125
|
+
* definition and would otherwise blow it up.
|
|
126
|
+
*/
|
|
127
|
+
extraBounds?: readonly number[]): [number, number];
|
|
128
|
+
/**
|
|
129
|
+
* The cartesian chart primitive — the rung that was missing between `ChartFrame` and raw visx
|
|
130
|
+
* (`docs/CHARTS-SPEC.md` §2).
|
|
131
|
+
*
|
|
132
|
+
* It owns, once, everything every chart repeated: measured margins, both y scales and their
|
|
133
|
+
* domains, the x scale and its tick thinning, grid, zones, axes, the shared cursor, the crosshair
|
|
134
|
+
* and its per-series dots, the hover/keyboard overlay, and the derived tooltip. The child draws
|
|
135
|
+
* marks and nothing else, so two charts cannot drift apart in any of those layers — that identity
|
|
136
|
+
* IS the consistency guarantee.
|
|
137
|
+
*
|
|
138
|
+
* Margins are MEASURED from the labels that will actually be painted, so a wide tick label widens
|
|
139
|
+
* its own gutter instead of clipping or needing a hand-tuned `margin` prop.
|
|
140
|
+
*/
|
|
141
|
+
export declare function CartesianChart<T>({ data, chartId, getX, series, y, y2, xTicks, formatX, zones, xZones, refLines, height, aspectRatio, fill, legend, tooltip, margin: marginOverride, cursorValue, ariaLabel, isPending, children, }: CartesianChartProps<T>): ReactNode;
|
|
142
|
+
export {};
|
|
143
|
+
//# sourceMappingURL=CartesianChart.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CartesianChart.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/CartesianChart.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { GridRows } from "@visx/grid";
|
|
3
|
+
import { Group } from "@visx/group";
|
|
4
|
+
import { scaleLinear, scalePoint } from "@visx/scale";
|
|
5
|
+
import { useMemo, useRef, useState } from "react";
|
|
6
|
+
import { VX } from "../../tokens/index.js";
|
|
7
|
+
import { useChartCursor } from "../hooks/useChartCursor.js";
|
|
8
|
+
import { autoMargin, probeAxisLabels } from "../layout/auto-margin.js";
|
|
9
|
+
import { deriveTooltipRows } from "../series.js";
|
|
10
|
+
import { padAutoLower } from "../utils/domain.js";
|
|
11
|
+
import { fmtAxisDate } from "../utils/format.js";
|
|
12
|
+
import { smartTicks, smartTicksEvery } from "../utils/ticks.js";
|
|
13
|
+
import { AxisBottomDate, AxisLeftNumeric, AxisRightNumeric } from "./Axes.js";
|
|
14
|
+
import { ChartFrame, resolveLegend } from "./ChartFrame.js";
|
|
15
|
+
import { ChartTooltipFloat, TooltipBody, TooltipHeader, TooltipRow } from "./ChartTooltip.js";
|
|
16
|
+
import { Crosshair, SeriesDot } from "./Crosshair.js";
|
|
17
|
+
import { HoverOverlay } from "./HoverOverlay.js";
|
|
18
|
+
import { XZoneRects } from "./XZoneRects.js";
|
|
19
|
+
import { ZoneRects } from "./ZoneRects.js";
|
|
20
|
+
const DEFAULT_TICKS = 5;
|
|
21
|
+
const DEFAULT_AUTO_PAD = 1.1;
|
|
22
|
+
function resolveAxisDomain(cfg, data, series, extraBounds = []) {
|
|
23
|
+
const domain = cfg?.domain ?? "auto";
|
|
24
|
+
if (Array.isArray(domain)) return domain;
|
|
25
|
+
if (typeof domain === "function") return domain(data, series);
|
|
26
|
+
let min = Infinity;
|
|
27
|
+
let max = -Infinity;
|
|
28
|
+
for (const d of data) {
|
|
29
|
+
for (const s of series) {
|
|
30
|
+
const v = s.getValue(d);
|
|
31
|
+
if (v === null) continue;
|
|
32
|
+
if (v < min) min = v;
|
|
33
|
+
if (v > max) max = v;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const v of extraBounds) {
|
|
37
|
+
if (!Number.isFinite(v)) continue;
|
|
38
|
+
if (v < min) min = v;
|
|
39
|
+
if (v > max) max = v;
|
|
40
|
+
}
|
|
41
|
+
if (min === Infinity) return [0, cfg?.autoMaxFloor ?? 1];
|
|
42
|
+
const pad = cfg?.autoPad ?? DEFAULT_AUTO_PAD;
|
|
43
|
+
const ceil = cfg?.autoMinCeil ?? 0;
|
|
44
|
+
const paddedMax = max > 0 ? max * pad : max < 0 ? max / pad : 0;
|
|
45
|
+
const upper = Math.max(paddedMax, cfg?.autoMaxFloor ?? -Infinity);
|
|
46
|
+
const lower = padAutoLower(Math.min(min, ceil), pad);
|
|
47
|
+
if (lower === upper) return [Math.min(lower, 0), upper === 0 ? 1 : upper * pad];
|
|
48
|
+
return [lower, upper];
|
|
49
|
+
}
|
|
50
|
+
function CartesianChart({
|
|
51
|
+
data,
|
|
52
|
+
chartId,
|
|
53
|
+
getX,
|
|
54
|
+
series,
|
|
55
|
+
y,
|
|
56
|
+
y2,
|
|
57
|
+
xTicks,
|
|
58
|
+
formatX = fmtAxisDate,
|
|
59
|
+
zones,
|
|
60
|
+
xZones,
|
|
61
|
+
refLines,
|
|
62
|
+
height,
|
|
63
|
+
aspectRatio,
|
|
64
|
+
fill,
|
|
65
|
+
legend,
|
|
66
|
+
tooltip,
|
|
67
|
+
margin: marginOverride,
|
|
68
|
+
cursorValue,
|
|
69
|
+
ariaLabel,
|
|
70
|
+
isPending,
|
|
71
|
+
children
|
|
72
|
+
}) {
|
|
73
|
+
const [highlighted, setHighlighted] = useState(null);
|
|
74
|
+
return /* @__PURE__ */ jsx(
|
|
75
|
+
ChartFrame,
|
|
76
|
+
{
|
|
77
|
+
series,
|
|
78
|
+
chartId,
|
|
79
|
+
...height !== void 0 && { height },
|
|
80
|
+
...aspectRatio !== void 0 && { aspectRatio },
|
|
81
|
+
...fill !== void 0 && { fill },
|
|
82
|
+
...ariaLabel !== void 0 && { ariaLabel },
|
|
83
|
+
...isPending !== void 0 && { isPending },
|
|
84
|
+
legend: resolveLegend(legend, { highlighted, onHighlight: setHighlighted }),
|
|
85
|
+
children: (plot) => /* @__PURE__ */ jsx(
|
|
86
|
+
CartesianPlot,
|
|
87
|
+
{
|
|
88
|
+
data,
|
|
89
|
+
chartId,
|
|
90
|
+
getX,
|
|
91
|
+
series,
|
|
92
|
+
...y !== void 0 && { y },
|
|
93
|
+
...y2 !== void 0 && { y2 },
|
|
94
|
+
...xTicks !== void 0 && { xTicks },
|
|
95
|
+
formatX,
|
|
96
|
+
...zones !== void 0 && { zones },
|
|
97
|
+
...xZones !== void 0 && { xZones },
|
|
98
|
+
...refLines !== void 0 && { refLines },
|
|
99
|
+
...tooltip !== void 0 && { tooltip },
|
|
100
|
+
...marginOverride !== void 0 && { marginOverride },
|
|
101
|
+
...cursorValue !== void 0 && { cursorValue },
|
|
102
|
+
...ariaLabel !== void 0 && { ariaLabel },
|
|
103
|
+
plot,
|
|
104
|
+
highlighted,
|
|
105
|
+
children
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
function CartesianPlot({
|
|
112
|
+
data,
|
|
113
|
+
chartId,
|
|
114
|
+
getX,
|
|
115
|
+
series,
|
|
116
|
+
y,
|
|
117
|
+
y2,
|
|
118
|
+
xTicks,
|
|
119
|
+
formatX = fmtAxisDate,
|
|
120
|
+
zones,
|
|
121
|
+
xZones,
|
|
122
|
+
refLines,
|
|
123
|
+
tooltip,
|
|
124
|
+
marginOverride,
|
|
125
|
+
cursorValue,
|
|
126
|
+
ariaLabel,
|
|
127
|
+
plot,
|
|
128
|
+
highlighted,
|
|
129
|
+
children
|
|
130
|
+
}) {
|
|
131
|
+
const { hidden } = plot;
|
|
132
|
+
const visible = useMemo(() => series.filter((s) => !hidden.has(s.key)), [series, hidden]);
|
|
133
|
+
const leftSeries = useMemo(() => visible.filter((s) => s.axis !== "right"), [visible]);
|
|
134
|
+
const rightSeries = useMemo(() => visible.filter((s) => s.axis === "right"), [visible]);
|
|
135
|
+
const keys = useMemo(() => data.map(getX), [data, getX]);
|
|
136
|
+
const [leftBounds, rightBounds] = useMemo(() => {
|
|
137
|
+
const left = [];
|
|
138
|
+
const right = [];
|
|
139
|
+
for (const z of zones ?? []) (z.axisSide === "right" ? right : left).push(z.from, z.to);
|
|
140
|
+
for (const l of refLines ?? []) (l.axis === "right" ? right : left).push(l.value);
|
|
141
|
+
return [left, right];
|
|
142
|
+
}, [zones, refLines]);
|
|
143
|
+
const leftDomain = useMemo(
|
|
144
|
+
() => resolveAxisDomain(y, data, leftSeries, leftBounds),
|
|
145
|
+
[y, data, leftSeries, leftBounds]
|
|
146
|
+
);
|
|
147
|
+
const rightDomain = useMemo(
|
|
148
|
+
() => y2 === void 0 ? null : resolveAxisDomain(y2, data, rightSeries, rightBounds),
|
|
149
|
+
[y2, data, rightSeries, rightBounds]
|
|
150
|
+
);
|
|
151
|
+
const leftTicks = y?.ticks ?? DEFAULT_TICKS;
|
|
152
|
+
const rightTicks = y2?.ticks ?? DEFAULT_TICKS;
|
|
153
|
+
const { labels: leftLabels, format: leftFormat } = useMemo(
|
|
154
|
+
() => probeAxisLabels({
|
|
155
|
+
domain: leftDomain,
|
|
156
|
+
ticks: leftTicks,
|
|
157
|
+
...y?.format !== void 0 && { format: y.format }
|
|
158
|
+
}),
|
|
159
|
+
[leftDomain, leftTicks, y?.format]
|
|
160
|
+
);
|
|
161
|
+
const { labels: rightLabels, format: rightFormat } = useMemo(
|
|
162
|
+
() => rightDomain === null ? { labels: [], format: (v) => String(v) } : probeAxisLabels({
|
|
163
|
+
domain: rightDomain,
|
|
164
|
+
ticks: rightTicks,
|
|
165
|
+
...y2?.format !== void 0 && { format: y2.format }
|
|
166
|
+
}),
|
|
167
|
+
[rightDomain, rightTicks, y2?.format]
|
|
168
|
+
);
|
|
169
|
+
const xLabels = useMemo(() => keys.map(formatX), [keys, formatX]);
|
|
170
|
+
const margin = useMemo(
|
|
171
|
+
() => autoMargin({
|
|
172
|
+
left: leftLabels,
|
|
173
|
+
right: rightLabels,
|
|
174
|
+
bottom: xLabels,
|
|
175
|
+
...marginOverride !== void 0 && { override: marginOverride }
|
|
176
|
+
}),
|
|
177
|
+
[leftLabels, rightLabels, xLabels, marginOverride]
|
|
178
|
+
);
|
|
179
|
+
const xMax = Math.max(plot.width - margin.left - margin.right, 0);
|
|
180
|
+
const yMax = Math.max(plot.height - margin.top - margin.bottom, 0);
|
|
181
|
+
const xScale = useMemo(
|
|
182
|
+
() => scalePoint({ domain: keys, range: [0, xMax], padding: 0.5 }),
|
|
183
|
+
[keys, xMax]
|
|
184
|
+
);
|
|
185
|
+
const yScale = useMemo(
|
|
186
|
+
() => scaleLinear({ domain: leftDomain, range: [yMax, 0] }),
|
|
187
|
+
[leftDomain, yMax]
|
|
188
|
+
);
|
|
189
|
+
const y2Scale = useMemo(
|
|
190
|
+
() => rightDomain === null ? null : scaleLinear({ domain: rightDomain, range: [yMax, 0] }),
|
|
191
|
+
[rightDomain, yMax]
|
|
192
|
+
);
|
|
193
|
+
const tickValues = useMemo(
|
|
194
|
+
() => xTicks === void 0 ? smartTicks(keys, xMax) : smartTicksEvery(keys, xTicks),
|
|
195
|
+
[keys, xMax, xTicks]
|
|
196
|
+
);
|
|
197
|
+
const cursor = useChartCursor({
|
|
198
|
+
data,
|
|
199
|
+
chartId,
|
|
200
|
+
getKey: getX,
|
|
201
|
+
xScale,
|
|
202
|
+
marginLeft: margin.left
|
|
203
|
+
});
|
|
204
|
+
const svgRef = useRef(null);
|
|
205
|
+
const tooltipEnabled = tooltip !== false;
|
|
206
|
+
const tooltipCfg = tooltip === false ? void 0 : tooltip;
|
|
207
|
+
const point = cursor.point;
|
|
208
|
+
const cursorX = point === null ? null : xScale(getX(point)) ?? null;
|
|
209
|
+
const badge = tooltipCfg?.label === void 0 || point === null ? null : tooltipCfg.label(point);
|
|
210
|
+
const tooltipSeries = useMemo(
|
|
211
|
+
() => visible.map(
|
|
212
|
+
(s) => s.formatValue === void 0 ? { ...s, formatValue: s.axis === "right" ? rightFormat : leftFormat } : s
|
|
213
|
+
),
|
|
214
|
+
[visible, leftFormat, rightFormat]
|
|
215
|
+
);
|
|
216
|
+
const rows = tooltipEnabled && point !== null ? deriveTooltipRows(tooltipSeries, point, leftFormat) : [];
|
|
217
|
+
const anchorX = tooltipCfg?.follow === false && point !== null && cursorX !== null ? cursorX : null;
|
|
218
|
+
const svgRect = anchorX === null ? void 0 : svgRef.current?.getBoundingClientRect();
|
|
219
|
+
const tooltipAnchor = svgRect === void 0 || anchorX === null ? cursor.anchor : { x: svgRect.left + margin.left + anchorX, y: svgRect.top + margin.top };
|
|
220
|
+
const ctx = {
|
|
221
|
+
data,
|
|
222
|
+
visible,
|
|
223
|
+
hidden,
|
|
224
|
+
xScale,
|
|
225
|
+
yScale,
|
|
226
|
+
y2Scale,
|
|
227
|
+
xMax,
|
|
228
|
+
yMax,
|
|
229
|
+
margin,
|
|
230
|
+
cursorPoint: point,
|
|
231
|
+
highlighted
|
|
232
|
+
};
|
|
233
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
234
|
+
/* @__PURE__ */ jsx("svg", { ref: svgRef, width: plot.width, height: plot.height, children: /* @__PURE__ */ jsxs(Group, { left: margin.left, top: margin.top, children: [
|
|
235
|
+
(y?.grid ?? true) && /* @__PURE__ */ jsx(GridRows, { scale: yScale, width: xMax, stroke: VX.grid, numTicks: leftTicks }),
|
|
236
|
+
xZones !== void 0 && /* @__PURE__ */ jsx(XZoneRects, { zones: xZones, height: yMax, xScale }),
|
|
237
|
+
zones !== void 0 && /* @__PURE__ */ jsx(ZoneRects, { zones, width: xMax, leftScale: yScale, rightScale: y2Scale }),
|
|
238
|
+
children(ctx),
|
|
239
|
+
refLines?.map((line) => {
|
|
240
|
+
const scale = line.axis === "right" && y2Scale !== null ? y2Scale : yScale;
|
|
241
|
+
return /* @__PURE__ */ jsx(
|
|
242
|
+
"line",
|
|
243
|
+
{
|
|
244
|
+
x1: 0,
|
|
245
|
+
x2: xMax,
|
|
246
|
+
y1: scale(line.value),
|
|
247
|
+
y2: scale(line.value),
|
|
248
|
+
stroke: line.color,
|
|
249
|
+
strokeWidth: 1,
|
|
250
|
+
strokeDasharray: line.dashed === true ? VX.dashArray : void 0
|
|
251
|
+
},
|
|
252
|
+
`${line.axis ?? "left"}-${line.value}`
|
|
253
|
+
);
|
|
254
|
+
}),
|
|
255
|
+
tooltipEnabled && point !== null && cursorX !== null && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
256
|
+
/* @__PURE__ */ jsx(Crosshair, { x: cursorX, top: 0, bottom: yMax }),
|
|
257
|
+
visible.map((s) => {
|
|
258
|
+
if (s.mark === "bar") return null;
|
|
259
|
+
const value = cursorValue === void 0 ? s.getValue(point) : cursorValue(point, s, visible);
|
|
260
|
+
if (value === null) return null;
|
|
261
|
+
const scale = s.axis === "right" && y2Scale !== null ? y2Scale : yScale;
|
|
262
|
+
const marker = s.getMarker?.(point);
|
|
263
|
+
return /* @__PURE__ */ jsx(
|
|
264
|
+
SeriesDot,
|
|
265
|
+
{
|
|
266
|
+
cx: cursorX,
|
|
267
|
+
cy: scale(value),
|
|
268
|
+
color: marker?.color ?? s.color,
|
|
269
|
+
...marker?.r !== void 0 && { r: marker.r }
|
|
270
|
+
},
|
|
271
|
+
s.key
|
|
272
|
+
);
|
|
273
|
+
})
|
|
274
|
+
] }),
|
|
275
|
+
/* @__PURE__ */ jsx(
|
|
276
|
+
AxisLeftNumeric,
|
|
277
|
+
{
|
|
278
|
+
scale: yScale,
|
|
279
|
+
numTicks: leftTicks,
|
|
280
|
+
tickFormat: (v) => leftFormat(Number(v))
|
|
281
|
+
}
|
|
282
|
+
),
|
|
283
|
+
y2Scale !== null && /* @__PURE__ */ jsx(
|
|
284
|
+
AxisRightNumeric,
|
|
285
|
+
{
|
|
286
|
+
scale: y2Scale,
|
|
287
|
+
left: xMax,
|
|
288
|
+
numTicks: rightTicks,
|
|
289
|
+
tickFormat: (v) => rightFormat(Number(v))
|
|
290
|
+
}
|
|
291
|
+
),
|
|
292
|
+
/* @__PURE__ */ jsx(
|
|
293
|
+
AxisBottomDate,
|
|
294
|
+
{
|
|
295
|
+
top: yMax,
|
|
296
|
+
scale: xScale,
|
|
297
|
+
tickValues,
|
|
298
|
+
tickFormat: (v) => formatX(String(v))
|
|
299
|
+
}
|
|
300
|
+
),
|
|
301
|
+
/* @__PURE__ */ jsx(
|
|
302
|
+
HoverOverlay,
|
|
303
|
+
{
|
|
304
|
+
width: xMax,
|
|
305
|
+
height: yMax,
|
|
306
|
+
onMove: cursor.onPointerMove,
|
|
307
|
+
onLeave: cursor.onPointerLeave,
|
|
308
|
+
onKeyDown: cursor.onKeyDown,
|
|
309
|
+
onBlur: cursor.onBlur,
|
|
310
|
+
valueMax: Math.max(data.length - 1, 0),
|
|
311
|
+
...point !== null && {
|
|
312
|
+
valueNow: data.indexOf(point),
|
|
313
|
+
valueText: formatX(getX(point))
|
|
314
|
+
},
|
|
315
|
+
...ariaLabel !== void 0 && { ariaLabel }
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
] }) }),
|
|
319
|
+
tooltipEnabled && cursor.isSource && point !== null && /* @__PURE__ */ jsxs(ChartTooltipFloat, { anchor: tooltipAnchor, children: [
|
|
320
|
+
/* @__PURE__ */ jsx(
|
|
321
|
+
TooltipHeader,
|
|
322
|
+
{
|
|
323
|
+
date: getX(point),
|
|
324
|
+
...badge !== null && { label: badge.text, labelColor: badge.color }
|
|
325
|
+
}
|
|
326
|
+
),
|
|
327
|
+
/* @__PURE__ */ jsxs(TooltipBody, { children: [
|
|
328
|
+
tooltipCfg?.prependRows?.(point),
|
|
329
|
+
rows.map((row) => /* @__PURE__ */ jsx(
|
|
330
|
+
TooltipRow,
|
|
331
|
+
{
|
|
332
|
+
color: row.color,
|
|
333
|
+
label: row.label,
|
|
334
|
+
value: row.value,
|
|
335
|
+
shape: row.shape,
|
|
336
|
+
dashed: row.dashed,
|
|
337
|
+
...row.strokeWidth !== void 0 && { strokeWidth: row.strokeWidth }
|
|
338
|
+
},
|
|
339
|
+
row.key
|
|
340
|
+
)),
|
|
341
|
+
tooltipCfg?.extraRows?.(point)
|
|
342
|
+
] })
|
|
343
|
+
] })
|
|
344
|
+
] });
|
|
345
|
+
}
|
|
346
|
+
export {
|
|
347
|
+
CartesianChart,
|
|
348
|
+
resolveAxisDomain
|
|
349
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CartesianChart.test.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/CartesianChart.test.ts"],"names":[],"mappings":""}
|
|
@@ -12,6 +12,13 @@ export type ChartFrameLegend = {
|
|
|
12
12
|
/** Hover-dim wiring lifted from the kind — optional. */
|
|
13
13
|
highlighted?: string | null;
|
|
14
14
|
onHighlight?: (key: string | null) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Clicking a legend entry hides that series. Defaults to true whenever there is more than one
|
|
17
|
+
* entry to toggle between — hiding the only series a chart draws is never useful. The hidden
|
|
18
|
+
* key set reaches the marks through the `children` render prop, so a hidden series leaves the
|
|
19
|
+
* plot, the tooltip, and the auto domain together (`docs/CHARTS-SPEC.md` §5).
|
|
20
|
+
*/
|
|
21
|
+
toggle?: boolean;
|
|
15
22
|
};
|
|
16
23
|
export type ChartFrameProps = {
|
|
17
24
|
/** Series identity — drives the derived legend. Pass the SAME array the kind draws + tooltips from. */
|
|
@@ -41,11 +48,15 @@ export type ChartFrameProps = {
|
|
|
41
48
|
* kind composing `ChartFrame` should accept and forward this from its own props.
|
|
42
49
|
*/
|
|
43
50
|
ariaLabel?: string;
|
|
44
|
-
/** Draw the SVG marks given the plot rect that already excludes the legend band
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
/** Draw the SVG marks given the plot rect that already excludes the legend band, plus the set
|
|
52
|
+
* of series keys the legend has toggled off. */
|
|
53
|
+
children: (plot: PlotRect) => ReactNode;
|
|
54
|
+
};
|
|
55
|
+
/** What `ChartFrame` hands its child: the resolved plot rect plus legend-toggle state. */
|
|
56
|
+
export type PlotRect = {
|
|
57
|
+
width: number;
|
|
58
|
+
height: number;
|
|
59
|
+
hidden: ReadonlySet<string>;
|
|
49
60
|
};
|
|
50
61
|
/** A kind's own hover-dim wiring, merged into the resolved `ChartFrameLegend`. Kinds without
|
|
51
62
|
* per-series highlight state (single-series `ZonedLine`, `DualPanel`) omit this. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartFrame.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartFrame.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"ChartFrame.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartFrame.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAA;AAIrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAOhF;0EAC0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,wBAAwB;IACxB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,uGAAuG;IACvG,MAAM,EAAE,SAAS,WAAW,EAAE,CAAA;IAC9B,8FAA8F;IAC9F,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,uFAAuF;IACvF,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iGAAiG;IACjG,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,6FAA6F;IAC7F,MAAM,CAAC,EAAE,gBAAgB,GAAG,KAAK,CAAA;IACjC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;oDACgD;IAChD,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,SAAS,CAAA;CACxC,CAAA;AAED,0FAA0F;AAC1F,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAC5B,CAAA;AAcD;oFACoF;AACpF,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;CAC1C,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,iBAAiB,GAAG,KAAK,GAAG,SAAS,EAC7C,KAAK,CAAC,EAAE,qBAAqB,GAC5B,gBAAgB,GAAG,KAAK,CAS1B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,MAAM,EACN,WAAW,EACX,IAAY,EACZ,QAA4B,EAC5B,OAAO,EACP,MAAW,EACX,SAAS,EACT,SAAiB,EACjB,QAAQ,GACT,EAAE,eAAe,GAAG,SAAS,CAsE7B"}
|