@kind-ui/charts 0.1.0 → 0.2.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/CHANGELOG.md +32 -0
- package/README.md +657 -865
- package/dist/activity-rings.d.ts +2 -1
- package/dist/activity-rings.js +6 -3
- package/dist/animation.d.ts +11 -3
- package/dist/animation.js +18 -10
- package/dist/area-chart.d.ts +3 -1
- package/dist/area-chart.js +15 -10
- package/dist/area-series.d.ts +9 -1
- package/dist/area-series.js +35 -8
- package/dist/bar-chart.d.ts +10 -3
- package/dist/bar-chart.js +17 -4
- package/dist/bar-series.d.ts +11 -1
- package/dist/bar-series.js +65 -13
- package/dist/box-plot.js +2 -2
- package/dist/category-cells.d.ts +7 -1
- package/dist/category-cells.js +32 -2
- package/dist/chart-background-pattern.d.ts +23 -0
- package/dist/chart-background-pattern.js +31 -0
- package/dist/chart-context.d.ts +6 -2
- package/dist/chart-interaction.d.ts +94 -0
- package/dist/chart-interaction.js +224 -0
- package/dist/combo-chart.d.ts +8 -5
- package/dist/combo-chart.js +14 -12
- package/dist/configured-line-chart.d.ts +5 -1
- package/dist/configured-line-chart.js +8 -5
- package/dist/emphasis.d.ts +5 -2
- package/dist/emphasis.js +18 -4
- package/dist/fill-pattern.d.ts +25 -0
- package/dist/fill-pattern.js +23 -0
- package/dist/heatmap.d.ts +3 -1
- package/dist/heatmap.js +34 -12
- package/dist/histogram-chart.js +10 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +8 -1
- package/dist/legend.d.ts +2 -0
- package/dist/legend.js +28 -7
- package/dist/line-chart.d.ts +13 -3
- package/dist/line-chart.js +54 -34
- package/dist/line-dash.d.ts +8 -0
- package/dist/line-dash.js +18 -0
- package/dist/line-series.d.ts +9 -1
- package/dist/line-series.js +38 -11
- package/dist/loading-cartesian-designs.d.ts +6 -0
- package/dist/loading-cartesian-designs.js +123 -0
- package/dist/loading-motion.d.ts +37 -0
- package/dist/loading-motion.js +88 -0
- package/dist/loading-polar-designs.d.ts +6 -0
- package/dist/loading-polar-designs.js +95 -0
- package/dist/loading-skeleton.d.ts +29 -0
- package/dist/loading-skeleton.js +143 -0
- package/dist/loading-standalone-designs.d.ts +5 -0
- package/dist/loading-standalone-designs.js +126 -0
- package/dist/percent-stack.d.ts +18 -0
- package/dist/percent-stack.js +33 -0
- package/dist/pie-chart.d.ts +7 -3
- package/dist/pie-chart.js +37 -6
- package/dist/pie-pin-identity.d.ts +4 -0
- package/dist/pie-pin-identity.js +10 -0
- package/dist/pie-series.d.ts +4 -0
- package/dist/pie-series.js +137 -14
- package/dist/pie-tooltip-pin.d.ts +4 -0
- package/dist/pie-tooltip-pin.js +52 -0
- package/dist/point-marker.d.ts +19 -0
- package/dist/point-marker.js +19 -0
- package/dist/polar-chart.d.ts +14 -5
- package/dist/polar-chart.js +41 -10
- package/dist/polar-series.js +68 -22
- package/dist/radar-interaction.js +10 -4
- package/dist/radial-category.d.ts +2 -0
- package/dist/reveal-clip.d.ts +16 -0
- package/dist/reveal-clip.js +18 -0
- package/dist/root.d.ts +5 -9
- package/dist/root.js +46 -11
- package/dist/sankey-chart.d.ts +5 -1
- package/dist/sankey-chart.js +64 -13
- package/dist/sankey-colors.d.ts +3 -0
- package/dist/sankey-interaction.d.ts +8 -0
- package/dist/sankey-interaction.js +32 -0
- package/dist/sankey-legend.d.ts +2 -1
- package/dist/sankey-legend.js +18 -3
- package/dist/sankey-node-label.d.ts +22 -0
- package/dist/sankey-node-label.js +34 -0
- package/dist/scatter-chart.d.ts +5 -3
- package/dist/scatter-chart.js +14 -4
- package/dist/scatter-series.js +5 -3
- package/dist/series-color.d.ts +10 -0
- package/dist/series-color.js +48 -0
- package/dist/series-interaction.d.ts +12 -0
- package/dist/series-interaction.js +82 -0
- package/dist/series-label.d.ts +7 -0
- package/dist/series-label.js +18 -0
- package/dist/series-paint.d.ts +8 -0
- package/dist/series-paint.js +27 -0
- package/dist/styles.css +151 -1
- package/dist/stylesheet-warning.d.ts +2 -0
- package/dist/stylesheet-warning.js +86 -0
- package/dist/tooltip-content.d.ts +8 -1
- package/dist/tooltip-content.js +15 -4
- package/dist/tooltip.d.ts +3 -1
- package/dist/tooltip.js +8 -5
- package/dist/types.d.ts +11 -2
- package/dist/waterfall-chart.d.ts +4 -0
- package/dist/waterfall-chart.js +7 -0
- package/package.json +2 -2
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { LoadingPulseFlow, LoadingPulseMark } from "./loading-motion.js";
|
|
3
|
+
// Authored clustered, diagonal, paired-hotspot and striped illustrations.
|
|
4
|
+
const heatmapPatterns = [
|
|
5
|
+
[
|
|
6
|
+
9, 9, 7, 2, 1, 1, 1, 2, 9, 8, 6, 2, 1, 1, 2, 1, 7, 6, 4, 2, 1, 2, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1,
|
|
7
|
+
1, 1, 1, 2, 1, 1, 2, 1,
|
|
8
|
+
],
|
|
9
|
+
[
|
|
10
|
+
9, 7, 2, 1, 1, 1, 1, 1, 2, 8, 9, 6, 1, 1, 1, 1, 1, 1, 3, 9, 8, 3, 1, 1, 1, 1, 1, 1, 5, 9, 7, 2,
|
|
11
|
+
1, 1, 1, 1, 1, 2, 7, 9,
|
|
12
|
+
],
|
|
13
|
+
[
|
|
14
|
+
1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 2, 5, 3, 1, 1, 4, 7, 5, 6, 9, 8, 2, 2, 8, 9, 8,
|
|
15
|
+
4, 8, 6, 1, 1, 6, 8, 5,
|
|
16
|
+
],
|
|
17
|
+
[
|
|
18
|
+
1, 8, 9, 1, 2, 1, 8, 9, 2, 7, 9, 1, 1, 1, 9, 7, 1, 9, 8, 2, 1, 2, 7, 9, 1, 8, 9, 1, 1, 1, 9, 8,
|
|
19
|
+
2, 9, 7, 1, 2, 1, 8, 9,
|
|
20
|
+
],
|
|
21
|
+
];
|
|
22
|
+
// Each flow width is conserved at its node junctions. These are decorative authored layouts.
|
|
23
|
+
const sankeyProfiles = [
|
|
24
|
+
{
|
|
25
|
+
nodes: [
|
|
26
|
+
[16, 20, 16, 60],
|
|
27
|
+
[16, 100, 16, 50],
|
|
28
|
+
[16, 170, 16, 50],
|
|
29
|
+
[298, 35, 24, 90],
|
|
30
|
+
[298, 145, 24, 70],
|
|
31
|
+
[608, 20, 16, 50],
|
|
32
|
+
[608, 95, 16, 40],
|
|
33
|
+
[608, 155, 16, 70],
|
|
34
|
+
],
|
|
35
|
+
flows: [
|
|
36
|
+
[32, 20, 298, 35, 60],
|
|
37
|
+
[32, 100, 298, 95, 30],
|
|
38
|
+
[32, 130, 298, 145, 20],
|
|
39
|
+
[32, 170, 298, 165, 50],
|
|
40
|
+
[322, 35, 608, 20, 50],
|
|
41
|
+
[322, 85, 608, 95, 40],
|
|
42
|
+
[322, 145, 608, 155, 70],
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
nodes: [
|
|
47
|
+
[16, 20, 16, 100],
|
|
48
|
+
[16, 140, 16, 80],
|
|
49
|
+
[298, 20, 24, 50],
|
|
50
|
+
[298, 90, 24, 60],
|
|
51
|
+
[298, 170, 24, 70],
|
|
52
|
+
[608, 25, 16, 100],
|
|
53
|
+
[608, 145, 16, 80],
|
|
54
|
+
],
|
|
55
|
+
flows: [
|
|
56
|
+
[32, 20, 298, 20, 50],
|
|
57
|
+
[32, 70, 298, 90, 50],
|
|
58
|
+
[32, 140, 298, 140, 10],
|
|
59
|
+
[32, 150, 298, 170, 70],
|
|
60
|
+
[322, 20, 608, 25, 50],
|
|
61
|
+
[322, 90, 608, 75, 50],
|
|
62
|
+
[322, 140, 608, 145, 10],
|
|
63
|
+
[322, 170, 608, 155, 70],
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
nodes: [
|
|
68
|
+
[16, 32, 16, 176],
|
|
69
|
+
[298, 12, 24, 64],
|
|
70
|
+
[298, 96, 24, 46],
|
|
71
|
+
[298, 162, 24, 66],
|
|
72
|
+
[608, 20, 16, 110],
|
|
73
|
+
[608, 154, 16, 66],
|
|
74
|
+
],
|
|
75
|
+
flows: [
|
|
76
|
+
[32, 32, 298, 12, 64],
|
|
77
|
+
[32, 96, 298, 96, 46],
|
|
78
|
+
[32, 142, 298, 162, 66],
|
|
79
|
+
[322, 12, 608, 20, 64],
|
|
80
|
+
[322, 96, 608, 84, 46],
|
|
81
|
+
[322, 162, 608, 154, 66],
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
nodes: [
|
|
86
|
+
[16, 12, 16, 30],
|
|
87
|
+
[16, 58, 16, 40],
|
|
88
|
+
[16, 114, 16, 50],
|
|
89
|
+
[16, 174, 16, 60],
|
|
90
|
+
[608, 20, 16, 60],
|
|
91
|
+
[608, 102, 16, 55],
|
|
92
|
+
[608, 174, 16, 65],
|
|
93
|
+
],
|
|
94
|
+
flows: [
|
|
95
|
+
[32, 12, 608, 20, 30],
|
|
96
|
+
[32, 58, 608, 50, 30],
|
|
97
|
+
[32, 88, 608, 102, 10],
|
|
98
|
+
[32, 114, 608, 112, 45],
|
|
99
|
+
[32, 159, 608, 174, 5],
|
|
100
|
+
[32, 174, 608, 179, 60],
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
/** Fixed illustrations; no user cells, flows, values, or native marks are sampled. */
|
|
105
|
+
export function StandaloneLoadingDesign({ family, seed, }) {
|
|
106
|
+
const profile = seed % 4;
|
|
107
|
+
if (family === "heatmap") {
|
|
108
|
+
return (_jsx("g", { fill: "currentColor", "data-profile": profile, children: heatmapPatterns[profile]?.map((intensity, index) => {
|
|
109
|
+
const row = Math.floor(index / 8);
|
|
110
|
+
const column = index % 8;
|
|
111
|
+
return (_jsx(LoadingPulseMark, { delay: ((row + column) / 11) * (0.55 / 0.8), span: 0.25 / 0.8, children: _jsx("rect", { "data-motion": "heatmap-cell", x: 16 + column * 77, y: 10 + row * 45, width: "69", height: "37", rx: "3", opacity: 0.2 + (intensity / 9) * 0.8 }) }, `${row}-${column}`));
|
|
112
|
+
}) }));
|
|
113
|
+
}
|
|
114
|
+
const design = sankeyProfiles[profile];
|
|
115
|
+
if (!design)
|
|
116
|
+
return null;
|
|
117
|
+
const band = ([x0, y0, x1, y1, width]) => {
|
|
118
|
+
const middle = (x0 + x1) / 2;
|
|
119
|
+
return `M ${x0} ${y0} C ${middle} ${y0} ${middle} ${y1} ${x1} ${y1} L ${x1} ${y1 + width} C ${middle} ${y1 + width} ${middle} ${y0 + width} ${x0} ${y0 + width} Z`;
|
|
120
|
+
};
|
|
121
|
+
const flow = ([x0, y0, x1, y1, width]) => {
|
|
122
|
+
const middle = (x0 + x1) / 2;
|
|
123
|
+
return `M ${x0} ${y0 + width / 2} C ${middle} ${y0 + width / 2} ${middle} ${y1 + width / 2} ${x1} ${y1 + width / 2}`;
|
|
124
|
+
};
|
|
125
|
+
return (_jsxs("g", { fill: "currentColor", "data-profile": profile, children: [design.flows.map((item) => (_jsxs(LoadingPulseFlow, { d: flow(item), width: item[4], delay: (item[0] / 640) * 0.55, children: [_jsx("path", { d: band(item), opacity: "0.5" }), _jsx("path", { "data-motion": "sankey-flow", d: flow(item), fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", opacity: "0.55" })] }, item.join("-")))), design.nodes.map(([x, y, width, height]) => (_jsx(LoadingPulseMark, { delay: (x / 640) * 0.55, span: 0.45, children: _jsx("rect", { x: x, y: y, width: width, height: height, rx: "2" }) }, `${x}-${y}`)))] }));
|
|
126
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TooltipContentProps } from "recharts";
|
|
2
|
+
type Entry = TooltipContentProps["payload"][number];
|
|
3
|
+
/** Return a fraction for selected entries; undefined retains ordinary formatting. */
|
|
4
|
+
export type NormalizedValue = (entry: Entry) => number | undefined;
|
|
5
|
+
export type PercentStackOptions = {
|
|
6
|
+
/** Raw members of this entry's native expand stack, including its own value.
|
|
7
|
+
* Return undefined for other series/axes. Match native hidden-series membership.
|
|
8
|
+
*/
|
|
9
|
+
values: (entry: Entry) => readonly (number | null | undefined)[] | undefined;
|
|
10
|
+
};
|
|
11
|
+
/** Formats an existing fraction; never normalizes chart data or changes domains. */
|
|
12
|
+
export declare function formatPercent(value: number): string;
|
|
13
|
+
/** Formatting only for native stackOffset="expand" scalar stacks. */
|
|
14
|
+
export declare function createPercentStack({ values }: PercentStackOptions): {
|
|
15
|
+
tickFormatter: typeof formatPercent;
|
|
16
|
+
normalizedValue: NormalizedValue;
|
|
17
|
+
};
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Formats an existing fraction; never normalizes chart data or changes domains. */
|
|
2
|
+
export function formatPercent(value) {
|
|
3
|
+
return `${Number((value * 100).toFixed(1))}%`;
|
|
4
|
+
}
|
|
5
|
+
/** Formatting only for native stackOffset="expand" scalar stacks. */
|
|
6
|
+
export function createPercentStack({ values }) {
|
|
7
|
+
const normalizedValue = (entry) => {
|
|
8
|
+
if (typeof entry.value !== "number" || !Number.isFinite(entry.value))
|
|
9
|
+
return undefined;
|
|
10
|
+
const members = values(entry);
|
|
11
|
+
if (!members || members.length === 0)
|
|
12
|
+
return undefined;
|
|
13
|
+
let total = 0;
|
|
14
|
+
let allZero = true;
|
|
15
|
+
for (const member of members) {
|
|
16
|
+
if (member == null)
|
|
17
|
+
continue;
|
|
18
|
+
if (!Number.isFinite(member))
|
|
19
|
+
return undefined;
|
|
20
|
+
total += member;
|
|
21
|
+
if (member !== 0)
|
|
22
|
+
allZero = false;
|
|
23
|
+
}
|
|
24
|
+
if (!Number.isFinite(total))
|
|
25
|
+
return undefined;
|
|
26
|
+
// Expand uses a signed sum. A cancelling sum has no normalized share.
|
|
27
|
+
if (total === 0)
|
|
28
|
+
return allZero && entry.value === 0 ? 0 : undefined;
|
|
29
|
+
const fraction = entry.value / total;
|
|
30
|
+
return Number.isFinite(fraction * 100) ? fraction : undefined;
|
|
31
|
+
};
|
|
32
|
+
return { tickFormatter: formatPercent, normalizedValue };
|
|
33
|
+
}
|
package/dist/pie-chart.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { type ComponentProps } from "react";
|
|
2
2
|
import { PieChart as EnginePieChart } from "recharts";
|
|
3
|
-
import { type
|
|
4
|
-
export type PieAnimation =
|
|
3
|
+
import { type BaseAnimation } from "./animation.js";
|
|
4
|
+
export type PieAnimation = BaseAnimation;
|
|
5
5
|
export type PieChartProps = ComponentProps<typeof EnginePieChart> & {
|
|
6
|
+
/** Initial tooltip category; one direct categoryKey PieSeries with explicit data only. */
|
|
7
|
+
defaultPinnedCategory?: string | undefined;
|
|
6
8
|
animate?: boolean | PieAnimation | undefined;
|
|
9
|
+
loading?: boolean | undefined;
|
|
10
|
+
loadingLabel?: string | undefined;
|
|
7
11
|
/** Entrance sweep only; native start/end angles and data order are unchanged. */
|
|
8
12
|
animationDirection?: "clockwise" | "anticlockwise" | undefined;
|
|
9
13
|
};
|
|
@@ -14,4 +18,4 @@ export declare const PieMotion: import("react").Context<{
|
|
|
14
18
|
direction: "clockwise" | "anticlockwise";
|
|
15
19
|
}>;
|
|
16
20
|
/** Native polar composition, shared interaction, and optional Motion-owned sector entrance. */
|
|
17
|
-
export declare function PieChart({ animate, animationDirection, children, ...props }: PieChartProps): import("react").JSX.Element;
|
|
21
|
+
export declare function PieChart({ animate, loading, loadingLabel, animationDirection, children, defaultPinnedCategory, ...props }: PieChartProps): import("react").JSX.Element;
|
package/dist/pie-chart.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { animate as animateValue } from "motion/react";
|
|
4
|
-
import { createContext, useCallback, useLayoutEffect, useRef, useState, useSyncExternalStore, } from "react";
|
|
4
|
+
import { createContext, memo, useCallback, useLayoutEffect, useRef, useState, useSyncExternalStore, } from "react";
|
|
5
5
|
import { PieChart as EnginePieChart } from "recharts";
|
|
6
6
|
import { MotionContext } from "./animation.js";
|
|
7
|
+
import { useChart } from "./chart-context.js";
|
|
7
8
|
import { LineChartFrame } from "./line-chart.js";
|
|
9
|
+
import { PolarLoadingDesign } from "./loading-polar-designs.js";
|
|
10
|
+
import { PieTooltipPin, pinnedPieIndex } from "./pie-tooltip-pin.js";
|
|
8
11
|
export const PieMotion = createContext({
|
|
9
12
|
reveal: false,
|
|
10
13
|
options: {},
|
|
@@ -21,11 +24,33 @@ const snapshot = () => window.matchMedia(query).matches;
|
|
|
21
24
|
const serverSnapshot = () => true;
|
|
22
25
|
const defaultHover = { type: "spring", stiffness: 210, damping: 28, mass: 0.8 };
|
|
23
26
|
/** Native polar composition, shared interaction, and optional Motion-owned sector entrance. */
|
|
24
|
-
export function PieChart({ animate = false, animationDirection = "clockwise", children, ...props }) {
|
|
27
|
+
export function PieChart({ animate = false, loading, loadingLabel, animationDirection = "clockwise", children, defaultPinnedCategory, ...props }) {
|
|
28
|
+
const { visibleSeries } = useChart();
|
|
29
|
+
const [initialCategory] = useState(defaultPinnedCategory);
|
|
30
|
+
const [pinCleared, setPinCleared] = useState(false);
|
|
31
|
+
const clearPin = useCallback(() => setPinCleared(true), []);
|
|
32
|
+
const pinIndex = initialCategory === undefined || pinCleared
|
|
33
|
+
? undefined
|
|
34
|
+
: visibleSeries !== undefined && !visibleSeries.includes(initialCategory)
|
|
35
|
+
? undefined
|
|
36
|
+
: pinnedPieIndex(children, initialCategory, visibleSeries);
|
|
37
|
+
useLayoutEffect(() => {
|
|
38
|
+
if (initialCategory !== undefined && pinIndex === undefined)
|
|
39
|
+
clearPin();
|
|
40
|
+
}, [initialCategory, pinIndex, clearPin]);
|
|
41
|
+
const chart = (_jsx(PieChartPlot, { ...props, animate: animate, loading: loading, loadingLabel: loadingLabel, animationDirection: animationDirection, children: children }));
|
|
42
|
+
return (_jsx(PieTooltipPin, { value: pinIndex, children: initialCategory === undefined ? (chart) : (_jsx("div", { style: { display: "contents" }, onPointerMoveCapture: clearPin, onPointerDownCapture: clearPin, onFocusCapture: clearPin, onKeyDownCapture: clearPin, children: chart })) }));
|
|
43
|
+
}
|
|
44
|
+
// Pin dismissal changes Tooltip context only. Keep the native plot's providers and
|
|
45
|
+
// geometry inputs intact while a pressed sector is awaiting its native click.
|
|
46
|
+
const PieChartPlot = memo(function PieChartPlot({ animate = false, loading, loadingLabel, animationDirection = "clockwise", children, ...props }) {
|
|
25
47
|
const reduced = useSyncExternalStore(subscribe, snapshot, serverSnapshot);
|
|
26
48
|
const [interacted, setInteracted] = useState(false);
|
|
27
|
-
const finish = useCallback(() =>
|
|
28
|
-
|
|
49
|
+
const finish = useCallback(() => {
|
|
50
|
+
if (!loading)
|
|
51
|
+
setInteracted(true);
|
|
52
|
+
}, [loading]);
|
|
53
|
+
const enabled = animate !== false && !reduced && !loading;
|
|
29
54
|
const interrupt = useCallback(() => {
|
|
30
55
|
if (enabled)
|
|
31
56
|
finish();
|
|
@@ -42,6 +67,12 @@ export function PieChart({ animate = false, animationDirection = "clockwise", ch
|
|
|
42
67
|
const duration = options.revealDurationMs ?? 1000;
|
|
43
68
|
const easing = options.revealEasing ?? "easeOut";
|
|
44
69
|
const started = useRef(false);
|
|
70
|
+
useLayoutEffect(() => {
|
|
71
|
+
if (loading) {
|
|
72
|
+
setInteracted(false);
|
|
73
|
+
started.current = false;
|
|
74
|
+
}
|
|
75
|
+
}, [loading]);
|
|
45
76
|
const previous = useRef([duration, easing, animationDirection]);
|
|
46
77
|
useLayoutEffect(() => {
|
|
47
78
|
const inputs = [duration, easing, animationDirection];
|
|
@@ -64,5 +95,5 @@ export function PieChart({ animate = false, animationDirection = "clockwise", ch
|
|
|
64
95
|
});
|
|
65
96
|
return () => controls.stop();
|
|
66
97
|
}, [reveal, duration, easing, finish]);
|
|
67
|
-
return (_jsx(MotionContext, { value: { enabled, transition: options.hoverTransition ?? defaultHover }, children: _jsx(PieMotion, { value: { reveal, options, progress, direction: animationDirection }, children: _jsx(LineChartFrame, { chartProps: props, engine: EnginePieChart, motionEnabled: enabled, interrupt: interrupt, children: children }) }) }));
|
|
68
|
-
}
|
|
98
|
+
return (_jsx(MotionContext, { value: { enabled, transition: options.hoverTransition ?? defaultHover }, children: _jsx(PieMotion, { value: { reveal, options, progress, direction: animationDirection }, children: _jsx(LineChartFrame, { chartProps: props, engine: EnginePieChart, loading: loading, loadingLabel: loadingLabel, loadingSkeleton: "pie", loadingDesign: (seed) => _jsx(PolarLoadingDesign, { family: "pie", seed: seed }), loadingAnimation: { ...options, direction: animationDirection }, motionEnabled: enabled, interrupt: interrupt, children: children }) }) }));
|
|
99
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Canonical exports register themselves; resolving children never imports their renderers.
|
|
2
|
+
const components = new WeakMap();
|
|
3
|
+
export function registerPiePinComponent(component, kind) {
|
|
4
|
+
components.set(component, kind);
|
|
5
|
+
}
|
|
6
|
+
export function piePinComponentKind(component) {
|
|
7
|
+
return typeof component === "function" || (typeof component === "object" && component !== null)
|
|
8
|
+
? components.get(component)
|
|
9
|
+
: undefined;
|
|
10
|
+
}
|
package/dist/pie-series.d.ts
CHANGED
|
@@ -5,8 +5,12 @@ import { type PieMaterial } from "./pie-material.js";
|
|
|
5
5
|
export type PieSeriesProps<DataPoint = unknown, Value = unknown> = Omit<ComponentProps<typeof Pie<DataPoint, Value>>, "isAnimationActive"> & {
|
|
6
6
|
/** Opt-in category colors from Root.config; requires explicit series data. */
|
|
7
7
|
categoryKey?: CategoryKey<DataPoint> | undefined;
|
|
8
|
+
/** Explicitly share this Pie's category identities and actions with Root/Legend. */
|
|
9
|
+
interactionBinding?: "root";
|
|
8
10
|
/** Finish on default native sectors; custom shapes, filters and CSS transforms keep ownership. */
|
|
9
11
|
material?: PieMaterial | undefined;
|
|
12
|
+
/** Categories receiving glow instead of material; requires categoryKey and explicit data. Unknown IDs are ignored. */
|
|
13
|
+
glowCategories?: readonly string[] | undefined;
|
|
10
14
|
/** Stable sector identity; defaults to the native nameKey value. */
|
|
11
15
|
emphasisKey?: ((payload: unknown) => string | number | undefined) | undefined;
|
|
12
16
|
};
|
package/dist/pie-series.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { Children, Fragment, isValidElement, use, useCallback, useId, useLayoutEffect, useRef, useState, } from "react";
|
|
3
|
+
import { Children, cloneElement, Fragment, isValidElement, memo, use, useCallback, useId, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
4
4
|
import { Cell, Pie, Sector } from "recharts";
|
|
5
|
-
import { categoryCells } from "./category-cells.js";
|
|
5
|
+
import { categoryCells, filterCategoryRows } from "./category-cells.js";
|
|
6
6
|
import { useChart } from "./chart-context.js";
|
|
7
|
+
import { useChartInteraction, useInteractionFocus } from "./chart-interaction.js";
|
|
7
8
|
import { EmphasisMark } from "./emphasis.js";
|
|
8
9
|
import { useChartKeyboard, useLineInteraction } from "./line-chart.js";
|
|
9
10
|
import { PieMotion } from "./pie-chart.js";
|
|
10
11
|
import { PieMaterialFilter } from "./pie-material.js";
|
|
12
|
+
import { registerPiePinComponent } from "./pie-pin-identity.js";
|
|
13
|
+
// Native Pie keys its animation subtree by props identity even with animation disabled.
|
|
14
|
+
// Pointer/pin context updates must not replace an unchanged pressed native sector.
|
|
15
|
+
const StablePie = memo(Pie);
|
|
11
16
|
// Recharts also supports Cell props as data when neither the chart nor Pie supplies rows.
|
|
12
17
|
function cellProps(children) {
|
|
13
18
|
const result = [];
|
|
@@ -30,11 +35,45 @@ function sameCells(previous, next) {
|
|
|
30
35
|
Object.keys(props).every((key) => Object.is(old[key], props[key])));
|
|
31
36
|
}));
|
|
32
37
|
}
|
|
38
|
+
// Capture original rendered identity before an inner Cell handler can commit new rows.
|
|
39
|
+
function captureCellIdentity(children, keyAt, identities) {
|
|
40
|
+
let index = 0;
|
|
41
|
+
function capture(parts) {
|
|
42
|
+
return Children.map(parts, (child) => {
|
|
43
|
+
if (!isValidElement(child))
|
|
44
|
+
return child;
|
|
45
|
+
if (child.type === Fragment)
|
|
46
|
+
return cloneElement(child, {}, capture(child.props.children));
|
|
47
|
+
if (child.type !== Cell)
|
|
48
|
+
return child;
|
|
49
|
+
const key = keyAt(index++);
|
|
50
|
+
if (key === undefined)
|
|
51
|
+
return child;
|
|
52
|
+
return cloneElement(child, {
|
|
53
|
+
onClick: (event) => {
|
|
54
|
+
identities.set(event.nativeEvent, key);
|
|
55
|
+
child.props.onClick?.(event);
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return capture(children);
|
|
61
|
+
}
|
|
33
62
|
// One chart-level angular window reveals unchanged native sectors.
|
|
34
|
-
function EntranceSector({ material, emphasisKey, scope, enabled, seriesStartAngle, seriesEndAngle, ...props }) {
|
|
63
|
+
function EntranceSector({ material, emphasisKey, scope, enabled, interactionKey, seriesStartAngle, seriesEndAngle, ...props }) {
|
|
35
64
|
const keyboard = useChartKeyboard();
|
|
65
|
+
const interaction = useChartInteraction();
|
|
36
66
|
const { reveal, progress, direction } = use(PieMotion);
|
|
37
|
-
const semantic =
|
|
67
|
+
const semantic = interactionKey
|
|
68
|
+
? interactionKey(props.index ?? -1)
|
|
69
|
+
: emphasisKey
|
|
70
|
+
? emphasisKey(props.payload)
|
|
71
|
+
: props.name;
|
|
72
|
+
const interactive = interactionKey !== undefined &&
|
|
73
|
+
interaction.interactive &&
|
|
74
|
+
interaction.markActivation &&
|
|
75
|
+
interaction.eligible.includes(String(semantic));
|
|
76
|
+
const focusRef = useInteractionFocus(String(semantic), interactive);
|
|
38
77
|
const generatedId = useId();
|
|
39
78
|
const sourceId = `kind-ui-pie-${generatedId.replace(/[^a-zA-Z0-9_-]/g, "_")}-paint`;
|
|
40
79
|
const maskId = `${sourceId}-alpha`;
|
|
@@ -151,25 +190,102 @@ function EntranceSector({ material, emphasisKey, scope, enabled, seriesStartAngl
|
|
|
151
190
|
const revealStart = fromStart ? seriesStartAngle : seriesStartAngle + nativeSpan;
|
|
152
191
|
const revealSpan = Math.abs(nativeSpan) * (clockwise ? -1 : 1);
|
|
153
192
|
const { className, cornerRadius, onClick, onMouseDown, onMouseUp, onMouseMove, onMouseOver, onMouseOut, onMouseEnter, onMouseLeave, ...sector } = props;
|
|
154
|
-
return (_jsx(EmphasisMark, { enabled: enabled &&
|
|
155
|
-
(emphasisKey !== undefined ? semantic !== undefined : typeof semantic === "string"), target: { kind: "sector", key: String(semantic), scope, seriesKey: String(semantic) }, keyboardActive: keyboard && props.isActive
|
|
193
|
+
return (_jsx(EmphasisMark, { ref: focusRef, "data-interaction-focus-key": interactive ? String(semantic) : undefined, persistent: interactionKey !== undefined, enabled: enabled &&
|
|
194
|
+
(emphasisKey !== undefined ? semantic !== undefined : typeof semantic === "string"), target: { kind: "sector", key: String(semantic), scope, seriesKey: String(semantic) }, keyboardActive: keyboard && props.isActive === true, role: interactive ? "button" : undefined, tabIndex: interactive ? 0 : undefined, "aria-label": interactive
|
|
195
|
+
? `${interaction.mode === "focus" ? "Highlight" : "Toggle"} ${String(semantic)}`
|
|
196
|
+
: undefined, "aria-pressed": interactive
|
|
197
|
+
? interaction.mode === "focus"
|
|
198
|
+
? interaction.selected === semantic
|
|
199
|
+
: true
|
|
200
|
+
: undefined, onKeyDown: (event) => {
|
|
201
|
+
if (interactive &&
|
|
202
|
+
event.target === event.currentTarget &&
|
|
203
|
+
!event.defaultPrevented &&
|
|
204
|
+
(event.key === "Enter" || event.key === " ")) {
|
|
205
|
+
if (!event.repeat)
|
|
206
|
+
interaction.activate({ kind: "category", key: String(semantic) }, "mark", event);
|
|
207
|
+
event.preventDefault();
|
|
208
|
+
}
|
|
209
|
+
}, children: _jsxs("g", { ref: markGroup, children: [maskEntrance && (_jsx("defs", { children: _jsx("mask", { id: entranceId, maskUnits: "userSpaceOnUse", x: props.cx - props.outerRadius - margin, y: props.cy - props.outerRadius - margin, width: (props.outerRadius + margin) * 2, height: (props.outerRadius + margin) * 2, style: { maskType: "alpha" }, children: _jsx(Sector, { fill: "white", "data-kind-ui": "pie-entrance-window", "data-direction": direction, "data-progress": progress, cx: props.cx, cy: props.cy, innerRadius: 0, outerRadius: props.outerRadius + margin, startAngle: revealStart, endAngle: revealStart + revealSpan * progress }) }) })), _jsxs("g", { mask: maskEntrance ? `url(#${entranceId})` : undefined, children: [materialized && (_jsxs("defs", { "data-kind-ui": "pie-material", "data-material": material, pointerEvents: "none", children: [_jsx(PieMaterialFilter, { material: material, id: filterId, strokeWidth: Math.max(0, Number.isFinite(resolvedStroke) ? resolvedStroke : 0, paintStroke), cx: props.cx, cy: props.cy, radius: props.outerRadius, thickness: props.outerRadius - props.innerRadius, bounds: paintBounds }), inset && (_jsx("mask", { id: maskId, maskUnits: "userSpaceOnUse", x: paintBounds?.x ?? Math.floor(props.cx - props.outerRadius - margin), y: paintBounds?.y ?? Math.floor(props.cy - props.outerRadius - margin), width: paintBounds?.width ?? Math.ceil(props.outerRadius * 2 + margin * 2) + 1, height: paintBounds?.height ?? Math.ceil(props.outerRadius * 2 + margin * 2) + 1, style: { maskType: "alpha" }, children: _jsx("use", { href: `#${sourceId}` }) }))] })), materialized && material === "glow" && (
|
|
156
210
|
// biome-ignore lint/a11y/noAriaHiddenOnFocusable: SVG decoration is explicitly nonfocusable and ignores pointer events.
|
|
157
|
-
_jsx("g", { pointerEvents: "none", focusable: "false", "aria-hidden": "true", "data-kind-ui": "pie-halo", style: { clipPath: paintClip }, transform: paintClip !== "none" ? clipTransform.forward : undefined, children: _jsx("g", { transform: paintClip !== "none" ? clipTransform.inverse : undefined, filter: `url(#${filterId})`, children: _jsx("use", { href: `#${sourceId}` }) }) })), _jsx("g", { ...(inset ? { filter: `url(#${filterId})` } : {}), ...(inset ? { mask: `url(#${maskId})` } : {}), children: _jsxs("g", { id: sourceId, children: [materialized && (_jsx("rect", { x: paintBounds?.x ?? props.cx - props.outerRadius - margin, y: paintBounds?.y ?? props.cy - props.outerRadius - margin, width: paintBounds?.width ?? props.outerRadius * 2 + margin * 2, height: paintBounds?.height ?? props.outerRadius * 2 + margin * 2, fill: "white", fillOpacity: 0, pointerEvents: "none" })), _jsx(Sector, { ...sector, ...(onClick ? { onClick } : {}), ...(onMouseDown ? { onMouseDown } : {}), ...(onMouseUp ? { onMouseUp } : {}), ...(onMouseMove ? { onMouseMove } : {}), ...(onMouseOver ? { onMouseOver } : {}), ...(onMouseOut ? { onMouseOut } : {}), ...(onMouseEnter ? { onMouseEnter } : {}), ...(onMouseLeave ? { onMouseLeave } : {}), ...(className !== undefined ? { className } : {}), ...(cornerRadius !== undefined ? { cornerRadius } : {}), "data-kind-ui": "pie-sector", "data-reveal": maskEntrance ? "on" : "off" })] }) })] })] }) }));
|
|
211
|
+
_jsx("g", { pointerEvents: "none", focusable: "false", "aria-hidden": "true", "data-kind-ui": "pie-halo", style: { clipPath: paintClip }, transform: paintClip !== "none" ? clipTransform.forward : undefined, children: _jsx("g", { transform: paintClip !== "none" ? clipTransform.inverse : undefined, filter: `url(#${filterId})`, children: _jsx("use", { href: `#${sourceId}` }) }) })), _jsx("g", { ...(inset ? { filter: `url(#${filterId})` } : {}), ...(inset ? { mask: `url(#${maskId})` } : {}), children: _jsxs("g", { id: sourceId, children: [materialized && (_jsx("rect", { x: paintBounds?.x ?? props.cx - props.outerRadius - margin, y: paintBounds?.y ?? props.cy - props.outerRadius - margin, width: paintBounds?.width ?? props.outerRadius * 2 + margin * 2, height: paintBounds?.height ?? props.outerRadius * 2 + margin * 2, fill: "white", fillOpacity: 0, pointerEvents: "none" })), _jsx(Sector, { ...sector, ...(onClick ? { onClick } : {}), ...(onMouseDown ? { onMouseDown } : {}), ...(onMouseUp ? { onMouseUp } : {}), ...(onMouseMove ? { onMouseMove } : {}), ...(onMouseOver ? { onMouseOver } : {}), ...(onMouseOut ? { onMouseOut } : {}), ...(onMouseEnter ? { onMouseEnter } : {}), ...(onMouseLeave ? { onMouseLeave } : {}), ...(className !== undefined ? { className } : {}), ...(cornerRadius !== undefined ? { cornerRadius } : {}), "data-kind-ui": "pie-sector", "data-sector-span": props.endAngle - props.startAngle, "data-reveal": maskEntrance ? "on" : "off" })] }) })] })] }) }));
|
|
158
212
|
}
|
|
159
213
|
/** A native Pie. Use innerRadius for donuts; category data, Cells and visibility are consumer-owned. */
|
|
160
214
|
export function PieSeries(props) {
|
|
161
|
-
const { material = "plain", emphasisKey, categoryKey, ...nativeProps } = props;
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
215
|
+
const { material = "plain", glowCategories, emphasisKey, categoryKey, interactionBinding, ...nativeProps } = props;
|
|
216
|
+
const interaction = useChartInteraction();
|
|
217
|
+
if (interactionBinding &&
|
|
218
|
+
(interaction.kind !== "category" || categoryKey === undefined || props.data === undefined))
|
|
219
|
+
throw new Error("PieSeries Root interaction binding requires category-kind Root, categoryKey and explicit data");
|
|
220
|
+
const filtered = useMemo(() => interactionBinding && props.data && categoryKey !== undefined
|
|
221
|
+
? filterCategoryRows(props.data, categoryKey, interaction.visible, props.children)
|
|
222
|
+
: undefined, [interactionBinding, props.data, categoryKey, interaction.visible, props.children]);
|
|
223
|
+
const data = filtered?.data ?? props.data;
|
|
224
|
+
const originalChildren = filtered?.children ?? props.children;
|
|
225
|
+
const resolveInteractionKey = useCallback((index) => {
|
|
226
|
+
// Cell props can override native payload fields; native index still addresses our rendered rows.
|
|
227
|
+
const row = data?.[index];
|
|
228
|
+
if (row === undefined)
|
|
229
|
+
return undefined;
|
|
230
|
+
if (typeof categoryKey === "function")
|
|
231
|
+
return categoryKey(row);
|
|
232
|
+
if (categoryKey !== undefined &&
|
|
233
|
+
typeof row === "object" &&
|
|
234
|
+
row !== null &&
|
|
235
|
+
Object.hasOwn(row, categoryKey))
|
|
236
|
+
return String(row[categoryKey]);
|
|
237
|
+
throw new Error("PieSeries interaction requires a stable category key");
|
|
238
|
+
}, [categoryKey, data]);
|
|
239
|
+
const interactionKey = interactionBinding ? resolveInteractionKey : undefined;
|
|
240
|
+
const { config, paints } = useChart();
|
|
241
|
+
const children = useMemo(() => categoryKey === undefined
|
|
242
|
+
? originalChildren
|
|
243
|
+
: categoryCells(data, categoryKey, config, paints, originalChildren, props.fill), [categoryKey, data, config, paints, originalChildren, props.fill]);
|
|
244
|
+
const identities = useRef(new WeakMap());
|
|
245
|
+
const boundChildren = useMemo(() => interactionKey && interaction.interactive && interaction.markActivation
|
|
246
|
+
? captureCellIdentity(children, interactionKey, identities.current)
|
|
247
|
+
: children, [children, interactionKey, interaction.interactive, interaction.markActivation]);
|
|
248
|
+
const latestClick = useRef({ handler: props.onClick, interactionKey, interaction });
|
|
249
|
+
useLayoutEffect(() => {
|
|
250
|
+
latestClick.current = { handler: props.onClick, interactionKey, interaction };
|
|
251
|
+
});
|
|
252
|
+
const activate = useCallback((...args) => {
|
|
253
|
+
// Capture the pressed row before a consumer can synchronously reorder/filter the data.
|
|
254
|
+
const pressed = latestClick.current;
|
|
255
|
+
const key = identities.current.get(args[2].nativeEvent) ?? pressed.interactionKey?.(args[1]);
|
|
256
|
+
pressed.handler?.(...args);
|
|
257
|
+
if (key !== undefined)
|
|
258
|
+
latestClick.current.interaction.activate({ kind: "category", key }, "mark", args[2]);
|
|
259
|
+
}, []);
|
|
260
|
+
const onClick = interactionBinding && interaction.interactive && interaction.markActivation
|
|
261
|
+
? activate
|
|
262
|
+
: props.onClick;
|
|
263
|
+
if (glowCategories !== undefined && categoryKey === undefined)
|
|
264
|
+
throw new Error("glowCategories requires categoryKey and explicit series data");
|
|
265
|
+
// Native sector indices align with explicit data, but membership uses the original
|
|
266
|
+
// row identity, before native Cell props can override payload fields.
|
|
267
|
+
const glowRows = useMemo(() => {
|
|
268
|
+
if (categoryKey === undefined || glowCategories === undefined)
|
|
269
|
+
return undefined;
|
|
270
|
+
const glowing = new Set(glowCategories);
|
|
271
|
+
return data?.map((row) => {
|
|
272
|
+
const key = typeof categoryKey === "function"
|
|
273
|
+
? categoryKey(row)
|
|
274
|
+
: row !== null && typeof row === "object" && Object.hasOwn(row, categoryKey)
|
|
275
|
+
? row[categoryKey]
|
|
276
|
+
: undefined;
|
|
277
|
+
return typeof key === "string" && glowing.has(key);
|
|
278
|
+
});
|
|
279
|
+
}, [data, categoryKey, glowCategories]);
|
|
166
280
|
const seriesId = useId();
|
|
167
281
|
const { invalidate, emphasisScope } = useLineInteraction();
|
|
168
282
|
const scope = `${emphasisScope}/${seriesId}`;
|
|
169
|
-
const sectorShape = useCallback((sector) => (_jsx(EntranceSector, { ...sector, material: material, scope: scope, emphasisKey: emphasisKey, enabled: props.activeShape === undefined && props.inactiveShape === undefined, seriesStartAngle: props.startAngle ?? 0, seriesEndAngle: props.endAngle ?? 360 })), [
|
|
283
|
+
const sectorShape = useCallback((sector) => (_jsx(EntranceSector, { ...sector, material: sector.index !== undefined && glowRows?.[sector.index] ? "glow" : material, interactionKey: interactionKey, scope: scope, emphasisKey: emphasisKey, enabled: props.activeShape === undefined && props.inactiveShape === undefined, seriesStartAngle: props.startAngle ?? 0, seriesEndAngle: props.endAngle ?? 360 })), [
|
|
170
284
|
material,
|
|
285
|
+
glowRows,
|
|
171
286
|
scope,
|
|
172
287
|
emphasisKey,
|
|
288
|
+
interactionKey,
|
|
173
289
|
props.activeShape,
|
|
174
290
|
props.inactiveShape,
|
|
175
291
|
props.startAngle,
|
|
@@ -193,6 +309,8 @@ export function PieSeries(props) {
|
|
|
193
309
|
props.activeShape,
|
|
194
310
|
props.inactiveShape,
|
|
195
311
|
material,
|
|
312
|
+
categoryKey,
|
|
313
|
+
glowCategories,
|
|
196
314
|
];
|
|
197
315
|
const cells = cellProps(props.children);
|
|
198
316
|
const previousCells = useRef(cells);
|
|
@@ -204,5 +322,10 @@ export function PieSeries(props) {
|
|
|
204
322
|
previousCells.current = cells;
|
|
205
323
|
previous.current = inputs;
|
|
206
324
|
});
|
|
207
|
-
return (_jsx(
|
|
325
|
+
return (_jsx(StablePie, { ...nativeProps, ...(data !== undefined ? { data } : {}), ...(onClick !== undefined ? { onClick } : {}), stroke: props.stroke ?? "none", shape: props.shape ?? sectorShape, ...(interactionBinding && props.shape === undefined && props.activeShape === undefined
|
|
326
|
+
? { activeShape: sectorShape }
|
|
327
|
+
: {}), ...(interactionBinding && props.shape === undefined && props.inactiveShape === undefined
|
|
328
|
+
? { inactiveShape: sectorShape }
|
|
329
|
+
: {}), isAnimationActive: false, children: boundChildren }));
|
|
208
330
|
}
|
|
331
|
+
registerPiePinComponent(PieSeries, "series");
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export declare const PieTooltipPin: import("react").Context<number | undefined>;
|
|
3
|
+
/** Resolve against current rows; the returned index is never retained as identity. */
|
|
4
|
+
export declare function pinnedPieIndex(children: ReactNode, category: string, visibleSeries?: readonly string[]): number | undefined;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Children, createContext, Fragment, isValidElement } from "react";
|
|
3
|
+
import { piePinComponentKind } from "./pie-pin-identity.js";
|
|
4
|
+
export const PieTooltipPin = createContext(undefined);
|
|
5
|
+
/** Resolve against current rows; the returned index is never retained as identity. */
|
|
6
|
+
export function pinnedPieIndex(children, category, visibleSeries) {
|
|
7
|
+
const series = [];
|
|
8
|
+
let tooltips = 0;
|
|
9
|
+
let unsupported = false;
|
|
10
|
+
function collect(nodes) {
|
|
11
|
+
Children.forEach(nodes, (child) => {
|
|
12
|
+
if (!isValidElement(child)) {
|
|
13
|
+
if (child !== null && child !== undefined && typeof child !== "boolean")
|
|
14
|
+
unsupported = true;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (child.type === Fragment)
|
|
18
|
+
collect(child.props.children);
|
|
19
|
+
else if (piePinComponentKind(child.type) === "series")
|
|
20
|
+
series.push(child.props);
|
|
21
|
+
else if (piePinComponentKind(child.type) === "tooltip")
|
|
22
|
+
tooltips += 1;
|
|
23
|
+
else
|
|
24
|
+
unsupported = true;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
collect(children);
|
|
28
|
+
const candidate = series[0];
|
|
29
|
+
if (series.length !== 1 ||
|
|
30
|
+
tooltips !== 1 ||
|
|
31
|
+
unsupported ||
|
|
32
|
+
!candidate?.data ||
|
|
33
|
+
candidate.categoryKey === undefined)
|
|
34
|
+
throw new Error("defaultPinnedCategory requires one direct PieSeries with explicit data and categoryKey, and one direct Tooltip");
|
|
35
|
+
const { data, categoryKey, hide } = candidate;
|
|
36
|
+
if (hide)
|
|
37
|
+
return undefined;
|
|
38
|
+
const keys = data.map((row) => typeof categoryKey === "function"
|
|
39
|
+
? categoryKey(row)
|
|
40
|
+
: row !== null && typeof row === "object" && Object.hasOwn(row, categoryKey)
|
|
41
|
+
? row[categoryKey]
|
|
42
|
+
: undefined);
|
|
43
|
+
// Ambiguous identities are never silently resolved by position, including hidden rows.
|
|
44
|
+
if (keys.filter((key) => key === category).length !== 1)
|
|
45
|
+
return undefined;
|
|
46
|
+
// Only a bound Pie filters its native data. Unbound composition keeps its original indices.
|
|
47
|
+
const renderedKeys = candidate.interactionBinding === "root" && visibleSeries !== undefined
|
|
48
|
+
? keys.filter((key) => typeof key === "string" && visibleSeries.includes(key))
|
|
49
|
+
: keys;
|
|
50
|
+
const index = renderedKeys.indexOf(category);
|
|
51
|
+
return index < 0 ? undefined : index;
|
|
52
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type DotProps } from "recharts";
|
|
2
|
+
export type PointStyle = "default" | "border" | "colored-border";
|
|
3
|
+
export type PointMarkerProps = DotProps & {
|
|
4
|
+
variant?: PointStyle | undefined;
|
|
5
|
+
};
|
|
6
|
+
/** Paint only: native coordinates, radius, events and inspection remain owned by Recharts. */
|
|
7
|
+
export declare function markerPaint(variant: PointStyle, color: string | undefined): {
|
|
8
|
+
fill?: never;
|
|
9
|
+
stroke?: never;
|
|
10
|
+
strokeWidth?: never;
|
|
11
|
+
strokeDasharray?: never;
|
|
12
|
+
} | {
|
|
13
|
+
fill: string | undefined;
|
|
14
|
+
stroke: string | undefined;
|
|
15
|
+
strokeWidth: number;
|
|
16
|
+
strokeDasharray: string;
|
|
17
|
+
};
|
|
18
|
+
/** Native Dot renderer: variant paint wins engine paint; SVG style remains consumer-owned. */
|
|
19
|
+
export declare function PointMarker({ variant, ...props }: PointMarkerProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Dot } from "recharts";
|
|
4
|
+
/** Paint only: native coordinates, radius, events and inspection remain owned by Recharts. */
|
|
5
|
+
export function markerPaint(variant, color) {
|
|
6
|
+
if (variant === "default")
|
|
7
|
+
return {};
|
|
8
|
+
const surface = "var(--kind-ui-chart-marker-surface, var(--card, white))";
|
|
9
|
+
return {
|
|
10
|
+
fill: variant === "border" ? color : surface,
|
|
11
|
+
stroke: variant === "border" ? surface : color,
|
|
12
|
+
strokeWidth: 2,
|
|
13
|
+
strokeDasharray: "none",
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** Native Dot renderer: variant paint wins engine paint; SVG style remains consumer-owned. */
|
|
17
|
+
export function PointMarker({ variant = "default", ...props }) {
|
|
18
|
+
return (_jsx(Dot, { ...props, ...markerPaint(variant, props.stroke ?? props.fill), "data-kind-ui": "point-marker", "data-point-style": variant }));
|
|
19
|
+
}
|