@@ -22,6 +22,127 @@ const CONFIG = {
2222 apiBaseUrl : '/api'
2323} ;
2424
25+ // ==========================================================================
26+ // Latency Color Thresholds & Gradients
27+ // ==========================================================================
28+
29+ // RGB values for smooth color interpolation on latency chart
30+ const LATENCY_RGB = {
31+ good : { r : 16 , g : 124 , b : 16 } , // Green - good (<150ms)
32+ degraded : { r : 255 , g : 185 , b : 0 } , // Yellow - degraded (150ms-1s)
33+ severe : { r : 255 , g : 140 , b : 0 } , // Orange - severe (1s+)
34+ critical : { r : 209 , g : 52 , b : 56 } // Red - critical (30s+)
35+ } ;
36+
37+ /**
38+ * Interpolates between two RGB colors.
39+ * @param {Object } color1 - Start color {r, g, b}
40+ * @param {Object } color2 - End color {r, g, b}
41+ * @param {number } t - Interpolation factor (0-1)
42+ * @returns {string } - RGB color string
43+ */
44+ function lerpColor ( color1 , color2 , t ) {
45+ t = Math . max ( 0 , Math . min ( 1 , t ) ) ; // Clamp to 0-1
46+ const r = Math . round ( color1 . r + ( color2 . r - color1 . r ) * t ) ;
47+ const g = Math . round ( color1 . g + ( color2 . g - color1 . g ) * t ) ;
48+ const b = Math . round ( color1 . b + ( color2 . b - color1 . b ) * t ) ;
49+ return `rgb(${ r } , ${ g } , ${ b } )` ;
50+ }
51+
52+ /**
53+ * Gets a smoothly interpolated color for a latency value.
54+ * Blends between threshold colors based on where the value falls.
55+ * @param {number } latencyMs - Latency value in milliseconds
56+ * @returns {string } - RGB color string
57+ */
58+ function getInterpolatedLatencyColor ( latencyMs ) {
59+ if ( latencyMs <= 0 ) return lerpColor ( LATENCY_RGB . good , LATENCY_RGB . good , 0 ) ;
60+
61+ // 0-150ms: green → yellow
62+ if ( latencyMs <= 150 ) {
63+ const t = latencyMs / 150 ;
64+ return lerpColor ( LATENCY_RGB . good , LATENCY_RGB . degraded , t ) ;
65+ }
66+
67+ // 150-1000ms: yellow → orange
68+ if ( latencyMs <= 1000 ) {
69+ const t = ( latencyMs - 150 ) / ( 1000 - 150 ) ;
70+ return lerpColor ( LATENCY_RGB . degraded , LATENCY_RGB . severe , t ) ;
71+ }
72+
73+ // 1000-30000ms: orange → red
74+ if ( latencyMs <= 30000 ) {
75+ const t = ( latencyMs - 1000 ) / ( 30000 - 1000 ) ;
76+ return lerpColor ( LATENCY_RGB . severe , LATENCY_RGB . critical , t ) ;
77+ }
78+
79+ // >30000ms: solid red
80+ return lerpColor ( LATENCY_RGB . critical , LATENCY_RGB . critical , 1 ) ;
81+ }
82+
83+ /**
84+ * Gets a smoothly interpolated RGBA color for a latency value (for gradient fills).
85+ * @param {number } latencyMs - Latency value in milliseconds
86+ * @param {number } alpha - Alpha value (0-1)
87+ * @returns {string } - RGBA color string
88+ */
89+ function getInterpolatedLatencyColorRGBA ( latencyMs , alpha ) {
90+ let r , g , b ;
91+
92+ if ( latencyMs <= 0 ) {
93+ r = LATENCY_RGB . good . r ; g = LATENCY_RGB . good . g ; b = LATENCY_RGB . good . b ;
94+ } else if ( latencyMs <= 150 ) {
95+ const t = latencyMs / 150 ;
96+ r = Math . round ( LATENCY_RGB . good . r + ( LATENCY_RGB . degraded . r - LATENCY_RGB . good . r ) * t ) ;
97+ g = Math . round ( LATENCY_RGB . good . g + ( LATENCY_RGB . degraded . g - LATENCY_RGB . good . g ) * t ) ;
98+ b = Math . round ( LATENCY_RGB . good . b + ( LATENCY_RGB . degraded . b - LATENCY_RGB . good . b ) * t ) ;
99+ } else if ( latencyMs <= 1000 ) {
100+ const t = ( latencyMs - 150 ) / ( 1000 - 150 ) ;
101+ r = Math . round ( LATENCY_RGB . degraded . r + ( LATENCY_RGB . severe . r - LATENCY_RGB . degraded . r ) * t ) ;
102+ g = Math . round ( LATENCY_RGB . degraded . g + ( LATENCY_RGB . severe . g - LATENCY_RGB . degraded . g ) * t ) ;
103+ b = Math . round ( LATENCY_RGB . degraded . b + ( LATENCY_RGB . severe . b - LATENCY_RGB . degraded . b ) * t ) ;
104+ } else if ( latencyMs <= 30000 ) {
105+ const t = ( latencyMs - 1000 ) / ( 30000 - 1000 ) ;
106+ r = Math . round ( LATENCY_RGB . severe . r + ( LATENCY_RGB . critical . r - LATENCY_RGB . severe . r ) * t ) ;
107+ g = Math . round ( LATENCY_RGB . severe . g + ( LATENCY_RGB . critical . g - LATENCY_RGB . severe . g ) * t ) ;
108+ b = Math . round ( LATENCY_RGB . severe . b + ( LATENCY_RGB . critical . b - LATENCY_RGB . severe . b ) * t ) ;
109+ } else {
110+ r = LATENCY_RGB . critical . r ; g = LATENCY_RGB . critical . g ; b = LATENCY_RGB . critical . b ;
111+ }
112+
113+ return `rgba(${ r } , ${ g } , ${ b } , ${ alpha } )` ;
114+ }
115+
116+ /**
117+ * Creates a vertical gradient for the latency chart with smooth color blending.
118+ * Adds many intermediate color stops for seamless transitions between thresholds.
119+ * @param {CanvasRenderingContext2D } ctx - Canvas context
120+ * @param {Object } chartArea - Chart area dimensions
121+ * @param {Object } scales - Chart scales
122+ * @returns {CanvasGradient } - The gradient fill
123+ */
124+ function createLatencyGradient ( ctx , chartArea , scales ) {
125+ if ( ! chartArea || ! scales . y ) return 'rgba(16, 124, 16, 0.2)' ;
126+
127+ const gradient = ctx . createLinearGradient ( 0 , chartArea . bottom , 0 , chartArea . top ) ;
128+ const yMax = scales . y . max || 200 ;
129+
130+ // Add many color stops for smooth blending (20 stops from bottom to top)
131+ const numStops = 20 ;
132+ for ( let i = 0 ; i <= numStops ; i ++ ) {
133+ const position = i / numStops ; // 0 = bottom, 1 = top
134+ const latencyAtPosition = position * yMax ;
135+
136+ // Alpha increases slightly with latency for better visual distinction
137+ const alpha = 0.25 + ( position * 0.25 ) ; // 0.25 at bottom to 0.50 at top
138+
139+ const color = getInterpolatedLatencyColorRGBA ( latencyAtPosition , alpha ) ;
140+ gradient . addColorStop ( position , color ) ;
141+ }
142+
143+ return gradient ;
144+ }
145+
25146// Probe visualization history (24-dot indicator)
26147const probeHistory = [ ] ;
27148const MAX_PROBE_DOTS = 24 ;
@@ -431,13 +552,29 @@ function initializeCharts() {
431552 {
432553 label : 'Latency (ms)' ,
433554 data : [ ] ,
434- borderColor : '#0078d4' ,
435- backgroundColor : 'rgba(0, 120, 212, 0.1)' ,
436- tension : 0.2 ,
437- fill : 'origin' ,
555+ // Segment-based border color - smooth gradient based on data value
556+ segment : {
557+ borderColor : ( ctx ) => {
558+ const p0 = ctx . p0 . parsed ?. y ;
559+ const p1 = ctx . p1 . parsed ?. y ;
560+ if ( p0 == null || p1 == null ) return 'rgba(0,0,0,0)' ;
561+ const value = Math . max ( p0 , p1 ) ;
562+ return getInterpolatedLatencyColor ( value ) ;
563+ } ,
564+ } ,
565+ borderColor : '#107c10' , // Default/fallback (green)
566+ // Dynamic gradient fill based on latency thresholds
567+ backgroundColor : ( context ) => {
568+ const chart = context . chart ;
569+ const { ctx, chartArea, scales } = chart ;
570+ if ( ! chartArea ) return 'rgba(16, 124, 16, 0.2)' ;
571+ return createLatencyGradient ( ctx , chartArea , scales ) ;
572+ } ,
573+ tension : 0.3 ,
574+ fill : true ,
438575 pointRadius : 0 , // Hide points for performance with many data points
439576 pointHoverRadius : 0 , // Disable hover circles to prevent visual artifacts
440- borderWidth : 1
577+ borderWidth : 1.5
441578 }
442579 ]
443580 } ,
0 commit comments