@kind-ui/charts 0.1.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 +20 -0
- package/LICENSE +21 -0
- package/README.md +966 -0
- package/dist/activity-rings.d.ts +47 -0
- package/dist/activity-rings.js +55 -0
- package/dist/animation.d.ts +27 -0
- package/dist/animation.js +102 -0
- package/dist/area-chart.d.ts +9 -0
- package/dist/area-chart.js +33 -0
- package/dist/area-clay.d.ts +2 -0
- package/dist/area-clay.js +6 -0
- package/dist/area-material.d.ts +8 -0
- package/dist/area-material.js +66 -0
- package/dist/area-series.d.ts +11 -0
- package/dist/area-series.js +34 -0
- package/dist/bar-category.d.ts +5 -0
- package/dist/bar-category.js +35 -0
- package/dist/bar-chart.d.ts +17 -0
- package/dist/bar-chart.js +42 -0
- package/dist/bar-clay.d.ts +4 -0
- package/dist/bar-clay.js +6 -0
- package/dist/bar-material.d.ts +8 -0
- package/dist/bar-material.js +19 -0
- package/dist/bar-paper.d.ts +2 -0
- package/dist/bar-paper.js +6 -0
- package/dist/bar-series.d.ts +13 -0
- package/dist/bar-series.js +178 -0
- package/dist/box-material.d.ts +14 -0
- package/dist/box-material.js +30 -0
- package/dist/box-plot.d.ts +50 -0
- package/dist/box-plot.js +218 -0
- package/dist/category-cells.d.ts +6 -0
- package/dist/category-cells.js +41 -0
- package/dist/chart-context.d.ts +11 -0
- package/dist/chart-context.js +9 -0
- package/dist/combo-chart.d.ts +16 -0
- package/dist/combo-chart.js +64 -0
- package/dist/configured-line-chart.d.ts +79 -0
- package/dist/configured-line-chart.js +77 -0
- package/dist/emphasis.d.ts +44 -0
- package/dist/emphasis.js +128 -0
- package/dist/heatmap-model.d.ts +38 -0
- package/dist/heatmap-model.js +73 -0
- package/dist/heatmap.d.ts +56 -0
- package/dist/heatmap.js +366 -0
- package/dist/histogram-chart.d.ts +14 -0
- package/dist/histogram-chart.js +27 -0
- package/dist/histogram-data.d.ts +17 -0
- package/dist/histogram-data.js +66 -0
- package/dist/histogram-material.d.ts +13 -0
- package/dist/histogram-material.js +8 -0
- package/dist/histogram-series.d.ts +16 -0
- package/dist/histogram-series.js +67 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +36 -0
- package/dist/legend.d.ts +16 -0
- package/dist/legend.js +30 -0
- package/dist/line-chart.d.ts +41 -0
- package/dist/line-chart.js +160 -0
- package/dist/line-material.d.ts +9 -0
- package/dist/line-material.js +24 -0
- package/dist/line-series.d.ts +15 -0
- package/dist/line-series.js +40 -0
- package/dist/pie-chart.d.ts +17 -0
- package/dist/pie-chart.js +68 -0
- package/dist/pie-material.d.ts +19 -0
- package/dist/pie-material.js +16 -0
- package/dist/pie-series.d.ts +14 -0
- package/dist/pie-series.js +208 -0
- package/dist/polar-chart.d.ts +33 -0
- package/dist/polar-chart.js +145 -0
- package/dist/polar-material.d.ts +7 -0
- package/dist/polar-material.js +9 -0
- package/dist/polar-series.d.ts +17 -0
- package/dist/polar-series.js +169 -0
- package/dist/radar-interaction.d.ts +29 -0
- package/dist/radar-interaction.js +138 -0
- package/dist/radial-bar-label.d.ts +15 -0
- package/dist/radial-bar-label.js +136 -0
- package/dist/radial-category.d.ts +4 -0
- package/dist/radial-category.js +4 -0
- package/dist/root.d.ts +14 -0
- package/dist/root.js +29 -0
- package/dist/sankey-chart.d.ts +33 -0
- package/dist/sankey-chart.js +142 -0
- package/dist/sankey-colors.d.ts +10 -0
- package/dist/sankey-colors.js +6 -0
- package/dist/sankey-data.d.ts +30 -0
- package/dist/sankey-data.js +65 -0
- package/dist/sankey-finish.d.ts +13 -0
- package/dist/sankey-finish.js +10 -0
- package/dist/sankey-legend.d.ts +14 -0
- package/dist/sankey-legend.js +9 -0
- package/dist/sankey-marks.d.ts +31 -0
- package/dist/sankey-marks.js +67 -0
- package/dist/sankey-table.d.ts +11 -0
- package/dist/sankey-table.js +8 -0
- package/dist/scatter-chart.d.ts +16 -0
- package/dist/scatter-chart.js +64 -0
- package/dist/scatter-material.d.ts +8 -0
- package/dist/scatter-material.js +39 -0
- package/dist/scatter-series.d.ts +11 -0
- package/dist/scatter-series.js +74 -0
- package/dist/scatter-tooltip.d.ts +24 -0
- package/dist/scatter-tooltip.js +96 -0
- package/dist/styles.css +443 -0
- package/dist/surface-material.d.ts +13 -0
- package/dist/surface-material.js +7 -0
- package/dist/tooltip-content.d.ts +19 -0
- package/dist/tooltip-content.js +60 -0
- package/dist/tooltip-number.d.ts +4 -0
- package/dist/tooltip-number.js +68 -0
- package/dist/tooltip.d.ts +29 -0
- package/dist/tooltip.js +73 -0
- package/dist/types.d.ts +11 -0
- package/dist/types.js +1 -0
- package/dist/waterfall-data.d.ts +24 -0
- package/dist/waterfall-data.js +45 -0
- package/dist/waterfall-series.d.ts +15 -0
- package/dist/waterfall-series.js +35 -0
- package/package.json +49 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use client";
|
|
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";
|
|
4
|
+
import { Cell, Pie, Sector } from "recharts";
|
|
5
|
+
import { categoryCells } from "./category-cells.js";
|
|
6
|
+
import { useChart } from "./chart-context.js";
|
|
7
|
+
import { EmphasisMark } from "./emphasis.js";
|
|
8
|
+
import { useChartKeyboard, useLineInteraction } from "./line-chart.js";
|
|
9
|
+
import { PieMotion } from "./pie-chart.js";
|
|
10
|
+
import { PieMaterialFilter } from "./pie-material.js";
|
|
11
|
+
// Recharts also supports Cell props as data when neither the chart nor Pie supplies rows.
|
|
12
|
+
function cellProps(children) {
|
|
13
|
+
const result = [];
|
|
14
|
+
Children.forEach(children, (child) => {
|
|
15
|
+
if (!isValidElement(child))
|
|
16
|
+
return;
|
|
17
|
+
if (child.type === Cell)
|
|
18
|
+
result.push(child.props);
|
|
19
|
+
else if (child.type === Fragment)
|
|
20
|
+
result.push(...cellProps(child.props.children));
|
|
21
|
+
});
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
function sameCells(previous, next) {
|
|
25
|
+
return (previous.length === next.length &&
|
|
26
|
+
next.every((props, index) => {
|
|
27
|
+
const old = previous[index];
|
|
28
|
+
return (old !== undefined &&
|
|
29
|
+
Object.keys(old).length === Object.keys(props).length &&
|
|
30
|
+
Object.keys(props).every((key) => Object.is(old[key], props[key])));
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
// One chart-level angular window reveals unchanged native sectors.
|
|
34
|
+
function EntranceSector({ material, emphasisKey, scope, enabled, seriesStartAngle, seriesEndAngle, ...props }) {
|
|
35
|
+
const keyboard = useChartKeyboard();
|
|
36
|
+
const { reveal, progress, direction } = use(PieMotion);
|
|
37
|
+
const semantic = emphasisKey ? emphasisKey(props.payload) : props.name;
|
|
38
|
+
const generatedId = useId();
|
|
39
|
+
const sourceId = `kind-ui-pie-${generatedId.replace(/[^a-zA-Z0-9_-]/g, "_")}-paint`;
|
|
40
|
+
const maskId = `${sourceId}-alpha`;
|
|
41
|
+
const filterId = `kind-ui-pie-${generatedId.replace(/[^a-zA-Z0-9_-]/g, "_")}-material`;
|
|
42
|
+
// Unresolved CSS stroke widths retain native rendering rather than receiving guessed bounds.
|
|
43
|
+
const strokeWidth = props.style?.strokeWidth ?? props.strokeWidth ?? 0;
|
|
44
|
+
const resolvedStroke = Number(strokeWidth);
|
|
45
|
+
const markGroup = useRef(null);
|
|
46
|
+
const [paintFilter, setPaintFilter] = useState("none");
|
|
47
|
+
const [paintClip, setPaintClip] = useState("none");
|
|
48
|
+
// Measure native ownership before attaching paint, including the first non-Plain render.
|
|
49
|
+
const [cssTransformOwned, setCssTransformOwned] = useState();
|
|
50
|
+
const [clipTransform, setClipTransform] = useState({ forward: "", inverse: "" });
|
|
51
|
+
const [paintBounds, setPaintBounds] = useState();
|
|
52
|
+
const [paintStroke, setPaintStroke] = useState(Number.isFinite(resolvedStroke) ? resolvedStroke : 0);
|
|
53
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: class/id changes can change stylesheet-owned paint.
|
|
54
|
+
useLayoutEffect(() => {
|
|
55
|
+
if ((material === "plain" && !reveal) ||
|
|
56
|
+
props.filter !== undefined ||
|
|
57
|
+
props.style?.filter !== undefined ||
|
|
58
|
+
!Number.isFinite(resolvedStroke))
|
|
59
|
+
return;
|
|
60
|
+
const path = markGroup.current?.querySelector("path");
|
|
61
|
+
if (!path)
|
|
62
|
+
return;
|
|
63
|
+
const computed = getComputedStyle(path);
|
|
64
|
+
// SVG attributes still receive finishes. CSS overrides keep native rasterization:
|
|
65
|
+
// offscreen masks can change their antialiased alpha even with identical geometry.
|
|
66
|
+
let attributeTransform = new DOMMatrix();
|
|
67
|
+
for (let i = 0; i < path.transform.baseVal.numberOfItems; i++)
|
|
68
|
+
attributeTransform = attributeTransform.multiply(path.transform.baseVal.getItem(i).matrix);
|
|
69
|
+
const computedTransform = computed.transform === "none" ? new DOMMatrix() : new DOMMatrix(computed.transform);
|
|
70
|
+
const differentTransform = ["a", "b", "c", "d", "e", "f"].some((key) => Math.abs(attributeTransform[key] - computedTransform[key]) >
|
|
71
|
+
0.00001 * Math.max(1, Math.abs(attributeTransform[key]), Math.abs(computedTransform[key])));
|
|
72
|
+
const ownsTransform = differentTransform ||
|
|
73
|
+
!computedTransform.is2D ||
|
|
74
|
+
computed.translate !== "none" ||
|
|
75
|
+
computed.rotate !== "none" ||
|
|
76
|
+
computed.scale !== "none" ||
|
|
77
|
+
(props.style?.transform !== undefined && computed.transform !== "none");
|
|
78
|
+
setCssTransformOwned((old) => (old === ownsTransform ? old : ownsTransform));
|
|
79
|
+
if (computed.filter !== paintFilter)
|
|
80
|
+
setPaintFilter(computed.filter);
|
|
81
|
+
if (computed.clipPath !== paintClip)
|
|
82
|
+
setPaintClip(computed.clipPath);
|
|
83
|
+
const width = Number.parseFloat(computed.strokeWidth);
|
|
84
|
+
if (Number.isFinite(width) && width !== paintStroke)
|
|
85
|
+
setPaintStroke(width);
|
|
86
|
+
const parentMatrix = markGroup.current?.getCTM();
|
|
87
|
+
const pathMatrix = path.getCTM();
|
|
88
|
+
if (parentMatrix && pathMatrix) {
|
|
89
|
+
if (parentMatrix.a * parentMatrix.d - parentMatrix.b * parentMatrix.c === 0)
|
|
90
|
+
return;
|
|
91
|
+
const matrix = parentMatrix.inverse().multiply(pathMatrix);
|
|
92
|
+
if (matrix.a * matrix.d - matrix.b * matrix.c === 0)
|
|
93
|
+
return;
|
|
94
|
+
const inverse = matrix.inverse();
|
|
95
|
+
const svgMatrix = (m) => `matrix(${m.a} ${m.b} ${m.c} ${m.d} ${m.e} ${m.f})`;
|
|
96
|
+
const nextTransform = { forward: svgMatrix(matrix), inverse: svgMatrix(inverse) };
|
|
97
|
+
setClipTransform((old) => old.forward === nextTransform.forward && old.inverse === nextTransform.inverse
|
|
98
|
+
? old
|
|
99
|
+
: nextTransform);
|
|
100
|
+
const center = new DOMPoint(props.cx, props.cy).matrixTransform(matrix);
|
|
101
|
+
const radius = props.outerRadius + (Number.isFinite(width) ? width : 0) / 2;
|
|
102
|
+
const rx = Math.hypot(matrix.a, matrix.c) * radius + 16;
|
|
103
|
+
const ry = Math.hypot(matrix.b, matrix.d) * radius + 16;
|
|
104
|
+
const x = Math.floor(center.x - rx);
|
|
105
|
+
const y = Math.floor(center.y - ry);
|
|
106
|
+
const next = {
|
|
107
|
+
x,
|
|
108
|
+
y,
|
|
109
|
+
width: Math.ceil(center.x + rx) - x,
|
|
110
|
+
height: Math.ceil(center.y + ry) - y,
|
|
111
|
+
};
|
|
112
|
+
setPaintBounds((old) => old &&
|
|
113
|
+
Object.keys(next).every((key) => old[key] === next[key])
|
|
114
|
+
? old
|
|
115
|
+
: next);
|
|
116
|
+
}
|
|
117
|
+
}, [
|
|
118
|
+
reveal,
|
|
119
|
+
material,
|
|
120
|
+
props.filter,
|
|
121
|
+
props.style,
|
|
122
|
+
props.className,
|
|
123
|
+
props.id,
|
|
124
|
+
props.clipPath,
|
|
125
|
+
props.transform,
|
|
126
|
+
props.cx,
|
|
127
|
+
props.cy,
|
|
128
|
+
props.outerRadius,
|
|
129
|
+
resolvedStroke,
|
|
130
|
+
paintFilter,
|
|
131
|
+
paintClip,
|
|
132
|
+
paintStroke,
|
|
133
|
+
]);
|
|
134
|
+
const materialized = material !== "plain" &&
|
|
135
|
+
props.filter === undefined &&
|
|
136
|
+
props.style?.filter === undefined &&
|
|
137
|
+
paintFilter === "none" &&
|
|
138
|
+
cssTransformOwned === false &&
|
|
139
|
+
Number.isFinite(resolvedStroke);
|
|
140
|
+
const inset = materialized && material !== "glow";
|
|
141
|
+
const margin = 16 + Math.max(Number.isFinite(resolvedStroke) ? resolvedStroke : 0, paintStroke) / 2;
|
|
142
|
+
const entranceId = `${sourceId}-entrance`;
|
|
143
|
+
// A mask affects paint only: native hit targets and event ownership stay intact.
|
|
144
|
+
const maskEntrance = reveal &&
|
|
145
|
+
cssTransformOwned === false &&
|
|
146
|
+
props.transform === undefined &&
|
|
147
|
+
props.style?.transform === undefined;
|
|
148
|
+
const nativeSpan = Math.max(-360, Math.min(360, seriesEndAngle - seriesStartAngle));
|
|
149
|
+
const clockwise = direction === "clockwise";
|
|
150
|
+
const fromStart = clockwise ? nativeSpan < 0 : nativeSpan >= 0;
|
|
151
|
+
const revealStart = fromStart ? seriesStartAngle : seriesStartAngle + nativeSpan;
|
|
152
|
+
const revealSpan = Math.abs(nativeSpan) * (clockwise ? -1 : 1);
|
|
153
|
+
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, 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
|
+
// 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" })] }) })] })] }) }));
|
|
158
|
+
}
|
|
159
|
+
/** A native Pie. Use innerRadius for donuts; category data, Cells and visibility are consumer-owned. */
|
|
160
|
+
export function PieSeries(props) {
|
|
161
|
+
const { material = "plain", emphasisKey, categoryKey, ...nativeProps } = props;
|
|
162
|
+
const { config } = useChart();
|
|
163
|
+
const children = categoryKey === undefined
|
|
164
|
+
? props.children
|
|
165
|
+
: categoryCells(props.data, categoryKey, config, props.children, props.fill);
|
|
166
|
+
const seriesId = useId();
|
|
167
|
+
const { invalidate, emphasisScope } = useLineInteraction();
|
|
168
|
+
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 })), [
|
|
170
|
+
material,
|
|
171
|
+
scope,
|
|
172
|
+
emphasisKey,
|
|
173
|
+
props.activeShape,
|
|
174
|
+
props.inactiveShape,
|
|
175
|
+
props.startAngle,
|
|
176
|
+
props.endAngle,
|
|
177
|
+
]);
|
|
178
|
+
const inputs = [
|
|
179
|
+
props.data,
|
|
180
|
+
props.dataKey,
|
|
181
|
+
props.nameKey,
|
|
182
|
+
props.cx,
|
|
183
|
+
props.cy,
|
|
184
|
+
props.innerRadius,
|
|
185
|
+
props.outerRadius,
|
|
186
|
+
props.startAngle,
|
|
187
|
+
props.endAngle,
|
|
188
|
+
props.paddingAngle,
|
|
189
|
+
props.minAngle,
|
|
190
|
+
props.cornerRadius,
|
|
191
|
+
props.hide,
|
|
192
|
+
props.shape,
|
|
193
|
+
props.activeShape,
|
|
194
|
+
props.inactiveShape,
|
|
195
|
+
material,
|
|
196
|
+
];
|
|
197
|
+
const cells = cellProps(props.children);
|
|
198
|
+
const previousCells = useRef(cells);
|
|
199
|
+
const previous = useRef(inputs);
|
|
200
|
+
useLayoutEffect(() => {
|
|
201
|
+
if (inputs.some((value, index) => value !== previous.current[index]) ||
|
|
202
|
+
!sameCells(previousCells.current, cells))
|
|
203
|
+
invalidate();
|
|
204
|
+
previousCells.current = cells;
|
|
205
|
+
previous.current = inputs;
|
|
206
|
+
});
|
|
207
|
+
return (_jsx(Pie, { ...nativeProps, stroke: props.stroke ?? "none", shape: props.shape ?? sectorShape, isAnimationActive: false, children: children }));
|
|
208
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ComponentProps } from "react";
|
|
2
|
+
import { RadarChart as EngineRadarChart, RadialBarChart as EngineRadialBarChart } from "recharts";
|
|
3
|
+
import { type LineAnimation } from "./animation.js";
|
|
4
|
+
import type { CategoryKey } from "./category-cells.js";
|
|
5
|
+
import { type RadarSelectionProps } from "./radar-interaction.js";
|
|
6
|
+
export type RadarAnimation = LineAnimation;
|
|
7
|
+
export type RadialBarAnimation = LineAnimation;
|
|
8
|
+
export type RadarChartProps<DataPoint = unknown> = ComponentProps<typeof EngineRadarChart<DataPoint>> & RadarSelectionProps & {
|
|
9
|
+
animate?: boolean | RadarAnimation | undefined;
|
|
10
|
+
};
|
|
11
|
+
export type RadialBarChartProps<DataPoint = unknown> = ComponentProps<typeof EngineRadialBarChart<DataPoint>> & {
|
|
12
|
+
/** Opt-in category colors from Root.config, resolved from original chart rows. */
|
|
13
|
+
categoryKey?: CategoryKey<DataPoint> | undefined;
|
|
14
|
+
animate?: boolean | RadialBarAnimation | undefined;
|
|
15
|
+
/** Entrance direction only; native chart and axis angles stay consumer-owned. */
|
|
16
|
+
animationDirection?: "clockwise" | "anticlockwise" | undefined;
|
|
17
|
+
};
|
|
18
|
+
export declare const PolarMotion: import("react").Context<{
|
|
19
|
+
reveal: boolean;
|
|
20
|
+
options: RadarAnimation;
|
|
21
|
+
}>;
|
|
22
|
+
export declare const RadarMotion: import("react").Context<{
|
|
23
|
+
reveal: boolean;
|
|
24
|
+
progress: number;
|
|
25
|
+
}>;
|
|
26
|
+
export declare const RadialMotion: import("react").Context<{
|
|
27
|
+
reveal: boolean;
|
|
28
|
+
progress: number;
|
|
29
|
+
direction: "clockwise" | "anticlockwise";
|
|
30
|
+
}>;
|
|
31
|
+
/** Native polar composition with shared metadata, interaction and optional Motion. */
|
|
32
|
+
export declare function RadarChart<DataPoint = unknown>({ animate, selection, selectedSeries, onSelectedSeriesChange, children, ...props }: RadarChartProps<DataPoint>): import("react").JSX.Element;
|
|
33
|
+
export declare function RadialBarChart<DataPoint = unknown>({ animate, animationDirection, categoryKey, children, ...props }: RadialBarChartProps<DataPoint>): import("react").JSX.Element;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { animate as animateValue } from "motion/react";
|
|
4
|
+
import { createContext, useCallback, useLayoutEffect, useRef, useState, useSyncExternalStore, } from "react";
|
|
5
|
+
import { RadarChart as EngineRadarChart, RadialBarChart as EngineRadialBarChart } from "recharts";
|
|
6
|
+
import { MotionContext } from "./animation.js";
|
|
7
|
+
import { LineChartFrame, useLineInteraction } from "./line-chart.js";
|
|
8
|
+
import { RadarSelectionProvider } from "./radar-interaction.js";
|
|
9
|
+
import { RadialCategory } from "./radial-category.js";
|
|
10
|
+
export const PolarMotion = createContext({
|
|
11
|
+
reveal: false,
|
|
12
|
+
options: {},
|
|
13
|
+
});
|
|
14
|
+
export const RadarMotion = createContext({ reveal: false, progress: 1 });
|
|
15
|
+
export const RadialMotion = createContext({
|
|
16
|
+
reveal: false,
|
|
17
|
+
progress: 1,
|
|
18
|
+
direction: "clockwise",
|
|
19
|
+
});
|
|
20
|
+
const query = "(prefers-reduced-motion: reduce)";
|
|
21
|
+
function subscribe(change) {
|
|
22
|
+
const media = window.matchMedia(query);
|
|
23
|
+
media.addEventListener("change", change);
|
|
24
|
+
return () => media.removeEventListener("change", change);
|
|
25
|
+
}
|
|
26
|
+
const snapshot = () => window.matchMedia(query).matches;
|
|
27
|
+
const serverSnapshot = () => true;
|
|
28
|
+
const defaultHover = { type: "spring", stiffness: 210, damping: 28, mass: 0.8 };
|
|
29
|
+
function usePolarMotion(animate) {
|
|
30
|
+
const reduced = useSyncExternalStore(subscribe, snapshot, serverSnapshot);
|
|
31
|
+
const [finished, setFinished] = useState(false);
|
|
32
|
+
const interrupt = useCallback(() => setFinished(true), []);
|
|
33
|
+
const enabled = animate !== false && !reduced;
|
|
34
|
+
const options = typeof animate === "object" ? animate : {};
|
|
35
|
+
return { enabled, options, reveal: enabled && !finished, interrupt };
|
|
36
|
+
}
|
|
37
|
+
// These native polar inputs can move marks without changing data or chart dimensions.
|
|
38
|
+
function PolarLifecycle({ layout, cx, cy, innerRadius, outerRadius, startAngle, endAngle, barGap, barCategoryGap, barSize, maxBarSize, stackOffset, reverseStackOrder, children, }) {
|
|
39
|
+
const { invalidate } = useLineInteraction();
|
|
40
|
+
const inputs = [
|
|
41
|
+
layout,
|
|
42
|
+
cx,
|
|
43
|
+
cy,
|
|
44
|
+
innerRadius,
|
|
45
|
+
outerRadius,
|
|
46
|
+
startAngle,
|
|
47
|
+
endAngle,
|
|
48
|
+
barGap,
|
|
49
|
+
barCategoryGap,
|
|
50
|
+
barSize,
|
|
51
|
+
maxBarSize,
|
|
52
|
+
stackOffset,
|
|
53
|
+
reverseStackOrder,
|
|
54
|
+
children,
|
|
55
|
+
];
|
|
56
|
+
const previous = useRef(inputs);
|
|
57
|
+
useLayoutEffect(() => {
|
|
58
|
+
if (inputs.some((value, index) => value !== previous.current[index]))
|
|
59
|
+
invalidate();
|
|
60
|
+
previous.current = inputs;
|
|
61
|
+
});
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/** Native polar composition with shared metadata, interaction and optional Motion. */
|
|
65
|
+
export function RadarChart({ animate = false, selection, selectedSeries, onSelectedSeriesChange, children, ...props }) {
|
|
66
|
+
const { enabled, options, reveal, interrupt } = usePolarMotion(animate);
|
|
67
|
+
const [progress, setProgress] = useState(1);
|
|
68
|
+
const duration = options.revealDurationMs ?? 1000;
|
|
69
|
+
const easing = options.revealEasing ?? "easeOut";
|
|
70
|
+
const started = useRef(false);
|
|
71
|
+
const previous = useRef([duration, easing]);
|
|
72
|
+
const previousEnabled = useRef(enabled);
|
|
73
|
+
useLayoutEffect(() => {
|
|
74
|
+
const inputs = [duration, easing];
|
|
75
|
+
if ((started.current && inputs.some((value, index) => value !== previous.current[index])) ||
|
|
76
|
+
(previousEnabled.current && !enabled))
|
|
77
|
+
interrupt();
|
|
78
|
+
previous.current = inputs;
|
|
79
|
+
previousEnabled.current = enabled;
|
|
80
|
+
});
|
|
81
|
+
useLayoutEffect(() => {
|
|
82
|
+
if (!reveal) {
|
|
83
|
+
setProgress(1);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
started.current = true;
|
|
87
|
+
setProgress(0);
|
|
88
|
+
const controls = animateValue(0, 1, {
|
|
89
|
+
duration: Math.max(0, duration) / 1000,
|
|
90
|
+
ease: easing,
|
|
91
|
+
onUpdate: setProgress,
|
|
92
|
+
onComplete: interrupt,
|
|
93
|
+
});
|
|
94
|
+
return () => controls.stop();
|
|
95
|
+
}, [reveal, duration, easing, interrupt]);
|
|
96
|
+
return (_jsx(MotionContext, { value: { enabled, transition: options.hoverTransition ?? defaultHover }, children: _jsx(RadarMotion, { value: { reveal, progress }, children: _jsx(PolarMotion, { value: { reveal, options }, children: _jsx(RadarSelectionProvider, { selection: selection, selectedSeries: selectedSeries, onSelectedSeriesChange: onSelectedSeriesChange, children: _jsxs(LineChartFrame, { engine: (EngineRadarChart), chartProps: props, motionEnabled: enabled, interrupt: interrupt, children: [_jsx(PolarLifecycle, { ...props, children: children }), children] }) }) }) }) }));
|
|
97
|
+
}
|
|
98
|
+
export function RadialBarChart({ animate = false, animationDirection = "clockwise", categoryKey, children, ...props }) {
|
|
99
|
+
if (categoryKey !== undefined && props.data === undefined)
|
|
100
|
+
throw new Error("RadialBarChart categoryKey requires explicit chart data");
|
|
101
|
+
const categories = categoryKey === undefined
|
|
102
|
+
? null
|
|
103
|
+
: {
|
|
104
|
+
data: props.data ?? [],
|
|
105
|
+
key: (row) => {
|
|
106
|
+
if (typeof categoryKey === "function")
|
|
107
|
+
return categoryKey(row);
|
|
108
|
+
return row !== null && typeof row === "object" && Object.hasOwn(row, categoryKey)
|
|
109
|
+
? row[categoryKey]
|
|
110
|
+
: "";
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
const { enabled, options, reveal, interrupt } = usePolarMotion(animate);
|
|
114
|
+
const [progress, setProgress] = useState(1);
|
|
115
|
+
const duration = options.revealDurationMs ?? 1000;
|
|
116
|
+
const easing = options.revealEasing ?? "easeOut";
|
|
117
|
+
const started = useRef(false);
|
|
118
|
+
const previous = useRef([duration, easing, animationDirection]);
|
|
119
|
+
const previousEnabled = useRef(enabled);
|
|
120
|
+
useLayoutEffect(() => {
|
|
121
|
+
const inputs = [duration, easing, animationDirection];
|
|
122
|
+
if (previousEnabled.current && !enabled)
|
|
123
|
+
interrupt();
|
|
124
|
+
previousEnabled.current = enabled;
|
|
125
|
+
if (started.current && inputs.some((value, index) => value !== previous.current[index]))
|
|
126
|
+
interrupt();
|
|
127
|
+
previous.current = inputs;
|
|
128
|
+
});
|
|
129
|
+
useLayoutEffect(() => {
|
|
130
|
+
if (!reveal) {
|
|
131
|
+
setProgress(1);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
started.current = true;
|
|
135
|
+
setProgress(0);
|
|
136
|
+
const controls = animateValue(0, 1, {
|
|
137
|
+
duration: Math.max(0, duration) / 1000,
|
|
138
|
+
ease: easing,
|
|
139
|
+
onUpdate: setProgress,
|
|
140
|
+
onComplete: interrupt,
|
|
141
|
+
});
|
|
142
|
+
return () => controls.stop();
|
|
143
|
+
}, [reveal, duration, easing, interrupt]);
|
|
144
|
+
return (_jsx(RadialCategory, { value: categories, children: _jsx(MotionContext, { value: { enabled, transition: options.hoverTransition ?? defaultHover }, children: _jsx(RadialMotion, { value: { reveal, progress, direction: animationDirection }, children: _jsx(PolarMotion, { value: { reveal, options }, children: _jsxs(LineChartFrame, { engine: (EngineRadialBarChart), chartProps: props, motionEnabled: enabled, interrupt: interrupt, children: [_jsx(PolarLifecycle, { ...props, children: children }), children] }) }) }) }) }));
|
|
145
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { LineMaterial } from "./line-material.js";
|
|
2
|
+
export type PolarMaterial = LineMaterial;
|
|
3
|
+
/** Finish only: Recharts owns polygons, sectors, coordinates and native paint. */
|
|
4
|
+
export declare function PolarMaterialFilter({ material, id, }: {
|
|
5
|
+
material: Exclude<PolarMaterial, "plain">;
|
|
6
|
+
id: string;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useChartHeight, useChartWidth } from "recharts";
|
|
4
|
+
/** Finish only: Recharts owns polygons, sectors, coordinates and native paint. */
|
|
5
|
+
export function PolarMaterialFilter({ material, id, }) {
|
|
6
|
+
const width = useChartWidth();
|
|
7
|
+
const height = useChartHeight();
|
|
8
|
+
return (_jsx("defs", { "data-kind-ui": "polar-material", "data-material": material, pointerEvents: "none", children: _jsxs("filter", { id: id, filterUnits: "userSpaceOnUse", x: -16, y: -16, width: (width ?? 0) + 32, height: (height ?? 0) + 32, colorInterpolationFilters: "sRGB", children: [_jsx("feComponentTransfer", { in: "SourceAlpha", result: "footprint", children: _jsx("feFuncA", { type: "linear", slope: 100000 }) }), material === "paper" ? (_jsxs(_Fragment, { children: [_jsx("feTurbulence", { type: "fractalNoise", baseFrequency: "0.65 0.3", numOctaves: 1, seed: 7, result: "fiber" }), _jsx("feFlood", { floodColor: "var(--kind-ui-polar-paper-fiber, #fff)", floodOpacity: "var(--kind-ui-polar-paper-grain, 0.14)" }), _jsx("feComposite", { in2: "fiber", operator: "in", result: "texture" }), _jsx("feComposite", { in: "texture", in2: "SourceGraphic", operator: "atop", result: "paper" }), _jsx("feMorphology", { in: "footprint", operator: "erode", radius: 1.2, result: "inside" }), _jsx("feComposite", { in: "footprint", in2: "inside", operator: "arithmetic", k2: 1, k3: -1, result: "edge" }), _jsx("feTurbulence", { type: "fractalNoise", baseFrequency: "0.08 0.2", numOctaves: 1, seed: 11, result: "pencil" }), _jsx("feColorMatrix", { in: "pencil", type: "luminanceToAlpha", result: "pencilMask" }), _jsx("feComponentTransfer", { in: "pencilMask", result: "sketch", children: _jsx("feFuncA", { type: "linear", slope: 1.6, intercept: -0.15 }) }), _jsx("feComposite", { in: "edge", in2: "sketch", operator: "in", result: "sketchEdge" }), _jsx("feColorMatrix", { in: "SourceGraphic", type: "matrix", values: "0.35 0 0 0 0 0 0.35 0 0 0 0 0 0.35 0 0 0 0 0 1 0", result: "ink" }), _jsx("feComposite", { in: "ink", in2: "sketchEdge", operator: "in", result: "contour" }), _jsx("feComposite", { in: "contour", in2: "paper", operator: "atop" })] })) : material === "clay" ? (_jsxs(_Fragment, { children: [_jsx("feOffset", { in: "footprint", dx: 4, dy: 4, result: "lower" }), _jsx("feComposite", { in: "footprint", in2: "lower", operator: "arithmetic", k2: 1, k3: -1, result: "top" }), _jsx("feGaussianBlur", { in: "top", stdDeviation: 3, result: "softTop" }), _jsx("feFlood", { floodColor: "var(--kind-ui-polar-clay-light, #fff)", floodOpacity: "var(--kind-ui-polar-clay-highlight, 0.55)" }), _jsx("feComposite", { in2: "softTop", operator: "in", result: "light" }), _jsx("feOffset", { in: "footprint", dx: -4, dy: -4, result: "upper" }), _jsx("feComposite", { in: "footprint", in2: "upper", operator: "arithmetic", k2: 1, k3: -1, result: "bottom" }), _jsx("feGaussianBlur", { in: "bottom", stdDeviation: 3, result: "softBottom" }), _jsx("feFlood", { floodColor: "var(--kind-ui-polar-clay-shade, #17212b)", floodOpacity: "var(--kind-ui-polar-clay-shadow, 0.32)" }), _jsx("feComposite", { in2: "softBottom", operator: "in", result: "shade" }), _jsx("feComposite", { in: "shade", in2: "SourceGraphic", operator: "atop", result: "shaded" }), _jsx("feComposite", { in: "light", in2: "shaded", operator: "atop", result: "body" }), _jsx("feTurbulence", { type: "fractalNoise", baseFrequency: 0.7, numOctaves: 1, seed: 17, result: "grain" }), _jsx("feFlood", { floodColor: "var(--kind-ui-polar-clay-light, #fff)", floodOpacity: 0.025 }), _jsx("feComposite", { in2: "grain", operator: "in", result: "texture" }), _jsx("feComposite", { in: "texture", in2: "body", operator: "atop" })] })) : (_jsxs(_Fragment, { children: [_jsx("feColorMatrix", { in: "SourceGraphic", type: "matrix", values: "0.6 0 0 0 0.4 0 0.6 0 0 0.4 0 0 0.6 0 0.4 0 0 0 1 0", result: "luminousPaint" }), _jsx("feGaussianBlur", { in: "luminousPaint", stdDeviation: 3, result: "halo" }), _jsx("feFlood", { floodOpacity: "var(--kind-ui-polar-glow-opacity, 0.65)", result: "strength" }), _jsx("feComposite", { in: "halo", in2: "strength", operator: "in", result: "softHalo" }), _jsx("feMorphology", { in: "footprint", operator: "dilate", radius: 1, result: "haloGuard" }), _jsx("feComposite", { in: "softHalo", in2: "haloGuard", operator: "out", result: "exterior" }), _jsx("feMorphology", { in: "footprint", operator: "erode", radius: 2.2, result: "inside" }), _jsx("feComposite", { in: "footprint", in2: "inside", operator: "arithmetic", k2: 1, k3: -1, result: "edge" }), _jsx("feFlood", { floodColor: "var(--kind-ui-polar-glow-light, #fff)", floodOpacity: 0.8 }), _jsx("feComposite", { in2: "edge", operator: "in", result: "light" }), _jsx("feComposite", { in: "light", in2: "SourceGraphic", operator: "atop", result: "body" }), _jsxs("feMerge", { children: [_jsx("feMergeNode", { in: "exterior" }), _jsx("feMergeNode", { in: "body" })] })] }))] }) }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ComponentProps } from "react";
|
|
2
|
+
import { Radar, RadialBar } from "recharts";
|
|
3
|
+
import { type PolarMaterial } from "./polar-material.js";
|
|
4
|
+
export type RadarSeriesProps<DataPoint = unknown, Value = unknown> = Omit<ComponentProps<typeof Radar<DataPoint, Value>>, "isAnimationActive"> & {
|
|
5
|
+
seriesKey?: string;
|
|
6
|
+
/** Finish on native marks; custom renderers and consumer filters retain ownership. */
|
|
7
|
+
material?: PolarMaterial | undefined;
|
|
8
|
+
};
|
|
9
|
+
export type RadialBarSeriesProps<DataPoint = unknown, Value = unknown> = Omit<ComponentProps<typeof RadialBar<DataPoint, Value>>, "isAnimationActive"> & {
|
|
10
|
+
seriesKey?: string;
|
|
11
|
+
/** Finish on native marks; custom renderers and consumer filters retain ownership. */
|
|
12
|
+
material?: PolarMaterial | undefined;
|
|
13
|
+
};
|
|
14
|
+
/** Registered native Radar; custom shapes, dots, labels and handlers remain native. */
|
|
15
|
+
export declare function RadarSeries<DataPoint = unknown, Value = unknown>({ seriesKey, hide, stroke, fill, className, material, ...props }: RadarSeriesProps<DataPoint, Value>): import("react").JSX.Element;
|
|
16
|
+
/** Series identity is independent from native category payload/Cell identity. */
|
|
17
|
+
export declare function RadialBarSeries<DataPoint = unknown, Value = unknown>({ seriesKey, hide, fill, className, material, ...props }: RadialBarSeriesProps<DataPoint, Value>): import("react").JSX.Element;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { createElement as _createElement } from "react";
|
|
3
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { animate, motion, useMotionValue } from "motion/react";
|
|
5
|
+
import { memo, use, useId, useLayoutEffect, useRef, useState } from "react";
|
|
6
|
+
import { DefaultZIndexes, Polygon, Radar, RadialBar, Sector, ZIndexLayer } from "recharts";
|
|
7
|
+
import { ActiveMarker } from "./animation.js";
|
|
8
|
+
import { categoryCells } from "./category-cells.js";
|
|
9
|
+
import { useChart } from "./chart-context.js";
|
|
10
|
+
import { useLineInteraction } from "./line-chart.js";
|
|
11
|
+
import { PolarMotion, RadarMotion, RadialMotion } from "./polar-chart.js";
|
|
12
|
+
import { PolarMaterialFilter } from "./polar-material.js";
|
|
13
|
+
import { RadarSelectionLayer, useRadarSelectionDot } from "./radar-interaction.js";
|
|
14
|
+
import { RadialCategory } from "./radial-category.js";
|
|
15
|
+
// Native Radar uses a props-identity animation key even with animation disabled.
|
|
16
|
+
// Avoid replacing its polygon for unrelated frame state during a pointer press.
|
|
17
|
+
const StableRadar = memo(Radar);
|
|
18
|
+
const radarActiveDot = _jsx(ActiveMarker, {});
|
|
19
|
+
function usePolarSeries(kind, props, geometry) {
|
|
20
|
+
const { config, visibleSeries } = useChart();
|
|
21
|
+
const { registerSeries, invalidate } = useLineInteraction();
|
|
22
|
+
const { reveal, options } = use(PolarMotion);
|
|
23
|
+
const generatedId = useId();
|
|
24
|
+
const id = props.id || generatedId;
|
|
25
|
+
const key = props.seriesKey ?? (typeof props.dataKey === "string" ? props.dataKey : undefined);
|
|
26
|
+
const hide = props.hide === true || (visibleSeries !== undefined && !visibleSeries.includes(key ?? ""));
|
|
27
|
+
const previous = useRef([...geometry, hide]);
|
|
28
|
+
useLayoutEffect(() => {
|
|
29
|
+
const inputs = [...geometry, hide];
|
|
30
|
+
if (inputs.some((value, index) => value !== previous.current[index]))
|
|
31
|
+
invalidate();
|
|
32
|
+
previous.current = inputs;
|
|
33
|
+
});
|
|
34
|
+
useLayoutEffect(() => {
|
|
35
|
+
if (key === undefined)
|
|
36
|
+
return;
|
|
37
|
+
return registerSeries(id, key);
|
|
38
|
+
}, [id, key, registerSeries]);
|
|
39
|
+
const opacity = useMotionValue(1);
|
|
40
|
+
const started = useRef(false);
|
|
41
|
+
const duration = options.revealDurationMs ?? 1000;
|
|
42
|
+
const easing = options.revealEasing ?? "easeOut";
|
|
43
|
+
useLayoutEffect(() => {
|
|
44
|
+
if (!reveal || hide) {
|
|
45
|
+
opacity.set(1);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!started.current) {
|
|
49
|
+
started.current = true;
|
|
50
|
+
opacity.set(0);
|
|
51
|
+
}
|
|
52
|
+
const controls = animate(opacity, 1, { duration: Math.max(0, duration) / 1000, ease: easing });
|
|
53
|
+
return () => {
|
|
54
|
+
controls.stop();
|
|
55
|
+
};
|
|
56
|
+
}, [reveal, hide, opacity, duration, easing]);
|
|
57
|
+
if (key === undefined && visibleSeries !== undefined)
|
|
58
|
+
throw new Error(`${kind} requires seriesKey for controlled non-string dataKey`);
|
|
59
|
+
const color = key !== undefined && Object.hasOwn(config, key) ? `var(--color-${key})` : undefined;
|
|
60
|
+
return { id, hide, color, opacity };
|
|
61
|
+
}
|
|
62
|
+
// Recharts injects its original computed points and callback props into this renderer.
|
|
63
|
+
// Only the native default polygon is masked; dots, labels and custom renderers stay owned.
|
|
64
|
+
function RadarEntrancePolygon(props) {
|
|
65
|
+
const { reveal, progress } = use(RadarMotion);
|
|
66
|
+
const maskId = `kind-ui-radar-entrance-${useId().replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
67
|
+
const center = props.points?.find((point) => Number.isFinite(point.cx) && Number.isFinite(point.cy));
|
|
68
|
+
const cx = center?.cx ?? 0;
|
|
69
|
+
const cy = center?.cy ?? 0;
|
|
70
|
+
const points = [...(props.points ?? []), ...(props.isRange ? (props.baseLinePoints ?? []) : [])];
|
|
71
|
+
const radius = Math.max(0, ...points.map((point) => Math.hypot(point.x - cx, point.y - cy)).filter(Number.isFinite));
|
|
72
|
+
const owned = props.mask !== undefined ||
|
|
73
|
+
props.filter !== undefined ||
|
|
74
|
+
props.transform !== undefined ||
|
|
75
|
+
props.style?.transform !== undefined ||
|
|
76
|
+
props.style?.mask !== undefined ||
|
|
77
|
+
props.style?.filter !== undefined;
|
|
78
|
+
const masked = reveal && center !== undefined && radius > 0 && !owned;
|
|
79
|
+
const { baseLinePoints, onClick, onMouseDown, onMouseUp, onMouseMove, onMouseOver, onMouseOut, ...paint } = props;
|
|
80
|
+
return (_jsxs(_Fragment, { children: [masked && (_jsx("defs", { children: _jsx("mask", { id: maskId, maskUnits: "userSpaceOnUse", x: cx - radius - 32, y: cy - radius - 32, width: radius * 2 + 64, height: radius * 2 + 64, style: { maskType: "alpha" }, children: _jsx("circle", { "data-kind-ui": "radar-entrance-window", cx: cx, cy: cy, r: (radius + 32) * progress, fill: "white" }) }) })), _createElement(Polygon, { ...paint, key: "paint", points: props.points, ...(onClick ? { onClick } : {}), ...(onMouseDown ? { onMouseDown } : {}), ...(onMouseUp ? { onMouseUp } : {}), ...(onMouseMove ? { onMouseMove } : {}), ...(onMouseOver ? { onMouseOver } : {}), ...(onMouseOut ? { onMouseOut } : {}), ...(props.isRange ? { baseLinePoints } : {}), onMouseEnter: (event) => props.onMouseEnter?.(props, event), onMouseLeave: (event) => props.onMouseLeave?.(props, event), ...(masked ? { mask: `url(#${maskId})` } : {}) })] }));
|
|
81
|
+
}
|
|
82
|
+
const renderRadarEntrance = (props) => _jsx(RadarEntrancePolygon, { ...props });
|
|
83
|
+
/** Registered native Radar; custom shapes, dots, labels and handlers remain native. */
|
|
84
|
+
export function RadarSeries({ seriesKey, hide, stroke, fill, className, material = "plain", ...props }) {
|
|
85
|
+
const series = usePolarSeries("RadarSeries", { ...props, seriesKey, hide }, [
|
|
86
|
+
props.dataKey,
|
|
87
|
+
seriesKey,
|
|
88
|
+
props.angleAxisId,
|
|
89
|
+
props.radiusAxisId,
|
|
90
|
+
props.baseLinePoints,
|
|
91
|
+
props.isRange,
|
|
92
|
+
]);
|
|
93
|
+
const selectionDot = useRadarSelectionDot(props.dot, seriesKey ?? (typeof props.dataKey === "string" ? props.dataKey : undefined));
|
|
94
|
+
const filterId = `kind-ui-polar-${useId().replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
95
|
+
const materialized = material !== "plain" &&
|
|
96
|
+
props.shape === undefined &&
|
|
97
|
+
props.filter === undefined &&
|
|
98
|
+
props.style?.filter === undefined;
|
|
99
|
+
return (_jsxs(ZIndexLayer, { zIndex: props.zIndex ?? DefaultZIndexes.area, children: [materialized && (_jsxs(_Fragment, { children: [_jsx(PolarMaterialFilter, { material: material, id: filterId }), _jsx("style", { children: `.${filterId} .recharts-radar-polygon > .recharts-polygon { filter: url(#${filterId}); }` })] })), _jsx(RadarSelectionLayer, { seriesKey: seriesKey ?? (typeof props.dataKey === "string" ? props.dataKey : undefined), hidden: series.hide, children: _jsx(motion.g, { "data-kind-ui": "radar-reveal", initial: false, children: _jsx(StableRadar, { activeDot: radarActiveDot, ...props, ...(props.shape === undefined ? { shape: renderRadarEntrance } : {}), ...(selectionDot !== undefined ? { dot: selectionDot } : {}), id: series.id, hide: series.hide, isAnimationActive: false, zIndex: 0, ...(stroke !== undefined
|
|
100
|
+
? { stroke }
|
|
101
|
+
: series.color !== undefined
|
|
102
|
+
? { stroke: series.color }
|
|
103
|
+
: {}), ...(fill !== undefined
|
|
104
|
+
? { fill }
|
|
105
|
+
: series.color !== undefined
|
|
106
|
+
? { fill: series.color }
|
|
107
|
+
: {}), className: ["kind-ui-radar-series", filterId, className].filter(Boolean).join(" ") }) }) })] }));
|
|
108
|
+
}
|
|
109
|
+
// Recharts computes every arc; the entrance masks its paint without changing its path.
|
|
110
|
+
function RadialEntranceSector(props) {
|
|
111
|
+
const { reveal, progress, direction } = use(RadialMotion);
|
|
112
|
+
const id = `kind-ui-radial-entrance-${useId().replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
113
|
+
const group = useRef(null);
|
|
114
|
+
const [nativeTransform, setNativeTransform] = useState();
|
|
115
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: class/id changes can change stylesheet transforms.
|
|
116
|
+
useLayoutEffect(() => {
|
|
117
|
+
const path = group.current?.querySelector("path:not([data-kind-ui])");
|
|
118
|
+
if (!path)
|
|
119
|
+
return;
|
|
120
|
+
const css = getComputedStyle(path);
|
|
121
|
+
setNativeTransform(css.transform !== "none" ||
|
|
122
|
+
css.translate !== "none" ||
|
|
123
|
+
css.rotate !== "none" ||
|
|
124
|
+
css.scale !== "none");
|
|
125
|
+
}, [props.className, props.style, props.transform, props.id]);
|
|
126
|
+
const { cx = 0, cy = 0, outerRadius = 0, startAngle = 0, endAngle = 0 } = props;
|
|
127
|
+
const nativeSpan = Math.max(-360, Math.min(360, endAngle - startAngle));
|
|
128
|
+
const clockwise = direction === "clockwise";
|
|
129
|
+
const fromStart = clockwise ? nativeSpan < 0 : nativeSpan >= 0;
|
|
130
|
+
const start = fromStart ? startAngle : startAngle + nativeSpan;
|
|
131
|
+
const span = Math.abs(nativeSpan) * (clockwise ? -1 : 1);
|
|
132
|
+
const radius = outerRadius + 16 + (Number(props.style?.strokeWidth ?? props.strokeWidth) || 0) / 2;
|
|
133
|
+
const mask = reveal &&
|
|
134
|
+
nativeTransform === false &&
|
|
135
|
+
props.transform === undefined &&
|
|
136
|
+
props.style?.transform === undefined;
|
|
137
|
+
return (_jsxs("g", { ref: group, children: [mask && (_jsx("defs", { children: _jsx("mask", { id: id, maskUnits: "userSpaceOnUse", x: cx - radius, y: cy - radius, width: radius * 2, height: radius * 2, style: { maskType: "alpha" }, children: _jsx(Sector, { "data-kind-ui": "radial-entrance-window", "data-direction": direction, fill: "white", cx: cx, cy: cy, innerRadius: 0, outerRadius: radius, startAngle: start, endAngle: start + span * progress }) }) })), _jsx("g", { mask: mask ? `url(#${id})` : undefined, children: _jsx(Sector, { ...props }) })] }));
|
|
138
|
+
}
|
|
139
|
+
/** Series identity is independent from native category payload/Cell identity. */
|
|
140
|
+
export function RadialBarSeries({ seriesKey, hide, fill, className, material = "plain", ...props }) {
|
|
141
|
+
const categories = use(RadialCategory);
|
|
142
|
+
const { config } = useChart();
|
|
143
|
+
const children = categories
|
|
144
|
+
? categoryCells(categories.data, categories.key, config, props.children, fill)
|
|
145
|
+
: props.children;
|
|
146
|
+
const series = usePolarSeries("RadialBarSeries", { ...props, seriesKey, hide }, [
|
|
147
|
+
props.dataKey,
|
|
148
|
+
seriesKey,
|
|
149
|
+
props.angleAxisId,
|
|
150
|
+
props.radiusAxisId,
|
|
151
|
+
props.stackId,
|
|
152
|
+
props.barSize,
|
|
153
|
+
props.maxBarSize,
|
|
154
|
+
props.minPointSize,
|
|
155
|
+
]);
|
|
156
|
+
const filterId = `kind-ui-polar-${useId().replace(/[^a-zA-Z0-9_-]/g, "_")}`;
|
|
157
|
+
const materialized = material !== "plain" &&
|
|
158
|
+
(props.shape === undefined || typeof props.shape === "boolean") &&
|
|
159
|
+
(props.activeShape === undefined || typeof props.activeShape === "boolean") &&
|
|
160
|
+
props.filter === undefined &&
|
|
161
|
+
props.style?.filter === undefined;
|
|
162
|
+
return (_jsxs(ZIndexLayer, { zIndex: props.zIndex ?? DefaultZIndexes.bar, children: [materialized && _jsx(PolarMaterialFilter, { material: material, id: filterId }), _jsx(motion.g, { "data-kind-ui": "radial-bar-reveal", initial: false, children: _jsx(RadialBar, { ...props, ...(props.shape === undefined ? { shape: RadialEntranceSector } : {}), ...(props.background === true
|
|
163
|
+
? { background: { fill: "var(--kind-ui-radial-track, #f1f1f1)" } }
|
|
164
|
+
: {}), ...(materialized ? { filter: `url(#${filterId})` } : {}), id: series.id, hide: series.hide, isAnimationActive: false, zIndex: 0, ...(fill !== undefined
|
|
165
|
+
? { fill }
|
|
166
|
+
: series.color !== undefined
|
|
167
|
+
? { fill: series.color }
|
|
168
|
+
: {}), className: ["kind-ui-radial-bar-series", className].filter(Boolean).join(" "), children: children }) })] }));
|
|
169
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type ComponentProps } from "react";
|
|
2
|
+
import { type Radar } from "recharts";
|
|
3
|
+
export type RadarSelectionProps = {
|
|
4
|
+
/** Opt in to persistent series selection. Native spoke inspection remains independent. */
|
|
5
|
+
selection?: "none" | "series" | undefined;
|
|
6
|
+
} & ({
|
|
7
|
+
selectedSeries?: undefined;
|
|
8
|
+
onSelectedSeriesChange?: ((next: string | null) => void) | undefined;
|
|
9
|
+
} | {
|
|
10
|
+
selectedSeries: string | null;
|
|
11
|
+
onSelectedSeriesChange: (next: string | null) => void;
|
|
12
|
+
});
|
|
13
|
+
/** Persistent selection is local to one RadarChart and never changes Root visibility. */
|
|
14
|
+
export declare function RadarSelectionProvider({ selection, selectedSeries, onSelectedSeriesChange, children, }: {
|
|
15
|
+
selection: RadarSelectionProps["selection"];
|
|
16
|
+
selectedSeries?: string | null | undefined;
|
|
17
|
+
onSelectedSeriesChange?: ((next: string | null) => void) | undefined;
|
|
18
|
+
children: React.ReactNode;
|
|
19
|
+
}): import("react").JSX.Element;
|
|
20
|
+
/** An extra paint/control layer preserves native shape, data, refs and event handlers. */
|
|
21
|
+
export declare function RadarSelectionLayer({ seriesKey, hidden, children, }: {
|
|
22
|
+
seriesKey: string | undefined;
|
|
23
|
+
hidden: boolean;
|
|
24
|
+
children: React.ReactNode;
|
|
25
|
+
}): import("react").JSX.Element;
|
|
26
|
+
type RadarDot = ComponentProps<typeof Radar>["dot"];
|
|
27
|
+
/** Recharts clones this public dot element with its original computed dot props. */
|
|
28
|
+
export declare function useRadarSelectionDot(dot: RadarDot, seriesKey: string | undefined): RadarDot;
|
|
29
|
+
export {};
|