代码重构

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.dpr = window.devicePixelRatio || 1;
this.padding = { top: 20, right: 16, bottom: 32, left: 56 };
this.currentMaxVal = 0;
this.prevMaxVal = 0;
this._resize = this.resize.bind(this);
window.addEventListener('resize', this._resize);
@@ -36,6 +39,21 @@ class AreaChart {
setData(data) {
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)
const MAX_POINTS = 1500;
if (data.timestamps.length > MAX_POINTS) {
@@ -51,6 +69,14 @@ class AreaChart {
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)
let combined = [];
if (this.p95Type === 'tx') {
@@ -112,14 +138,15 @@ class AreaChart {
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
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 sizes = ['B/s', 'KB/s', 'MB/s', 'GB/s', 'TB/s'];
let unitIdx = Math.floor(Math.log(Math.max(1, maxDataVal)) / Math.log(k));
@@ -149,9 +176,13 @@ class AreaChart {
const len = timestamps.length;
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 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
ctx.strokeStyle = 'rgba(99, 102, 241, 0.08)';
@@ -187,16 +218,18 @@ class AreaChart {
// Always show last label
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
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)',
'#6366f1', len);
}
// Draw RX area (on top)
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)',
'#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;
const useSimple = len > 250;
const getPVal = (i) => (prevValues && i < prevValues.length) ? prevValues[i] : 0;
// Fill
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++) {
const currY = getY(values[i] || 0, getPVal(i));
if (useSimple) {
ctx.lineTo(getX(i), getY(values[i] || 0));
ctx.lineTo(getX(i), currY);
} else {
const prevX = getX(i - 1);
const currX = getX(i);
const prevY = getY(values[i - 1] || 0);
const currY = getY(values[i] || 0);
const prevY = getY(values[i - 1] || 0, getPVal(i - 1));
const midX = (prevX + currX) / 2;
ctx.bezierCurveTo(midX, prevY, midX, currY, currX, currY);
}
@@ -263,15 +297,15 @@ class AreaChart {
// Stroke
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++) {
const currY = getY(values[i] || 0, getPVal(i));
if (useSimple) {
ctx.lineTo(getX(i), getY(values[i] || 0));
ctx.lineTo(getX(i), currY);
} else {
const prevX = getX(i - 1);
const currX = getX(i);
const prevY = getY(values[i - 1] || 0);
const currY = getY(values[i] || 0);
const prevY = getY(values[i - 1] || 0, getPVal(i - 1));
const midX = (prevX + currX) / 2;
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.animProgress = 0;
this.prevMaxVal = 0;
this.currentMaxVal = 0;
this._resize = this.resize.bind(this);
window.addEventListener('resize', this._resize);
this.resize();
@@ -319,7 +356,30 @@ class MetricChart {
}
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 };
// 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();
}
@@ -356,27 +416,18 @@ class MetricChart {
return;
}
// Determine Y max
let maxVal = 0;
if (series) {
// For stacked CPU, max sum should be 100
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;
}
}
// Determine Y max (interpolated)
const targetMax = this.currentMaxVal || 0.1;
const startMax = this.prevMaxVal || targetMax;
const maxVal = startMax + (targetMax - startMax) * this.animProgress;
const len = timestamps.length;
const xStep = chartW / (len - 1);
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
ctx.strokeStyle = 'rgba(99, 102, 241, 0.05)';
@@ -421,19 +472,23 @@ class MetricChart {
];
let currentBase = new Array(len).fill(0);
let prevBase = new Array(len).fill(0);
modes.forEach(mode => {
const vals = series[mode.name];
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.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++) {
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--) {
ctx.lineTo(getX(i), getY(currentBase[i]));
ctx.lineTo(getX(i), getY(currentBase[i], getPVal(prevBase, i)));
}
ctx.closePath();
ctx.fillStyle = mode.color;
@@ -441,17 +496,18 @@ class MetricChart {
// Stroke
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++) {
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.lineWidth = 1;
ctx.stroke();
// Update base
// Update boxes for next series
for (let i = 0; i < len; i++) {
currentBase[i] += vals[i];
if (prevBase) prevBase[i] = (prevBase[i] || 0) + getPVal(prevVals, i);
}
});
@@ -470,16 +526,19 @@ class MetricChart {
} else {
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.moveTo(getX(0), getY(values[0]));
ctx.moveTo(getX(0), getY(values[0], getPVal(0)));
for (let i = 1; i < len; i++) {
const currY = getY(values[i], getPVal(i));
if (useSimple) {
ctx.lineTo(getX(i), getY(values[i]));
ctx.lineTo(getX(i), currY);
} else {
const prevX = getX(i - 1);
const currX = getX(i);
const prevY = getY(values[i - 1]);
const currY = getY(values[i]);
const prevY = getY(values[i - 1], getPVal(i - 1));
const midX = (prevX + currX) / 2;
ctx.bezierCurveTo(midX, prevY, midX, currY, currX, currY);
}