From d296ba2f1f3baeb6a816d222da5345784b44dd7f Mon Sep 17 00:00:00 2001 From: Fernando Gonzalez Date: Wed, 14 Oct 2026 19:59:43 -0400 Subject: [PATCH] ui: temperature and heat-rise charts start their y-axis at 0 The heat-rise chart, the heat-rise-vs-ambient scatter, the session Temperatures chart and the live derate forecast scaled their y-axis to the data, so they started around 20 C. They now start at 0, like the power, current and relay charts already did. scatterChart gains the same zeroBase option lineChart has. Left on auto-scale: Wi-Fi RSSI (negative dBm) and SNR. Phase voltages and pilot already reach 0 in the data. Co-Authored-By: Claude Opus 5.5 --- wallmonitor/static/app.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/wallmonitor/static/app.js b/wallmonitor/static/app.js index a5e4bf6..4a31f2d 100644 --- a/wallmonitor/static/app.js +++ b/wallmonitor/static/app.js @@ -244,11 +244,11 @@ function timeTickFormat(ts, spanS) { } /* scatterChart(box, {series: [{name, color, points: [[x, y, title?]]}], - xLabel, height, digits}) — numeric x axis (not time), one dot per point. + xLabel, height, digits, zeroBase}) — numeric x axis (not time), one dot per point. Used where the relationship between two measured quantities is the story (e.g. fitted heat rise vs window ambient), not their history. */ // scatterChart(box, {series: [{name, color, points: [[x, y, meta]]}], xLabel, -// height?, digits?, tip(meta, dataset) -> Node[]}) — numeric x axis, nearest- +// height?, digits?, zeroBase?, tip(meta, dataset) -> Node[]}) — numeric x axis, nearest- // dot tooltip (custom content via opts.tip). Rebuilt from scratch per call. function scatterChart(box, opts) { box.style.minHeight = `${opts.height || 210}px`; @@ -267,7 +267,8 @@ function scatterChart(box, opts) { return; } const xt = niceTicks(Math.min(...allPts.map((p) => p[0])), Math.max(...allPts.map((p) => p[0])), 6); - const yt = niceTicks(Math.min(...allPts.map((p) => p[1])), Math.max(...allPts.map((p) => p[1])), 4); + const yLo = Math.min(...allPts.map((p) => p[1])); + const yt = niceTicks(opts.zeroBase ? Math.min(0, yLo) : yLo, Math.max(...allPts.map((p) => p[1])), 4); const xOf = (value) => margin.left + ((value - xt.min) / (xt.max - xt.min || 1)) * pw; const yOf = (value) => margin.top + ph - ((value - yt.min) / (yt.max - yt.min || 1)) * ph; const root = svg("svg", { viewBox: `0 0 ${width} ${height}`, width, height }); @@ -1222,7 +1223,7 @@ async function viewLive(root) { } lineChart(fcast.box, { series, - unit: "°C", digits: 1, xFrom, xTo: projecting ? now + FORECAST_FUTURE_S : now, height: 210, + unit: "°C", digits: 1, zeroBase: true, xFrom, xTo: projecting ? now + FORECAST_FUTURE_S : now, height: 210, refLines: [ { value: HANDLE_TRIP_C, label: `${HANDLE_TRIP_C}°C handle → alert 40 derate` }, { value: HANDLE_CLEAR_C, label: `${HANDLE_CLEAR_C}°C → clears`, below: true }, @@ -1499,7 +1500,7 @@ async function viewSessionDetail(root, id) { gap: FORECAST_GAP_S, points: firmPts }); } lineChart(temp.box, { - series: tempSeries, unit: "°C", digits: 1, xFrom, xTo, height: 190, + series: tempSeries, unit: "°C", digits: 1, zeroBase: true, xFrom, xTo, height: 190, refLines: [ { value: HANDLE_TRIP_C, label: `${HANDLE_TRIP_C}°C handle → alert 40 derate` }, { value: PCBA_THROTTLE_C, label: `≈${PCBA_THROTTLE_C}°C PCBA throttle (approx.)` }, @@ -1762,7 +1763,7 @@ async function viewAlerts(root, rangeKey = "7d") { const calMarks = ((calEvents && calEvents.events) || []).map((ev) => ({ ts: ev.ts, label: "idle-offset recalibrated" })); lineChart(rise.box, { series: [{ name: "Rise (°C)", color: colors.s1, points: fits.map((fit) => [fit.start_ts, fit.rise_ref_c]) }], - unit: "°C", digits: 1, height: 180, vlines: calMarks, + unit: "°C", digits: 1, zeroBase: true, height: 180, vlines: calMarks, }); if (drift) { // The verdict carries its own uncertainty — a slope from a handful of @@ -1895,7 +1896,7 @@ async function viewAlerts(root, rangeKey = "7d") { { name: "start-only ambient", color: colors.s3, points: fits.filter((fit) => fit.ambient_drift_c == null && fit.ambient_c != null).map(fitPoint) }, ], - height: 210, + height: 210, zeroBase: true, tip: (fit) => { const nodes = [el("div", { class: "tt-time" }, `Session ${fit.session_id} · ${fmtDT(fit.start_ts)}`)]; nodes.push(ttRow("heat rise", `+${fmtNum(fit.rise_ref_c, 1)} °C @ 48 A`));