@kind-ui/charts 0.1.1 → 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 +26 -0
- package/README.md +674 -1
- 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
package/dist/line-series.d.ts
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import { type ComponentProps } from "react";
|
|
2
2
|
import { Line } from "recharts";
|
|
3
|
+
import { type LineDashAnimation } from "./line-dash.js";
|
|
3
4
|
import { type LineMaterial } from "./line-material.js";
|
|
5
|
+
import { type PointStyle } from "./point-marker.js";
|
|
4
6
|
type DefaultLineDataKey = Extract<ComponentProps<typeof Line>["dataKey"], (row: never) => unknown>;
|
|
5
7
|
export type LineSeriesProps<DataPoint = Parameters<DefaultLineDataKey>[0], Value = ReturnType<DefaultLineDataKey>> = ComponentProps<typeof Line<DataPoint, Value>> & {
|
|
6
8
|
/** Metadata/visibility key, required only for function or numeric data keys. */
|
|
7
9
|
seriesKey?: string;
|
|
10
|
+
/** Optional point paint; explicit native dot takes precedence. */
|
|
11
|
+
pointStyle?: PointStyle;
|
|
12
|
+
/** Independent active point paint; explicit native activeDot takes precedence. */
|
|
13
|
+
activePointStyle?: PointStyle;
|
|
8
14
|
/** Material on the default SVG curve; custom shape/filter retain consumer ownership. */
|
|
9
15
|
material?: LineMaterial;
|
|
16
|
+
/** Continuous default-curve dashes; requires a numeric native strokeDasharray. */
|
|
17
|
+
dashAnimation?: false | LineDashAnimation;
|
|
10
18
|
};
|
|
11
19
|
/** A registered Recharts Line with Root colors and controlled visibility. */
|
|
12
|
-
export declare function LineSeries<DataPoint = Parameters<DefaultLineDataKey>[0], Value = ReturnType<DefaultLineDataKey>>({ seriesKey, hide, stroke, className, material, renderWhileHidden, ...props }: LineSeriesProps<DataPoint, Value> & {
|
|
20
|
+
export declare function LineSeries<DataPoint = Parameters<DefaultLineDataKey>[0], Value = ReturnType<DefaultLineDataKey>>({ seriesKey, pointStyle, activePointStyle: _activePointStyle, hide, stroke, className, material, dashAnimation, renderWhileHidden, ...props }: LineSeriesProps<DataPoint, Value> & {
|
|
13
21
|
renderWhileHidden?: boolean;
|
|
14
22
|
}): import("react").JSX.Element;
|
|
15
23
|
export {};
|
package/dist/line-series.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useId, useLayoutEffect, useRef } from "react";
|
|
4
|
-
import { Line } from "recharts";
|
|
4
|
+
import { DefaultZIndexes, Line, ZIndexLayer } from "recharts";
|
|
5
5
|
import { useChart } from "./chart-context.js";
|
|
6
6
|
import { useLineInteraction } from "./line-chart.js";
|
|
7
|
+
import { dashCycle, dashDuration } from "./line-dash.js";
|
|
7
8
|
import { MaterialCurve } from "./line-material.js";
|
|
9
|
+
import { PointMarker } from "./point-marker.js";
|
|
10
|
+
import { SeriesInteractionLayer, useSeriesInteraction } from "./series-interaction.js";
|
|
8
11
|
/** A registered Recharts Line with Root colors and controlled visibility. */
|
|
9
|
-
export function LineSeries({ seriesKey, hide, stroke, className, material = "plain", renderWhileHidden = false, ...props }) {
|
|
10
|
-
const { config, visibleSeries } = useChart();
|
|
12
|
+
export function LineSeries({ seriesKey, pointStyle = "default", activePointStyle: _activePointStyle, hide, stroke, className, material = "plain", dashAnimation = false, renderWhileHidden = false, ...props }) {
|
|
13
|
+
const { config, paints, visibleSeries } = useChart();
|
|
11
14
|
const { registerSeries, invalidate } = useLineInteraction();
|
|
12
15
|
const generatedId = useId();
|
|
13
16
|
const id = props.id || generatedId;
|
|
@@ -29,12 +32,36 @@ export function LineSeries({ seriesKey, hide, stroke, className, material = "pla
|
|
|
29
32
|
}, [id, key, registerSeries]);
|
|
30
33
|
if (key === undefined && visibleSeries !== undefined)
|
|
31
34
|
throw new Error("LineSeries requires seriesKey for controlled non-string dataKey");
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
const interaction = useSeriesInteraction(key, effectiveHide, hide === true, props.data, props.onClick);
|
|
36
|
+
const color = stroke ?? (key && Object.hasOwn(config, key) ? paints[key] : undefined);
|
|
37
|
+
const cycle = dashCycle(props.style?.strokeDasharray ?? props.strokeDasharray);
|
|
38
|
+
const duration = dashAnimation && dashDuration(dashAnimation);
|
|
39
|
+
const dashed = dashAnimation !== false &&
|
|
40
|
+
duration !== undefined &&
|
|
41
|
+
cycle !== undefined &&
|
|
42
|
+
!effectiveHide &&
|
|
43
|
+
props.shape === undefined &&
|
|
44
|
+
props.isAnimationActive !== true;
|
|
45
|
+
const offset = props.style?.strokeDashoffset ?? props.strokeDashoffset ?? 0;
|
|
46
|
+
const baseline = Number.isFinite(Number(offset)) ? `${Number(offset)}px` : offset;
|
|
47
|
+
return (_jsx(ZIndexLayer, { zIndex: props.zIndex ?? DefaultZIndexes.line, children: _jsx(SeriesInteractionLayer, { seriesKey: key, hidden: effectiveHide, children: _jsx(Line, { isAnimationActive: false, ...props, ...(interaction.onClick !== undefined ? { onClick: interaction.onClick } : {}), zIndex: 0, ...(props.dot === undefined && pointStyle !== "default"
|
|
48
|
+
? { dot: _jsx(PointMarker, { variant: pointStyle }) }
|
|
49
|
+
: {}), ...(material !== "plain" && props.shape === undefined && props.filter === undefined
|
|
50
|
+
? {
|
|
51
|
+
shape: (_jsx(MaterialCurve, { material: material, filterId: `${generatedId}-material`, materialWidth: props.strokeWidth ??
|
|
52
|
+
(material === "clay" ? 6 : material === "paper" ? 2.5 : 3) })),
|
|
53
|
+
strokeLinecap: props.strokeLinecap ?? (props.strokeDasharray ? "butt" : "round"),
|
|
54
|
+
strokeLinejoin: props.strokeLinejoin ?? "round",
|
|
55
|
+
}
|
|
56
|
+
: {}), style: dashed
|
|
57
|
+
? {
|
|
58
|
+
...props.style,
|
|
59
|
+
"--kind-ui-dash-cycle": `${cycle}px`,
|
|
60
|
+
"--kind-ui-dash-offset": baseline,
|
|
61
|
+
"--kind-ui-dash-duration": `${duration}ms`,
|
|
62
|
+
"--kind-ui-dash-direction": dashAnimation && dashAnimation.direction === "reverse" ? "reverse" : "normal",
|
|
63
|
+
}
|
|
64
|
+
: props.style, id: id, hide: renderedHide, ...(color !== undefined ? { stroke: color } : {}), className: ["kind-ui-line-series", dashed && "kind-ui-line-dash", className]
|
|
65
|
+
.filter(Boolean)
|
|
66
|
+
.join(" ") }) }) }));
|
|
40
67
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type CartesianLoadingFamily = "area" | "bar" | "combo" | "scatter" | "waterfall" | "histogram" | "box-plot";
|
|
2
|
+
/** Original bounded illustrations: seeded variation never reads consumer data or scales. */
|
|
3
|
+
export declare function CartesianLoadingDesign({ family, seed, }: {
|
|
4
|
+
family: CartesianLoadingFamily;
|
|
5
|
+
seed: number;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { LoadingPulseMark } from "./loading-motion.js";
|
|
4
|
+
const barProfiles = [
|
|
5
|
+
[35, 64, 92, 128, 164, 188, 216],
|
|
6
|
+
[211, 178, 154, 114, 82, 57, 30],
|
|
7
|
+
[185, 74, 209, 47, 149, 91, 195],
|
|
8
|
+
[38, 105, 173, 218, 154, 90, 32],
|
|
9
|
+
];
|
|
10
|
+
const curveProfiles = [
|
|
11
|
+
[208, 181, 142, 115, 88, 51, 27],
|
|
12
|
+
[28, 52, 89, 116, 145, 180, 209],
|
|
13
|
+
[39, 88, 172, 210, 168, 87, 35],
|
|
14
|
+
[190, 51, 158, 33, 194, 74, 142],
|
|
15
|
+
];
|
|
16
|
+
const waterfallProfiles = [
|
|
17
|
+
[192, 131, 160, 70, 117, 48],
|
|
18
|
+
[39, 109, 66, 161, 131, 204],
|
|
19
|
+
[172, 41, 182, 64, 194, 106],
|
|
20
|
+
[203, 162, 91, 145, 55, 83],
|
|
21
|
+
];
|
|
22
|
+
const boxCenters = [
|
|
23
|
+
[174, 144, 114, 84, 54],
|
|
24
|
+
[55, 86, 117, 148, 179],
|
|
25
|
+
[131, 75, 159, 92, 143],
|
|
26
|
+
[86, 143, 61, 168, 111],
|
|
27
|
+
];
|
|
28
|
+
const bound = (value, low, high) => Math.max(low, Math.min(high, value));
|
|
29
|
+
/** Original bounded illustrations: seeded variation never reads consumer data or scales. */
|
|
30
|
+
export function CartesianLoadingDesign({ family, seed, }) {
|
|
31
|
+
const variation = (slot) => {
|
|
32
|
+
const value = Math.sin(seed * 13.37 + slot * 7.91) * 43758.5453;
|
|
33
|
+
return (value - Math.floor(value)) * 2 - 1;
|
|
34
|
+
};
|
|
35
|
+
const profile = ((Math.trunc(seed) % 4) + 4) % 4;
|
|
36
|
+
const heights = (barProfiles[profile] ?? barProfiles[0]).map((height, index) => ({
|
|
37
|
+
x: 16 + index * 91,
|
|
38
|
+
height: bound(height + variation(index) * 12, 20, 220),
|
|
39
|
+
}));
|
|
40
|
+
const ys = (curveProfiles[profile] ?? curveProfiles[0]).map((y, index) => bound(y + variation(index + 20) * 10, 15, 220));
|
|
41
|
+
let areaCurve = `M 12 ${ys[0] ?? 120}`;
|
|
42
|
+
for (let index = 1; index < ys.length; index++) {
|
|
43
|
+
const x = 12 + index * (616 / 6);
|
|
44
|
+
const previousX = 12 + (index - 1) * (616 / 6);
|
|
45
|
+
areaCurve += ` C ${previousX + 51} ${ys[index - 1]} ${x - 51} ${ys[index]} ${x} ${ys[index]}`;
|
|
46
|
+
}
|
|
47
|
+
if (family === "area")
|
|
48
|
+
return (_jsx("path", { "data-loading-motion": "reveal", d: `${areaCurve} L 628 238 L 12 238 Z`, fill: "currentColor" }));
|
|
49
|
+
if (family === "bar")
|
|
50
|
+
return (_jsx("g", { "data-loading-motion": "grow", children: heights.map(({ x, height }) => (_jsx("rect", { x: x, y: 238 - height, width: 60, height: height, rx: 3, fill: "currentColor" }, x))) }));
|
|
51
|
+
if (family === "combo")
|
|
52
|
+
return (_jsxs(_Fragment, { children: [_jsx("g", { "data-loading-motion": "grow", children: heights.map(({ x, height }) => (_jsx("rect", { x: x, y: 238 - height * 0.67, width: 50, height: height * 0.67, rx: 3, fill: "currentColor", opacity: 0.5 }, x))) }), _jsxs("g", { children: [_jsx("path", { "data-loading-motion": "area-reveal", d: `${areaCurve} L 628 238 L 12 238 Z`, fill: "currentColor", opacity: 0.22 }), _jsx("path", { "data-loading-motion": "reveal", d: areaCurve, fill: "none", stroke: "currentColor", strokeWidth: 2.25, vectorEffect: "non-scaling-stroke", strokeLinecap: "round" })] })] }));
|
|
53
|
+
if (family === "scatter") {
|
|
54
|
+
const points = Array.from({ length: 24 }, (_, index) => {
|
|
55
|
+
const u = (variation(index + 50) + 1) / 2;
|
|
56
|
+
const v = (variation(index + 80) + 1) / 2;
|
|
57
|
+
let cx;
|
|
58
|
+
let cy;
|
|
59
|
+
if (profile === 0 || profile === 1) {
|
|
60
|
+
cx = 24 + index * 25;
|
|
61
|
+
cy = profile === 0 ? 211 - index * 7.6 : 28 + index * 7.6;
|
|
62
|
+
cy += variation(index + 110) * 24;
|
|
63
|
+
}
|
|
64
|
+
else if (profile === 2) {
|
|
65
|
+
const cluster = index % 3;
|
|
66
|
+
cx = [114, 328, 528][cluster] ?? 328;
|
|
67
|
+
cy = [166, 62, 159][cluster] ?? 110;
|
|
68
|
+
cx += (u - 0.5) * 126;
|
|
69
|
+
cy += (v - 0.5) * 83;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
cx = 22 + u * 596;
|
|
73
|
+
cy = 18 + v * 204;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
id: `point-${index}`,
|
|
77
|
+
cx: bound(cx, 12, 628),
|
|
78
|
+
cy: bound(cy, 12, 228),
|
|
79
|
+
r: 4 + (variation(index + 140) + 1) * 3,
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
return (_jsx(_Fragment, { children: points.map(({ id, cx, cy, r }) => (_jsx(LoadingPulseMark, { delay: (cx / 640) * 0.65, span: 0.35, children: _jsx("circle", { "data-loading-motion": "point", cx: cx, cy: cy, r: r, fill: "currentColor" }) }, id))) }));
|
|
83
|
+
}
|
|
84
|
+
if (family === "histogram") {
|
|
85
|
+
const gaussian = (x, peak, spread) => Math.exp(-((x - peak) ** 2) / (2 * spread ** 2));
|
|
86
|
+
const bins = Array.from({ length: 12 }, (_, index) => {
|
|
87
|
+
const density = profile === 0
|
|
88
|
+
? gaussian(index, 2.5, 1.8)
|
|
89
|
+
: profile === 1
|
|
90
|
+
? gaussian(index, 8.5, 1.8)
|
|
91
|
+
: profile === 2
|
|
92
|
+
? Math.max(gaussian(index, 2, 1.15), gaussian(index, 8.5, 1.5))
|
|
93
|
+
: index === 0
|
|
94
|
+
? 0.4
|
|
95
|
+
: Math.exp(-(index - 1) / 3.8);
|
|
96
|
+
return {
|
|
97
|
+
x: 8 + index * 52,
|
|
98
|
+
height: bound(18 + density * 194 + variation(index + 170) * 8, 10, 220),
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
return (_jsx("g", { "data-loading-motion": "grow", children: bins.map(({ x, height }) => (_jsx("rect", { x: x, y: 238 - height, width: 50, height: height, fill: "currentColor" }, x))) }));
|
|
102
|
+
}
|
|
103
|
+
if (family === "waterfall") {
|
|
104
|
+
const tops = (waterfallProfiles[profile] ?? waterfallProfiles[0]).map((top, index) => bound(top + variation(index + 200) * 9, 16, 220));
|
|
105
|
+
const levels = [238, ...tops, 238];
|
|
106
|
+
return (_jsx("g", { "data-loading-motion": "grow", children: levels.slice(1).map((top, index) => {
|
|
107
|
+
const previous = levels[index] ?? 238;
|
|
108
|
+
const x = 16 + index * 91;
|
|
109
|
+
return (_jsxs("g", { children: [_jsx("rect", { x: x, y: Math.min(top, previous), width: 48, height: Math.abs(top - previous), rx: 2, fill: "currentColor" }), index < 6 && (_jsx("path", { d: `M ${x + 48} ${top} H ${x + 91}`, fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeDasharray: "4 4", vectorEffect: "non-scaling-stroke" }))] }, x));
|
|
110
|
+
}) }));
|
|
111
|
+
}
|
|
112
|
+
const boxes = (boxCenters[profile] ?? boxCenters[0]).map((center, index) => {
|
|
113
|
+
const median = center + variation(index + 230) * 9;
|
|
114
|
+
const lowerSpan = 14 + (variation(index + 250) + 1) * 19;
|
|
115
|
+
const upperSpan = 12 + (variation(index + 270) + 1) * 20;
|
|
116
|
+
const q1 = bound(median - lowerSpan, 22, median - 6);
|
|
117
|
+
const q3 = bound(median + upperSpan, median + 6, 216);
|
|
118
|
+
const low = bound(q1 - 10 - (variation(index + 290) + 1) * 14, 10, q1 - 5);
|
|
119
|
+
const high = bound(q3 + 10 + (variation(index + 310) + 1) * 14, q3 + 5, 230);
|
|
120
|
+
return { x: 60 + index * 124, low, q1, median, q3, high };
|
|
121
|
+
});
|
|
122
|
+
return (_jsx("g", { "data-loading-motion": "grow", children: boxes.map(({ x, low, q1, median, q3, high }) => (_jsxs("g", { stroke: "currentColor", strokeWidth: 2, vectorEffect: "non-scaling-stroke", children: [_jsx("path", { d: `M ${x} ${low} V ${q1} M ${x} ${q3} V ${high} M ${x - 18} ${low} H ${x + 18} M ${x - 18} ${high} H ${x + 18}`, fill: "none" }), _jsx("rect", { x: x - 27, y: q1, width: 54, height: q3 - q1, fill: "currentColor", fillOpacity: 0.4, rx: 2 }), _jsx("path", { d: `M ${x - 27} ${median} H ${x + 27}`, fill: "none" })] }, x))) }));
|
|
123
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type MotionValue, type Transition } from "motion/react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
export type LoadingAnimation = {
|
|
4
|
+
revealDurationMs?: number;
|
|
5
|
+
revealEasing?: Transition["ease"];
|
|
6
|
+
layout?: "horizontal" | "vertical";
|
|
7
|
+
direction?: "clockwise" | "anticlockwise";
|
|
8
|
+
lineReveal?: false | Pick<LoadingAnimation, "revealDurationMs" | "revealEasing">;
|
|
9
|
+
areaReveal?: false | Pick<LoadingAnimation, "revealDurationMs" | "revealEasing">;
|
|
10
|
+
barReveal?: false | Pick<LoadingAnimation, "revealDurationMs" | "revealEasing">;
|
|
11
|
+
};
|
|
12
|
+
export declare function useLoadingProgress(pulse: number, duration: number, easing: Transition["ease"], startDelay?: number, continuousTrail?: boolean): {
|
|
13
|
+
progress: MotionValue<number>;
|
|
14
|
+
reduced: boolean;
|
|
15
|
+
};
|
|
16
|
+
export declare const LoadingProgress: import("react").Context<{
|
|
17
|
+
progress: MotionValue<number>;
|
|
18
|
+
reduced: boolean;
|
|
19
|
+
} | null>;
|
|
20
|
+
/** The same screen/diagonal ordering as native scatter and heatmap entrances, with a soft trail. */
|
|
21
|
+
export declare function LoadingPulseMark({ delay, span, children, }: {
|
|
22
|
+
delay: number;
|
|
23
|
+
span: number;
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
}): import("react").JSX.Element;
|
|
26
|
+
export declare function LoadingAngularBand({ progress, index, direction, }: {
|
|
27
|
+
progress: MotionValue<number>;
|
|
28
|
+
index: number;
|
|
29
|
+
direction: "clockwise" | "anticlockwise";
|
|
30
|
+
}): import("react").JSX.Element;
|
|
31
|
+
/** Native Sankey's source ordering and path-length reveal, with a following erase. */
|
|
32
|
+
export declare function LoadingPulseFlow({ d, width, delay, children, }: {
|
|
33
|
+
d: string;
|
|
34
|
+
width: number;
|
|
35
|
+
delay: number;
|
|
36
|
+
children: ReactNode;
|
|
37
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { animate, motion, useMotionValue, useTransform, } from "motion/react";
|
|
4
|
+
import { createContext, use, useId, useLayoutEffect, useRef, useSyncExternalStore, } from "react";
|
|
5
|
+
const query = "(prefers-reduced-motion: reduce)";
|
|
6
|
+
function subscribe(change) {
|
|
7
|
+
const media = window.matchMedia(query);
|
|
8
|
+
media.addEventListener("change", change);
|
|
9
|
+
return () => media.removeEventListener("change", change);
|
|
10
|
+
}
|
|
11
|
+
export function useLoadingProgress(pulse, duration, easing, startDelay = 0, continuousTrail = false) {
|
|
12
|
+
const reduced = useSyncExternalStore(subscribe, () => window.matchMedia(query).matches, () => true);
|
|
13
|
+
const progress = useMotionValue(0);
|
|
14
|
+
const easingCache = useRef(easing);
|
|
15
|
+
if (Array.isArray(easing) && Array.isArray(easingCache.current)) {
|
|
16
|
+
if (JSON.stringify(easing) !== JSON.stringify(easingCache.current))
|
|
17
|
+
easingCache.current = easing;
|
|
18
|
+
}
|
|
19
|
+
else if (easing !== easingCache.current)
|
|
20
|
+
easingCache.current = easing;
|
|
21
|
+
const stableEasing = easingCache.current;
|
|
22
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: a new invisible pulse deliberately rearms the entrance.
|
|
23
|
+
useLayoutEffect(() => {
|
|
24
|
+
if (reduced || duration === 0) {
|
|
25
|
+
progress.set(1);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
progress.set(0);
|
|
29
|
+
// RadialBar keeps one angular velocity through the closing seam. Splitting
|
|
30
|
+
// an eased reveal and a linear trail nearly stops, then abruptly speeds up.
|
|
31
|
+
if (continuousTrail) {
|
|
32
|
+
const controls = animate(progress, 1.65, {
|
|
33
|
+
duration: (duration * 1.65) / 1000,
|
|
34
|
+
delay: startDelay / 1000,
|
|
35
|
+
ease: "linear",
|
|
36
|
+
});
|
|
37
|
+
return () => controls.stop();
|
|
38
|
+
}
|
|
39
|
+
let trail;
|
|
40
|
+
const controls = animate(progress, 1, {
|
|
41
|
+
duration: duration / 1000,
|
|
42
|
+
delay: startDelay / 1000,
|
|
43
|
+
ease: stableEasing ?? "easeOut",
|
|
44
|
+
onComplete: () => {
|
|
45
|
+
trail = animate(progress, 1.65, { duration: (duration * 0.65) / 1000, ease: "linear" });
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
return () => {
|
|
49
|
+
controls.stop();
|
|
50
|
+
trail?.stop();
|
|
51
|
+
};
|
|
52
|
+
}, [progress, pulse, duration, stableEasing, reduced, startDelay, continuousTrail]);
|
|
53
|
+
return { progress, reduced: reduced || duration === 0 };
|
|
54
|
+
}
|
|
55
|
+
export const LoadingProgress = createContext(null);
|
|
56
|
+
/** The same screen/diagonal ordering as native scatter and heatmap entrances, with a soft trail. */
|
|
57
|
+
export function LoadingPulseMark({ delay, span, children, }) {
|
|
58
|
+
const context = use(LoadingProgress);
|
|
59
|
+
const fallback = useMotionValue(1);
|
|
60
|
+
const opacity = useTransform(context?.progress ?? fallback, (value) => context?.reduced
|
|
61
|
+
? 1
|
|
62
|
+
: Math.max(0, Math.min(1, (value - delay) / span, (delay + span + 0.4 - value) / 0.4)));
|
|
63
|
+
return (_jsx(motion.g, { "data-kind-ui": "loading-mark", style: { opacity }, children: children }));
|
|
64
|
+
}
|
|
65
|
+
export function LoadingAngularBand({ progress, index, direction, }) {
|
|
66
|
+
const sign = direction === "anticlockwise" ? -1 : 1;
|
|
67
|
+
const offset = useTransform(progress, (value) => -sign * value * 100);
|
|
68
|
+
const opacity = useTransform(progress, (value) => {
|
|
69
|
+
const angle = value * 360 - index * 18;
|
|
70
|
+
return (Math.max(0, Math.min(1, angle / 9, (360 - angle) / 9)) * Math.sin(((index + 1) / 9) * Math.PI));
|
|
71
|
+
});
|
|
72
|
+
return (_jsx(motion.circle, { "data-kind-ui": "loading-angular", cx: "320", cy: "120", r: "120", fill: "none", stroke: "white", strokeWidth: "240", pathLength: "100", strokeDasharray: "5 95", strokeDashoffset: offset, style: { opacity }, transform: `rotate(${-90 - sign * index * 18} 320 120)` }));
|
|
73
|
+
}
|
|
74
|
+
/** Native Sankey's source ordering and path-length reveal, with a following erase. */
|
|
75
|
+
export function LoadingPulseFlow({ d, width, delay, children, }) {
|
|
76
|
+
const context = use(LoadingProgress);
|
|
77
|
+
const fallback = useMotionValue(1);
|
|
78
|
+
const id = `loading-flow-${useId().replace(/:/g, "")}`;
|
|
79
|
+
const dash = useTransform(context?.progress ?? fallback, (value) => {
|
|
80
|
+
if (context?.reduced)
|
|
81
|
+
return "1 1";
|
|
82
|
+
const lead = Math.max(0, Math.min(1, (value - delay) / 0.45));
|
|
83
|
+
const trail = Math.max(0, Math.min(1, (value - delay - 0.45) / 0.4));
|
|
84
|
+
return `${Math.max(0, lead - trail)} 1`;
|
|
85
|
+
});
|
|
86
|
+
const offset = useTransform(context?.progress ?? fallback, (value) => context?.reduced ? 0 : -Math.max(0, Math.min(1, (value - delay - 0.45) / 0.4)));
|
|
87
|
+
return (_jsxs("g", { children: [_jsx("defs", { children: _jsxs("mask", { id: id, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "640", height: "240", children: [_jsx("filter", { id: `${id}-soft`, filterUnits: "userSpaceOnUse", x: "-32", y: "-32", width: "704", height: "304", children: _jsx("feGaussianBlur", { stdDeviation: "2" }) }), _jsx(motion.path, { filter: `url(#${id}-soft)`, "data-kind-ui": "loading-flow-window", d: d, fill: "none", stroke: "white", strokeWidth: width + 2, pathLength: "1", strokeDasharray: dash, strokeDashoffset: offset })] }) }), _jsx("g", { mask: `url(#${id})`, children: children })] }));
|
|
88
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type PolarLoadingFamily = "pie" | "radar" | "radial-bar" | "activity-rings";
|
|
2
|
+
/** A bounded decorative profile, independent of the native chart data. */
|
|
3
|
+
export declare function PolarLoadingDesign({ family, seed, }: {
|
|
4
|
+
family: PolarLoadingFamily;
|
|
5
|
+
seed?: number;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { animate, motion, useMotionValue } from "motion/react";
|
|
4
|
+
import { memo, use, useLayoutEffect, useMemo } from "react";
|
|
5
|
+
import { LoadingProgress } from "./loading-motion.js";
|
|
6
|
+
function point(radius, angle) {
|
|
7
|
+
const radians = (angle * Math.PI) / 180;
|
|
8
|
+
return [320 + Math.cos(radians) * radius, 120 + Math.sin(radians) * radius];
|
|
9
|
+
}
|
|
10
|
+
function arc(radius, start, end) {
|
|
11
|
+
const [sx, sy] = point(radius, start);
|
|
12
|
+
const [ex, ey] = point(radius, end);
|
|
13
|
+
return `M ${sx} ${sy} A ${radius} ${radius} 0 ${end - start > 180 ? 1 : 0} 1 ${ex} ${ey}`;
|
|
14
|
+
}
|
|
15
|
+
function polygon(radius) {
|
|
16
|
+
return [-90, -30, 30, 90, 150, 210].map((angle) => point(radius, angle).join(",")).join(" ");
|
|
17
|
+
}
|
|
18
|
+
/** A bounded decorative profile, independent of the native chart data. */
|
|
19
|
+
export function PolarLoadingDesign({ family, seed = 0, }) {
|
|
20
|
+
const cycle = Math.abs(Math.trunc(seed));
|
|
21
|
+
const variant = cycle % 4;
|
|
22
|
+
// Four authored profiles guarantee visibly distinct adjacent pulses. Small
|
|
23
|
+
// deterministic deviations keep later cycles fresh without random render work.
|
|
24
|
+
const deviation = (index, amount) => {
|
|
25
|
+
let hash = (cycle ^ Math.imul(index + 1, 0x45d9f3b)) >>> 0;
|
|
26
|
+
hash = Math.imul(hash ^ (hash >>> 16), 0x45d9f3b) >>> 0;
|
|
27
|
+
return ((hash % 101) / 50 - 1) * amount;
|
|
28
|
+
};
|
|
29
|
+
const sectorProfiles = [
|
|
30
|
+
[70, 20, 10],
|
|
31
|
+
[9, 15, 7, 30, 12, 27],
|
|
32
|
+
[10, 45, 30, 15],
|
|
33
|
+
[35, 8, 12, 10, 35],
|
|
34
|
+
];
|
|
35
|
+
if (family === "pie") {
|
|
36
|
+
const weights = (sectorProfiles[variant] ?? sectorProfiles[0] ?? []).map((weight, index) => Math.max(5, weight + deviation(index, 2)));
|
|
37
|
+
const total = weights.reduce((sum, weight) => sum + weight, 0);
|
|
38
|
+
let angle = -90;
|
|
39
|
+
return (_jsx("g", { fill: "currentColor", children: weights.map((weight) => {
|
|
40
|
+
const start = angle + 2;
|
|
41
|
+
angle += (weight / total) * 360;
|
|
42
|
+
return _jsx("path", { d: `${arc(100, start, angle - 2)} L 320 120 Z` }, start);
|
|
43
|
+
}) }));
|
|
44
|
+
}
|
|
45
|
+
if (family === "radar") {
|
|
46
|
+
const reduced = use(LoadingProgress)?.reduced ?? true;
|
|
47
|
+
return (_jsxs("g", { "data-kind-ui": "loading-radar-motion", stroke: "currentColor", fill: "none", strokeWidth: "2", vectorEffect: "non-scaling-stroke", children: [[34, 67, 100].map((radius) => (_jsx("polygon", { points: polygon(radius), opacity: "0.4" }, radius))), [-90, -30, 30, 90, 150, 210].map((angle) => {
|
|
48
|
+
const [x, y] = point(100, angle);
|
|
49
|
+
return _jsx("line", { x1: "320", y1: "120", x2: x, y2: y, opacity: "0.35" }, angle);
|
|
50
|
+
}), _jsx(RadarLoadingPolygon, { seed: seed, layer: 0, reduced: reduced }), _jsx(RadarLoadingPolygon, { seed: seed, layer: 1, reduced: reduced })] }));
|
|
51
|
+
}
|
|
52
|
+
const rings = family === "activity-rings" ? [91, 66, 41] : [97, 73, 49, 25];
|
|
53
|
+
const sweeps = family === "activity-rings"
|
|
54
|
+
? [
|
|
55
|
+
[325, 80, 190],
|
|
56
|
+
[85, 315, 55],
|
|
57
|
+
[200, 50, 325],
|
|
58
|
+
[50, 205, 100],
|
|
59
|
+
][variant]
|
|
60
|
+
: [
|
|
61
|
+
[325, 70, 190, 45],
|
|
62
|
+
[75, 310, 45, 220],
|
|
63
|
+
[195, 45, 325, 90],
|
|
64
|
+
[45, 200, 90, 315],
|
|
65
|
+
][variant];
|
|
66
|
+
return (_jsx("g", { fill: "none", stroke: "currentColor", strokeWidth: family === "activity-rings" ? 17 : 13, children: rings.map((radius, index) => (_jsxs("g", { children: [_jsx("circle", { cx: "320", cy: "120", r: radius, opacity: "0.2" }), _jsx("path", { d: arc(radius, -90, -90 + Math.max(35, Math.min(338, (sweeps?.[index] ?? 180) + deviation(index, 9)))), strokeLinecap: "round" })] }, radius))) }));
|
|
67
|
+
}
|
|
68
|
+
const radarProfiles = [
|
|
69
|
+
[96, 28, 42, 90, 25, 35],
|
|
70
|
+
[30, 94, 32, 45, 94, 28],
|
|
71
|
+
[40, 35, 96, 28, 45, 94],
|
|
72
|
+
[86, 72, 28, 34, 82, 96],
|
|
73
|
+
];
|
|
74
|
+
/** Identical vertex order makes every interpolated shape valid and continuous. */
|
|
75
|
+
const RadarLoadingPolygon = memo(function RadarLoadingPolygon({ seed, layer, reduced, }) {
|
|
76
|
+
const frames = useMemo(() => {
|
|
77
|
+
const first = (Math.abs(Math.trunc(seed)) + layer * 2) % radarProfiles.length;
|
|
78
|
+
const points = Array.from({ length: radarProfiles.length }, (_, step) => (radarProfiles[(first + step) % radarProfiles.length] ?? radarProfiles[0])
|
|
79
|
+
.map((radius, index) => point(radius * (layer === 0 ? 1 : 0.82), -90 + index * 60).join(","))
|
|
80
|
+
.join(" "));
|
|
81
|
+
return [...points, points[0] ?? ""];
|
|
82
|
+
}, [seed, layer]);
|
|
83
|
+
const points = useMotionValue(frames[0] ?? "");
|
|
84
|
+
useLayoutEffect(() => {
|
|
85
|
+
if (reduced) {
|
|
86
|
+
points.set(frames[0] ?? "");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// A persistent MotionValue keeps host rerenders from resetting an in-flight
|
|
90
|
+
// SVG attribute back to its first keyframe.
|
|
91
|
+
const controls = animate(points, frames, { duration: 8, ease: "easeInOut", repeat: Infinity });
|
|
92
|
+
return () => controls.stop();
|
|
93
|
+
}, [points, frames, reduced]);
|
|
94
|
+
return (_jsx(motion.polygon, { "data-kind-ui": "loading-radar-polygon", points: points, fill: "currentColor", fillOpacity: layer === 0 ? 0.2 : 0.12, strokeWidth: layer === 0 ? 3 : 2, strokeLinejoin: "round" }));
|
|
95
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
import { type LoadingAnimation } from "./loading-motion.js";
|
|
3
|
+
export type LoadingFamily = "line" | "area" | "bar" | "combo" | "scatter" | "waterfall" | "histogram" | "box-plot" | "pie" | "radar" | "radial-bar" | "activity-rings" | "heatmap" | "sankey";
|
|
4
|
+
/** Internal composition keeps unused family illustrations out of single-chart bundles. */
|
|
5
|
+
export type LoadingDesign = (seed: number) => ReactNode;
|
|
6
|
+
/** Fixed decorative design; native plot bounds supply only its available surface. */
|
|
7
|
+
export declare function LoadingSkeletonSurface({ family, seed: suppliedSeed, width, height, x, y, style, animation, design, }: {
|
|
8
|
+
family: LoadingFamily;
|
|
9
|
+
seed?: number;
|
|
10
|
+
width?: number | string;
|
|
11
|
+
height?: number | string;
|
|
12
|
+
x?: number;
|
|
13
|
+
y?: number;
|
|
14
|
+
style?: CSSProperties;
|
|
15
|
+
animation?: LoadingAnimation | undefined;
|
|
16
|
+
design?: LoadingDesign | undefined;
|
|
17
|
+
}): import("react").JSX.Element;
|
|
18
|
+
export declare function ChartLoadingSkeleton({ family, seed, animation, design, }: {
|
|
19
|
+
family: LoadingFamily;
|
|
20
|
+
seed: number;
|
|
21
|
+
animation?: LoadingAnimation | undefined;
|
|
22
|
+
design?: LoadingDesign | undefined;
|
|
23
|
+
}): import("react").JSX.Element | null;
|
|
24
|
+
export declare function LoadingStatus({ loading, label, }: {
|
|
25
|
+
loading?: boolean | undefined;
|
|
26
|
+
label?: string | undefined;
|
|
27
|
+
}): import("react").JSX.Element | null;
|
|
28
|
+
/** Cycle state belongs to the persistent host, so resize/render cannot change a design. */
|
|
29
|
+
export declare function useLoadingSeed(loading: boolean | undefined): number;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { motion, useTransform } from "motion/react";
|
|
4
|
+
import { useId, useState } from "react";
|
|
5
|
+
import { usePlotArea } from "recharts";
|
|
6
|
+
import { LoadingAngularBand, LoadingProgress, useLoadingProgress, } from "./loading-motion.js";
|
|
7
|
+
const cartesianEase = [0.25, 0.1, 0.25, 1];
|
|
8
|
+
const lineDesign = (seed) => (_jsx("path", { d: linePath(seed), fill: "none", stroke: "currentColor", strokeWidth: "2.25", vectorEffect: "non-scaling-stroke", strokeLinecap: "round" }));
|
|
9
|
+
const lineProfiles = [
|
|
10
|
+
[212, 188, 155, 174, 105, 81, 45, 24],
|
|
11
|
+
[28, 62, 42, 110, 132, 178, 158, 215],
|
|
12
|
+
[38, 75, 151, 220, 203, 139, 63, 28],
|
|
13
|
+
[195, 43, 174, 26, 210, 73, 186, 42],
|
|
14
|
+
];
|
|
15
|
+
function linePath(seed) {
|
|
16
|
+
const profile = lineProfiles[seed % 4] ?? lineProfiles[0] ?? [];
|
|
17
|
+
const points = profile.map((y, index) => [
|
|
18
|
+
12 + index * 88,
|
|
19
|
+
Math.max(12, Math.min(225, y + ((Math.imul(seed + index * 11, 2654435761) >>> 0) % 23) - 11)),
|
|
20
|
+
]);
|
|
21
|
+
let d = `M ${points[0]?.join(" ")}`;
|
|
22
|
+
for (let i = 1; i < points.length; i++) {
|
|
23
|
+
const previous = points[i - 1];
|
|
24
|
+
const point = points[i];
|
|
25
|
+
if (!previous || !point)
|
|
26
|
+
continue;
|
|
27
|
+
const middle = ((previous[0] ?? 0) + (point[0] ?? 0)) / 2;
|
|
28
|
+
d += ` C ${middle} ${previous[1]} ${middle} ${point[1]} ${point.join(" ")}`;
|
|
29
|
+
}
|
|
30
|
+
return d;
|
|
31
|
+
}
|
|
32
|
+
/** Fixed decorative design; native plot bounds supply only its available surface. */
|
|
33
|
+
export function LoadingSkeletonSurface({ family, seed: suppliedSeed, width = "100%", height = "100%", x, y, style, animation, design = lineDesign, }) {
|
|
34
|
+
const id = useId().replace(/:/g, "");
|
|
35
|
+
const seed = suppliedSeed ??
|
|
36
|
+
Array.from(id).reduce((value, char) => (value * 31 + char.charCodeAt(0)) >>> 0, 7);
|
|
37
|
+
const [pulse, setPulse] = useState(0);
|
|
38
|
+
const designSeed = seed + pulse;
|
|
39
|
+
const isCartesian = [
|
|
40
|
+
"line",
|
|
41
|
+
"area",
|
|
42
|
+
"bar",
|
|
43
|
+
"waterfall",
|
|
44
|
+
"histogram",
|
|
45
|
+
"box-plot",
|
|
46
|
+
"combo",
|
|
47
|
+
].includes(family);
|
|
48
|
+
const duration = Math.max(0, animation?.revealDurationMs ??
|
|
49
|
+
(family === "scatter" ? 700 : family === "heatmap" ? 800 : family === "sankey" ? 450 : 1000));
|
|
50
|
+
const easing = animation?.revealEasing ?? (isCartesian ? cartesianEase : "easeOut");
|
|
51
|
+
const part = (kind) => {
|
|
52
|
+
const option = animation?.[kind];
|
|
53
|
+
return {
|
|
54
|
+
duration: family !== "combo" || option === false
|
|
55
|
+
? 0
|
|
56
|
+
: Math.max(0, option?.revealDurationMs ?? duration),
|
|
57
|
+
easing: option === false ? easing : (option?.revealEasing ?? easing),
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
const line = part("lineReveal");
|
|
61
|
+
const area = part("areaReveal");
|
|
62
|
+
const bar = part("barReveal");
|
|
63
|
+
const cycleDuration = family === "combo" ? Math.max(line.duration, area.duration, bar.duration) : duration;
|
|
64
|
+
const cycleMs = cycleDuration * 2 + 200;
|
|
65
|
+
const startDelay = cycleMs * 0.12;
|
|
66
|
+
const { progress, reduced } = useLoadingProgress(pulse, duration, easing, startDelay, family === "radial-bar");
|
|
67
|
+
const lineMotion = useLoadingProgress(pulse, line.duration, line.easing, startDelay);
|
|
68
|
+
const areaMotion = useLoadingProgress(pulse, area.duration, area.easing, startDelay);
|
|
69
|
+
const barMotion = useLoadingProgress(pulse, bar.duration, bar.easing, startDelay);
|
|
70
|
+
const horizontalProgress = family === "combo" ? lineMotion.progress : progress;
|
|
71
|
+
const verticalProgress = family === "combo" ? barMotion.progress : progress;
|
|
72
|
+
const horizontalReduced = family === "combo" ? lineMotion.reduced : reduced;
|
|
73
|
+
const verticalReduced = family === "combo" ? barMotion.reduced : reduced;
|
|
74
|
+
const horizontal = animation?.layout === "vertical";
|
|
75
|
+
const position = useTransform(horizontalProgress, (value) => (value - 0.5) * 640);
|
|
76
|
+
const verticalPosition = useTransform(verticalProgress, (value) => 240 - value * 240);
|
|
77
|
+
const areaPosition = useTransform(areaMotion.progress, (value) => (value - 0.5) * 640);
|
|
78
|
+
const barPosition = useTransform(barMotion.progress, (value) => (value - 0.5) * 640);
|
|
79
|
+
const pulseStyle = {
|
|
80
|
+
...style,
|
|
81
|
+
// Radar's two polygon MotionValues continuously morph; disabling the CSS
|
|
82
|
+
// opacity pulse keeps them visible rather than disabling their animation.
|
|
83
|
+
...(cycleDuration === 0 || family === "radar" ? { animation: "none" } : {}),
|
|
84
|
+
"--kind-ui-loading-cycle": `${cycleMs}ms`,
|
|
85
|
+
"--kind-ui-loading-horizontal": `url(#${id}-horizontal)`,
|
|
86
|
+
"--kind-ui-loading-vertical": `url(#${id}-vertical)`,
|
|
87
|
+
"--kind-ui-loading-area": `url(#${id}-area)`,
|
|
88
|
+
};
|
|
89
|
+
const polar = ["pie", "radar", "radial-bar", "activity-rings"].includes(family);
|
|
90
|
+
return (_jsxs("svg", { "data-kind-ui": "chart-loading-skeleton", "data-family": family, "data-loading-layout": animation?.layout, "data-loading-motion": family === "line" ||
|
|
91
|
+
family === "area" ||
|
|
92
|
+
["bar", "waterfall", "histogram", "box-plot"].includes(family)
|
|
93
|
+
? "sweep"
|
|
94
|
+
: family === "radar"
|
|
95
|
+
? "morph"
|
|
96
|
+
: polar
|
|
97
|
+
? "angular"
|
|
98
|
+
: family === "heatmap"
|
|
99
|
+
? "wave"
|
|
100
|
+
: family === "sankey"
|
|
101
|
+
? "flow"
|
|
102
|
+
: family === "scatter"
|
|
103
|
+
? "emerge"
|
|
104
|
+
: family === "combo"
|
|
105
|
+
? "combined"
|
|
106
|
+
: "grow", onAnimationIteration: (event) => {
|
|
107
|
+
if (event.target === event.currentTarget && event.animationName === "kind-ui-loading-pulse")
|
|
108
|
+
setPulse((value) => value + 1);
|
|
109
|
+
}, "aria-hidden": "true", focusable: "false", x: x, y: y, width: width, height: height, style: pulseStyle, "data-reveal-duration": duration, viewBox: polar ? "200 0 240 240" : "0 0 640 240", preserveAspectRatio: polar || family === "scatter" ? "xMidYMid meet" : "none", children: [_jsxs("defs", { children: [_jsxs("linearGradient", { id: `${id}-soft-x`, children: [_jsx("stop", { offset: "0", stopColor: "white", stopOpacity: "0" }), _jsx("stop", { offset: "0.35", stopColor: "white" }), _jsx("stop", { offset: "0.75", stopColor: "white" }), _jsx("stop", { offset: "1", stopColor: "white", stopOpacity: "0" })] }), _jsxs("linearGradient", { id: `${id}-soft-y`, x1: "0", y1: "1", x2: "0", y2: "0", children: [_jsx("stop", { offset: "0", stopColor: "white", stopOpacity: "0" }), _jsx("stop", { offset: "0.35", stopColor: "white" }), _jsx("stop", { offset: "0.75", stopColor: "white" }), _jsx("stop", { offset: "1", stopColor: "white", stopOpacity: "0" })] }), _jsx("mask", { id: `${id}-horizontal`, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "640", height: "240", children: horizontalReduced ? (_jsx("rect", { width: "640", height: "240", fill: "white" })) : (_jsx(motion.rect, { "data-kind-ui": "loading-leading-window", x: position, y: "0", width: "320", height: "240", fill: `url(#${id}-soft-x)` })) }), _jsx("mask", { id: `${id}-vertical`, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "640", height: "240", children: verticalReduced ? (_jsx("rect", { width: "640", height: "240", fill: "white" })) : (_jsx(motion.rect, { "data-kind-ui": "loading-leading-window", x: "0", y: verticalPosition, width: "640", height: "120", fill: `url(#${id}-soft-y)` })) }), _jsx("mask", { id: `${id}-area`, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "640", height: "240", children: areaMotion.reduced ? (_jsx("rect", { width: "640", height: "240", fill: "white" })) : (_jsx(motion.rect, { x: areaPosition, y: "0", width: "320", height: "240", fill: `url(#${id}-soft-x)` })) }), _jsx("mask", { id: `${id}-bar-horizontal`, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "640", height: "240", children: barMotion.reduced ? (_jsx("rect", { width: "640", height: "240", fill: "white" })) : (_jsx(motion.rect, { x: barPosition, y: "0", width: "320", height: "240", fill: `url(#${id}-soft-x)` })) }), _jsx("mask", { id: `${id}-angular`, maskUnits: "userSpaceOnUse", x: "0", y: "0", width: "640", height: "240", children: reduced ? (_jsx("rect", { width: "640", height: "240", fill: "white" })) : ([0, 18, 36, 54, 72, 90, 108, 126].map((angle) => (_jsx(LoadingAngularBand, { progress: progress, index: angle / 18, direction: animation?.direction ?? "clockwise" }, angle)))) })] }), _jsx("g", { "data-kind-ui": "loading-design", opacity: "0.48", mask: family === "radar"
|
|
110
|
+
? undefined
|
|
111
|
+
: polar
|
|
112
|
+
? `url(#${id}-angular)`
|
|
113
|
+
: family === "line" || family === "area"
|
|
114
|
+
? `url(#${id}-horizontal)`
|
|
115
|
+
: family === "bar" ||
|
|
116
|
+
family === "waterfall" ||
|
|
117
|
+
family === "histogram" ||
|
|
118
|
+
family === "box-plot"
|
|
119
|
+
? `url(#${id}-horizontal)`
|
|
120
|
+
: undefined, children: _jsx(LoadingProgress, { value: { progress, reduced }, children: _jsx("g", { transform: horizontal && ["bar", "waterfall", "histogram", "box-plot"].includes(family)
|
|
121
|
+
? "matrix(0 .375 -2.6666667 0 640 0)"
|
|
122
|
+
: undefined, children: design(family === "radar" ? seed : designSeed) }) }) })] }));
|
|
123
|
+
}
|
|
124
|
+
export function ChartLoadingSkeleton({ family, seed, animation, design, }) {
|
|
125
|
+
const plot = usePlotArea();
|
|
126
|
+
if (!plot || plot.width <= 0 || plot.height <= 0)
|
|
127
|
+
return null;
|
|
128
|
+
return (_jsx(LoadingSkeletonSurface, { family: family, seed: seed, animation: animation, design: design, x: plot.x, y: plot.y, width: plot.width, height: plot.height }));
|
|
129
|
+
}
|
|
130
|
+
export function LoadingStatus({ loading, label, }) {
|
|
131
|
+
return loading === undefined ? null : (_jsx("span", { role: "status", "data-kind-ui": "chart-loading-status", children: loading ? (label ?? "Loading chart") : "" }));
|
|
132
|
+
}
|
|
133
|
+
/** Cycle state belongs to the persistent host, so resize/render cannot change a design. */
|
|
134
|
+
export function useLoadingSeed(loading) {
|
|
135
|
+
const id = useId();
|
|
136
|
+
const [state, setState] = useState({ pending: !!loading, cycle: 0 });
|
|
137
|
+
let cycle = state.cycle;
|
|
138
|
+
if (state.pending !== !!loading) {
|
|
139
|
+
cycle += loading ? 1 : 0;
|
|
140
|
+
setState({ pending: !!loading, cycle });
|
|
141
|
+
}
|
|
142
|
+
return Array.from(id).reduce((value, char) => (value * 31 + char.charCodeAt(0)) >>> 0, 7) + cycle;
|
|
143
|
+
}
|