Add Y-axis (score) and X-axis (time) labels to forecast sparkline

This commit is contained in:
Graham McIntire 2026-03-31 17:13:11 -05:00
parent 88655e40a6
commit 09a26f1167
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59

View file

@ -122,25 +122,27 @@ function buildLoadingHTML(detail) {
function buildForecastSvg(forecast) {
if (!forecast || forecast.length < 2) return ""
const w = 240, h = 48, pad = 2
const marginL = 28, marginR = 6, marginT = 4, marginB = 16
const w = 260, h = 72
const plotW = w - marginL - marginR
const plotH = h - marginT - marginB
const n = forecast.length
const now = new Date()
const stepX = (w - pad * 2) / (n - 1)
const points = forecast.map((f, i) => {
const x = pad + i * stepX
const y = pad + (h - pad * 2) * (1 - f.score / 100)
const x = marginL + (i / (n - 1)) * plotW
const y = marginT + plotH * (1 - f.score / 100)
return { x, y, score: f.score, time: new Date(f.time) }
})
const polyline = points.map(p => `${p.x},${p.y}`).join(" ")
// Find the "now" index
// "Now" dot
const nowIdx = points.reduce((best, p, i) =>
Math.abs(p.time - now) < Math.abs(points[best].time - now) ? i : best, 0)
const nowPt = points[nowIdx]
// Trend arrow
// Trend
const firstScore = points[0].score
const lastScore = points[points.length - 1].score
const diff = lastScore - firstScore
@ -149,9 +151,20 @@ function buildForecastSvg(forecast) {
else if (diff < -5) trend = `<span style="color:#ff4f4f;">&#9660; Declining</span>`
else trend = `<span style="color:#ffe566;">&#8594; Steady</span>`
// Score labels at start and end
const startLabel = `<text x="${pad}" y="${h - 2}" font-size="9" fill="rgba(255,255,255,0.5)">${firstScore}</text>`
const endLabel = `<text x="${w - pad - 12}" y="${h - 2}" font-size="9" fill="rgba(255,255,255,0.5)">${lastScore}</text>`
// Y-axis: score labels
const yLabels = [0, 50, 100].map(s => {
const y = marginT + plotH * (1 - s / 100)
return `<text x="${marginL - 4}" y="${y + 3}" font-size="8" fill="rgba(255,255,255,0.4)" text-anchor="end">${s}</text>
<line x1="${marginL}" y1="${y}" x2="${marginL + plotW}" y2="${y}" stroke="rgba(255,255,255,0.08)" stroke-width="1"/>`
}).join("")
// X-axis: time labels (first, middle, last)
const timeIdxs = [0, Math.floor(n / 2), n - 1]
const xLabels = timeIdxs.map(i => {
const p = points[i]
const label = `${p.time.getUTCHours().toString().padStart(2, "0")}:00`
return `<text x="${p.x}" y="${h - 2}" font-size="8" fill="rgba(255,255,255,0.4)" text-anchor="middle">${label}</text>`
}).join("")
return `
<div style="padding:6px 10px;border-top:1px solid rgba(255,255,255,0.15);">
@ -160,10 +173,10 @@ function buildForecastSvg(forecast) {
<span style="font-size:11px;">${trend}</span>
</div>
<svg width="${w}" height="${h}" style="display:block;">
${yLabels}
${xLabels}
<polyline points="${polyline}" fill="none" stroke="#00ffa3" stroke-width="2" stroke-linejoin="round"/>
<circle cx="${nowPt.x}" cy="${nowPt.y}" r="3" fill="#fff" stroke="#00ffa3" stroke-width="1.5"/>
${startLabel}
${endLabel}
</svg>
</div>`
}