perf: downsample history to daily averages and fix chart overflow
This commit is contained in:
@@ -197,7 +197,7 @@
|
||||
<div><h2 id="history-title" style="margin:0;font-size:1rem">PostgreSQL history</h2><p id="history-status" class="subtitle">Select a RID flow station to load the last 7 days</p></div>
|
||||
<select id="history-range" style="padding:9px 12px;border:1px solid var(--border);border-radius:10px;background:white"><option value="24">24 hours</option><option value="168" selected>7 days</option><option value="720">30 days</option><option value="2160">90 days</option><option value="876000">All time</option></select>
|
||||
</div>
|
||||
<div style="height:260px;margin-top:14px"><canvas id="history-chart" aria-label="Historical water level and discharge chart"></canvas></div>
|
||||
<div style="height:260px;margin-top:14px;overflow:hidden;position:relative"><canvas id="history-chart" aria-label="Historical water level and discharge chart" style="display:block"></canvas></div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@@ -324,19 +324,41 @@
|
||||
const response = await fetch(`/measurements/history/${encodeURIComponent(stationCode)}?hours=${$('history-range').value}`);
|
||||
if (!response.ok) throw new Error((await response.json()).detail || `HTTP ${response.status}`);
|
||||
const rows = await response.json();
|
||||
|
||||
// Downsample to daily averages to prevent browser freezing with 50k+ points
|
||||
const downsample = (data) => {
|
||||
const buckets = {};
|
||||
data.forEach((row, i) => {
|
||||
const date = new Date(row.timestamp);
|
||||
const key = `${date.getUTCFullYear()}-${date.getUTCMonth()}-${date.getUTCDate()}`;
|
||||
if (!buckets[key]) buckets[key] = { ts: row.timestamp, discharge: [], level: [], count: 0 };
|
||||
const b = buckets[key];
|
||||
if (row.discharge != null) b.discharge.push(row.discharge);
|
||||
if (row.water_level != null) b.level.push(row.water_level);
|
||||
b.count++;
|
||||
});
|
||||
return Object.values(buckets).map((b) => ({
|
||||
timestamp: b.ts,
|
||||
discharge: b.discharge.length ? b.discharge.reduce((a, c) => a + c, 0) / b.discharge.length : null,
|
||||
water_level: b.level.length ? b.level.reduce((a, c) => a + c, 0) / b.level.length : null,
|
||||
}));
|
||||
};
|
||||
|
||||
const sampled = rows.length > 2000 ? downsample(rows) : rows;
|
||||
|
||||
if (state.historyChart) state.historyChart.destroy();
|
||||
state.historyChart = new Chart($('history-chart'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: rows.map((row) => new Date(row.timestamp).toLocaleString('en-TH', { timeZone: 'Asia/Bangkok', month: 'short', day: 'numeric', year: '2-digit', hour: '2-digit' })),
|
||||
labels: sampled.map((row) => new Date(row.timestamp).toLocaleString('en-TH', { timeZone: 'Asia/Bangkok', month: 'short', day: 'numeric', year: '2-digit', hour: '2-digit' })),
|
||||
datasets: [
|
||||
{ label: 'Discharge (m³/s)', data: rows.map((row) => row.discharge), borderColor: '#087da5', backgroundColor: 'rgba(8,125,165,.12)', yAxisID: 'flow', pointRadius: 0, tension: .25 },
|
||||
{ label: 'Water level (m)', data: rows.map((row) => row.water_level), borderColor: '#d99018', yAxisID: 'level', pointRadius: 0, tension: .25 }
|
||||
{ label: 'Discharge (m³/s)', data: sampled.map((row) => row.discharge), borderColor: '#087da5', backgroundColor: 'rgba(8,125,165,.12)', yAxisID: 'flow', pointRadius: 0, tension: .25, parsing: false },
|
||||
{ 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, interaction: { mode: 'index', intersect: false }, scales: { flow: { type: 'linear', position: 'left' }, level: { type: 'linear', position: 'right', grid: { drawOnChartArea: false } }, x: { ticks: { maxTicksLimit: 10 } } } }
|
||||
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 } } } }
|
||||
});
|
||||
$('history-status').textContent = rows.length ? `${rows.length} measurements from PostgreSQL` : 'No PostgreSQL measurements in this period';
|
||||
$('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' });
|
||||
} catch (error) {
|
||||
$('history-status').textContent = `History unavailable: ${error.message}`;
|
||||
|
||||
Reference in New Issue
Block a user