diff --git a/public/api.php b/public/api.php
index 0c93022..6da77a6 100644
--- a/public/api.php
+++ b/public/api.php
@@ -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,
diff --git a/public/assets/app.js b/public/assets/app.js
index bb03d66..f903a1d 100644
--- a/public/assets/app.js
+++ b/public/assets/app.js
@@ -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
-
+
${Math.round(safeMax)} ${unitLabel}
${Math.round(safeMax / 2)} ${unitLabel}
-
-
-
+
+
+
-
-
-
+
+
+
`;
}
diff --git a/public/index.php b/public/index.php
index 8a77a39..5b7c3b8 100644
--- a/public/index.php
+++ b/public/index.php
@@ -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';
?>
diff --git a/version.json b/version.json
index f6a8455..0cc6f21 100644
--- a/version.json
+++ b/version.json
@@ -1,12 +1,12 @@
{
- "version": "1.3.9",
+ "version": "1.4.0",
"codename": "Helios",
"release_date": "2026-11-04",
"min_php_version": "8.2",
"changelog": [
+ "Lissage élégant des graphiques CPU/RAM et Réseau (splines Bézier cubiques sans angles vifs)",
+ "Amélioration des transitions et gradients dynamiques sur les graphiques du tableau de bord",
"Mise à jour instantanée du numéro de version sur tous les badges sans aucun délai de cache navigateur",
- "Invalidation automatique du cache local et rechargement propre anti-cache après chaque mise à jour",
- "Affichage serveur natif du numéro de version installée directement dans le HTML généré",
- "Suppression définitive de l'indication des identifiants et mot de passe par défaut sur l'écran de connexion"
+ "Invalidation automatique du cache local et rechargement propre anti-cache après chaque mise à jour"
]
}