@jcsoftdev/ui 0.1.3 → 0.1.5
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/dist/charts/core/distribution/xy-points.js +3 -3
- package/dist/charts/core/motion.d.ts +6 -0
- package/dist/charts/core/motion.js +9 -3
- package/dist/charts/core/use-drill.js +1 -1
- package/dist/charts/donut-chart/donut-chart.js +1 -1
- package/dist/charts/gauge-chart/gauge-chart.d.ts +1 -1
- package/dist/charts/gauge-chart/gauge-chart.js +14 -7
- package/dist/charts/heatmap-chart/heatmap-grid.js +2 -2
- package/dist/charts/histogram/histogram.js +2 -2
- package/dist/charts/maps/geo/use-map-view.js +1 -1
- package/dist/charts/tree-chart/tree-chart.js +2 -2
- package/dist/charts/tree-chart/use-tree-transition.js +1 -1
- package/package.json +1 -1
- package/src/charts/core/distribution/xy-points.tsx +3 -3
- package/src/charts/core/motion.ts +18 -4
- package/src/charts/core/use-drill.ts +1 -1
- package/src/charts/donut-chart/donut-chart.tsx +1 -1
- package/src/charts/gauge-chart/gauge-chart.tsx +18 -8
- package/src/charts/heatmap-chart/heatmap-grid.tsx +2 -2
- package/src/charts/histogram/histogram.tsx +2 -2
- package/src/charts/maps/geo/use-map-view.ts +1 -1
- package/src/charts/tree-chart/tree-chart.tsx +2 -2
- package/src/charts/tree-chart/use-tree-transition.ts +1 -1
|
@@ -14,7 +14,7 @@ import { ChartFrame } from "../chart-frame.js";
|
|
|
14
14
|
import { tint } from "../color-scale.js";
|
|
15
15
|
import { ChartDataTable } from "../data-table.js";
|
|
16
16
|
import { formatNumber, resolveFormat } from "../format.js";
|
|
17
|
-
import { useEntryAnimation } from "../motion.js";
|
|
17
|
+
import { fadeKeyframes, useEntryAnimation } from "../motion.js";
|
|
18
18
|
import { TONE_COLORS } from "../palette.js";
|
|
19
19
|
import { formatDate } from "../scales/time.js";
|
|
20
20
|
import { ChartTooltip } from "../tooltip.js";
|
|
@@ -633,7 +633,7 @@ function XYPointPlot({
|
|
|
633
633
|
);
|
|
634
634
|
});
|
|
635
635
|
root.querySelectorAll("[data-trend]").forEach((line) => {
|
|
636
|
-
line.animate(
|
|
636
|
+
line.animate(fadeKeyframes(line), {
|
|
637
637
|
duration: 500,
|
|
638
638
|
delay: 500,
|
|
639
639
|
easing: "ease-out",
|
|
@@ -647,7 +647,7 @@ function XYPointPlot({
|
|
|
647
647
|
wrapRef,
|
|
648
648
|
(root) => {
|
|
649
649
|
root.querySelectorAll("[data-canvas]").forEach((layer) => {
|
|
650
|
-
layer.animate(
|
|
650
|
+
layer.animate(fadeKeyframes(layer), {
|
|
651
651
|
duration: 450,
|
|
652
652
|
easing: "ease-out",
|
|
653
653
|
fill: "backwards"
|
|
@@ -19,6 +19,12 @@ export interface EntryAnimationOptions {
|
|
|
19
19
|
export declare function useEntryAnimation<T extends Element>(ref: RefObject<T | null>, play: (root: T) => void, { disabled, replayKey }?: EntryAnimationOptions): void;
|
|
20
20
|
/** Reveals children left to right by scaling a clip rectangle. */
|
|
21
21
|
export declare function revealClip(element: Element, duration?: number, delay?: number): void;
|
|
22
|
+
/**
|
|
23
|
+
* Fade-in keyframes that end at the element's own opacity. Ending at 1 would
|
|
24
|
+
* show a translucent element at full strength, then snap it back when the
|
|
25
|
+
* animation finishes.
|
|
26
|
+
*/
|
|
27
|
+
export declare function fadeKeyframes(element: Element): Keyframe[];
|
|
22
28
|
/** Fades an element in. */
|
|
23
29
|
export declare function fadeIn(element: Element, duration?: number, delay?: number): void;
|
|
24
30
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { useIsomorphicLayoutEffect } from "./use-chart-size.js";
|
|
3
4
|
const QUERY = "(prefers-reduced-motion: reduce)";
|
|
4
5
|
function useReducedMotion() {
|
|
5
6
|
const [reduced, setReduced] = useState(false);
|
|
@@ -21,7 +22,7 @@ function prefersReducedMotion() {
|
|
|
21
22
|
return typeof window !== "undefined" && !!window.matchMedia?.(QUERY).matches;
|
|
22
23
|
}
|
|
23
24
|
function useEntryAnimation(ref, play, { disabled = false, replayKey } = {}) {
|
|
24
|
-
|
|
25
|
+
useIsomorphicLayoutEffect(() => {
|
|
25
26
|
const root = ref.current;
|
|
26
27
|
if (disabled || !root || typeof root.animate !== "function" || prefersReducedMotion()) return;
|
|
27
28
|
play(root);
|
|
@@ -35,8 +36,12 @@ function revealClip(element, duration = 900, delay = 0) {
|
|
|
35
36
|
fill: "backwards"
|
|
36
37
|
});
|
|
37
38
|
}
|
|
39
|
+
function fadeKeyframes(element) {
|
|
40
|
+
const resting = typeof getComputedStyle === "function" ? getComputedStyle(element).opacity : "1";
|
|
41
|
+
return [{ opacity: 0 }, { opacity: resting || "1" }];
|
|
42
|
+
}
|
|
38
43
|
function fadeIn(element, duration = 400, delay = 0) {
|
|
39
|
-
element.animate(
|
|
44
|
+
element.animate(fadeKeyframes(element), {
|
|
40
45
|
duration,
|
|
41
46
|
delay,
|
|
42
47
|
easing: "ease-out",
|
|
@@ -60,7 +65,7 @@ function useAnimatedNumber(target, {
|
|
|
60
65
|
const startedAt = performance.now();
|
|
61
66
|
let frame = 0;
|
|
62
67
|
const tick = (now) => {
|
|
63
|
-
const progress = Math.min(1, (now - startedAt) / duration);
|
|
68
|
+
const progress = Math.min(1, Math.max(0, (now - startedAt) / duration));
|
|
64
69
|
const eased = 1 - (1 - progress) ** 3;
|
|
65
70
|
setValue(start + (target - start) * eased);
|
|
66
71
|
if (progress < 1) frame = requestAnimationFrame(tick);
|
|
@@ -74,6 +79,7 @@ export {
|
|
|
74
79
|
EASE_OUT,
|
|
75
80
|
easeOutCubic,
|
|
76
81
|
fadeIn,
|
|
82
|
+
fadeKeyframes,
|
|
77
83
|
revealClip,
|
|
78
84
|
useAnimatedNumber,
|
|
79
85
|
useEntryAnimation,
|
|
@@ -50,7 +50,7 @@ function useAnimatedView(target, { disabled = false, duration = 650 } = {}) {
|
|
|
50
50
|
let frame = 0;
|
|
51
51
|
const start = performance.now();
|
|
52
52
|
const step = (now) => {
|
|
53
|
-
const t = Math.min(1, (now - start) / duration);
|
|
53
|
+
const t = Math.min(1, Math.max(0, (now - start) / duration));
|
|
54
54
|
const next = interpolateView(from, to, easeOutCubic(t));
|
|
55
55
|
current.current = next;
|
|
56
56
|
setView(next);
|
|
@@ -620,7 +620,7 @@ function useTweenedValues(items, disabled) {
|
|
|
620
620
|
const started = performance.now();
|
|
621
621
|
let frame = 0;
|
|
622
622
|
const step = (now) => {
|
|
623
|
-
const t = easeOutCubic(Math.min(1, (now - started) / 700));
|
|
623
|
+
const t = easeOutCubic(Math.min(1, Math.max(0, (now - started) / 700)));
|
|
624
624
|
const next = {};
|
|
625
625
|
for (const id of Object.keys(target)) {
|
|
626
626
|
const start = from[id] ?? 0;
|
|
@@ -22,7 +22,7 @@ export interface GaugeChartProps {
|
|
|
22
22
|
variant?: "semi" | "three-quarter";
|
|
23
23
|
/** Filled arc (`progress`) or a pointer over the bands (`needle`). */
|
|
24
24
|
mode?: "progress" | "needle";
|
|
25
|
-
/** Colored bands. With `progress`
|
|
25
|
+
/** Colored bands. With `progress` the track stays neutral, the arc takes the color of the band it is in, and a band shows only while hovered or focused. */
|
|
26
26
|
ranges?: readonly GaugeRange[];
|
|
27
27
|
height?: number | "fill";
|
|
28
28
|
/** Width / height ratio; sizes the drawing from the container width when `height` is not "fill". */
|
|
@@ -6,7 +6,13 @@ import { DECORATIVE } from "../core/a11y.js";
|
|
|
6
6
|
import { ChartFrame } from "../core/chart-frame.js";
|
|
7
7
|
import { ChartDataTable } from "../core/data-table.js";
|
|
8
8
|
import { resolveFormat } from "../core/format.js";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
EASE_OUT,
|
|
11
|
+
fadeIn,
|
|
12
|
+
fadeKeyframes,
|
|
13
|
+
useAnimatedNumber,
|
|
14
|
+
useEntryAnimation
|
|
15
|
+
} from "../core/motion.js";
|
|
10
16
|
import { TONE_COLORS } from "../core/palette.js";
|
|
11
17
|
import { arcLine, degToRad, polarPoint, radToDeg } from "../core/polar.js";
|
|
12
18
|
import { ChartTooltip } from "../core/tooltip.js";
|
|
@@ -202,7 +208,7 @@ function GaugePlot({
|
|
|
202
208
|
svgRef,
|
|
203
209
|
(root) => {
|
|
204
210
|
root.querySelectorAll("[data-band]").forEach((node, i) => {
|
|
205
|
-
node.animate(
|
|
211
|
+
node.animate(fadeKeyframes(node), {
|
|
206
212
|
duration: 500,
|
|
207
213
|
delay: i * 90,
|
|
208
214
|
easing: EASE_OUT,
|
|
@@ -276,7 +282,7 @@ function GaugePlot({
|
|
|
276
282
|
)
|
|
277
283
|
}
|
|
278
284
|
) }) : null,
|
|
279
|
-
bands.length === 0 ? /* @__PURE__ */ jsx(
|
|
285
|
+
bands.length === 0 || !needle ? /* @__PURE__ */ jsx(
|
|
280
286
|
"path",
|
|
281
287
|
{
|
|
282
288
|
d: arcLine(cx, cy, mid, a0 + capAngle, a1 - capAngle),
|
|
@@ -285,7 +291,8 @@ function GaugePlot({
|
|
|
285
291
|
strokeLinecap: roundedCaps ? "round" : "butt",
|
|
286
292
|
style: { stroke: "var(--chart-grid)" }
|
|
287
293
|
}
|
|
288
|
-
) :
|
|
294
|
+
) : null,
|
|
295
|
+
bands.length > 0 ? /* @__PURE__ */ jsx("g", { mask: roundedCaps ? `url(#${uid}-track)` : void 0, children: bandPaths.map((path, index) => {
|
|
289
296
|
const item = bands[index];
|
|
290
297
|
if (!item || !path) return null;
|
|
291
298
|
return /* @__PURE__ */ jsx(
|
|
@@ -294,14 +301,14 @@ function GaugePlot({
|
|
|
294
301
|
d: path,
|
|
295
302
|
width: band,
|
|
296
303
|
color: rangeColor(item),
|
|
297
|
-
opacity: needle ? 0.92 : 0.
|
|
298
|
-
dimmed: focused != null && focused !== index,
|
|
304
|
+
opacity: needle ? 0.92 : focused === index ? 0.32 : 0,
|
|
305
|
+
dimmed: needle && focused != null && focused !== index,
|
|
299
306
|
onEnter: (event) => nav.hover(index, event),
|
|
300
307
|
onLeave: nav.leave
|
|
301
308
|
},
|
|
302
309
|
index
|
|
303
310
|
);
|
|
304
|
-
}) }),
|
|
311
|
+
}) }) : null,
|
|
305
312
|
!needle && fraction > 0 ? /* @__PURE__ */ jsx(
|
|
306
313
|
"path",
|
|
307
314
|
{
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
rangeColor,
|
|
9
9
|
scaleGradient
|
|
10
10
|
} from "../core/color-scale.js";
|
|
11
|
-
import { EASE_OUT, useEntryAnimation } from "../core/motion.js";
|
|
11
|
+
import { EASE_OUT, fadeKeyframes, useEntryAnimation } from "../core/motion.js";
|
|
12
12
|
import { ChartTooltip } from "../core/tooltip.js";
|
|
13
13
|
import { DEFAULT_LABELS } from "../core/types.js";
|
|
14
14
|
import { useCanvasPaint } from "../core/use-canvas.js";
|
|
@@ -99,7 +99,7 @@ function HeatmapGrid({
|
|
|
99
99
|
svgRef,
|
|
100
100
|
(element) => {
|
|
101
101
|
element.querySelectorAll("[data-column]").forEach((group, index) => {
|
|
102
|
-
group.animate(
|
|
102
|
+
group.animate(fadeKeyframes(group), {
|
|
103
103
|
duration: 420,
|
|
104
104
|
delay: Math.min(index * (360 / Math.max(1, columns)), 360),
|
|
105
105
|
easing: EASE_OUT,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from "../core/distribution/stats.js";
|
|
19
19
|
import { useDistributionLegend } from "../core/distribution/use-legend.js";
|
|
20
20
|
import { formatNumber, formatPercent, resolveFormat } from "../core/format.js";
|
|
21
|
-
import { EASE_OUT, useEntryAnimation } from "../core/motion.js";
|
|
21
|
+
import { EASE_OUT, fadeKeyframes, useEntryAnimation } from "../core/motion.js";
|
|
22
22
|
import { barPath, linePath } from "../core/path.js";
|
|
23
23
|
import { ChartTooltip } from "../core/tooltip.js";
|
|
24
24
|
function Histogram({
|
|
@@ -285,7 +285,7 @@ function HistogramPlot({
|
|
|
285
285
|
});
|
|
286
286
|
});
|
|
287
287
|
root.querySelectorAll("[data-curve]").forEach((curve) => {
|
|
288
|
-
curve.animate(
|
|
288
|
+
curve.animate(fadeKeyframes(curve), {
|
|
289
289
|
duration: 500,
|
|
290
290
|
delay: 420,
|
|
291
291
|
easing: "ease-out",
|
|
@@ -59,7 +59,7 @@ function useMapView({
|
|
|
59
59
|
const from = viewRef.current;
|
|
60
60
|
const start = performance.now();
|
|
61
61
|
const step = (now) => {
|
|
62
|
-
const t = Math.min(1, (now - start) / DURATION);
|
|
62
|
+
const t = Math.min(1, Math.max(0, (now - start) / DURATION));
|
|
63
63
|
const e = easeOut(t);
|
|
64
64
|
const k = from.k * (goal.k / from.k) ** e;
|
|
65
65
|
apply({ k, x: from.x + (goal.x - from.x) * e, y: from.y + (goal.y - from.y) * e });
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import { cn } from "../../utils/cn.js";
|
|
11
11
|
import { ChartFrame } from "../core/chart-frame.js";
|
|
12
12
|
import { ChartDataTable } from "../core/data-table.js";
|
|
13
|
-
import { EASE_OUT, useEntryAnimation } from "../core/motion.js";
|
|
13
|
+
import { EASE_OUT, fadeKeyframes, useEntryAnimation } from "../core/motion.js";
|
|
14
14
|
import { treeFitView } from "../core/tree-fit.js";
|
|
15
15
|
import {
|
|
16
16
|
treeEdgePath,
|
|
@@ -388,7 +388,7 @@ function TreePlot({
|
|
|
388
388
|
);
|
|
389
389
|
});
|
|
390
390
|
root.querySelectorAll("[data-tree-edges]").forEach((element) => {
|
|
391
|
-
element.animate(
|
|
391
|
+
element.animate(fadeKeyframes(element), {
|
|
392
392
|
duration: 500,
|
|
393
393
|
delay: 160,
|
|
394
394
|
easing: "ease-out",
|
|
@@ -58,7 +58,7 @@ function useTreeTransition(layout, parentOf, { disabled = false, duration = 360
|
|
|
58
58
|
let frame = 0;
|
|
59
59
|
const start = performance.now();
|
|
60
60
|
const step = (now) => {
|
|
61
|
-
const t = Math.min(1, (now - start) / duration);
|
|
61
|
+
const t = Math.min(1, Math.max(0, (now - start) / duration));
|
|
62
62
|
if (t >= 1) {
|
|
63
63
|
show(final);
|
|
64
64
|
return;
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@ import { ChartFrame } from "../chart-frame.js";
|
|
|
17
17
|
import { tint } from "../color-scale.js";
|
|
18
18
|
import { ChartDataTable } from "../data-table.js";
|
|
19
19
|
import { formatNumber, resolveFormat, type ValueFormat } from "../format.js";
|
|
20
|
-
import { useEntryAnimation } from "../motion.js";
|
|
20
|
+
import { fadeKeyframes, useEntryAnimation } from "../motion.js";
|
|
21
21
|
import { type ChartTone, TONE_COLORS } from "../palette.js";
|
|
22
22
|
import { formatDate } from "../scales/time.js";
|
|
23
23
|
import { ChartTooltip, type TooltipRow } from "../tooltip.js";
|
|
@@ -858,7 +858,7 @@ function XYPointPlot({
|
|
|
858
858
|
);
|
|
859
859
|
});
|
|
860
860
|
root.querySelectorAll("[data-trend]").forEach((line) => {
|
|
861
|
-
line.animate(
|
|
861
|
+
line.animate(fadeKeyframes(line), {
|
|
862
862
|
duration: 500,
|
|
863
863
|
delay: 500,
|
|
864
864
|
easing: "ease-out",
|
|
@@ -873,7 +873,7 @@ function XYPointPlot({
|
|
|
873
873
|
wrapRef,
|
|
874
874
|
(root) => {
|
|
875
875
|
root.querySelectorAll("[data-canvas]").forEach((layer) => {
|
|
876
|
-
layer.animate(
|
|
876
|
+
layer.animate(fadeKeyframes(layer), {
|
|
877
877
|
duration: 450,
|
|
878
878
|
easing: "ease-out",
|
|
879
879
|
fill: "backwards",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { type RefObject, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { useIsomorphicLayoutEffect } from "./use-chart-size.js";
|
|
4
5
|
|
|
5
6
|
const QUERY = "(prefers-reduced-motion: reduce)";
|
|
6
7
|
|
|
@@ -47,8 +48,10 @@ export function useEntryAnimation<T extends Element>(
|
|
|
47
48
|
play: (root: T) => void,
|
|
48
49
|
{ disabled = false, replayKey }: EntryAnimationOptions = {},
|
|
49
50
|
): void {
|
|
50
|
-
//
|
|
51
|
-
|
|
51
|
+
// A layout effect starts the animations before the first paint. With a plain
|
|
52
|
+
// effect the final state paints for one frame, then `fill: "backwards"` hides
|
|
53
|
+
// it again, which reads as a flicker. Replays only when the key changes.
|
|
54
|
+
useIsomorphicLayoutEffect(() => {
|
|
52
55
|
const root = ref.current;
|
|
53
56
|
if (disabled || !root || typeof root.animate !== "function" || prefersReducedMotion()) return;
|
|
54
57
|
play(root);
|
|
@@ -65,9 +68,19 @@ export function revealClip(element: Element, duration = 900, delay = 0): void {
|
|
|
65
68
|
});
|
|
66
69
|
}
|
|
67
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Fade-in keyframes that end at the element's own opacity. Ending at 1 would
|
|
73
|
+
* show a translucent element at full strength, then snap it back when the
|
|
74
|
+
* animation finishes.
|
|
75
|
+
*/
|
|
76
|
+
export function fadeKeyframes(element: Element): Keyframe[] {
|
|
77
|
+
const resting = typeof getComputedStyle === "function" ? getComputedStyle(element).opacity : "1";
|
|
78
|
+
return [{ opacity: 0 }, { opacity: resting || "1" }];
|
|
79
|
+
}
|
|
80
|
+
|
|
68
81
|
/** Fades an element in. */
|
|
69
82
|
export function fadeIn(element: Element, duration = 400, delay = 0): void {
|
|
70
|
-
element.animate(
|
|
83
|
+
element.animate(fadeKeyframes(element), {
|
|
71
84
|
duration,
|
|
72
85
|
delay,
|
|
73
86
|
easing: "ease-out",
|
|
@@ -100,7 +113,8 @@ export function useAnimatedNumber(
|
|
|
100
113
|
const startedAt = performance.now();
|
|
101
114
|
let frame = 0;
|
|
102
115
|
const tick = (now: number) => {
|
|
103
|
-
|
|
116
|
+
// rAF timestamps are the frame start and can precede `startedAt`.
|
|
117
|
+
const progress = Math.min(1, Math.max(0, (now - startedAt) / duration));
|
|
104
118
|
const eased = 1 - (1 - progress) ** 3;
|
|
105
119
|
setValue(start + (target - start) * eased);
|
|
106
120
|
if (progress < 1) frame = requestAnimationFrame(tick);
|
|
@@ -86,7 +86,7 @@ export function useAnimatedView(
|
|
|
86
86
|
let frame = 0;
|
|
87
87
|
const start = performance.now();
|
|
88
88
|
const step = (now: number) => {
|
|
89
|
-
const t = Math.min(1, (now - start) / duration);
|
|
89
|
+
const t = Math.min(1, Math.max(0, (now - start) / duration));
|
|
90
90
|
const next = interpolateView(from, to, easeOutCubic(t));
|
|
91
91
|
current.current = next;
|
|
92
92
|
setView(next);
|
|
@@ -810,7 +810,7 @@ function useTweenedValues(items: readonly Resolved[], disabled: boolean): number
|
|
|
810
810
|
const started = performance.now();
|
|
811
811
|
let frame = 0;
|
|
812
812
|
const step = (now: number) => {
|
|
813
|
-
const t = easeOutCubic(Math.min(1, (now - started) / 700));
|
|
813
|
+
const t = easeOutCubic(Math.min(1, Math.max(0, (now - started) / 700)));
|
|
814
814
|
const next: Record<string, number> = {};
|
|
815
815
|
for (const id of Object.keys(target)) {
|
|
816
816
|
const start = from[id] ?? 0;
|
|
@@ -6,7 +6,13 @@ import { DECORATIVE } from "../core/a11y.js";
|
|
|
6
6
|
import { ChartFrame } from "../core/chart-frame.js";
|
|
7
7
|
import { ChartDataTable } from "../core/data-table.js";
|
|
8
8
|
import { resolveFormat, type ValueFormat } from "../core/format.js";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
EASE_OUT,
|
|
11
|
+
fadeIn,
|
|
12
|
+
fadeKeyframes,
|
|
13
|
+
useAnimatedNumber,
|
|
14
|
+
useEntryAnimation,
|
|
15
|
+
} from "../core/motion.js";
|
|
10
16
|
import { type ChartTone, TONE_COLORS } from "../core/palette.js";
|
|
11
17
|
import { arcLine, degToRad, polarPoint, radToDeg } from "../core/polar.js";
|
|
12
18
|
import { ChartTooltip } from "../core/tooltip.js";
|
|
@@ -35,7 +41,7 @@ export interface GaugeChartProps {
|
|
|
35
41
|
variant?: "semi" | "three-quarter";
|
|
36
42
|
/** Filled arc (`progress`) or a pointer over the bands (`needle`). */
|
|
37
43
|
mode?: "progress" | "needle";
|
|
38
|
-
/** Colored bands. With `progress`
|
|
44
|
+
/** Colored bands. With `progress` the track stays neutral, the arc takes the color of the band it is in, and a band shows only while hovered or focused. */
|
|
39
45
|
ranges?: readonly GaugeRange[];
|
|
40
46
|
height?: number | "fill";
|
|
41
47
|
/** Width / height ratio; sizes the drawing from the container width when `height` is not "fill". */
|
|
@@ -309,7 +315,7 @@ function GaugePlot({
|
|
|
309
315
|
svgRef,
|
|
310
316
|
(root) => {
|
|
311
317
|
root.querySelectorAll("[data-band]").forEach((node, i) => {
|
|
312
|
-
node.animate(
|
|
318
|
+
node.animate(fadeKeyframes(node), {
|
|
313
319
|
duration: 500,
|
|
314
320
|
delay: i * 90,
|
|
315
321
|
easing: EASE_OUT,
|
|
@@ -400,7 +406,7 @@ function GaugePlot({
|
|
|
400
406
|
</defs>
|
|
401
407
|
) : null}
|
|
402
408
|
|
|
403
|
-
{bands.length === 0 ? (
|
|
409
|
+
{bands.length === 0 || !needle ? (
|
|
404
410
|
<path
|
|
405
411
|
d={arcLine(cx, cy, mid, a0 + capAngle, a1 - capAngle)}
|
|
406
412
|
fill="none"
|
|
@@ -408,7 +414,8 @@ function GaugePlot({
|
|
|
408
414
|
strokeLinecap={roundedCaps ? "round" : "butt"}
|
|
409
415
|
style={{ stroke: "var(--chart-grid)" }}
|
|
410
416
|
/>
|
|
411
|
-
) :
|
|
417
|
+
) : null}
|
|
418
|
+
{bands.length > 0 ? (
|
|
412
419
|
<g mask={roundedCaps ? `url(#${uid}-track)` : undefined}>
|
|
413
420
|
{bandPaths.map((path, index) => {
|
|
414
421
|
const item = bands[index];
|
|
@@ -420,15 +427,18 @@ function GaugePlot({
|
|
|
420
427
|
d={path}
|
|
421
428
|
width={band}
|
|
422
429
|
color={rangeColor(item)}
|
|
423
|
-
|
|
424
|
-
|
|
430
|
+
// In progress mode the bands behind the arc would show as a
|
|
431
|
+
// patchwork of tints next to it, so they stay invisible hit areas
|
|
432
|
+
// and only appear while explored.
|
|
433
|
+
opacity={needle ? 0.92 : focused === index ? 0.32 : 0}
|
|
434
|
+
dimmed={needle && focused != null && focused !== index}
|
|
425
435
|
onEnter={(event) => nav.hover(index, event)}
|
|
426
436
|
onLeave={nav.leave}
|
|
427
437
|
/>
|
|
428
438
|
);
|
|
429
439
|
})}
|
|
430
440
|
</g>
|
|
431
|
-
)}
|
|
441
|
+
) : null}
|
|
432
442
|
|
|
433
443
|
{!needle && fraction > 0 ? (
|
|
434
444
|
<path
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
scaleGradient,
|
|
12
12
|
} from "../core/color-scale.js";
|
|
13
13
|
import type { ValueFormatter } from "../core/format.js";
|
|
14
|
-
import { EASE_OUT, useEntryAnimation } from "../core/motion.js";
|
|
14
|
+
import { EASE_OUT, fadeKeyframes, useEntryAnimation } from "../core/motion.js";
|
|
15
15
|
import { ChartTooltip } from "../core/tooltip.js";
|
|
16
16
|
import { type ChartLabels, DEFAULT_LABELS } from "../core/types.js";
|
|
17
17
|
import { useCanvasPaint } from "../core/use-canvas.js";
|
|
@@ -172,7 +172,7 @@ export function HeatmapGrid({
|
|
|
172
172
|
svgRef,
|
|
173
173
|
(element) => {
|
|
174
174
|
element.querySelectorAll("[data-column]").forEach((group, index) => {
|
|
175
|
-
group.animate(
|
|
175
|
+
group.animate(fadeKeyframes(group), {
|
|
176
176
|
duration: 420,
|
|
177
177
|
delay: Math.min(index * (360 / Math.max(1, columns)), 360),
|
|
178
178
|
easing: EASE_OUT,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
} from "../core/distribution/stats.js";
|
|
21
21
|
import { type ColoredSeries, useDistributionLegend } from "../core/distribution/use-legend.js";
|
|
22
22
|
import { formatNumber, formatPercent, resolveFormat, type ValueFormat } from "../core/format.js";
|
|
23
|
-
import { EASE_OUT, useEntryAnimation } from "../core/motion.js";
|
|
23
|
+
import { EASE_OUT, fadeKeyframes, useEntryAnimation } from "../core/motion.js";
|
|
24
24
|
import { barPath, linePath } from "../core/path.js";
|
|
25
25
|
import { ChartTooltip, type TooltipRow } from "../core/tooltip.js";
|
|
26
26
|
|
|
@@ -400,7 +400,7 @@ function HistogramPlot({
|
|
|
400
400
|
});
|
|
401
401
|
});
|
|
402
402
|
root.querySelectorAll("[data-curve]").forEach((curve) => {
|
|
403
|
-
curve.animate(
|
|
403
|
+
curve.animate(fadeKeyframes(curve), {
|
|
404
404
|
duration: 500,
|
|
405
405
|
delay: 420,
|
|
406
406
|
easing: "ease-out",
|
|
@@ -90,7 +90,7 @@ export function useMapView<E extends Element>({
|
|
|
90
90
|
const from = viewRef.current;
|
|
91
91
|
const start = performance.now();
|
|
92
92
|
const step = (now: number) => {
|
|
93
|
-
const t = Math.min(1, (now - start) / DURATION);
|
|
93
|
+
const t = Math.min(1, Math.max(0, (now - start) / DURATION));
|
|
94
94
|
const e = easeOut(t);
|
|
95
95
|
// Interpolate zoom geometrically so the speed feels constant.
|
|
96
96
|
const k = from.k * (goal.k / from.k) ** e;
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { cn } from "../../utils/cn.js";
|
|
13
13
|
import { ChartFrame } from "../core/chart-frame.js";
|
|
14
14
|
import { ChartDataTable } from "../core/data-table.js";
|
|
15
|
-
import { EASE_OUT, useEntryAnimation } from "../core/motion.js";
|
|
15
|
+
import { EASE_OUT, fadeKeyframes, useEntryAnimation } from "../core/motion.js";
|
|
16
16
|
import { type TreeFitMode, treeFitView } from "../core/tree-fit.js";
|
|
17
17
|
import {
|
|
18
18
|
type TreeDirection,
|
|
@@ -568,7 +568,7 @@ function TreePlot({
|
|
|
568
568
|
);
|
|
569
569
|
});
|
|
570
570
|
root.querySelectorAll("[data-tree-edges]").forEach((element) => {
|
|
571
|
-
element.animate(
|
|
571
|
+
element.animate(fadeKeyframes(element), {
|
|
572
572
|
duration: 500,
|
|
573
573
|
delay: 160,
|
|
574
574
|
easing: "ease-out",
|
|
@@ -92,7 +92,7 @@ export function useTreeTransition(
|
|
|
92
92
|
let frame = 0;
|
|
93
93
|
const start = performance.now();
|
|
94
94
|
const step = (now: number) => {
|
|
95
|
-
const t = Math.min(1, (now - start) / duration);
|
|
95
|
+
const t = Math.min(1, Math.max(0, (now - start) / duration));
|
|
96
96
|
if (t >= 1) {
|
|
97
97
|
show(final);
|
|
98
98
|
return;
|