代码重构
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user