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
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
type XScale = (x: string) => number | undefined;
|
|
2
|
-
/**
|
|
3
|
-
* Wires a chart into the shared-cursor HoverContext plus the local tooltip state.
|
|
4
|
-
*
|
|
5
|
-
* Returns everything a chart needs to:
|
|
6
|
-
* - snap mouse position to the nearest data point (by x-category)
|
|
7
|
-
* - broadcast that hover to other charts via HoverContext
|
|
8
|
-
* - read back hover from other charts (for ghost crosshair + dot)
|
|
9
|
-
* - drive its local tooltip
|
|
10
|
-
*
|
|
11
|
-
* Every non-sparkline chart kind MUST use this hook. Do not reimplement the
|
|
12
|
-
* closest-point loop inline — drift across 15+ charts is the exact problem
|
|
13
|
-
* this hook prevents.
|
|
14
|
-
*/
|
|
15
|
-
export declare function useHoverSync<T>({ data, chartId, getKey, xScale, marginLeft, resolveKey, }: {
|
|
16
|
-
data: T[];
|
|
17
|
-
chartId: string;
|
|
18
|
-
getKey: (d: T) => string;
|
|
19
|
-
xScale: XScale;
|
|
20
|
-
marginLeft: number;
|
|
21
|
-
/**
|
|
22
|
-
* Overrides how a SIBLING's broadcast key resolves to one of THIS chart's own points. Only
|
|
23
|
-
* affects reading another chart's hover — this chart's own hover (via `getKey`/`xScale`) is
|
|
24
|
-
* untouched.
|
|
25
|
-
*
|
|
26
|
-
* Cross-chart sync is exact string match by default (`pointByKey.get(ctx.key)`), so two charts
|
|
27
|
-
* share a cursor only if they emit identical key strings. A chart that downsamples/folds its
|
|
28
|
-
* domain to fit a narrow viewport (e.g. ~288 buckets folded into ~97 columns at 390px) no longer
|
|
29
|
-
* owns most of the keys its unfolded siblings broadcast — the shared crosshair then appears on
|
|
30
|
-
* roughly one hover in three, with no rule a reader can infer, which is worse than no shared
|
|
31
|
-
* cursor at all. Pass `resolveKey` to map a foreign key onto whichever of this chart's own points
|
|
32
|
-
* swallowed it (e.g. the folded bucket the key falls into).
|
|
33
|
-
*
|
|
34
|
-
* Omit it and behaviour is byte-identical to today.
|
|
35
|
-
*/
|
|
36
|
-
resolveKey?: (key: string) => T | null;
|
|
37
|
-
}): {
|
|
38
|
-
tip: import("./useChartTooltip.js").TooltipState<T>;
|
|
39
|
-
tooltipRef: import("react").RefObject<HTMLDivElement | null>;
|
|
40
|
-
syncedPoint: T | null;
|
|
41
|
-
isDirectHover: boolean;
|
|
42
|
-
handleMouse: (event: React.MouseEvent<SVGRectElement>) => void;
|
|
43
|
-
handleLeave: () => void;
|
|
44
|
-
};
|
|
45
|
-
export {};
|
|
46
|
-
//# sourceMappingURL=useHoverSync.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useHoverSync.d.ts","sourceRoot":"","sources":["../../../src/charts/hooks/useHoverSync.ts"],"names":[],"mappings":"AAKA,KAAK,MAAM,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;AAE/C;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,EAC9B,IAAI,EACJ,OAAO,EACP,MAAM,EACN,MAAM,EACN,UAAU,EACV,UAAU,GACX,EAAE;IACD,IAAI,EAAE,CAAC,EAAE,CAAA;IACT,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAA;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,GAAG,IAAI,CAAA;CACvC;;;;;yBA6CW,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC;;EAyD3C"}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { localPoint } from "@visx/event";
|
|
2
|
-
import { useCallback, useContext, useMemo, useRef } from "react";
|
|
3
|
-
import { DEFAULT_NO_OP_SET_HOVER, HoverContext } from "../hover-context.js";
|
|
4
|
-
import { useChartTooltip } from "./useChartTooltip.js";
|
|
5
|
-
function useHoverSync({
|
|
6
|
-
data,
|
|
7
|
-
chartId,
|
|
8
|
-
getKey,
|
|
9
|
-
xScale,
|
|
10
|
-
marginLeft,
|
|
11
|
-
resolveKey
|
|
12
|
-
}) {
|
|
13
|
-
const ctx = useContext(HoverContext);
|
|
14
|
-
const ctxRef = useRef(ctx);
|
|
15
|
-
ctxRef.current = ctx;
|
|
16
|
-
const warnedRef = useRef(false);
|
|
17
|
-
if (process.env["NODE_ENV"] !== "production" && ctx.setHover === DEFAULT_NO_OP_SET_HOVER && !warnedRef.current) {
|
|
18
|
-
warnedRef.current = true;
|
|
19
|
-
console.warn(
|
|
20
|
-
"[charts] useHoverSync used outside <HoverContext.Provider>. Cross-chart cursor sync will not work."
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
const { tip, show, hide, tooltipRef, lastDateRef } = useChartTooltip();
|
|
24
|
-
const getKeyRef = useRef(getKey);
|
|
25
|
-
getKeyRef.current = getKey;
|
|
26
|
-
const xScaleRef = useRef(xScale);
|
|
27
|
-
xScaleRef.current = xScale;
|
|
28
|
-
const pointByKey = useMemo(() => {
|
|
29
|
-
const m = /* @__PURE__ */ new Map();
|
|
30
|
-
for (const d of data) m.set(getKeyRef.current(d), d);
|
|
31
|
-
return m;
|
|
32
|
-
}, [data]);
|
|
33
|
-
const handleMouse = useCallback(
|
|
34
|
-
(event) => {
|
|
35
|
-
const point = localPoint(event);
|
|
36
|
-
if (!point || data.length === 0) return;
|
|
37
|
-
const px = point.x - marginLeft;
|
|
38
|
-
let closest = data[0];
|
|
39
|
-
let minDist = Infinity;
|
|
40
|
-
for (const d of data) {
|
|
41
|
-
const sx = xScaleRef.current(getKeyRef.current(d)) ?? 0;
|
|
42
|
-
const dist = Math.abs(sx - px);
|
|
43
|
-
if (dist < minDist) {
|
|
44
|
-
minDist = dist;
|
|
45
|
-
closest = d;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
show(closest, event);
|
|
49
|
-
const key = getKeyRef.current(closest);
|
|
50
|
-
if (lastDateRef.current !== key) {
|
|
51
|
-
lastDateRef.current = key;
|
|
52
|
-
ctxRef.current.setHover(key, chartId);
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
[data, chartId, marginLeft, show, lastDateRef]
|
|
56
|
-
);
|
|
57
|
-
const handleLeave = useCallback(() => {
|
|
58
|
-
hide();
|
|
59
|
-
if (ctxRef.current.source === chartId) ctxRef.current.setHover(null, null);
|
|
60
|
-
}, [hide, chartId]);
|
|
61
|
-
const syncedPoint = ctx.key ? resolveKey ? resolveKey(ctx.key) : pointByKey.get(ctx.key) ?? null : tip?.data ?? null;
|
|
62
|
-
const hasProvider = ctx.setHover !== DEFAULT_NO_OP_SET_HOVER;
|
|
63
|
-
const isDirectHover = hasProvider ? ctx.source === chartId : tip !== null;
|
|
64
|
-
return {
|
|
65
|
-
tip,
|
|
66
|
-
tooltipRef,
|
|
67
|
-
syncedPoint,
|
|
68
|
-
isDirectHover,
|
|
69
|
-
handleMouse,
|
|
70
|
-
handleLeave
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
export {
|
|
74
|
-
useHoverSync
|
|
75
|
-
};
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export type HoverCtx = {
|
|
2
|
-
key: string | null;
|
|
3
|
-
source: string | null;
|
|
4
|
-
setHover: (key: string | null, source: string | null) => void;
|
|
5
|
-
};
|
|
6
|
-
/**
|
|
7
|
-
* Sentinel identity used as the default context's `setHover`. `useHoverSync` compares against it
|
|
8
|
-
* (`ctx.setHover !== DEFAULT_NO_OP_SET_HOVER`) to decide whether a `ChartHoverSync` provider is
|
|
9
|
-
* mounted — which is load-bearing, not just a dev warning: standalone charts (no provider) drive
|
|
10
|
-
* their tooltip + crosshair from local hover, while inside a provider only the broadcast source
|
|
11
|
-
* owns the tooltip. CONTRACT: a real provider MUST supply its own `setHover`, never re-use this
|
|
12
|
-
* no-op (a provider that passes this sentinel would be (mis)read as "no provider").
|
|
13
|
-
* Exported only for that identity check — do NOT call directly.
|
|
14
|
-
*/
|
|
15
|
-
export declare const DEFAULT_NO_OP_SET_HOVER: HoverCtx['setHover'];
|
|
16
|
-
/**
|
|
17
|
-
* Shared-cursor context — charts write the hovered key (an opaque category/date string) + their
|
|
18
|
-
* own chartId; other charts read and show a ghost crosshair + dot on the same key.
|
|
19
|
-
*/
|
|
20
|
-
export declare const HoverContext: import("react").Context<HoverCtx>;
|
|
21
|
-
//# sourceMappingURL=hover-context.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hover-context.d.ts","sourceRoot":"","sources":["../../src/charts/hover-context.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;CAC9D,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,EAAE,QAAQ,CAAC,UAAU,CAAY,CAAA;AAErE;;;GAGG;AACH,eAAO,MAAM,YAAY,mCAIvB,CAAA"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
export type ChartHoverSyncProps = {
|
|
3
|
-
children: ReactNode;
|
|
4
|
-
};
|
|
5
|
-
/**
|
|
6
|
-
* Activates the shared-cursor HoverContext: holds the hovered key (an opaque
|
|
7
|
-
* category/date string) + source chartId in one place so every descendant
|
|
8
|
-
* chart's `useHoverSync` reads and writes the same cursor. Hovering one chart
|
|
9
|
-
* shows a ghost crosshair + dot on all sibling charts under this provider.
|
|
10
|
-
*
|
|
11
|
-
* Wrap a group of charts that should share a cursor in `<ChartHoverSync>`.
|
|
12
|
-
*/
|
|
13
|
-
export declare function ChartHoverSync({ children }: ChartHoverSyncProps): import("react").JSX.Element;
|
|
14
|
-
//# sourceMappingURL=hover-sync.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hover-sync.d.ts","sourceRoot":"","sources":["../../src/charts/hover-sync.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKtC,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,SAAS,CAAA;CACpB,CAAA;AAOD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE,mBAAmB,+BAc/D"}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useMemo, useState } from "react";
|
|
3
|
-
import { HoverContext } from "./hover-context.js";
|
|
4
|
-
function ChartHoverSync({ children }) {
|
|
5
|
-
const [hover, setHoverState] = useState({ key: null, source: null });
|
|
6
|
-
const setHover = useCallback((key, source) => {
|
|
7
|
-
setHoverState({ key, source });
|
|
8
|
-
}, []);
|
|
9
|
-
const value = useMemo(
|
|
10
|
-
() => ({ key: hover.key, source: hover.source, setHover }),
|
|
11
|
-
[hover.key, hover.source, setHover]
|
|
12
|
-
);
|
|
13
|
-
return /* @__PURE__ */ jsx(HoverContext.Provider, { value, children });
|
|
14
|
-
}
|
|
15
|
-
export {
|
|
16
|
-
ChartHoverSync
|
|
17
|
-
};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
export type ResponsiveChartProps = {
|
|
3
|
-
/**
|
|
4
|
-
* Fixed height in pixels. Used when `aspectRatio` is not set. Default 240.
|
|
5
|
-
* Ignored when `aspectRatio` is set — height is derived from measured width.
|
|
6
|
-
*/
|
|
7
|
-
height?: number;
|
|
8
|
-
/**
|
|
9
|
-
* When set, height = Math.round(width / aspectRatio) and `height` prop is ignored.
|
|
10
|
-
* E.g. `aspectRatio={16 / 9}` produces a 16:9 panel.
|
|
11
|
-
*/
|
|
12
|
-
aspectRatio?: number;
|
|
13
|
-
/** ResizeObserver debounce in milliseconds. Default 0 (immediate). */
|
|
14
|
-
debounceMs?: number;
|
|
15
|
-
/**
|
|
16
|
-
* Accessible text alternative for the chart, applied as `aria-label` (+ `role="img"`) on the
|
|
17
|
-
* outer measuring container — e.g. when wrapping `Heatmap`, which measures via this primitive
|
|
18
|
-
* rather than `ChartFrame`.
|
|
19
|
-
*/
|
|
20
|
-
ariaLabel?: string;
|
|
21
|
-
/** Render prop — called with the measured `{ width, height }`. */
|
|
22
|
-
children: (size: {
|
|
23
|
-
width: number;
|
|
24
|
-
height: number;
|
|
25
|
-
}) => ReactNode;
|
|
26
|
-
};
|
|
27
|
-
/**
|
|
28
|
-
* Render-prop container that measures its own width and derives a final height
|
|
29
|
-
* from `height` (default 240) or `aspectRatio`. Renders nothing until the first
|
|
30
|
-
* measurement arrives (`width > 0`). Backed by `@visx/responsive`'s `useParentSize`.
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* <ResponsiveChart height={320}>
|
|
34
|
-
* {({ width, height }) => (
|
|
35
|
-
* <Bars width={width} height={height} data={data} ... />
|
|
36
|
-
* )}
|
|
37
|
-
* </ResponsiveChart>
|
|
38
|
-
*
|
|
39
|
-
* @example aspectRatio
|
|
40
|
-
* <ResponsiveChart aspectRatio={16 / 9}>
|
|
41
|
-
* {({ width, height }) => <MyChart width={width} height={height} />}
|
|
42
|
-
* </ResponsiveChart>
|
|
43
|
-
*/
|
|
44
|
-
export declare function ResponsiveChart({ height, aspectRatio, debounceMs, ariaLabel, children, }: ResponsiveChartProps): ReactNode;
|
|
45
|
-
//# sourceMappingURL=ResponsiveChart.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ResponsiveChart.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ResponsiveChart.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,kEAAkE;IAClE,QAAQ,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,SAAS,CAAA;CACjE,CAAA;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,EAC9B,MAAY,EACZ,WAAW,EACX,UAAc,EACd,SAAS,EACT,QAAQ,GACT,EAAE,oBAAoB,GAAG,SAAS,CAclC"}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useChartSize } from "../hooks/useChartSize.js";
|
|
3
|
-
function ResponsiveChart({
|
|
4
|
-
height = 240,
|
|
5
|
-
aspectRatio,
|
|
6
|
-
debounceMs = 0,
|
|
7
|
-
ariaLabel,
|
|
8
|
-
children
|
|
9
|
-
}) {
|
|
10
|
-
const { ref, width } = useChartSize(debounceMs);
|
|
11
|
-
const resolvedHeight = aspectRatio !== void 0 ? Math.round(width / aspectRatio) : height;
|
|
12
|
-
return /* @__PURE__ */ jsx(
|
|
13
|
-
"div",
|
|
14
|
-
{
|
|
15
|
-
ref,
|
|
16
|
-
style: { width: "100%" },
|
|
17
|
-
...ariaLabel !== void 0 && { role: "img", "aria-label": ariaLabel },
|
|
18
|
-
children: width > 0 ? children({ width, height: resolvedHeight }) : null
|
|
19
|
-
}
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
export {
|
|
23
|
-
ResponsiveChart
|
|
24
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { useCallback, useRef, useState } from 'react'
|
|
2
|
-
|
|
3
|
-
export type TooltipState<T> = { data: T; x: number; y: number } | null
|
|
4
|
-
|
|
5
|
-
export function useChartTooltip<T>() {
|
|
6
|
-
const [tip, setTip] = useState<TooltipState<T>>(null)
|
|
7
|
-
const tooltipRef = useRef<HTMLDivElement>(null)
|
|
8
|
-
const lastDateRef = useRef<string | null>(null)
|
|
9
|
-
|
|
10
|
-
const show = useCallback((data: T, event: React.MouseEvent) => {
|
|
11
|
-
const el = tooltipRef.current
|
|
12
|
-
const width = el?.offsetWidth ?? 0
|
|
13
|
-
const height = el?.offsetHeight ?? 0
|
|
14
|
-
const margin = 8
|
|
15
|
-
const vw = window.innerWidth
|
|
16
|
-
const vh = window.innerHeight
|
|
17
|
-
const overflowsRight = event.clientX + 12 + width + margin > vw
|
|
18
|
-
const x = overflowsRight ? Math.max(margin, event.clientX - 12 - width) : event.clientX + 12
|
|
19
|
-
const y = Math.min(Math.max(margin, event.clientY - 12), vh - height - margin)
|
|
20
|
-
// State-driven positioning: the tooltip element reads x/y from `tip`. (A prior direct
|
|
21
|
-
// el.style.left/top write here was dead — the React render overwrote it the same frame.)
|
|
22
|
-
setTip({ data, x, y })
|
|
23
|
-
}, [])
|
|
24
|
-
|
|
25
|
-
const hide = useCallback(() => {
|
|
26
|
-
setTip(null)
|
|
27
|
-
lastDateRef.current = null
|
|
28
|
-
}, [])
|
|
29
|
-
|
|
30
|
-
return { tip, show, hide, tooltipRef, lastDateRef }
|
|
31
|
-
}
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import { localPoint } from '@visx/event'
|
|
2
|
-
import { useCallback, useContext, useMemo, useRef } from 'react'
|
|
3
|
-
import { DEFAULT_NO_OP_SET_HOVER, HoverContext } from '../hover-context'
|
|
4
|
-
import { useChartTooltip } from './useChartTooltip'
|
|
5
|
-
|
|
6
|
-
type XScale = (x: string) => number | undefined
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Wires a chart into the shared-cursor HoverContext plus the local tooltip state.
|
|
10
|
-
*
|
|
11
|
-
* Returns everything a chart needs to:
|
|
12
|
-
* - snap mouse position to the nearest data point (by x-category)
|
|
13
|
-
* - broadcast that hover to other charts via HoverContext
|
|
14
|
-
* - read back hover from other charts (for ghost crosshair + dot)
|
|
15
|
-
* - drive its local tooltip
|
|
16
|
-
*
|
|
17
|
-
* Every non-sparkline chart kind MUST use this hook. Do not reimplement the
|
|
18
|
-
* closest-point loop inline — drift across 15+ charts is the exact problem
|
|
19
|
-
* this hook prevents.
|
|
20
|
-
*/
|
|
21
|
-
export function useHoverSync<T>({
|
|
22
|
-
data,
|
|
23
|
-
chartId,
|
|
24
|
-
getKey,
|
|
25
|
-
xScale,
|
|
26
|
-
marginLeft,
|
|
27
|
-
resolveKey,
|
|
28
|
-
}: {
|
|
29
|
-
data: T[]
|
|
30
|
-
chartId: string
|
|
31
|
-
getKey: (d: T) => string
|
|
32
|
-
xScale: XScale
|
|
33
|
-
marginLeft: number
|
|
34
|
-
/**
|
|
35
|
-
* Overrides how a SIBLING's broadcast key resolves to one of THIS chart's own points. Only
|
|
36
|
-
* affects reading another chart's hover — this chart's own hover (via `getKey`/`xScale`) is
|
|
37
|
-
* untouched.
|
|
38
|
-
*
|
|
39
|
-
* Cross-chart sync is exact string match by default (`pointByKey.get(ctx.key)`), so two charts
|
|
40
|
-
* share a cursor only if they emit identical key strings. A chart that downsamples/folds its
|
|
41
|
-
* domain to fit a narrow viewport (e.g. ~288 buckets folded into ~97 columns at 390px) no longer
|
|
42
|
-
* owns most of the keys its unfolded siblings broadcast — the shared crosshair then appears on
|
|
43
|
-
* roughly one hover in three, with no rule a reader can infer, which is worse than no shared
|
|
44
|
-
* cursor at all. Pass `resolveKey` to map a foreign key onto whichever of this chart's own points
|
|
45
|
-
* swallowed it (e.g. the folded bucket the key falls into).
|
|
46
|
-
*
|
|
47
|
-
* Omit it and behaviour is byte-identical to today.
|
|
48
|
-
*/
|
|
49
|
-
resolveKey?: (key: string) => T | null
|
|
50
|
-
}) {
|
|
51
|
-
const ctx = useContext(HoverContext)
|
|
52
|
-
// Latest context in a ref so the mouse callbacks stay referentially stable: the provider's value
|
|
53
|
-
// is a NEW object on every hover broadcast, and depending on `ctx` would re-create both callbacks
|
|
54
|
-
// (and re-bind HoverOverlay's listeners) in every sibling chart on every cursor move.
|
|
55
|
-
const ctxRef = useRef(ctx)
|
|
56
|
-
ctxRef.current = ctx
|
|
57
|
-
const warnedRef = useRef(false)
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
process.env['NODE_ENV'] !== 'production' &&
|
|
61
|
-
ctx.setHover === DEFAULT_NO_OP_SET_HOVER &&
|
|
62
|
-
!warnedRef.current
|
|
63
|
-
) {
|
|
64
|
-
warnedRef.current = true
|
|
65
|
-
// eslint-disable-next-line no-console
|
|
66
|
-
console.warn(
|
|
67
|
-
'[charts] useHoverSync used outside <HoverContext.Provider>. Cross-chart cursor sync will not work.',
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const { tip, show, hide, tooltipRef, lastDateRef } = useChartTooltip<T>()
|
|
72
|
-
|
|
73
|
-
// Latest accessors in refs, same reasoning as `ctxRef` above: the natural call style is an
|
|
74
|
-
// inline arrow for `getKey`/`xScale`, which is fresh on every render, and this file already
|
|
75
|
-
// pays for that hazard once (see the comment above `ctxRef`) without applying the fix here — a
|
|
76
|
-
// several-hundred-entry `pointByKey` Map was rebuilding on every render of a page that
|
|
77
|
-
// re-renders on a heartbeat. Trade-off: because `getKey` is read through a ref, changing key
|
|
78
|
-
// SEMANTICS without changing `data` will NOT rebuild the map. That's the intended trade — a
|
|
79
|
-
// `getKey` that changes meaning for the same data is pathological; a `getKey` that changes
|
|
80
|
-
// identity every render is the normal case.
|
|
81
|
-
const getKeyRef = useRef(getKey)
|
|
82
|
-
getKeyRef.current = getKey
|
|
83
|
-
const xScaleRef = useRef(xScale)
|
|
84
|
-
xScaleRef.current = xScale
|
|
85
|
-
|
|
86
|
-
// O(1) lookup of a point by its key. Under a provider every sibling resolves the broadcast
|
|
87
|
-
// key each frame; an O(n) `data.find` per sibling per move is N×O(M). This makes it N×O(1).
|
|
88
|
-
const pointByKey = useMemo(() => {
|
|
89
|
-
const m = new Map<string, T>()
|
|
90
|
-
for (const d of data) m.set(getKeyRef.current(d), d)
|
|
91
|
-
return m
|
|
92
|
-
}, [data])
|
|
93
|
-
|
|
94
|
-
const handleMouse = useCallback(
|
|
95
|
-
(event: React.MouseEvent<SVGRectElement>) => {
|
|
96
|
-
const point = localPoint(event)
|
|
97
|
-
if (!point || data.length === 0) return
|
|
98
|
-
const px = point.x - marginLeft
|
|
99
|
-
let closest: T = data[0] as T
|
|
100
|
-
let minDist = Infinity
|
|
101
|
-
for (const d of data) {
|
|
102
|
-
const sx = xScaleRef.current(getKeyRef.current(d)) ?? 0
|
|
103
|
-
const dist = Math.abs(sx - px)
|
|
104
|
-
if (dist < minDist) {
|
|
105
|
-
minDist = dist
|
|
106
|
-
closest = d
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
show(closest, event)
|
|
110
|
-
const key = getKeyRef.current(closest)
|
|
111
|
-
if (lastDateRef.current !== key) {
|
|
112
|
-
lastDateRef.current = key
|
|
113
|
-
ctxRef.current.setHover(key, chartId)
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
[data, chartId, marginLeft, show, lastDateRef],
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
const handleLeave = useCallback(() => {
|
|
120
|
-
hide()
|
|
121
|
-
// Only clear the SHARED hover if this chart currently owns it. When the cursor moves quickly from
|
|
122
|
-
// chart A to chart B, A's `mouseleave` can fire AFTER B's `mousemove` has already claimed the
|
|
123
|
-
// hover — an unconditional clear would then wipe B's cursor and flicker every sibling crosshair.
|
|
124
|
-
if (ctxRef.current.source === chartId) ctxRef.current.setHover(null, null)
|
|
125
|
-
}, [hide, chartId])
|
|
126
|
-
|
|
127
|
-
// The point the crosshair + synced dots track. Inside a <ChartHoverSync> this follows the
|
|
128
|
-
// broadcast key (so every sibling paints a ghost crosshair at the same x). WITHOUT a provider the
|
|
129
|
-
// chart is standalone, so it falls back to THIS chart's own hovered point (`tip.data`) — otherwise
|
|
130
|
-
// a chart outside a provider would never draw a crosshair/dots at all. When `resolveKey` is
|
|
131
|
-
// supplied it replaces the default exact-match lookup for reading a SIBLING's broadcast key (see
|
|
132
|
-
// its doc comment above) — this chart's own hover is unaffected either way.
|
|
133
|
-
const syncedPoint = ctx.key
|
|
134
|
-
? resolveKey
|
|
135
|
-
? resolveKey(ctx.key)
|
|
136
|
-
: (pointByKey.get(ctx.key) ?? null)
|
|
137
|
-
: (tip?.data ?? null)
|
|
138
|
-
// Which chart owns the floating tooltip. Inside a provider the hovered chart is the one whose id
|
|
139
|
-
// matches the broadcast source (siblings show a ghost crosshair only). Standalone, its own local
|
|
140
|
-
// hover (`tip`) drives the tooltip.
|
|
141
|
-
const hasProvider = ctx.setHover !== DEFAULT_NO_OP_SET_HOVER
|
|
142
|
-
const isDirectHover = hasProvider ? ctx.source === chartId : tip !== null
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
tip,
|
|
146
|
-
tooltipRef,
|
|
147
|
-
syncedPoint,
|
|
148
|
-
isDirectHover,
|
|
149
|
-
handleMouse,
|
|
150
|
-
handleLeave,
|
|
151
|
-
}
|
|
152
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { createContext } from 'react'
|
|
2
|
-
|
|
3
|
-
export type HoverCtx = {
|
|
4
|
-
key: string | null
|
|
5
|
-
source: string | null
|
|
6
|
-
setHover: (key: string | null, source: string | null) => void
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Sentinel identity used as the default context's `setHover`. `useHoverSync` compares against it
|
|
11
|
-
* (`ctx.setHover !== DEFAULT_NO_OP_SET_HOVER`) to decide whether a `ChartHoverSync` provider is
|
|
12
|
-
* mounted — which is load-bearing, not just a dev warning: standalone charts (no provider) drive
|
|
13
|
-
* their tooltip + crosshair from local hover, while inside a provider only the broadcast source
|
|
14
|
-
* owns the tooltip. CONTRACT: a real provider MUST supply its own `setHover`, never re-use this
|
|
15
|
-
* no-op (a provider that passes this sentinel would be (mis)read as "no provider").
|
|
16
|
-
* Exported only for that identity check — do NOT call directly.
|
|
17
|
-
*/
|
|
18
|
-
export const DEFAULT_NO_OP_SET_HOVER: HoverCtx['setHover'] = () => {}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Shared-cursor context — charts write the hovered key (an opaque category/date string) + their
|
|
22
|
-
* own chartId; other charts read and show a ghost crosshair + dot on the same key.
|
|
23
|
-
*/
|
|
24
|
-
export const HoverContext = createContext<HoverCtx>({
|
|
25
|
-
key: null,
|
|
26
|
-
source: null,
|
|
27
|
-
setHover: DEFAULT_NO_OP_SET_HOVER,
|
|
28
|
-
})
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react'
|
|
2
|
-
import type { HoverCtx } from './hover-context'
|
|
3
|
-
import { useCallback, useMemo, useState } from 'react'
|
|
4
|
-
import { HoverContext } from './hover-context'
|
|
5
|
-
|
|
6
|
-
export type ChartHoverSyncProps = {
|
|
7
|
-
children: ReactNode
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
type HoverState = {
|
|
11
|
-
key: string | null
|
|
12
|
-
source: string | null
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Activates the shared-cursor HoverContext: holds the hovered key (an opaque
|
|
17
|
-
* category/date string) + source chartId in one place so every descendant
|
|
18
|
-
* chart's `useHoverSync` reads and writes the same cursor. Hovering one chart
|
|
19
|
-
* shows a ghost crosshair + dot on all sibling charts under this provider.
|
|
20
|
-
*
|
|
21
|
-
* Wrap a group of charts that should share a cursor in `<ChartHoverSync>`.
|
|
22
|
-
*/
|
|
23
|
-
export function ChartHoverSync({ children }: ChartHoverSyncProps) {
|
|
24
|
-
// oxlint-disable-next-line react/hook-use-state -- raw setter; renaming shadows setHover (the context callback)
|
|
25
|
-
const [hover, setHoverState] = useState<HoverState>({ key: null, source: null })
|
|
26
|
-
|
|
27
|
-
const setHover = useCallback<HoverCtx['setHover']>((key, source) => {
|
|
28
|
-
setHoverState({ key, source })
|
|
29
|
-
}, [])
|
|
30
|
-
|
|
31
|
-
const value = useMemo<HoverCtx>(
|
|
32
|
-
() => ({ key: hover.key, source: hover.source, setHover }),
|
|
33
|
-
[hover.key, hover.source, setHover],
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
return <HoverContext.Provider value={value}>{children}</HoverContext.Provider>
|
|
37
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react'
|
|
2
|
-
import { useChartSize } from '../hooks/useChartSize'
|
|
3
|
-
|
|
4
|
-
export type ResponsiveChartProps = {
|
|
5
|
-
/**
|
|
6
|
-
* Fixed height in pixels. Used when `aspectRatio` is not set. Default 240.
|
|
7
|
-
* Ignored when `aspectRatio` is set — height is derived from measured width.
|
|
8
|
-
*/
|
|
9
|
-
height?: number
|
|
10
|
-
/**
|
|
11
|
-
* When set, height = Math.round(width / aspectRatio) and `height` prop is ignored.
|
|
12
|
-
* E.g. `aspectRatio={16 / 9}` produces a 16:9 panel.
|
|
13
|
-
*/
|
|
14
|
-
aspectRatio?: number
|
|
15
|
-
/** ResizeObserver debounce in milliseconds. Default 0 (immediate). */
|
|
16
|
-
debounceMs?: number
|
|
17
|
-
/**
|
|
18
|
-
* Accessible text alternative for the chart, applied as `aria-label` (+ `role="img"`) on the
|
|
19
|
-
* outer measuring container — e.g. when wrapping `Heatmap`, which measures via this primitive
|
|
20
|
-
* rather than `ChartFrame`.
|
|
21
|
-
*/
|
|
22
|
-
ariaLabel?: string
|
|
23
|
-
/** Render prop — called with the measured `{ width, height }`. */
|
|
24
|
-
children: (size: { width: number; height: number }) => ReactNode
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Render-prop container that measures its own width and derives a final height
|
|
29
|
-
* from `height` (default 240) or `aspectRatio`. Renders nothing until the first
|
|
30
|
-
* measurement arrives (`width > 0`). Backed by `@visx/responsive`'s `useParentSize`.
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* <ResponsiveChart height={320}>
|
|
34
|
-
* {({ width, height }) => (
|
|
35
|
-
* <Bars width={width} height={height} data={data} ... />
|
|
36
|
-
* )}
|
|
37
|
-
* </ResponsiveChart>
|
|
38
|
-
*
|
|
39
|
-
* @example aspectRatio
|
|
40
|
-
* <ResponsiveChart aspectRatio={16 / 9}>
|
|
41
|
-
* {({ width, height }) => <MyChart width={width} height={height} />}
|
|
42
|
-
* </ResponsiveChart>
|
|
43
|
-
*/
|
|
44
|
-
export function ResponsiveChart({
|
|
45
|
-
height = 240,
|
|
46
|
-
aspectRatio,
|
|
47
|
-
debounceMs = 0,
|
|
48
|
-
ariaLabel,
|
|
49
|
-
children,
|
|
50
|
-
}: ResponsiveChartProps): ReactNode {
|
|
51
|
-
const { ref, width } = useChartSize(debounceMs)
|
|
52
|
-
|
|
53
|
-
const resolvedHeight = aspectRatio !== undefined ? Math.round(width / aspectRatio) : height
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<div
|
|
57
|
-
ref={ref}
|
|
58
|
-
style={{ width: '100%' }}
|
|
59
|
-
{...(ariaLabel !== undefined && { role: 'img', 'aria-label': ariaLabel })}
|
|
60
|
-
>
|
|
61
|
-
{width > 0 ? children({ width, height: resolvedHeight }) : null}
|
|
62
|
-
</div>
|
|
63
|
-
)
|
|
64
|
-
}
|