Skip to content

Commit ee7278e

Browse files
committed
update chart color gradients
1 parent 5a65167 commit ee7278e

File tree

1 file changed

+142
-5
lines changed

1 file changed

+142
-5
lines changed

src/PerfProblemSimulator/wwwroot/js/dashboard.js

Lines changed: 142 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
26147
const probeHistory = [];
27148
const 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

Comments
 (0)