From 3514218ff214d87e964d48b3ac91cfe0eedfa4ac Mon Sep 17 00:00:00 2001 From: Miguel Date: Fri, 15 Aug 2025 12:13:42 +0200 Subject: [PATCH] feat: Normalize timestamps for chart data and update time window for plot configuration --- config/data/plot_definitions.json | 2 +- frontend/src/components/ChartjsPlot.jsx | 62 ++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/config/data/plot_definitions.json b/config/data/plot_definitions.json index 92f69bd..d7b9d62 100644 --- a/config/data/plot_definitions.json +++ b/config/data/plot_definitions.json @@ -21,7 +21,7 @@ "point_hover_radius": 4, "point_radius": 1, "stepped": true, - "time_window": 60, + "time_window": 10, "trigger_enabled": false, "trigger_on_true": true } diff --git a/frontend/src/components/ChartjsPlot.jsx b/frontend/src/components/ChartjsPlot.jsx index 0281a29..ded98fa 100644 --- a/frontend/src/components/ChartjsPlot.jsx +++ b/frontend/src/components/ChartjsPlot.jsx @@ -314,6 +314,38 @@ const ChartjsPlot = ({ session, height = '400px' }) => { } }, []); + // Helper function to normalize timestamps for consistent chart data + const normalizeTimestamp = useCallback((timestamp) => { + if (timestamp instanceof Date) { + return timestamp.getTime(); + } + + let raw = timestamp; + if (typeof raw === 'string') { + const asNum = Number(raw); + if (Number.isFinite(asNum)) { + raw = asNum; + } else { + const parsed = Date.parse(raw); + if (Number.isFinite(parsed)) { + raw = parsed; + } else { + console.warn('⚠️ Invalid timestamp string:', timestamp); + return Date.now(); // fallback to current time + } + } + } + + if (typeof raw !== 'number' || !Number.isFinite(raw)) { + console.warn('⚠️ Invalid timestamp value:', timestamp); + return Date.now(); // fallback to current time + } + + // Normalize seconds to milliseconds + const normalized = raw < 1e12 ? raw * 1000 : raw; + return normalized; + }, []); + const createStreamingChart = useCallback(async () => { const cfg = resolvedConfigRef.current || session?.config; if (!canvasRef.current || !cfg) return; @@ -600,7 +632,7 @@ const ChartjsPlot = ({ session, height = '400px' }) => { dataByVariable[variable] = []; } dataByVariable[variable].push({ - x: new Date(timestamp), + x: normalizeTimestamp(timestamp), y: value }); }); @@ -612,6 +644,12 @@ const ChartjsPlot = ({ session, height = '400px' }) => { // Sort points by timestamp to ensure proper order historicalPoints.sort((a, b) => a.x - b.x); datasets[index].data = historicalPoints; + + // Update lastPushedX tracking for streaming continuity + const lastPoint = historicalPoints[historicalPoints.length - 1]; + if (lastPoint && typeof lastPoint.x === 'number') { + sessionDataRef.current.lastPushedXByDataset.set(index, lastPoint.x); + } } }); @@ -844,7 +882,7 @@ const ChartjsPlot = ({ session, height = '400px' }) => { setError(error.message); setIsLoading(false); } - }, [isZoomEnabled]); + }, [isZoomEnabled, normalizeTimestamp]); const onStreamingRefresh = useCallback(async (chart) => { const sessionId = sessionDataRef.current.sessionId; @@ -1081,7 +1119,7 @@ const ChartjsPlot = ({ session, height = '400px' }) => { dataByVariable[variable] = []; } dataByVariable[variable].push({ - x: new Date(timestamp), + x: normalizeTimestamp(timestamp), y: value }); }); @@ -1093,6 +1131,12 @@ const ChartjsPlot = ({ session, height = '400px' }) => { // Sort points by timestamp to ensure proper order historicalPoints.sort((a, b) => a.x - b.x); chart.data.datasets[index].data = historicalPoints; + + // Update lastPushedX tracking for streaming continuity + const lastPoint = historicalPoints[historicalPoints.length - 1]; + if (lastPoint && typeof lastPoint.x === 'number') { + sessionDataRef.current.lastPushedXByDataset.set(index, lastPoint.x); + } } }); @@ -1129,7 +1173,7 @@ const ChartjsPlot = ({ session, height = '400px' }) => { sessionData.ingestPaused = false; sessionData.isPaused = false; sessionData.userOverrideUntil = Date.now() + 3000; - }, [startManualRefresh, loadHistoricalData, getEnabledVariables, session?.config, setIsLoadingHistorical, setDataPointsCount]); + }, [startManualRefresh, loadHistoricalData, getEnabledVariables, session?.config, setIsLoadingHistorical, setDataPointsCount, normalizeTimestamp]); const clearChart = useCallback(() => { if (!chartRef.current) return; @@ -1201,7 +1245,7 @@ const ChartjsPlot = ({ session, height = '400px' }) => { dataByVariable[variable] = []; } dataByVariable[variable].push({ - x: new Date(timestamp), + x: normalizeTimestamp(timestamp), y: value }); }); @@ -1213,6 +1257,12 @@ const ChartjsPlot = ({ session, height = '400px' }) => { // Sort points by timestamp to ensure proper order historicalPoints.sort((a, b) => a.x - b.x); chartRef.current.data.datasets[index].data = historicalPoints; + + // Update lastPushedX tracking for streaming continuity + const lastPoint = historicalPoints[historicalPoints.length - 1]; + if (lastPoint && typeof lastPoint.x === 'number') { + sessionDataRef.current.lastPushedXByDataset.set(index, lastPoint.x); + } } }); @@ -1238,7 +1288,7 @@ const ChartjsPlot = ({ session, height = '400px' }) => { } catch (error) { console.error('❌ Error during zoom reset with data reload:', error); } - }, [session, getEnabledVariables, loadHistoricalData]); + }, [session, getEnabledVariables, loadHistoricalData, normalizeTimestamp]); // Update configuration directly (for real-time style changes) const updateConfig = useCallback(async (newConfig) => {