Skip to content

Commit c8dc20b

Browse files
fix: Changing tile sparklines to use a transform to animate movement instead of sampling a point every 10ms
1 parent 2451a9e commit c8dc20b

File tree

8 files changed

+300
-79
lines changed

8 files changed

+300
-79
lines changed

src/api/atoms.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,17 @@ export const tileTimerAtom = atom<number[] | undefined>(undefined);
8383
export const bootProgressAtom = atom<BootProgress | undefined>(undefined);
8484
export const startupProgressAtom = atom<StartupProgress | undefined>(undefined);
8585

86-
export const gossipNetworkStatsAtom = atom<GossipNetworkStats | undefined>(
86+
export const gossipNetworkStatsAtom = rafAtom<GossipNetworkStats | undefined>(
8787
undefined,
8888
);
8989

9090
export const gossipPeersSizeAtom = atom<GossipPeersSize | undefined>(undefined);
91-
export const gossipPeersRowsUpdateAtom = atom<GossipPeersRowsUpdate | undefined>(
92-
undefined,
93-
);
94-
export const gossipPeersCellUpdateAtom = atom<GossipPeersCellUpdate | undefined>(
95-
undefined,
96-
);
91+
export const gossipPeersRowsUpdateAtom = atom<
92+
GossipPeersRowsUpdate | undefined
93+
>(undefined);
94+
export const gossipPeersCellUpdateAtom = atom<
95+
GossipPeersCellUpdate | undefined
96+
>(undefined);
9797

9898
export const tpsHistoryAtom = atom<TpsHistory | undefined>(undefined);
9999

src/clockUtils.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
type Sub = (now: number, dt: number) => void;
2+
3+
export function clockSub(intervalMs: number) {
4+
const subs = new Set<Sub>();
5+
let id: number | null = null;
6+
let last = performance.now();
7+
8+
function startChartClock() {
9+
if (id == null) {
10+
stopChartClock();
11+
}
12+
13+
const loop = () => {
14+
const now = performance.now();
15+
if (now - last >= intervalMs) {
16+
const dt = now - last;
17+
last = now;
18+
// all subscribers update in the same frame
19+
subs.forEach((fn) => fn(now, dt));
20+
}
21+
id = requestAnimationFrame(loop);
22+
};
23+
id = requestAnimationFrame(loop);
24+
}
25+
26+
function stopChartClock() {
27+
if (id != null) cancelAnimationFrame(id);
28+
id = null;
29+
}
30+
31+
function subscribeClock(fn: Sub) {
32+
subs.add(fn);
33+
return () => {
34+
subs.delete(fn);
35+
};
36+
}
37+
38+
startChartClock();
39+
40+
return { subscribeClock, stopChartClock };
41+
}

src/features/Overview/SlotPerformance/TileSparkLine.tsx

Lines changed: 86 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useMeasure } from "react-use";
2-
import { useMemo } from "react";
2+
import { useLayoutEffect, useMemo, useRef } from "react";
33
import {
44
tileBusyGreenColor,
55
tileBusyRedColor,
@@ -13,6 +13,11 @@ import {
1313
useScaledDataPoints,
1414
} from "./useTileSparkline";
1515
import styles from "./tileSparkline.module.css";
16+
import clsx from "clsx";
17+
18+
// 4 slots worth
19+
const windowMs = 400 * 4;
20+
const updateIntervalMs = 80;
1621

1722
interface TileParkLineProps {
1823
value?: number;
@@ -25,18 +30,19 @@ export default function TileSparkLine({
2530
value,
2631
queryBusy,
2732
height = 24,
28-
includeBg,
33+
includeBg = true,
2934
}: TileParkLineProps) {
3035
const [svgRef, { width }] = useMeasure<SVGSVGElement>();
3136

32-
const { scaledDataPoints, range } = useScaledDataPoints({
33-
value,
34-
queryBusy,
35-
rollingWindowMs: 1600,
36-
height,
37-
width,
38-
updateIntervalMs: 10,
39-
});
37+
const { scaledDataPoints, range, pxPerTick, chartTickMs, isLive } =
38+
useScaledDataPoints({
39+
value,
40+
queryBusy,
41+
windowMs,
42+
height,
43+
width,
44+
updateIntervalMs,
45+
});
4046

4147
return (
4248
<Sparkline
@@ -45,6 +51,9 @@ export default function TileSparkLine({
4551
range={range}
4652
height={height}
4753
background={includeBg ? undefined : "unset"}
54+
pxPerTick={pxPerTick}
55+
tickMs={chartTickMs}
56+
isLive={isLive}
4857
/>
4958
);
5059
}
@@ -59,6 +68,9 @@ interface SparklineProps {
5968
showRange?: boolean;
6069
height: number;
6170
background?: string;
71+
pxPerTick: number;
72+
tickMs: number;
73+
isLive: boolean;
6274
}
6375
export function Sparkline({
6476
svgRef,
@@ -67,8 +79,13 @@ export function Sparkline({
6779
showRange = false,
6880
height,
6981
background = tileSparklineBackgroundColor,
82+
pxPerTick,
83+
tickMs,
84+
isLive,
7085
}: SparklineProps) {
71-
const points = scaledDataPoints.map(({ x, y }) => `${x},${y}`).join(" ");
86+
const gRef = useRef<SVGGElement | null>(null);
87+
const polyRef = useRef<SVGPolylineElement | null>(null);
88+
const animateRef = useRef<Animation | null>(null);
7289

7390
// where the gradient colors start / end, given y scale and offset
7491
const gradientRange: SparklineRange = useMemo(() => {
@@ -79,6 +96,51 @@ export function Sparkline({
7996
return [bottom, top];
8097
}, [height, range]);
8198

99+
const points = useMemo(
100+
() => scaledDataPoints.map(({ x, y }) => `${x},${y}`).join(" "),
101+
[scaledDataPoints],
102+
);
103+
104+
useLayoutEffect(() => {
105+
const el = gRef.current;
106+
if (!el) return;
107+
108+
if (isLive) {
109+
// Only initialize animate object the first time
110+
if (!animateRef.current) {
111+
animateRef.current = el.animate(
112+
[
113+
{ transform: "translate3d(0px, 0, 0)" },
114+
{ transform: "translate3d(0px, 0, 0)" },
115+
],
116+
{ duration: tickMs, easing: "linear", fill: "forwards" },
117+
);
118+
animateRef.current.cancel();
119+
}
120+
121+
animateRef.current.finish();
122+
123+
polyRef.current?.setAttribute("points", points);
124+
125+
const effect = animateRef.current.effect as KeyframeEffect;
126+
effect.setKeyframes([
127+
{ transform: "translate3d(0px, 0, 0)" },
128+
{ transform: `translate3d(${-pxPerTick}px, 0, 0)` },
129+
]);
130+
effect.updateTiming({
131+
duration: tickMs,
132+
easing: "linear",
133+
fill: "forwards",
134+
});
135+
136+
animateRef.current.currentTime = 0;
137+
animateRef.current.play();
138+
} else {
139+
polyRef.current?.setAttribute("points", points);
140+
animateRef.current?.cancel();
141+
}
142+
}, [isLive, points, pxPerTick, tickMs]);
143+
82144
return (
83145
<>
84146
<svg
@@ -88,14 +150,18 @@ export function Sparkline({
88150
height={`${height}px`}
89151
fill="none"
90152
style={{ background }}
153+
shapeRendering="optimizeSpeed"
91154
>
92-
<polyline
93-
points={points}
94-
stroke="url(#paint0_linear_2971_11300)"
95-
widths={2}
96-
strokeWidth={strokeLineWidth}
97-
strokeLinecap="round"
98-
/>
155+
<g ref={gRef} className={styles.gTransform}>
156+
<polyline
157+
ref={polyRef}
158+
stroke="url(#paint0_linear_2971_11300)"
159+
strokeWidth={strokeLineWidth}
160+
strokeLinecap="butt"
161+
vectorEffect="non-scaling-stroke"
162+
pointerEvents="none"
163+
/>
164+
</g>
99165

100166
<defs>
101167
<linearGradient
@@ -114,10 +180,10 @@ export function Sparkline({
114180

115181
{showRange && (
116182
<>
117-
<div className={styles.rangeLabel} style={{ top: 0 }}>
183+
<div className={clsx(styles.rangeLabel, styles.top)}>
118184
{Math.round(range[1] * 100)}%
119185
</div>
120-
<div className={styles.rangeLabel} style={{ bottom: 0 }}>
186+
<div className={clsx(styles.rangeLabel, styles.bottom)}>
121187
{Math.round(range[0] * 100)}%
122188
</div>
123189
</>

src/features/Overview/SlotPerformance/atoms.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,15 +123,16 @@ export enum DisplayType {
123123

124124
export const sankeyDisplayTypeAtom = atom(DisplayType.Count);
125125

126-
export const liveTileTimerfallAtom = atom((get) => {
126+
//** Returns either the live tile timers, or undefined if a slot is selected */
127+
export const liveTileTimerAtom = atom((get) => {
127128
const selectedSlot = get(selectedSlotAtom);
128129
if (selectedSlot) return;
129130

130131
return get(tileTimerAtom);
131132
});
132133

133134
export const groupedLiveIdlePerTileAtom = atom((get) => {
134-
const liveTileTimers = get(liveTileTimerfallAtom);
135+
const liveTileTimers = get(liveTileTimerAtom);
135136
const tiles = get(tilesAtom);
136137

137138
return liveTileTimers?.reduce<Record<TileType, number[]>>(

src/features/Overview/SlotPerformance/tileSparkline.module.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,15 @@
55
font-weight: 400;
66
color: var(--tile-sparkline-range-text-color);
77
}
8+
9+
.top {
10+
top: 0;
11+
}
12+
13+
.bottom {
14+
bottom: 0;
15+
}
16+
17+
.g-transform {
18+
will-change: transform;
19+
}

0 commit comments

Comments
 (0)