refactor: extract shared time-series chart renderer from CPU chart
Pulls the CPU chart's SVG-drawing logic into a parameterized renderTimeSeriesChart(elId, pts, opts) helper (yDomain, colorFor, clipId, unitSuffix), keeping renderCpuChart's signature and behavior unchanged. Task 5 will reuse this for the RTT chart. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEimzMv4Q5EjFg3hoiZ69T
This commit is contained in:
@@ -809,15 +809,11 @@
|
|||||||
return json.samples || [];
|
return json.samples || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderCpuChart(hostname, samples) {
|
function renderTimeSeriesChart(elId, pts, opts) {
|
||||||
const el = document.getElementById(`cpu-chart-${hostname}`);
|
const el = document.getElementById(elId);
|
||||||
if (!el || !samples.length) return;
|
if (!el || pts.length < 2) { if (el) el.style.display = 'none'; return; }
|
||||||
|
|
||||||
const pts = samples
|
|
||||||
.filter(s => s.data.cpu_percent != null)
|
|
||||||
.map(s => ({ t: s.timestamp, v: s.data.cpu_percent }));
|
|
||||||
if (pts.length < 2) { el.style.display = 'none'; return; }
|
|
||||||
|
|
||||||
|
const unitSuffix = opts.unitSuffix || '';
|
||||||
const W = 600, H = 80, PAD = { top: 6, right: 8, bottom: 18, left: 28 };
|
const W = 600, H = 80, PAD = { top: 6, right: 8, bottom: 18, left: 28 };
|
||||||
const cW = W - PAD.left - PAD.right;
|
const cW = W - PAD.left - PAD.right;
|
||||||
const cH = H - PAD.top - PAD.bottom;
|
const cH = H - PAD.top - PAD.bottom;
|
||||||
@@ -826,13 +822,15 @@
|
|||||||
const tRange = tMax - tMin || 1;
|
const tRange = tMax - tMin || 1;
|
||||||
const x = t => PAD.left + ((t - tMin) / tRange) * cW;
|
const x = t => PAD.left + ((t - tMin) / tRange) * cW;
|
||||||
|
|
||||||
// Auto-scale Y axis with 10% padding, clamped to [0, 100]
|
// Auto-scale Y axis with 10% padding, optionally clamped to opts.yDomain
|
||||||
const vMin = Math.min(...pts.map(p => p.v));
|
const vMin = Math.min(...pts.map(p => p.v));
|
||||||
const vMax = Math.max(...pts.map(p => p.v));
|
const vMax = Math.max(...pts.map(p => p.v));
|
||||||
const vRange = vMax - vMin || 1;
|
const vRange = vMax - vMin || 1;
|
||||||
const vPad = Math.max(vRange * 0.1, 1);
|
const vPad = Math.max(vRange * 0.1, 1);
|
||||||
const yLow = Math.max(0, vMin - vPad);
|
const domainLow = Array.isArray(opts.yDomain) ? opts.yDomain[0] : 0;
|
||||||
const yHigh = Math.min(100, vMax + vPad);
|
const domainHigh = Array.isArray(opts.yDomain) ? opts.yDomain[1] : Infinity;
|
||||||
|
const yLow = Math.max(domainLow, vMin - vPad);
|
||||||
|
const yHigh = Math.min(domainHigh, vMax + vPad);
|
||||||
const yRange = yHigh - yLow || 1;
|
const yRange = yHigh - yLow || 1;
|
||||||
const y = v => PAD.top + cH - ((v - yLow) / yRange) * cH;
|
const y = v => PAD.top + cH - ((v - yLow) / yRange) * cH;
|
||||||
|
|
||||||
@@ -842,10 +840,9 @@
|
|||||||
pts.map(p => `L${x(p.t).toFixed(1)},${y(p.v).toFixed(1)}`).join(' ') +
|
pts.map(p => `L${x(p.t).toFixed(1)},${y(p.v).toFixed(1)}`).join(' ') +
|
||||||
` L${x(pts[pts.length-1].t).toFixed(1)},${(PAD.top + cH).toFixed(1)} Z`;
|
` L${x(pts[pts.length-1].t).toFixed(1)},${(PAD.top + cH).toFixed(1)} Z`;
|
||||||
|
|
||||||
// Color based on latest absolute CPU %
|
// Color based on latest value
|
||||||
const latest = pts[pts.length - 1].v;
|
const latest = pts[pts.length - 1].v;
|
||||||
const strokeColor = latest > 90 ? '#e53935' : latest > 70 ? '#fb8c00' : '#43a047';
|
const { stroke: strokeColor, fill: fillColor } = opts.colorFor(latest);
|
||||||
const fillColor = latest > 90 ? '#ffcdd2' : latest > 70 ? '#ffe0b2' : '#c8e6c9';
|
|
||||||
|
|
||||||
// Compute nice tick step for ~3-5 grid lines
|
// Compute nice tick step for ~3-5 grid lines
|
||||||
const rawStep = yRange / 4;
|
const rawStep = yRange / 4;
|
||||||
@@ -855,7 +852,7 @@
|
|||||||
let gridLines = '';
|
let gridLines = '';
|
||||||
for (let v = tickStart; v <= yHigh + 0.001; v += niceStep) {
|
for (let v = tickStart; v <= yHigh + 0.001; v += niceStep) {
|
||||||
const yy = y(v).toFixed(1);
|
const yy = y(v).toFixed(1);
|
||||||
const label = Number.isInteger(v) ? v : v.toFixed(1);
|
const label = (Number.isInteger(v) ? v : v.toFixed(1)) + unitSuffix;
|
||||||
gridLines += `<line x1="${PAD.left}" y1="${yy}" x2="${PAD.left + cW}" y2="${yy}" stroke="#e0e0e0" stroke-width="1"/>`;
|
gridLines += `<line x1="${PAD.left}" y1="${yy}" x2="${PAD.left + cW}" y2="${yy}" stroke="#e0e0e0" stroke-width="1"/>`;
|
||||||
gridLines += `<text x="${(PAD.left - 3).toFixed(1)}" y="${yy}" text-anchor="end" dominant-baseline="middle" font-size="8" fill="#999">${label}</text>`;
|
gridLines += `<text x="${(PAD.left - 3).toFixed(1)}" y="${yy}" text-anchor="end" dominant-baseline="middle" font-size="8" fill="#999">${label}</text>`;
|
||||||
}
|
}
|
||||||
@@ -872,14 +869,14 @@
|
|||||||
el.innerHTML = `<svg viewBox="0 0 ${W} ${H}" preserveAspectRatio="none"
|
el.innerHTML = `<svg viewBox="0 0 ${W} ${H}" preserveAspectRatio="none"
|
||||||
style="width:100%;height:${H}px;display:block;">
|
style="width:100%;height:${H}px;display:block;">
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="cpu-clip-${hostname}">
|
<clipPath id="${opts.clipId}">
|
||||||
<rect x="${PAD.left}" y="${PAD.top}" width="${cW}" height="${cH}"/>
|
<rect x="${PAD.left}" y="${PAD.top}" width="${cW}" height="${cH}"/>
|
||||||
</clipPath>
|
</clipPath>
|
||||||
</defs>
|
</defs>
|
||||||
${gridLines}
|
${gridLines}
|
||||||
<line x1="${PAD.left}" y1="${PAD.top}" x2="${PAD.left}" y2="${PAD.top + cH}" stroke="#ccc" stroke-width="1"/>
|
<line x1="${PAD.left}" y1="${PAD.top}" x2="${PAD.left}" y2="${PAD.top + cH}" stroke="#ccc" stroke-width="1"/>
|
||||||
<line x1="${PAD.left}" y1="${PAD.top + cH}" x2="${PAD.left + cW}" y2="${PAD.top + cH}" stroke="#ccc" stroke-width="1"/>
|
<line x1="${PAD.left}" y1="${PAD.top + cH}" x2="${PAD.left + cW}" y2="${PAD.top + cH}" stroke="#ccc" stroke-width="1"/>
|
||||||
<g clip-path="url(#cpu-clip-${hostname})">
|
<g clip-path="url(#${opts.clipId})">
|
||||||
<path d="${areaPath}" fill="${fillColor}" opacity="0.6"/>
|
<path d="${areaPath}" fill="${fillColor}" opacity="0.6"/>
|
||||||
<polyline points="${linePoints}" fill="none" stroke="${strokeColor}" stroke-width="1.5" stroke-linejoin="round"/>
|
<polyline points="${linePoints}" fill="none" stroke="${strokeColor}" stroke-width="1.5" stroke-linejoin="round"/>
|
||||||
</g>
|
</g>
|
||||||
@@ -887,6 +884,21 @@
|
|||||||
</svg>`;
|
</svg>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderCpuChart(hostname, samples) {
|
||||||
|
const pts = samples
|
||||||
|
.filter(s => s.data.cpu_percent != null)
|
||||||
|
.map(s => ({ t: s.timestamp, v: s.data.cpu_percent }));
|
||||||
|
|
||||||
|
renderTimeSeriesChart(`cpu-chart-${hostname}`, pts, {
|
||||||
|
yDomain: [0, 100],
|
||||||
|
clipId: `cpu-clip-${hostname}`,
|
||||||
|
colorFor: (latest) => ({
|
||||||
|
stroke: latest > 90 ? '#e53935' : latest > 70 ? '#fb8c00' : '#43a047',
|
||||||
|
fill: latest > 90 ? '#ffcdd2' : latest > 70 ? '#ffe0b2' : '#c8e6c9',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function renderCpuTable(hostname, d) {
|
function renderCpuTable(hostname, d) {
|
||||||
const KEYS = [
|
const KEYS = [
|
||||||
['cpu_percent', 'CPU Usage', 'bar'],
|
['cpu_percent', 'CPU Usage', 'bar'],
|
||||||
|
|||||||
Reference in New Issue
Block a user