diff --git a/app.py b/app.py index 3f68e58..c32f4dd 100644 --- a/app.py +++ b/app.py @@ -205,6 +205,7 @@ def get_timeseries(phase_id): datetime.fromisoformat(end_param.replace("Z", "+00:00")) except Exception: return api_error(400, "bad_datetime", "start/end must be ISO 8601") + time_filter = f"time >= '{start_param}' AND time <= '{end_param}'" interval = config.DEFAULT_INTERVAL else: @@ -213,6 +214,7 @@ def get_timeseries(phase_id): parse_range(clean_range) except Exception: return api_error(400, "bad_range", "range must be like 24h or 7d") + time_filter = f"time > now() - {clean_range}" interval = config.TIME_RANGES.get(range_param, {}).get("interval", config.DEFAULT_INTERVAL) @@ -221,17 +223,21 @@ def get_timeseries(phase_id): FROM "{config.MEASUREMENT}" WHERE "entity_id" = '{entity_id}' AND {time_filter} - GROUP BY time({interval}) fill(none) + GROUP BY time({interval}) fill(null) ''' client = get_influx_client() try: result = client.query(query) - data = [ - {"time": p["time"], "voltage": round(p["voltage"], 2)} - for p in result.get_points() - if p.get("voltage") is not None - ] + data = [] + for p in result.get_points(): + v = p.get("voltage") + data.append( + { + "time": p["time"], + "voltage": (round(float(v), 2) if v is not None else None), + } + ) return jsonify(data) except Exception as e: app.logger.error(f"Timeseries Error: {e} | Query: {query}") diff --git a/static/js/chart.js b/static/js/chart.js index b3b873e..375a91c 100644 --- a/static/js/chart.js +++ b/static/js/chart.js @@ -82,7 +82,7 @@ window.setupMainChart = function setupMainChart() { legend: { labels: { color: '#c9d1d9', - filter: (item) => !['Zanik', 'Powrot', 'Awaria'].some(word => item.text.includes(word)) + filter: (item) => !['Zanik', 'Powrot', 'Awaria', 'Brak danych'].some(word => item.text.includes(word)) } }, tooltip: { diff --git a/static/js/data.js b/static/js/data.js index 0652de0..58421ee 100644 --- a/static/js/data.js +++ b/static/js/data.js @@ -1,35 +1,73 @@ window.processPhaseData = function processPhaseData(id, data) { const lineData = []; - const outagePoints = []; const recoveryPoints = []; const outageLineData = []; + + const gapBars = []; + const outageBars = []; + let wasOutage = false; + const Y_TOP = 255; + const GAP_BAR_VALUE = Y_TOP; + const OUT_BAR_VALUE = Y_TOP; + data.forEach(p => { const v = p.voltage; const t = new Date(p.time); - if (v < 180 && v !== null) { - const pOut = { x: t, y: 195, realV: v }; - outagePoints.push(pOut); - outageLineData.push(pOut); + if (v === null || v === undefined) { + lineData.push({ x: t, y: null }); + outageLineData.push({ x: t, y: null }); + gapBars.push({ x: t, y: GAP_BAR_VALUE }); + return; + } + + if (v < 180) { + outageBars.push({ x: t, y: OUT_BAR_VALUE }); lineData.push({ x: t, y: null }); + outageLineData.push({ x: t, y: 195, realV: v }); + wasOutage = true; - } else { - if (wasOutage) { - const pRec = { x: t, y: v, realV: v }; - recoveryPoints.push(pRec); - outageLineData.push(pRec); - wasOutage = false; - } else { - outageLineData.push({ x: t, y: null }); - } - lineData.push({ x: t, y: v }); + return; } + + if (wasOutage) { + const pRec = { x: t, y: v, realV: v }; + recoveryPoints.push(pRec); + outageLineData.push(pRec); + wasOutage = false; + } else { + outageLineData.push({ x: t, y: null }); + } + + lineData.push({ x: t, y: v }); }); return { + gapDataset: gapBars.length ? { + type: 'bar', + label: 'Brak danych ' + window.phasesConfig[id].label, + data: gapBars, + backgroundColor: 'rgba(160,160,160,0.14)', + borderWidth: 0, + barPercentage: 1.0, + categoryPercentage: 1.0, + order: -50 + } : null, + + outageShadeDataset: outageBars.length ? { + type: 'bar', + label: 'Zanik ' + window.phasesConfig[id].label, + data: outageBars, + backgroundColor: 'rgba(255,0,0,0.10)', + borderWidth: 0, + barPercentage: 1.0, + categoryPercentage: 1.0, + order: -40 + } : null, + lineDataset: { label: window.phasesConfig[id].label, data: lineData, @@ -38,8 +76,10 @@ window.processPhaseData = function processPhaseData(id, data) { tension: 0.1, borderWidth: 2, spanGaps: false, - pointRadius: 0 + pointRadius: 0, + order: 0 }, + outageLine: { label: 'Awaria ' + window.phasesConfig[id].label, data: outageLineData, @@ -49,18 +89,10 @@ window.processPhaseData = function processPhaseData(id, data) { pointRadius: 0, fill: false, spanGaps: false, - showLine: true + showLine: true, + order: 1 }, - outageDataset: outagePoints.length ? { - label: 'Zanik ' + window.phasesConfig[id].label, - data: outagePoints, - type: 'scatter', - pointRadius: 4, - pointBackgroundColor: '#ff0000', - pointBorderColor: '#fff', - pointBorderWidth: 1, - z: 99 - } : null, + recoveryDataset: recoveryPoints.length ? { label: 'Powrot ' + window.phasesConfig[id].label, data: recoveryPoints, @@ -69,7 +101,8 @@ window.processPhaseData = function processPhaseData(id, data) { pointBackgroundColor: '#3fb950', pointBorderColor: '#fff', pointBorderWidth: 1, - z: 99 + z: 99, + order: 11 } : null }; }; @@ -89,6 +122,11 @@ window.reloadDataForRange = async function reloadDataForRange(min, max, rangeNam try { const raw = await fetch(`/api/timeseries/${id}?${urlParams}`).then(r => r.json()); const proc = window.processPhaseData(id, raw); + + if (proc.gapDataset) newDatasets.push(proc.gapDataset); + if (proc.outageShadeDataset) newDatasets.push(proc.outageShadeDataset); + + newDatasets.push(proc.lineDataset); if (proc.outageLine) newDatasets.push(proc.outageLine); if (proc.outageDataset) newDatasets.push(proc.outageDataset);