diff --git a/src/static/dashboard.html b/src/static/dashboard.html index cd7d470..941e393 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -356,7 +356,37 @@ { label: 'Water level (m)', data: sampled.map((row) => row.water_level), borderColor: '#d99018', yAxisID: 'level', pointRadius: 0, tension: .25, parsing: false } ] }, - options: { responsive: true, maintainAspectRatio: false, animation: { duration: 0 }, interaction: { mode: 'index', intersect: false }, scales: { flow: { type: 'linear', position: 'left' }, level: { type: 'linear', position: 'right', grid: { drawOnChartArea: false } }, x: { ticks: { maxTicksLimit: 12 } } } } + options: { + responsive: true, maintainAspectRatio: false, animation: { duration: 0 }, + interaction: { mode: 'index', intersect: false }, + scales: { + flow: { type: 'linear', position: 'left' }, + level: { type: 'linear', position: 'right', grid: { drawOnChartArea: false } }, + x: { ticks: { maxTicksLimit: 12 } } + }, + plugins: [{ + id: 'flood-bands', + beforeDraw(chart) { + const { ctx, scales, chartArea } = chart; + const levelScale = scales.level; + const zones = [ + { min: levelScale.min, max: 3.0, color: 'rgba(30,139,96,.08)' }, // normal + { min: 3.0, max: 4.5, color: 'rgba(217,144,24,.12)' }, // warning + { min: 4.5, max: levelScale.max, color: 'rgba(204,75,55,.15)' }, // danger + ]; + zones.forEach((z) => { + const top = levelScale.getPixelForValue(Math.min(z.max, levelScale.max)); + const bottom = levelScale.getPixelForValue(Math.max(z.min, levelScale.min)); + if (top < bottom) { + ctx.save(); + ctx.fillStyle = z.color; + ctx.fillRect(chartArea.left, top, chartArea.right - chartArea.left, bottom - top); + ctx.restore(); + } + }); + } + }] + } }); $('history-status').textContent = rows.length ? `${rows.length} measurements (${sampled.length} daily) from PostgreSQL` : 'No PostgreSQL measurements in this period'; $('history-card').scrollIntoView({ behavior: 'smooth', block: 'nearest' });