@@ -17,14 +17,11 @@ export function LineChart({
1717 color : string
1818 unit ?: string
1919} ) {
20- // Responsive sizing: chart fills its container width
2120 const containerRef = useRef < HTMLDivElement | null > ( null )
2221 const [ containerWidth , setContainerWidth ] = useState < number > ( 420 )
2322 const width = containerWidth
24- const height = 176
25- // Add a touch more space below the axis so curves never visually clip it
26- const padding = { top : 18 , right : 18 , bottom : 32 , left : 42 }
27- // Observe container width for responsiveness
23+ const height = 166
24+ const padding = { top : 16 , right : 28 , bottom : 26 , left : 26 }
2825 useEffect ( ( ) => {
2926 if ( ! containerRef . current ) return
3027 const element = containerRef . current
@@ -36,7 +33,6 @@ export function LineChart({
3633 }
3734 } )
3835 ro . observe ( element )
39- // Initialize once immediately
4036 const rect = element . getBoundingClientRect ( )
4137 if ( rect ?. width ) setContainerWidth ( Math . max ( 280 , Math . floor ( rect . width ) ) )
4238 return ( ) => ro . disconnect ( )
@@ -67,22 +63,26 @@ export function LineChart({
6763 )
6864 }
6965
70- // Ensure nice padding on the y-domain so the line never hugs the axes
7166 const rawMax = Math . max ( ...data . map ( ( d ) => d . value ) , 1 )
7267 const rawMin = Math . min ( ...data . map ( ( d ) => d . value ) , 0 )
7368 const paddedMax = rawMax === 0 ? 1 : rawMax * 1.1
74- const paddedMin = Math . min ( 0 , rawMin ) // never below zero for our metrics
75- const maxValue = Math . ceil ( paddedMax )
76- const minValue = Math . floor ( paddedMin )
69+ const paddedMin = Math . min ( 0 , rawMin )
70+ const unitSuffixPre = ( unit || '' ) . trim ( ) . toLowerCase ( )
71+ let maxValue = Math . ceil ( paddedMax )
72+ let minValue = Math . floor ( paddedMin )
73+ if ( unitSuffixPre === 'ms' ) {
74+ maxValue = Math . max ( 1000 , Math . ceil ( paddedMax / 1000 ) * 1000 )
75+ minValue = 0
76+ }
7777 const valueRange = maxValue - minValue || 1
7878
7979 const yMin = padding . top + 3
8080 const yMax = padding . top + chartHeight - 3
8181
8282 const scaledPoints = data . map ( ( d , i ) => {
83- const x = padding . left + ( i / ( data . length - 1 || 1 ) ) * chartWidth
83+ const usableW = Math . max ( 1 , chartWidth - 8 )
84+ const x = padding . left + ( i / ( data . length - 1 || 1 ) ) * usableW
8485 const rawY = padding . top + chartHeight - ( ( d . value - minValue ) / valueRange ) * chartHeight
85- // keep the line safely within the plotting area to avoid clipping behind the x-axis
8686 const y = Math . max ( yMin , Math . min ( yMax , rawY ) )
8787 return { x, y }
8888 } )
@@ -101,7 +101,6 @@ export function LineChart({
101101 let cp1y = p1 . y + ( ( p2 . y - p0 . y ) / 6 ) * tension
102102 const cp2x = p2 . x - ( ( p3 . x - p1 . x ) / 6 ) * tension
103103 let cp2y = p2 . y - ( ( p3 . y - p1 . y ) / 6 ) * tension
104- // Clamp control points vertically to avoid bezier overshoot below the axis
105104 cp1y = Math . max ( yMin , Math . min ( yMax , cp1y ) )
106105 cp2y = Math . max ( yMin , Math . min ( yMax , cp2y ) )
107106 d += ` C ${ cp1x } ${ cp1y } , ${ cp2x } ${ cp2y } , ${ p2 . x } ${ p2 . y } `
@@ -110,14 +109,17 @@ export function LineChart({
110109 } ) ( )
111110
112111 return (
113- < div ref = { containerRef } className = 'w-full rounded-[11px] border bg-card p-4 shadow-sm' >
112+ < div
113+ ref = { containerRef }
114+ className = 'w-full overflow-hidden rounded-[11px] border bg-card p-4 shadow-sm'
115+ >
114116 < h4 className = 'mb-3 font-medium text-foreground text-sm' > { label } </ h4 >
115117 < TooltipProvider delayDuration = { 0 } >
116118 < div className = 'relative' style = { { width, height } } >
117119 < svg
118120 width = { width }
119121 height = { height }
120- className = 'overflow-visible '
122+ className = 'overflow-hidden '
121123 onMouseMove = { ( e ) => {
122124 if ( scaledPoints . length === 0 ) return
123125 const rect = ( e . currentTarget as SVGSVGElement ) . getBoundingClientRect ( )
@@ -138,7 +140,7 @@ export function LineChart({
138140 < rect
139141 x = { padding . left }
140142 y = { yMin }
141- width = { chartWidth }
143+ width = { Math . max ( 1 , chartWidth - 8 ) }
142144 height = { chartHeight - ( yMin - padding . top ) * 2 }
143145 rx = '2'
144146 />
@@ -156,7 +158,7 @@ export function LineChart({
156158
157159 { [ 0.25 , 0.5 , 0.75 ] . map ( ( p ) => (
158160 < line
159- key = { p }
161+ key = { ` ${ label } -grid- ${ p } ` }
160162 x1 = { padding . left }
161163 y1 = { padding . top + chartHeight * p }
162164 x2 = { width - padding . right }
@@ -189,7 +191,6 @@ export function LineChart({
189191 style = { { mixBlendMode : isDark ? 'screen' : 'normal' } }
190192 />
191193 ) : (
192- // Single-point series: show a dot so the value doesn't "disappear"
193194 < circle cx = { scaledPoints [ 0 ] . x } cy = { scaledPoints [ 0 ] . y } r = '3' fill = { color } />
194195 ) }
195196
@@ -215,9 +216,10 @@ export function LineChart({
215216
216217 { ( ( ) => {
217218 if ( data . length < 2 ) return null
219+ const usableW = Math . max ( 1 , chartWidth - 8 )
218220 const idx = [ 0 , Math . floor ( data . length / 2 ) , data . length - 1 ]
219221 return idx . map ( ( i ) => {
220- const x = padding . left + ( i / ( data . length - 1 || 1 ) ) * chartWidth
222+ const x = padding . left + ( i / ( data . length - 1 || 1 ) ) * usableW
221223 const tsSource = data [ i ] ?. timestamp
222224 if ( ! tsSource ) return null
223225 const ts = new Date ( tsSource )
@@ -226,10 +228,10 @@ export function LineChart({
226228 : ts . toLocaleString ( 'en-US' , { month : 'short' , day : 'numeric' } )
227229 return (
228230 < text
229- key = { i }
231+ key = { ` ${ label } -x-axis- ${ i } ` }
230232 x = { x }
231233 y = { height - padding . bottom + 14 }
232- fontSize = '10 '
234+ fontSize = '9 '
233235 textAnchor = 'middle'
234236 fill = 'hsl(var(--muted-foreground))'
235237 >
@@ -239,26 +241,41 @@ export function LineChart({
239241 } )
240242 } ) ( ) }
241243
242- < text
243- x = { padding . left - 10 }
244- y = { padding . top }
245- textAnchor = 'end'
246- fontSize = '10'
247- fill = 'hsl(var(--muted-foreground))'
248- >
249- { maxValue }
250- { unit }
251- </ text >
252- < text
253- x = { padding . left - 10 }
254- y = { height - padding . bottom }
255- textAnchor = 'end'
256- fontSize = '10'
257- fill = 'hsl(var(--muted-foreground))'
258- >
259- { minValue }
260- { unit }
261- </ text >
244+ { ( ( ) => {
245+ const unitSuffix = ( unit || '' ) . trim ( )
246+ const showInTicks = unitSuffix === '%'
247+ const fmtCompact = ( v : number ) =>
248+ new Intl . NumberFormat ( 'en-US' , {
249+ notation : 'compact' ,
250+ maximumFractionDigits : 1 ,
251+ } )
252+ . format ( v )
253+ . toLowerCase ( )
254+ return (
255+ < >
256+ < text
257+ x = { padding . left - 8 }
258+ y = { padding . top }
259+ textAnchor = 'end'
260+ fontSize = '9'
261+ fill = 'hsl(var(--muted-foreground))'
262+ >
263+ { fmtCompact ( maxValue ) }
264+ { showInTicks ? unit : '' }
265+ </ text >
266+ < text
267+ x = { padding . left - 8 }
268+ y = { height - padding . bottom }
269+ textAnchor = 'end'
270+ fontSize = '9'
271+ fill = 'hsl(var(--muted-foreground))'
272+ >
273+ { fmtCompact ( minValue ) }
274+ { showInTicks ? unit : '' }
275+ </ text >
276+ </ >
277+ )
278+ } ) ( ) }
262279
263280 < line
264281 x1 = { padding . left }
@@ -283,7 +300,7 @@ export function LineChart({
283300 } else if ( u . toLowerCase ( ) . includes ( 'ms' ) ) {
284301 formatted = `${ Math . round ( val ) } ms`
285302 } else if ( u . toLowerCase ( ) . includes ( 'exec' ) ) {
286- formatted = `${ Math . round ( val ) } ${ u } ` // keep label like " execs"
303+ formatted = `${ Math . round ( val ) } ${ u } `
287304 } else {
288305 formatted = `${ Math . round ( val ) } ${ u } `
289306 }
0 commit comments