代码重构

This commit is contained in:
CN-JS-HuiBai
2026-04-05 22:59:04 +08:00
parent 28432c9c23
commit 84972cdaeb

View File

@@ -15,6 +15,9 @@ class AreaChart {
this.p95Type = 'tx'; // 'tx', 'rx', 'both' this.p95Type = 'tx'; // 'tx', 'rx', 'both'
this.dpr = window.devicePixelRatio || 1; this.dpr = window.devicePixelRatio || 1;
this.padding = { top: 20, right: 16, bottom: 32, left: 56 }; this.padding = { top: 20, right: 16, bottom: 32, left: 56 };
this.currentMaxVal = 0;
this.prevMaxVal = 0;
this._resize = this.resize.bind(this); this._resize = this.resize.bind(this);
window.addEventListener('resize', this._resize); window.addEventListener('resize', this._resize);
@@ -36,6 +39,21 @@ class AreaChart {
setData(data) { setData(data) {
if (!data || !data.timestamps) return; if (!data || !data.timestamps) return;
// Store old data for smooth transition before updating this.data
// Only clone if there is data to clone; otherwise use empty set
if (this.data && this.data.timestamps && this.data.timestamps.length > 0) {
this.prevData = {
timestamps: [...this.data.timestamps],
rx: [...this.data.rx],
tx: [...this.data.tx]
};
} else {
this.prevData = { timestamps: [], rx: [], tx: [] };
}
// Smoothly transition max value context too
this.prevMaxVal = this.currentMaxVal || 0;
// Downsample if data is too dense (target ~1500 points for performance) // Downsample if data is too dense (target ~1500 points for performance)
const MAX_POINTS = 1500; const MAX_POINTS = 1500;
if (data.timestamps.length > MAX_POINTS) { if (data.timestamps.length > MAX_POINTS) {
@@ -51,6 +69,14 @@ class AreaChart {
this.data = data; this.data = data;
} }
// Refresh currentMaxVal target for interpolation in draw()
let rawMax = 1024;
for (let i = 0; i < this.data.rx.length; i++) {
if (this.showRx) rawMax = Math.max(rawMax, this.data.rx[i] || 0);
if (this.showTx) rawMax = Math.max(rawMax, this.data.tx[i] || 0);
}
this.currentMaxVal = rawMax;
// Calculate P95 (95th percentile) // Calculate P95 (95th percentile)
let combined = []; let combined = [];
if (this.p95Type === 'tx') { if (this.p95Type === 'tx') {
@@ -112,14 +138,15 @@ class AreaChart {
return; return;
} }
// Find max raw value
let maxDataVal = 1024; // Minimum 1KB/s scale
for (let i = 0; i < rx.length; i++) {
if (this.showRx) maxDataVal = Math.max(maxDataVal, rx[i] || 0);
if (this.showTx) maxDataVal = Math.max(maxDataVal, tx[i] || 0);
}
// Determine consistent unit based on max data value // Determine consistent unit based on max data value
let maxDataVal = 1024;
if (this.prevMaxVal && this.animProgress < 1) {
// Interpolate the max value context to keep vertical scale smooth
maxDataVal = this.prevMaxVal + (this.currentMaxVal - this.prevMaxVal) * (this.animProgress || 0);
} else {
maxDataVal = this.currentMaxVal;
}
const k = 1024; const k = 1024;
const sizes = ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s']; const sizes = ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s'];
let unitIdx = Math.floor(Math.log(Math.max(1, maxDataVal)) / Math.log(k)); let unitIdx = Math.floor(Math.log(Math.max(1, maxDataVal)) / Math.log(k));
@@ -149,9 +176,13 @@ class AreaChart {
const len = timestamps.length; const len = timestamps.length;
const xStep = chartW / (len - 1); const xStep = chartW / (len - 1);
// Helper to get point // Helper to get point with smooth value transition
const getX = (i) => p.left + i * xStep; const getX = (i) => p.left + i * xStep;
const getY = (val) => p.top + chartH - (val / (maxVal || 1)) * chartH * this.animProgress; const getY = (val, prevVal = 0) => {
// Interpolate value from previous state to new state
const actualVal = prevVal + (val - prevVal) * this.animProgress;
return p.top + chartH - (actualVal / (maxVal || 1)) * chartH;
};
// Draw grid lines // Draw grid lines
ctx.strokeStyle = 'rgba(99, 102, 241, 0.08)'; ctx.strokeStyle = 'rgba(99, 102, 241, 0.08)';
@@ -187,16 +218,18 @@ class AreaChart {
// Always show last label // Always show last label
ctx.fillText(formatTime(timestamps[len - 1]), getX(len - 1), h - 8); ctx.fillText(formatTime(timestamps[len - 1]), getX(len - 1), h - 8);
const getPVal = (arr, i) => (arr && i < arr.length) ? arr[i] : 0;
// Draw TX area // Draw TX area
if (this.showTx) { if (this.showTx) {
this.drawArea(ctx, tx, getX, getY, chartH, p, this.drawArea(ctx, tx, this.prevData ? this.prevData.tx : null, getX, getY, chartH, p,
'rgba(99, 102, 241, 0.25)', 'rgba(99, 102, 241, 0.02)', 'rgba(99, 102, 241, 0.25)', 'rgba(99, 102, 241, 0.02)',
'#6366f1', len); '#6366f1', len);
} }
// Draw RX area (on top) // Draw RX area (on top)
if (this.showRx) { if (this.showRx) {
this.drawArea(ctx, rx, getX, getY, chartH, p, this.drawArea(ctx, rx, this.prevData ? this.prevData.rx : null, getX, getY, chartH, p,
'rgba(6, 182, 212, 0.25)', 'rgba(6, 182, 212, 0.02)', 'rgba(6, 182, 212, 0.25)', 'rgba(6, 182, 212, 0.02)',
'#06b6d4', len); '#06b6d4', len);
} }
@@ -231,22 +264,23 @@ class AreaChart {
} }
} }
drawArea(ctx, values, getX, getY, chartH, p, fillColorTop, fillColorBottom, strokeColor, len) { drawArea(ctx, values, prevValues, getX, getY, chartH, p, fillColorTop, fillColorBottom, strokeColor, len) {
if (!values || values.length === 0) return; if (!values || values.length === 0) return;
const useSimple = len > 250; const useSimple = len > 250;
const getPVal = (i) => (prevValues && i < prevValues.length) ? prevValues[i] : 0;
// Fill // Fill
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(getX(0), getY(values[0] || 0)); ctx.moveTo(getX(0), getY(values[0] || 0, getPVal(0)));
for (let i = 1; i < len; i++) { for (let i = 1; i < len; i++) {
const currY = getY(values[i] || 0, getPVal(i));
if (useSimple) { if (useSimple) {
ctx.lineTo(getX(i), getY(values[i] || 0)); ctx.lineTo(getX(i), currY);
} else { } else {
const prevX = getX(i - 1); const prevX = getX(i - 1);
const currX = getX(i); const currX = getX(i);
const prevY = getY(values[i - 1] || 0); const prevY = getY(values[i - 1] || 0, getPVal(i - 1));
const currY = getY(values[i] || 0);
const midX = (prevX + currX) / 2; const midX = (prevX + currX) / 2;
ctx.bezierCurveTo(midX, prevY, midX, currY, currX, currY); ctx.bezierCurveTo(midX, prevY, midX, currY, currX, currY);
} }
@@ -263,15 +297,15 @@ class AreaChart {
// Stroke // Stroke
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(getX(0), getY(values[0] || 0)); ctx.moveTo(getX(0), getY(values[0] || 0, getPVal(0)));
for (let i = 1; i < len; i++) { for (let i = 1; i < len; i++) {
const currY = getY(values[i] || 0, getPVal(i));
if (useSimple) { if (useSimple) {
ctx.lineTo(getX(i), getY(values[i] || 0)); ctx.lineTo(getX(i), currY);
} else { } else {
const prevX = getX(i - 1); const prevX = getX(i - 1);
const currX = getX(i); const currX = getX(i);
const prevY = getY(values[i - 1] || 0); const prevY = getY(values[i - 1] || 0, getPVal(i - 1));
const currY = getY(values[i] || 0);
const midX = (prevX + currX) / 2; const midX = (prevX + currX) / 2;
ctx.bezierCurveTo(midX, prevY, midX, currY, currX, currY); ctx.bezierCurveTo(midX, prevY, midX, currY, currX, currY);
} }
@@ -298,6 +332,9 @@ class MetricChart {
this.padding = { top: 10, right: 10, bottom: 20, left: 60 }; this.padding = { top: 10, right: 10, bottom: 20, left: 60 };
this.animProgress = 0; this.animProgress = 0;
this.prevMaxVal = 0;
this.currentMaxVal = 0;
this._resize = this.resize.bind(this); this._resize = this.resize.bind(this);
window.addEventListener('resize', this._resize); window.addEventListener('resize', this._resize);
this.resize(); this.resize();
@@ -319,7 +356,30 @@ class MetricChart {
} }
setData(data) { setData(data) {
if (this.data && this.data.values && this.data.values.length > 0) {
this.prevData = JSON.parse(JSON.stringify(this.data));
} else {
this.prevData = { timestamps: [], values: [], series: null };
}
this.prevMaxVal = this.currentMaxVal || 0;
this.data = data || { timestamps: [], values: [], series: null }; this.data = data || { timestamps: [], values: [], series: null };
// Target max
if (this.data.series) {
this.currentMaxVal = 100;
} else {
const raw = Math.max(...(this.data.values || []), 0.1);
if (this.unit === '%' && raw <= 100) {
if (raw > 80) this.currentMaxVal = 100;
else if (raw > 40) this.currentMaxVal = 80;
else if (raw > 20) this.currentMaxVal = 50;
else this.currentMaxVal = 25;
} else {
this.currentMaxVal = raw * 1.25;
}
}
this.animate(); this.animate();
} }
@@ -356,27 +416,18 @@ class MetricChart {
return; return;
} }
// Determine Y max // Determine Y max (interpolated)
let maxVal = 0; const targetMax = this.currentMaxVal || 0.1;
if (series) { const startMax = this.prevMaxVal || targetMax;
// For stacked CPU, max sum should be 100 const maxVal = startMax + (targetMax - startMax) * this.animProgress;
maxVal = 100;
} else {
maxVal = Math.max(...values, 0.1);
if (this.unit === '%' && maxVal <= 100) {
if (maxVal > 80) maxVal = 100;
else if (maxVal > 40) maxVal = 80;
else if (maxVal > 20) maxVal = 50;
else maxVal = 25;
} else {
maxVal = maxVal * 1.25;
}
}
const len = timestamps.length; const len = timestamps.length;
const xStep = chartW / (len - 1); const xStep = chartW / (len - 1);
const getX = (i) => p.left + i * xStep; const getX = (i) => p.left + i * xStep;
const getY = (val) => p.top + chartH - (val / (maxVal || 1)) * chartH * this.animProgress; const getY = (val, prevVal = 0) => {
const actualVal = prevVal + (val - prevVal) * this.animProgress;
return p.top + chartH - (actualVal / (maxVal || 1)) * chartH;
};
// Grid // Grid
ctx.strokeStyle = 'rgba(99, 102, 241, 0.05)'; ctx.strokeStyle = 'rgba(99, 102, 241, 0.05)';
@@ -421,19 +472,23 @@ class MetricChart {
]; ];
let currentBase = new Array(len).fill(0); let currentBase = new Array(len).fill(0);
let prevBase = new Array(len).fill(0);
modes.forEach(mode => { modes.forEach(mode => {
const vals = series[mode.name]; const vals = series[mode.name];
if (!vals) return; if (!vals) return;
const prevVals = (this.prevData && this.prevData.series) ? this.prevData.series[mode.name] : null;
const getPVal = (arr, idx) => (arr && idx < arr.length) ? arr[idx] : 0;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(getX(0), getY(currentBase[0] + vals[0])); ctx.moveTo(getX(0), getY(currentBase[0] + vals[0], getPVal(prevBase, 0) + getPVal(prevVals, 0)));
for (let i = 1; i < len; i++) { for (let i = 1; i < len; i++) {
ctx.lineTo(getX(i), getY(currentBase[i] + vals[i])); ctx.lineTo(getX(i), getY(currentBase[i] + vals[i], getPVal(prevBase, i) + getPVal(prevVals, i)));
} }
ctx.lineTo(getX(len - 1), getY(currentBase[len - 1])); ctx.lineTo(getX(len - 1), getY(currentBase[len - 1], getPVal(prevBase, len - 1)));
for (let i = len - 1; i >= 0; i--) { for (let i = len - 1; i >= 0; i--) {
ctx.lineTo(getX(i), getY(currentBase[i])); ctx.lineTo(getX(i), getY(currentBase[i], getPVal(prevBase, i)));
} }
ctx.closePath(); ctx.closePath();
ctx.fillStyle = mode.color; ctx.fillStyle = mode.color;
@@ -441,17 +496,18 @@ class MetricChart {
// Stroke // Stroke
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(getX(0), getY(currentBase[0] + vals[0])); ctx.moveTo(getX(0), getY(currentBase[0] + vals[0], getPVal(prevBase, 0) + getPVal(prevVals, 0)));
for (let i = 1; i < len; i++) { for (let i = 1; i < len; i++) {
ctx.lineTo(getX(i), getY(currentBase[i] + vals[i])); ctx.lineTo(getX(i), getY(currentBase[i] + vals[i], getPVal(prevBase, i) + getPVal(prevVals, i)));
} }
ctx.strokeStyle = mode.stroke; ctx.strokeStyle = mode.stroke;
ctx.lineWidth = 1; ctx.lineWidth = 1;
ctx.stroke(); ctx.stroke();
// Update base // Update boxes for next series
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
currentBase[i] += vals[i]; currentBase[i] += vals[i];
if (prevBase) prevBase[i] = (prevBase[i] || 0) + getPVal(prevVals, i);
} }
}); });
@@ -470,16 +526,19 @@ class MetricChart {
} else { } else {
const useSimple = len > 250; const useSimple = len > 250;
const prevVals = this.prevData ? this.prevData.values : null;
const getPVal = (i) => (prevVals && i < prevVals.length) ? prevVals[i] : 0;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(getX(0), getY(values[0])); ctx.moveTo(getX(0), getY(values[0], getPVal(0)));
for (let i = 1; i < len; i++) { for (let i = 1; i < len; i++) {
const currY = getY(values[i], getPVal(i));
if (useSimple) { if (useSimple) {
ctx.lineTo(getX(i), getY(values[i])); ctx.lineTo(getX(i), currY);
} else { } else {
const prevX = getX(i - 1); const prevX = getX(i - 1);
const currX = getX(i); const currX = getX(i);
const prevY = getY(values[i - 1]); const prevY = getY(values[i - 1], getPVal(i - 1));
const currY = getY(values[i]);
const midX = (prevX + currX) / 2; const midX = (prevX + currX) / 2;
ctx.bezierCurveTo(midX, prevY, midX, currY, currX, currY); ctx.bezierCurveTo(midX, prevY, midX, currY, currX, currY);
} }