diff --git a/apix-admin/src/main/resources/META-INF/resources/admin.js b/apix-admin/src/main/resources/META-INF/resources/admin.js
index bc1b514..3ba56ec 100644
--- a/apix-admin/src/main/resources/META-INF/resources/admin.js
+++ b/apix-admin/src/main/resources/META-INF/resources/admin.js
@@ -185,7 +185,12 @@
var ly = yS(rateLimit);
svg.append('line').attr('x1', ML).attr('x2', W).attr('y1', ly).attr('y2', ly).attr('class', 'rate-limit-line');
- var path = svg.append('path').attr('class', 'rate-area');
+ // Clip path — area can never escape the plot region regardless of data spikes
+ var clipId = 'rc-' + sid.slice(0, 8);
+ svg.append('defs').append('clipPath').attr('id', clipId)
+ .append('rect').attr('x', ML).attr('y', 0).attr('width', W - ML).attr('height', CH);
+
+ var path = svg.append('path').attr('class', 'rate-area').attr('clip-path', 'url(#' + clipId + ')');
function fmt(n) {
return n >= 1e6 ? (n / 1e6).toFixed(1) + 'M' : n >= 1e3 ? (n / 1e3).toFixed(1) + 'k' : String(n);
@@ -200,8 +205,9 @@
}
// Chart bucket: flush accumulated delta every 5 s regardless of poll rate
+ // Cap at rateLimit so the area never visually exceeds 100% (live label shows true value)
setInterval(function () {
- var rpm = Math.round(accDelta * (60000 / CHART_TICK));
+ var rpm = Math.min(Math.round(accDelta * (60000 / CHART_TICK)), rateLimit);
buf.push(rpm); buf.shift();
path.attr('d', areaFn(buf));
accDelta = 0;
diff --git a/apix-admin/src/main/resources/templates/DashboardResource/global.html b/apix-admin/src/main/resources/templates/DashboardResource/global.html
index 71877df..85e9c7c 100644
--- a/apix-admin/src/main/resources/templates/DashboardResource/global.html
+++ b/apix-admin/src/main/resources/templates/DashboardResource/global.html
@@ -106,6 +106,6 @@ nav {
-
+