feat(charts): smooth CPU/RAM and Network curves using cubic Bézier splines (v1.4.0)
This commit is contained in:
1 parent
c5eb849095
commit
0948a8da24
4 files changed
+59
-23
No files matched your search
+1
-1
@@ -140,7 +140,7 @@ try {
|
||||
$installedVerData = file_exists("{$appDir}/version.json")
|
||||
? json_decode((string)file_get_contents("{$appDir}/version.json"), true)
|
||||
: [];
|
||||
$installedVer = $installedVerData['version'] ?? '1.3.9';
|
||||
$installedVer = $installedVerData['version'] ?? '1.4.0';
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
|
||||
+53
-17
@@ -629,20 +629,56 @@ function drawDualAreaChart(svgId, seriesA, seriesB, maxVal, colorA, colorB, unit
|
||||
const padTop = 14;
|
||||
const padBottom = 18;
|
||||
const plotH = height - padTop - padBottom;
|
||||
const baselineY = padTop + plotH;
|
||||
const safeMax = maxVal > 0 ? maxVal : 100;
|
||||
|
||||
const toPoints = (arr) => arr.map((val, idx) => {
|
||||
const x = (idx / (arr.length - 1)) * width;
|
||||
const ratio = Math.max(0, Math.min(1, val / safeMax));
|
||||
const y = padTop + plotH - (ratio * plotH);
|
||||
return `${x.toFixed(1)},${y.toFixed(1)}`;
|
||||
});
|
||||
const clampY = (y) => Math.max(padTop, Math.min(baselineY, y));
|
||||
|
||||
const ptsA = toPoints(seriesA);
|
||||
const ptsB = toPoints(seriesB);
|
||||
// Génération de courbes splines adoucies (Catmull-Rom / Bézier cubique) pour éliminer les angles pointus
|
||||
const computeSmoothPaths = (arr, tension = 0.2) => {
|
||||
if (!arr || arr.length === 0) return { line: '', area: '' };
|
||||
|
||||
const areaA = `0,${padTop + plotH} ` + ptsA.join(' ') + ` ${width},${padTop + plotH}`;
|
||||
const areaB = `0,${padTop + plotH} ` + ptsB.join(' ') + ` ${width},${padTop + plotH}`;
|
||||
const points = arr.map((val, idx) => {
|
||||
const x = (idx / (arr.length - 1)) * width;
|
||||
const ratio = Math.max(0, Math.min(1, val / safeMax));
|
||||
const y = baselineY - (ratio * plotH);
|
||||
return { x, y };
|
||||
});
|
||||
|
||||
if (points.length === 1) {
|
||||
return {
|
||||
line: `M ${points[0].x.toFixed(1)},${points[0].y.toFixed(1)}`,
|
||||
area: `M 0,${baselineY} L ${points[0].x.toFixed(1)},${points[0].y.toFixed(1)} L ${width},${baselineY} Z`
|
||||
};
|
||||
}
|
||||
|
||||
let line = `M ${points[0].x.toFixed(1)},${points[0].y.toFixed(1)}`;
|
||||
|
||||
for (let i = 0; i < points.length - 1; i++) {
|
||||
const p0 = points[i === 0 ? i : i - 1];
|
||||
const p1 = points[i];
|
||||
const p2 = points[i + 1];
|
||||
const p3 = points[i + 2 < points.length ? i + 2 : i + 1];
|
||||
|
||||
let cp1x = p1.x + (p2.x - p0.x) * tension;
|
||||
let cp1y = p1.y + (p2.y - p0.y) * tension;
|
||||
|
||||
let cp2x = p2.x - (p3.x - p1.x) * tension;
|
||||
let cp2y = p2.y - (p3.y - p1.y) * tension;
|
||||
|
||||
// Clamping vertical pour garantir que la courbe ne déborde pas sous l'axe ni au-dessus du max
|
||||
cp1y = clampY(cp1y);
|
||||
cp2y = clampY(cp2y);
|
||||
|
||||
line += ` C ${cp1x.toFixed(1)},${cp1y.toFixed(1)} ${cp2x.toFixed(1)},${cp2y.toFixed(1)} ${p2.x.toFixed(1)},${p2.y.toFixed(1)}`;
|
||||
}
|
||||
|
||||
const area = `${line} L ${width},${baselineY} L 0,${baselineY} Z`;
|
||||
return { line, area };
|
||||
};
|
||||
|
||||
const pathA = computeSmoothPaths(seriesA);
|
||||
const pathB = computeSmoothPaths(seriesB);
|
||||
|
||||
const gradIdA = `${svgId}-grad-a`;
|
||||
const gradIdB = `${svgId}-grad-b`;
|
||||
@@ -662,18 +698,18 @@ function drawDualAreaChart(svgId, seriesA, seriesB, maxVal, colorA, colorB, unit
|
||||
<!-- Lignes de grille horizontales -->
|
||||
<line x1="0" y1="${padTop}" x2="${width}" y2="${padTop}" stroke="#2c323f" stroke-dasharray="3,3" stroke-width="1"/>
|
||||
<line x1="0" y1="${padTop + plotH / 2}" x2="${width}" y2="${padTop + plotH / 2}" stroke="#2c323f" stroke-dasharray="3,3" stroke-width="1"/>
|
||||
<line x1="0" y1="${padTop + plotH}" x2="${width}" y2="${padTop + plotH}" stroke="#3d4657" stroke-width="1"/>
|
||||
<line x1="0" y1="${baselineY}" x2="${width}" y2="${baselineY}" stroke="#3d4657" stroke-width="1"/>
|
||||
|
||||
<text x="6" y="${padTop + 10}" fill="#94a3b8" font-size="10" font-family="monospace">${Math.round(safeMax)} ${unitLabel}</text>
|
||||
<text x="6" y="${padTop + plotH / 2 - 4}" fill="#64748b" font-size="10" font-family="monospace">${Math.round(safeMax / 2)} ${unitLabel}</text>
|
||||
|
||||
<!-- Courbe B -->
|
||||
<polygon points="${areaB}" fill="url(#${gradIdB})"/>
|
||||
<polyline fill="none" stroke="${colorB}" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" points="${ptsB.join(' ')}"/>
|
||||
<!-- Courbe B lissée avec dégradé -->
|
||||
<path d="${pathB.area}" fill="url(#${gradIdB})"/>
|
||||
<path d="${pathB.line}" fill="none" stroke="${colorB}" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
|
||||
<!-- Courbe A -->
|
||||
<polygon points="${areaA}" fill="url(#${gradIdA})"/>
|
||||
<polyline fill="none" stroke="${colorA}" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" points="${ptsA.join(' ')}"/>
|
||||
<!-- Courbe A lissée avec dégradé -->
|
||||
<path d="${pathA.area}" fill="url(#${gradIdA})"/>
|
||||
<path d="${pathA.line}" fill="none" stroke="${colorA}" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ $isAuth = Auth::isAuthenticated();
|
||||
$currentUser = Auth::getUsername();
|
||||
|
||||
$appVerData = file_exists(__DIR__ . '/../version.json') ? json_decode((string)file_get_contents(__DIR__ . '/../version.json'), true) : [];
|
||||
$appVersion = $appVerData['version'] ?? '1.3.9';
|
||||
$appVersion = $appVerData['version'] ?? '1.4.0';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr" class="h-full">
|
||||
|
||||
Reference in new issue
Block a user