wykres z gap

This commit is contained in:
Mateusz Gruszczyński
2026-03-02 17:39:54 +01:00
parent 6bd4b525b1
commit 8e14d38c38
3 changed files with 79 additions and 35 deletions

18
app.py
View File

@@ -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}")

View File

@@ -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: {

View File

@@ -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);