From bc8138c3dcdba11cdf12907ae5bf662304a57421 Mon Sep 17 00:00:00 2001 From: znetsixe Date: Tue, 14 Apr 2026 08:04:43 +0200 Subject: [PATCH] fix(charts): add all required FlowFuse ui-chart properties + document in rule set Charts rendered blank because the helper was missing 15+ required FlowFuse properties. The critical three: - interpolation: "linear" (no line drawn without it) - yAxisProperty: "payload" + yAxisPropertyType: "msg" (chart didn't know which msg field to plot) - xAxisPropertyType: "timestamp" (chart didn't know the x source) Also: width/height must be numbers not strings, colors/textColor/ gridColor arrays must be present, and stackSeries/bins/xAxisFormat/ xAxisFormatType all need explicit values. Fixed the ui_chart helper to include every property from the working rotatingMachine/examples/03-Dashboard.json charts. Added the full required-property template + gotcha list to the flow-layout rule set (Section 4) so this class of bug is caught by reference on the next chart build. Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/rules/node-red-flow-layout.md | 45 +++++ .../build_flow.py | 67 +++++-- .../pumpingstation-3pumps-dashboard/flow.json | 176 ++++++++++++++---- 3 files changed, 235 insertions(+), 53 deletions(-) diff --git a/.claude/rules/node-red-flow-layout.md b/.claude/rules/node-red-flow-layout.md index 114c8b1..273f1cf 100644 --- a/.claude/rules/node-red-flow-layout.md +++ b/.claude/rules/node-red-flow-layout.md @@ -99,6 +99,51 @@ ui-chart with `category: "topic"` + `categoryType: "msg"` plots one series per u - One chart per **metric type** (one chart for flow, one for power). - Each chart receives msgs whose `topic` is the **series label** (e.g. `Pump A`, `Pump B`, `Pump C`). +### Required chart properties (FlowFuse ui-chart renders blank without ALL of these) + +Derived from working charts in rotatingMachine/examples/03-Dashboard. Every property listed below is mandatory — omit any one and the chart renders blank with no error message. + +```json +{ + "type": "ui-chart", + "chartType": "line", + "interpolation": "linear", + "category": "topic", + "categoryType": "msg", + "xAxisType": "time", + "xAxisProperty": "", + "xAxisPropertyType": "timestamp", + "xAxisFormat": "", + "xAxisFormatType": "auto", + "yAxisProperty": "payload", + "yAxisPropertyType": "msg", + "action": "append", + "stackSeries": false, + "pointShape": "circle", + "pointRadius": 4, + "showLegend": true, + "bins": 10, + "width": 12, + "height": 6, + "removeOlder": "15", + "removeOlderUnit": "60", + "removeOlderPoints": "", + "colors": ["#0095FF","#FF0000","#FF7F0E","#2CA02C","#A347E1","#D62728","#FF9896","#9467BD","#C5B0D5"], + "textColor": ["#666666"], + "textColorDefault": true, + "gridColor": ["#e5e5e5"], + "gridColorDefault": true +} +``` + +**Key gotchas:** +- `interpolation` MUST be set (`"linear"`, `"step"`, `"bezier"`, `"cubic"`, `"cubic-mono"`). Without it: no line drawn. +- `yAxisProperty: "payload"` + `yAxisPropertyType: "msg"` tells the chart WHERE in the msg to find the y-value. Without these: chart has no data to plot. +- `xAxisPropertyType: "timestamp"` tells the chart to use `msg.timestamp` (or auto-generated) for the x-axis. +- `width` and `height` are **numbers, not strings**. `width: 12` (correct) vs `width: "12"` (may break). +- `removeOlderPoints: ""` (empty string) → retention is controlled by removeOlder + removeOlderUnit only. Set to a number string to additionally cap points per series. +- `colors` array defines the palette for auto-assigned series colours. Provide at least 3. + ### The trend-split function pattern A common bug: feeding both flow and power msgs to a single function output that wires to both charts. Both charts then plot all metrics, garbling the legend. diff --git a/examples/pumpingstation-3pumps-dashboard/build_flow.py b/examples/pumpingstation-3pumps-dashboard/build_flow.py index 5a4a834..bd2bce8 100644 --- a/examples/pumpingstation-3pumps-dashboard/build_flow.py +++ b/examples/pumpingstation-3pumps-dashboard/build_flow.py @@ -258,32 +258,65 @@ def ui_switch(node_id, tab, x, y, group, name, label, on_value, off_value, def ui_chart(node_id, tab, x, y, group, name, label, - width="12", height="6", - remove_older="10", remove_older_unit="60", - remove_older_points="200", - ymin=None, ymax=None, order=1): + width=12, height=6, + remove_older="15", remove_older_unit="60", + remove_older_points="", + y_axis_label="", ymin=None, ymax=None, order=1): """ - FlowFuse ui-chart. Dimensions are in grid units: - width="12" = full group width - height="6" = about 300 px tall - `remove_older` + `remove_older_unit` define the rolling window - (e.g. "10" + "60" = 10 minutes). `remove_older_points` caps points - per series. + FlowFuse ui-chart (line type, time x-axis). + + IMPORTANT: This template is derived from the working charts in + rotatingMachine/examples/03-Dashboard.json. Every field listed below + is required or the chart renders blank. Key gotchas: + + - `width` / `height` must be NUMBERS not strings. + - `interpolation` must be set ("linear", "step", "bezier", + "cubic", "cubic-mono") or no line is drawn. + - `yAxisProperty: "payload"` + `yAxisPropertyType: "msg"` tells + the chart WHERE in the msg to find the y-value. Without these + the chart has no data to plot. + - `xAxisPropertyType: "timestamp"` tells the chart to use + msg.timestamp (or auto-generated timestamp) for the x-axis. + - `removeOlderPoints` should be "" (empty string) to let + removeOlder + removeOlderUnit control retention, OR a number + string to cap points per series. """ return { "id": node_id, "type": "ui-chart", "z": tab, "group": group, - "name": name, "label": label, "order": order, "chartType": "line", + "name": name, "label": label, "order": order, + "chartType": "line", + "interpolation": "linear", + # Series identification "category": "topic", "categoryType": "msg", - "xAxisLabel": "", "xAxisType": "time", "xAxisTimeFormat": "auto", - "yAxisLabel": "", "ymin": "" if ymin is None else str(ymin), + # X-axis (time) + "xAxisLabel": "", "xAxisType": "time", + "xAxisProperty": "", "xAxisPropertyType": "timestamp", + "xAxisFormat": "", "xAxisFormatType": "auto", + "xmin": "", "xmax": "", + # Y-axis (msg.payload) + "yAxisLabel": y_axis_label, + "yAxisProperty": "payload", "yAxisPropertyType": "msg", + "ymin": "" if ymin is None else str(ymin), "ymax": "" if ymax is None else str(ymax), - "action": "append", "pointShape": "circle", "pointRadius": 2, - "showLegend": True, + # Data retention "removeOlder": str(remove_older), "removeOlderUnit": str(remove_older_unit), "removeOlderPoints": str(remove_older_points), - "colors": [], "textColor": [], "textColorDefault": True, - "width": str(width), "height": str(height), "className": "", + # Rendering + "action": "append", + "stackSeries": False, + "pointShape": "circle", "pointRadius": 4, + "showLegend": True, + "bins": 10, + # Colours (defaults — chart auto-cycles through these per series) + "colors": [ + "#0095FF", "#FF0000", "#FF7F0E", "#2CA02C", + "#A347E1", "#D62728", "#FF9896", "#9467BD", "#C5B0D5", + ], + "textColor": ["#666666"], "textColorDefault": True, + "gridColor": ["#e5e5e5"], "gridColorDefault": True, + # Editor layout + dimensions (NUMBERS, not strings) + "width": int(width), "height": int(height), "className": "", "x": x, "y": y, "wires": [[]], } diff --git a/examples/pumpingstation-3pumps-dashboard/flow.json b/examples/pumpingstation-3pumps-dashboard/flow.json index 955bd81..590ab77 100644 --- a/examples/pumpingstation-3pumps-dashboard/flow.json +++ b/examples/pumpingstation-3pumps-dashboard/flow.json @@ -3102,26 +3102,52 @@ "label": "Flow per pump (m\u00b3/h)", "order": 1, "chartType": "line", + "interpolation": "linear", "category": "topic", "categoryType": "msg", "xAxisLabel": "", "xAxisType": "time", - "xAxisTimeFormat": "auto", + "xAxisProperty": "", + "xAxisPropertyType": "timestamp", + "xAxisFormat": "", + "xAxisFormatType": "auto", + "xmin": "", + "xmax": "", "yAxisLabel": "", + "yAxisProperty": "payload", + "yAxisPropertyType": "msg", "ymin": "", "ymax": "", - "action": "append", - "pointShape": "circle", - "pointRadius": 2, - "showLegend": true, "removeOlder": "10", "removeOlderUnit": "60", "removeOlderPoints": "300", - "colors": [], - "textColor": [], + "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, - "width": "12", - "height": "8", + "gridColor": [ + "#e5e5e5" + ], + "gridColorDefault": true, + "width": 12, + "height": 8, "className": "", "x": 900, "y": 2320, @@ -3138,26 +3164,52 @@ "label": "Power per pump (kW)", "order": 1, "chartType": "line", + "interpolation": "linear", "category": "topic", "categoryType": "msg", "xAxisLabel": "", "xAxisType": "time", - "xAxisTimeFormat": "auto", + "xAxisProperty": "", + "xAxisPropertyType": "timestamp", + "xAxisFormat": "", + "xAxisFormatType": "auto", + "xmin": "", + "xmax": "", "yAxisLabel": "", + "yAxisProperty": "payload", + "yAxisPropertyType": "msg", "ymin": "", "ymax": "", - "action": "append", - "pointShape": "circle", - "pointRadius": 2, - "showLegend": true, "removeOlder": "10", "removeOlderUnit": "60", "removeOlderPoints": "300", - "colors": [], - "textColor": [], + "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, - "width": "12", - "height": "8", + "gridColor": [ + "#e5e5e5" + ], + "gridColorDefault": true, + "width": 12, + "height": 8, "className": "", "x": 900, "y": 2400, @@ -3174,26 +3226,52 @@ "label": "Flow per pump (m\u00b3/h)", "order": 1, "chartType": "line", + "interpolation": "linear", "category": "topic", "categoryType": "msg", "xAxisLabel": "", "xAxisType": "time", - "xAxisTimeFormat": "auto", + "xAxisProperty": "", + "xAxisPropertyType": "timestamp", + "xAxisFormat": "", + "xAxisFormatType": "auto", + "xmin": "", + "xmax": "", "yAxisLabel": "", + "yAxisProperty": "payload", + "yAxisPropertyType": "msg", "ymin": "", "ymax": "", - "action": "append", - "pointShape": "circle", - "pointRadius": 2, - "showLegend": true, "removeOlder": "60", "removeOlderUnit": "60", "removeOlderPoints": "1800", - "colors": [], - "textColor": [], + "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, - "width": "12", - "height": "8", + "gridColor": [ + "#e5e5e5" + ], + "gridColorDefault": true, + "width": 12, + "height": 8, "className": "", "x": 900, "y": 2480, @@ -3210,26 +3288,52 @@ "label": "Power per pump (kW)", "order": 1, "chartType": "line", + "interpolation": "linear", "category": "topic", "categoryType": "msg", "xAxisLabel": "", "xAxisType": "time", - "xAxisTimeFormat": "auto", + "xAxisProperty": "", + "xAxisPropertyType": "timestamp", + "xAxisFormat": "", + "xAxisFormatType": "auto", + "xmin": "", + "xmax": "", "yAxisLabel": "", + "yAxisProperty": "payload", + "yAxisPropertyType": "msg", "ymin": "", "ymax": "", - "action": "append", - "pointShape": "circle", - "pointRadius": 2, - "showLegend": true, "removeOlder": "60", "removeOlderUnit": "60", "removeOlderPoints": "1800", - "colors": [], - "textColor": [], + "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, - "width": "12", - "height": "8", + "gridColor": [ + "#e5e5e5" + ], + "gridColorDefault": true, + "width": 12, + "height": 8, "className": "", "x": 900, "y": 2560,