98 lines
2.7 KiB
JavaScript
98 lines
2.7 KiB
JavaScript
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}`;
|
|
}
|