fix(chart): restore realtime updates and axes
This commit is contained in:
parent
fec7b60d6b
commit
e55c1d5efb
|
|
@ -14,6 +14,7 @@ import {
|
|||
import { startLogs, startPointSocket } from "./logs.js";
|
||||
import {
|
||||
clearBatchBinding,
|
||||
browseAndLoadTree,
|
||||
clearSelectedPoints,
|
||||
createPoints,
|
||||
loadPoints,
|
||||
|
|
@ -47,7 +48,7 @@ function bindEvents() {
|
|||
dom.nodeTree.innerHTML = '<div class="muted">Click "Load Nodes" to fetch node tree</div>';
|
||||
dom.pointSourceNodeCount.textContent = "Nodes: 0";
|
||||
});
|
||||
dom.browseNodesBtn.addEventListener("click", () => withStatus(loadTree()));
|
||||
dom.browseNodesBtn.addEventListener("click", () => withStatus(browseAndLoadTree()));
|
||||
dom.refreshTreeBtn.addEventListener("click", () => withStatus(loadTree()));
|
||||
dom.createPointsBtn.addEventListener("click", () => withStatus(createPoints()));
|
||||
dom.closeModalBtn.addEventListener("click", () => dom.pointModal.classList.add("hidden"));
|
||||
|
|
|
|||
|
|
@ -3,13 +3,53 @@ import { dom } from "./dom.js";
|
|||
import { state } from "./state.js";
|
||||
|
||||
function normalizeChartItem(item) {
|
||||
let valueNumber = null;
|
||||
if (typeof item?.value_number === "number" && Number.isFinite(item.value_number)) {
|
||||
valueNumber = item.value_number;
|
||||
} else if (typeof item?.value === "number" && Number.isFinite(item.value)) {
|
||||
valueNumber = item.value;
|
||||
} else if (typeof item?.value === "boolean") {
|
||||
valueNumber = item.value ? 1 : 0;
|
||||
} else if (typeof item?.value?.float === "number" && Number.isFinite(item.value.float)) {
|
||||
valueNumber = item.value.float;
|
||||
} else if (typeof item?.value?.int === "number" && Number.isFinite(item.value.int)) {
|
||||
valueNumber = item.value.int;
|
||||
} else if (typeof item?.value?.uint === "number" && Number.isFinite(item.value.uint)) {
|
||||
valueNumber = item.value.uint;
|
||||
} else if (typeof item?.value?.bool === "boolean") {
|
||||
valueNumber = item.value.bool ? 1 : 0;
|
||||
} else if (typeof item?.value_text === "string") {
|
||||
const parsed = Number(item.value_text);
|
||||
if (Number.isFinite(parsed)) {
|
||||
valueNumber = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
timestamp: item?.timestamp || "",
|
||||
valueNumber: typeof item?.value_number === "number" ? item.value_number : null,
|
||||
valueText: item?.value_text || "",
|
||||
valueNumber,
|
||||
valueText: item?.value_text || (valueNumber === null ? "" : String(valueNumber)),
|
||||
};
|
||||
}
|
||||
|
||||
function formatAxisValue(value) {
|
||||
if (!Number.isFinite(value)) {
|
||||
return "--";
|
||||
}
|
||||
if (Math.abs(value) >= 1000 || Math.abs(value) < 0.01) {
|
||||
return value.toExponential(2);
|
||||
}
|
||||
return value.toFixed(2);
|
||||
}
|
||||
|
||||
function formatTimeLabel(timestamp) {
|
||||
if (!timestamp) {
|
||||
return "--";
|
||||
}
|
||||
const match = String(timestamp).match(/(\d{2}:\d{2}:\d{2})/);
|
||||
return match ? match[1] : String(timestamp);
|
||||
}
|
||||
|
||||
export async function openChart(pointId, pointName) {
|
||||
state.chartPointId = pointId;
|
||||
state.chartPointName = pointName || "Point";
|
||||
|
|
@ -72,10 +112,56 @@ export function renderChart() {
|
|||
max += 1;
|
||||
}
|
||||
|
||||
const padding = { top: 20, right: 20, bottom: 36, left: 52 };
|
||||
const padding = { top: 20, right: 20, bottom: 42, left: 64 };
|
||||
const plotWidth = width - padding.left - padding.right;
|
||||
const plotHeight = height - padding.top - padding.bottom;
|
||||
|
||||
ctx.strokeStyle = "#cbd5e1";
|
||||
ctx.lineWidth = 1;
|
||||
|
||||
for (let i = 0; i <= 4; i += 1) {
|
||||
const y = padding.top + (plotHeight / 4) * i;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(padding.left, y);
|
||||
ctx.lineTo(width - padding.right, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(padding.left, padding.top);
|
||||
ctx.lineTo(padding.left, height - padding.bottom);
|
||||
ctx.lineTo(width - padding.right, height - padding.bottom);
|
||||
ctx.strokeStyle = "#94a3b8";
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = "#64748b";
|
||||
ctx.font = "12px Segoe UI";
|
||||
for (let i = 0; i <= 4; i += 1) {
|
||||
const value = max - ((max - min) / 4) * i;
|
||||
const y = padding.top + (plotHeight / 4) * i;
|
||||
ctx.fillText(formatAxisValue(value), 8, y + 4);
|
||||
}
|
||||
|
||||
const firstLabel = formatTimeLabel(state.chartData[0]?.timestamp);
|
||||
const middleLabel = formatTimeLabel(
|
||||
state.chartData[Math.floor((state.chartData.length - 1) / 2)]?.timestamp,
|
||||
);
|
||||
const lastLabel = formatTimeLabel(state.chartData[state.chartData.length - 1]?.timestamp);
|
||||
|
||||
ctx.fillText(firstLabel, padding.left, height - 12);
|
||||
const middleWidth = ctx.measureText(middleLabel).width;
|
||||
ctx.fillText(middleLabel, padding.left + plotWidth / 2 - middleWidth / 2, height - 12);
|
||||
const lastWidth = ctx.measureText(lastLabel).width;
|
||||
ctx.fillText(lastLabel, width - padding.right - lastWidth, height - 12);
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(16, padding.top + plotHeight / 2);
|
||||
ctx.rotate(-Math.PI / 2);
|
||||
ctx.fillStyle = "#64748b";
|
||||
ctx.fillText("Value", 0, 0);
|
||||
ctx.restore();
|
||||
ctx.fillText("Time", width / 2 - 12, height - 28);
|
||||
|
||||
ctx.strokeStyle = "#2563eb";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
|
|
|
|||
|
|
@ -123,6 +123,17 @@ export async function loadTree() {
|
|||
updatePointSourceNodeCount();
|
||||
}
|
||||
|
||||
export async function browseAndLoadTree() {
|
||||
const sourceId = dom.pointSourceSelect.value || state.selectedSourceId;
|
||||
if (!sourceId) {
|
||||
throw new Error("Select a source first");
|
||||
}
|
||||
|
||||
state.selectedSourceId = sourceId;
|
||||
await apiFetch(`/api/source/${sourceId}/browse`, { method: "POST" });
|
||||
await loadTree();
|
||||
}
|
||||
|
||||
export async function createPoints() {
|
||||
if (!state.selectedNodeIds.size) {
|
||||
return;
|
||||
|
|
|
|||
Loading…
Reference in New Issue