From c8f149e2048cb38d0cae32355a30a69b4e8c3f61 Mon Sep 17 00:00:00 2001 From: znetsixe Date: Tue, 14 Apr 2026 13:19:32 +0200 Subject: [PATCH] feat(dashboard): split basin charts by unit + add y-axis labels to all charts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flow: m³/h, Power: kW, Basin Level: m, Basin Fill: % (0-100 fixed). Level and fill in separate chart groups with their own gauges. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../build_flow.py | 80 ++++--- .../pumpingstation-3pumps-dashboard/flow.json | 216 +++++++++++++++--- 2 files changed, 233 insertions(+), 63 deletions(-) diff --git a/examples/pumpingstation-3pumps-dashboard/build_flow.py b/examples/pumpingstation-3pumps-dashboard/build_flow.py index b3f1de5..bcca11c 100644 --- a/examples/pumpingstation-3pumps-dashboard/build_flow.py +++ b/examples/pumpingstation-3pumps-dashboard/build_flow.py @@ -755,12 +755,14 @@ def build_ui_tab(): ui_group(g_pump_b, "4b. Pump B", PG, width=4, order=6), ui_group(g_pump_c, "4c. Pump C", PG, width=4, order=7), # Trends on separate pages - ui_group(g_trend_short_flow, "Flow (10 min)", PG_SHORT, width=12, order=1), - ui_group(g_trend_short_power, "Power (10 min)", PG_SHORT, width=12, order=2), - ui_group("ui_grp_trend_short_basin", "Basin (10 min)", PG_SHORT, width=12, order=3), - ui_group(g_trend_long_flow, "Flow (1 hour)", PG_LONG, width=12, order=1), - ui_group(g_trend_long_power, "Power (1 hour)", PG_LONG, width=12, order=2), - ui_group("ui_grp_trend_long_basin", "Basin (1 hour)", PG_LONG, width=12, order=3), + ui_group(g_trend_short_flow, "Flow (10 min)", PG_SHORT, width=12, order=1), + ui_group(g_trend_short_power, "Power (10 min)", PG_SHORT, width=12, order=2), + ui_group("ui_grp_trend_short_basin_level", "Basin Level (10 min)", PG_SHORT, width=12, order=3), + ui_group("ui_grp_trend_short_basin_fill", "Basin Fill (10 min)", PG_SHORT, width=12, order=4), + ui_group(g_trend_long_flow, "Flow (1 hour)", PG_LONG, width=12, order=1), + ui_group(g_trend_long_power, "Power (1 hour)", PG_LONG, width=12, order=2), + ui_group("ui_grp_trend_long_basin_level", "Basin Level (1 hour)", PG_LONG, width=12, order=3), + ui_group("ui_grp_trend_long_basin_fill", "Basin Fill (1 hour)", PG_LONG, width=12, order=4), ] nodes.append(comment("c_ui_title", TAB_UI, LANE_X[2], 20, @@ -929,10 +931,10 @@ def build_ui_tab(): ["ui_ps_netflow"], ["ui_ps_timeleft"], ["ui_ps_qin"], - # Trend + gauge outputs (short + long page gauges) - ["trend_short_basin", "trend_long_basin", "gauge_ps_fill", "gauge_ps_fill_long"], # fill % - ["trend_short_basin", "trend_long_basin", "gauge_ps_level", "gauge_ps_level_long"], # level - ["trend_short_basin", "trend_long_basin"], # net flow + # Trend + gauge outputs — split level and fill to separate charts + ["trend_short_fill", "trend_long_fill", "gauge_ps_fill", "gauge_ps_fill_long"], # fill % → fill chart + gauges + ["trend_short_level", "trend_long_level", "gauge_ps_level", "gauge_ps_level_long"], # level → level chart + gauges + ["trend_short_level", "trend_long_level"], # net flow → level chart (shared axis) ], )) @@ -1082,16 +1084,16 @@ def build_ui_tab(): nodes.append(ui_chart( "trend_short_flow", TAB_UI, LANE_X[3], y_charts + 40, g_trend_short_flow, - "Flow per pump — 10 min", "Flow per pump (m³/h)", - width="12", height="8", + "Flow per pump — 10 min", "Flow per pump", + width=12, height=8, y_axis_label="m³/h", remove_older="10", remove_older_unit="60", remove_older_points="300", order=1, )) nodes.append(ui_chart( "trend_short_power", TAB_UI, LANE_X[3], y_charts + 120, g_trend_short_power, - "Power per pump — 10 min", "Power per pump (kW)", - width="12", height="8", + "Power per pump — 10 min", "Power per pump", + width=12, height=8, y_axis_label="kW", remove_older="10", remove_older_unit="60", remove_older_points="300", order=1, )) @@ -1099,16 +1101,16 @@ def build_ui_tab(): nodes.append(ui_chart( "trend_long_flow", TAB_UI, LANE_X[3], y_charts + 200, g_trend_long_flow, - "Flow per pump — 1 hour", "Flow per pump (m³/h)", - width="12", height="8", + "Flow per pump — 1 hour", "Flow per pump", + width=12, height=8, y_axis_label="m³/h", remove_older="60", remove_older_unit="60", remove_older_points="1800", order=1, )) nodes.append(ui_chart( "trend_long_power", TAB_UI, LANE_X[3], y_charts + 280, g_trend_long_power, - "Power per pump — 1 hour", "Power per pump (kW)", - width="12", height="8", + "Power per pump — 1 hour", "Power per pump", + width=12, height=8, y_axis_label="kW", remove_older="60", remove_older_unit="60", remove_older_points="1800", order=1, )) @@ -1130,25 +1132,41 @@ def build_ui_tab(): {"color": "#f44336", "from": 95}, ] - for suffix, grp, remove_older, remove_points, y_off in [ - ("short", "ui_grp_trend_short_basin", "10", "300", 360), - ("long", "ui_grp_trend_long_basin", "60", "1800", 540), + for suffix, remove_older, remove_points, y_off in [ + ("short", "10", "300", 360), + ("long", "60", "1800", 540), ]: - # Basin trend chart (width 8 to leave room for gauges) + label = "10 min" if suffix == "short" else "1 hour" + grp_level = f"ui_grp_trend_{suffix}_basin_level" + grp_fill = f"ui_grp_trend_{suffix}_basin_fill" + + # Basin LEVEL chart (m) — also receives net flow (m³/h) on right axis + # via eCharts dual-axis configured by the first data message nodes.append(ui_chart( - f"trend_{suffix}_basin", TAB_UI, LANE_X[3], y_charts + y_off, - grp, - f"Basin — {'10 min' if suffix == 'short' else '1 hour'}", "Basin metrics", - width=8, height=8, + f"trend_{suffix}_level", TAB_UI, LANE_X[3], y_charts + y_off, + grp_level, + f"Basin Level — {label}", "Basin Level + Net Flow", + width=8, height=8, y_axis_label="m", remove_older=remove_older, remove_older_unit="60", remove_older_points=remove_points, - y_axis_label="", order=1, + order=1, )) - # Tank gauge: basin level 0–3 m + + # Basin FILL chart (%) — simple single-axis + nodes.append(ui_chart( + f"trend_{suffix}_fill", TAB_UI, LANE_X[3], y_charts + y_off + 80, + grp_fill, + f"Basin Fill — {label}", "Basin Fill", + width=8, height=6, y_axis_label="%", + remove_older=remove_older, remove_older_unit="60", + remove_older_points=remove_points, + ymin=0, ymax=100, order=1, + )) + # Tank gauge: basin level 0–4 m — in the level group gauge_id_suffix = "" if suffix == "short" else "_long" nodes.append({ "id": f"gauge_ps_level{gauge_id_suffix}", "type": "ui-gauge", - "z": TAB_UI, "group": grp, + "z": TAB_UI, "group": grp_level, "name": f"Basin level gauge ({suffix})", "gtype": "gauge-tank", "gstyle": "Rounded", "title": "Level", "units": "m", @@ -1159,10 +1177,10 @@ def build_ui_tab(): "icon": "", "sizeGauge": 20, "sizeGap": 2, "sizeSegments": 10, "x": LANE_X[4], "y": y_charts + y_off, "wires": [], }) - # 270° arc: fill % + # 270° arc: fill % — in the fill group nodes.append({ "id": f"gauge_ps_fill{gauge_id_suffix}", "type": "ui-gauge", - "z": TAB_UI, "group": grp, + "z": TAB_UI, "group": grp_fill, "name": f"Basin fill gauge ({suffix})", "gtype": "gauge-34", "gstyle": "Rounded", "title": "Fill", "units": "%", diff --git a/examples/pumpingstation-3pumps-dashboard/flow.json b/examples/pumpingstation-3pumps-dashboard/flow.json index a061d00..623d7f6 100644 --- a/examples/pumpingstation-3pumps-dashboard/flow.json +++ b/examples/pumpingstation-3pumps-dashboard/flow.json @@ -1355,9 +1355,9 @@ "visible": true }, { - "id": "ui_grp_trend_short_basin", + "id": "ui_grp_trend_short_basin_level", "type": "ui-group", - "name": "Basin (10 min)", + "name": "Basin Level (10 min)", "page": "ui_page_short_trends", "width": "12", "height": "1", @@ -1368,6 +1368,20 @@ "disabled": false, "visible": true }, + { + "id": "ui_grp_trend_short_basin_fill", + "type": "ui-group", + "name": "Basin Fill (10 min)", + "page": "ui_page_short_trends", + "width": "12", + "height": "1", + "order": 4, + "showTitle": true, + "className": "", + "groupType": "default", + "disabled": false, + "visible": true + }, { "id": "ui_grp_trend_long_flow", "type": "ui-group", @@ -1397,9 +1411,9 @@ "visible": true }, { - "id": "ui_grp_trend_long_basin", + "id": "ui_grp_trend_long_basin_level", "type": "ui-group", - "name": "Basin (1 hour)", + "name": "Basin Level (1 hour)", "page": "ui_page_long_trends", "width": "12", "height": "1", @@ -1410,6 +1424,20 @@ "disabled": false, "visible": true }, + { + "id": "ui_grp_trend_long_basin_fill", + "type": "ui-group", + "name": "Basin Fill (1 hour)", + "page": "ui_page_long_trends", + "width": "12", + "height": "1", + "order": 4, + "showTitle": true, + "className": "", + "groupType": "default", + "disabled": false, + "visible": true + }, { "id": "c_ui_title", "type": "comment", @@ -1899,20 +1927,20 @@ "ui_ps_qin" ], [ - "trend_short_basin", - "trend_long_basin", + "trend_short_fill", + "trend_long_fill", "gauge_ps_fill", "gauge_ps_fill_long" ], [ - "trend_short_basin", - "trend_long_basin", + "trend_short_level", + "trend_long_level", "gauge_ps_level", "gauge_ps_level_long" ], [ - "trend_short_basin", - "trend_long_basin" + "trend_short_level", + "trend_long_level" ] ] }, @@ -3170,7 +3198,7 @@ "z": "tab_ui", "group": "ui_grp_trend_short_flow", "name": "Flow per pump \u2014 10 min", - "label": "Flow per pump (m\u00b3/h)", + "label": "Flow per pump", "order": 1, "chartType": "line", "interpolation": "linear", @@ -3184,7 +3212,7 @@ "xAxisFormatType": "auto", "xmin": "", "xmax": "", - "yAxisLabel": "", + "yAxisLabel": "m\u00b3/h", "yAxisProperty": "payload", "yAxisPropertyType": "msg", "ymin": "", @@ -3232,7 +3260,7 @@ "z": "tab_ui", "group": "ui_grp_trend_short_power", "name": "Power per pump \u2014 10 min", - "label": "Power per pump (kW)", + "label": "Power per pump", "order": 1, "chartType": "line", "interpolation": "linear", @@ -3246,7 +3274,7 @@ "xAxisFormatType": "auto", "xmin": "", "xmax": "", - "yAxisLabel": "", + "yAxisLabel": "kW", "yAxisProperty": "payload", "yAxisPropertyType": "msg", "ymin": "", @@ -3294,7 +3322,7 @@ "z": "tab_ui", "group": "ui_grp_trend_long_flow", "name": "Flow per pump \u2014 1 hour", - "label": "Flow per pump (m\u00b3/h)", + "label": "Flow per pump", "order": 1, "chartType": "line", "interpolation": "linear", @@ -3308,7 +3336,7 @@ "xAxisFormatType": "auto", "xmin": "", "xmax": "", - "yAxisLabel": "", + "yAxisLabel": "m\u00b3/h", "yAxisProperty": "payload", "yAxisPropertyType": "msg", "ymin": "", @@ -3356,7 +3384,7 @@ "z": "tab_ui", "group": "ui_grp_trend_long_power", "name": "Power per pump \u2014 1 hour", - "label": "Power per pump (kW)", + "label": "Power per pump", "order": 1, "chartType": "line", "interpolation": "linear", @@ -3370,7 +3398,7 @@ "xAxisFormatType": "auto", "xmin": "", "xmax": "", - "yAxisLabel": "", + "yAxisLabel": "kW", "yAxisProperty": "payload", "yAxisPropertyType": "msg", "ymin": "", @@ -3413,12 +3441,12 @@ ] }, { - "id": "trend_short_basin", + "id": "trend_short_level", "type": "ui-chart", "z": "tab_ui", - "group": "ui_grp_trend_short_basin", - "name": "Basin \u2014 10 min", - "label": "Basin metrics", + "group": "ui_grp_trend_short_basin_level", + "name": "Basin Level \u2014 10 min", + "label": "Basin Level + Net Flow", "order": 1, "chartType": "line", "interpolation": "linear", @@ -3432,7 +3460,7 @@ "xAxisFormatType": "auto", "xmin": "", "xmax": "", - "yAxisLabel": "", + "yAxisLabel": "m", "yAxisProperty": "payload", "yAxisPropertyType": "msg", "ymin": "", @@ -3474,11 +3502,73 @@ [] ] }, + { + "id": "trend_short_fill", + "type": "ui-chart", + "z": "tab_ui", + "group": "ui_grp_trend_short_basin_fill", + "name": "Basin Fill \u2014 10 min", + "label": "Basin Fill", + "order": 1, + "chartType": "line", + "interpolation": "linear", + "category": "topic", + "categoryType": "msg", + "xAxisLabel": "", + "xAxisType": "time", + "xAxisProperty": "", + "xAxisPropertyType": "timestamp", + "xAxisFormat": "", + "xAxisFormatType": "auto", + "xmin": "", + "xmax": "", + "yAxisLabel": "%", + "yAxisProperty": "payload", + "yAxisPropertyType": "msg", + "ymin": "0", + "ymax": "100", + "removeOlder": "10", + "removeOlderUnit": "60", + "removeOlderPoints": "300", + "action": "append", + "stackSeries": false, + "pointShape": "circle", + "pointRadius": 4, + "showLegend": true, + "bins": 10, + "colors": [ + "#0095FF", + "#FF0000", + "#FF7F0E", + "#2CA02C", + "#A347E1", + "#D62728", + "#FF9896", + "#9467BD", + "#C5B0D5" + ], + "textColor": [ + "#666666" + ], + "textColorDefault": true, + "gridColor": [ + "#e5e5e5" + ], + "gridColorDefault": true, + "width": 8, + "height": 6, + "className": "", + "x": 900, + "y": 2720, + "wires": [ + [] + ] + }, { "id": "gauge_ps_level", "type": "ui-gauge", "z": "tab_ui", - "group": "ui_grp_trend_short_basin", + "group": "ui_grp_trend_short_basin_level", "name": "Basin level gauge (short)", "gtype": "gauge-tank", "gstyle": "Rounded", @@ -3525,7 +3615,7 @@ "id": "gauge_ps_fill", "type": "ui-gauge", "z": "tab_ui", - "group": "ui_grp_trend_short_basin", + "group": "ui_grp_trend_short_basin_fill", "name": "Basin fill gauge (short)", "gtype": "gauge-34", "gstyle": "Rounded", @@ -3569,12 +3659,12 @@ "wires": [] }, { - "id": "trend_long_basin", + "id": "trend_long_level", "type": "ui-chart", "z": "tab_ui", - "group": "ui_grp_trend_long_basin", - "name": "Basin \u2014 1 hour", - "label": "Basin metrics", + "group": "ui_grp_trend_long_basin_level", + "name": "Basin Level \u2014 1 hour", + "label": "Basin Level + Net Flow", "order": 1, "chartType": "line", "interpolation": "linear", @@ -3588,7 +3678,7 @@ "xAxisFormatType": "auto", "xmin": "", "xmax": "", - "yAxisLabel": "", + "yAxisLabel": "m", "yAxisProperty": "payload", "yAxisPropertyType": "msg", "ymin": "", @@ -3630,11 +3720,73 @@ [] ] }, + { + "id": "trend_long_fill", + "type": "ui-chart", + "z": "tab_ui", + "group": "ui_grp_trend_long_basin_fill", + "name": "Basin Fill \u2014 1 hour", + "label": "Basin Fill", + "order": 1, + "chartType": "line", + "interpolation": "linear", + "category": "topic", + "categoryType": "msg", + "xAxisLabel": "", + "xAxisType": "time", + "xAxisProperty": "", + "xAxisPropertyType": "timestamp", + "xAxisFormat": "", + "xAxisFormatType": "auto", + "xmin": "", + "xmax": "", + "yAxisLabel": "%", + "yAxisProperty": "payload", + "yAxisPropertyType": "msg", + "ymin": "0", + "ymax": "100", + "removeOlder": "60", + "removeOlderUnit": "60", + "removeOlderPoints": "1800", + "action": "append", + "stackSeries": false, + "pointShape": "circle", + "pointRadius": 4, + "showLegend": true, + "bins": 10, + "colors": [ + "#0095FF", + "#FF0000", + "#FF7F0E", + "#2CA02C", + "#A347E1", + "#D62728", + "#FF9896", + "#9467BD", + "#C5B0D5" + ], + "textColor": [ + "#666666" + ], + "textColorDefault": true, + "gridColor": [ + "#e5e5e5" + ], + "gridColorDefault": true, + "width": 8, + "height": 6, + "className": "", + "x": 900, + "y": 2900, + "wires": [ + [] + ] + }, { "id": "gauge_ps_level_long", "type": "ui-gauge", "z": "tab_ui", - "group": "ui_grp_trend_long_basin", + "group": "ui_grp_trend_long_basin_level", "name": "Basin level gauge (long)", "gtype": "gauge-tank", "gstyle": "Rounded", @@ -3681,7 +3833,7 @@ "id": "gauge_ps_fill_long", "type": "ui-gauge", "z": "tab_ui", - "group": "ui_grp_trend_long_basin", + "group": "ui_grp_trend_long_basin_fill", "name": "Basin fill gauge (long)", "gtype": "gauge-34", "gstyle": "Rounded",