11import { useMeasure } from "react-use" ;
2- import { useMemo } from "react" ;
2+ import { useLayoutEffect , useMemo , useRef } from "react" ;
33import {
44 tileBusyGreenColor ,
55 tileBusyRedColor ,
@@ -13,6 +13,11 @@ import {
1313 useScaledDataPoints ,
1414} from "./useTileSparkline" ;
1515import 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
1722interface 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}
6375export 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 </ >
0 commit comments