import { apiFetch } from "./api.js"; import { dom } from "./dom.js"; import { state } from "./state.js"; function normalizeChartItem(item) { return { timestamp: item?.timestamp || "", valueNumber: typeof item?.value_number === "number" ? item.value_number : null, valueText: item?.value_text || "", }; } export async function openChart(pointId, pointName) { state.chartPointId = pointId; state.chartPointName = pointName || "Point"; dom.chartTitle.textContent = `${state.chartPointName} Chart`; const items = await apiFetch(`/api/point/${pointId}/history?limit=120`); state.chartData = (items || []) .map(normalizeChartItem) .filter((item) => item.valueNumber !== null); renderChart(); } export function appendChartPoint(item) { if (!state.chartPointId) { return; } const normalized = normalizeChartItem(item); if (normalized.valueNumber === null) { return; } const last = state.chartData[state.chartData.length - 1]; if ( last && last.timestamp === normalized.timestamp && last.valueText === normalized.valueText && last.valueNumber === normalized.valueNumber ) { return; } state.chartData.push(normalized); if (state.chartData.length > 120) { state.chartData = state.chartData.slice(-120); } renderChart(); } export function renderChart() { const ctx = dom.chartCanvas.getContext("2d"); const width = dom.chartCanvas.width; const height = dom.chartCanvas.height; ctx.clearRect(0, 0, width, height); if (!state.chartData.length) { ctx.fillStyle = "#94a3b8"; ctx.font = "14px Segoe UI"; ctx.fillText("Click a point row to view its chart", 24, 40); dom.chartSummary.textContent = "Click a point row to view its chart"; return; } const values = state.chartData.map((item) => item.valueNumber); let min = Math.min(...values); let max = Math.max(...values); if (min === max) { min -= 1; max += 1; } const padding = { top: 20, right: 20, bottom: 36, left: 52 }; const plotWidth = width - padding.left - padding.right; const plotHeight = height - padding.top - padding.bottom; ctx.strokeStyle = "#2563eb"; ctx.lineWidth = 2; ctx.beginPath(); state.chartData.forEach((item, index) => { const x = padding.left + (plotWidth * index) / Math.max(1, state.chartData.length - 1); const y = padding.top + ((max - item.valueNumber) / (max - min)) * plotHeight; if (index === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } }); ctx.stroke(); const latest = state.chartData[state.chartData.length - 1]; dom.chartSummary.textContent = `Latest ${state.chartData.length} points, current value ${latest.valueText || latest.valueNumber}`; }