feat(dashboard): add basin fill gauge, countdown, and basin trend charts
Some checks failed
CI / lint-and-test (push) Has been cancelled
Some checks failed
CI / lint-and-test (push) Has been cancelled
PS control page now shows 7 fields instead of 5:
- Direction (filling/draining/steady)
- Basin level (m)
- Basin volume (m³)
- Fill level (%)
- Net flow (m³/h, signed)
- Time to full/empty (countdown in min or s)
- Inflow (m³/h)
Two new trend pages per time window (short 10 min / long 1 hour):
- Basin chart: 3 series (Basin fill %, Basin level m, Net flow m³/h)
on both Trends 10 min and Trends 1 hour pages.
PS formatter now extracts direction, netFlow, seconds from the delta-
compressed port 0 cache and computes fillPct from vol/maxVol. Dispatcher
sends 10 outputs (7 text + 3 trend numerics to both short+long basin
charts).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -611,14 +611,26 @@ def build_process_tab():
|
||||
"const vol = find('volume.predicted.');\n"
|
||||
"const qIn = find('flow.measured.upstream.') || find('flow.measured.in.');\n"
|
||||
"const qOut = find('flow.measured.downstream.') || find('flow.measured.out.');\n"
|
||||
"// Compute derived metrics\n"
|
||||
"const maxVol = 9.33; // must match basinVolume * basinHeight / basinHeight = basinVolume / surfaceArea * height\n"
|
||||
"const fillPct = vol != null ? Math.round(Number(vol) / maxVol * 100) : null;\n"
|
||||
"const netM3h = (c.netFlow != null) ? Number(c.netFlow) * 3600 : null;\n"
|
||||
"const seconds = (c.seconds != null && Number.isFinite(Number(c.seconds))) ? Number(c.seconds) : null;\n"
|
||||
"const timeStr = seconds != null ? (seconds > 60 ? Math.round(seconds/60) + ' min' : Math.round(seconds) + ' s') : 'n/a';\n"
|
||||
"msg.payload = {\n"
|
||||
" direction: c.direction || 'steady',\n"
|
||||
" level: lvl != null ? Number(lvl).toFixed(2) + ' m' : 'n/a',\n"
|
||||
" volume: vol != null ? Number(vol).toFixed(1) + ' m³' : 'n/a',\n"
|
||||
" fillPct: fillPct != null ? fillPct + '%' : 'n/a',\n"
|
||||
" netFlow: netM3h != null ? netM3h.toFixed(0) + ' m³/h' : 'n/a',\n"
|
||||
" timeLeft: timeStr,\n"
|
||||
" qIn: qIn != null ? (Number(qIn) * 3600).toFixed(0) + ' m³/h' : 'n/a',\n"
|
||||
" qOut: qOut != null ? (Number(qOut) * 3600).toFixed(0) + ' m³/h' : 'n/a',\n"
|
||||
" state: c.state || c.direction || 'idle',\n"
|
||||
" // Numerics for trends\n"
|
||||
" levelNum: lvl != null ? Number(lvl) : null,\n"
|
||||
" volumeNum: vol != null ? Number(vol) : null,\n"
|
||||
" fillPctNum: fillPct,\n"
|
||||
" netFlowNum: netM3h,\n"
|
||||
"};\n"
|
||||
"return msg;",
|
||||
outputs=1, wires=[["lout_evt_ps"]],
|
||||
@@ -721,8 +733,10 @@ def build_ui_tab():
|
||||
# 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),
|
||||
]
|
||||
|
||||
nodes.append(comment("c_ui_title", TAB_UI, LANE_X[2], 20,
|
||||
@@ -863,30 +877,56 @@ def build_ui_tab():
|
||||
CH_PS_EVT, source_out_ids=["lout_evt_ps"],
|
||||
downstream=["dispatch_ps"]
|
||||
))
|
||||
# PS dispatcher: 10 outputs — 7 text fields + 3 trend numerics
|
||||
nodes.append(function_node(
|
||||
"dispatch_ps", TAB_UI, LANE_X[1], y + 160,
|
||||
"dispatch PS",
|
||||
"const p = msg.payload || {};\n"
|
||||
"const ts = Date.now();\n"
|
||||
"return [\n"
|
||||
" {payload: String(p.state || 'idle')},\n"
|
||||
" {payload: String(p.direction || 'steady')},\n"
|
||||
" {payload: String(p.level || 'n/a')},\n"
|
||||
" {payload: String(p.volume || 'n/a')},\n"
|
||||
" {payload: String(p.fillPct || 'n/a')},\n"
|
||||
" {payload: String(p.netFlow || 'n/a')},\n"
|
||||
" {payload: String(p.timeLeft || 'n/a')},\n"
|
||||
" {payload: String(p.qIn || 'n/a')},\n"
|
||||
" {payload: String(p.qOut || 'n/a')},\n"
|
||||
" // Trend numerics\n"
|
||||
" p.fillPctNum != null ? {topic: 'Basin fill', payload: p.fillPctNum, timestamp: ts} : null,\n"
|
||||
" p.levelNum != null ? {topic: 'Basin level', payload: p.levelNum, timestamp: ts} : null,\n"
|
||||
" p.netFlowNum != null ? {topic: 'Net flow', payload: p.netFlowNum,timestamp: ts} : null,\n"
|
||||
"];",
|
||||
outputs=5,
|
||||
wires=[["ui_ps_state"], ["ui_ps_level"], ["ui_ps_volume"], ["ui_ps_qin"], ["ui_ps_qout"]],
|
||||
outputs=10,
|
||||
wires=[
|
||||
["ui_ps_direction"],
|
||||
["ui_ps_level"],
|
||||
["ui_ps_volume"],
|
||||
["ui_ps_fill"],
|
||||
["ui_ps_netflow"],
|
||||
["ui_ps_timeleft"],
|
||||
["ui_ps_qin"],
|
||||
# Trend outputs → both short + long charts
|
||||
["trend_short_basin", "trend_long_basin"], # fill %
|
||||
["trend_short_basin", "trend_long_basin"], # level
|
||||
["trend_short_basin", "trend_long_basin"], # net flow
|
||||
],
|
||||
))
|
||||
nodes.append(ui_text("ui_ps_state", TAB_UI, LANE_X[2], y + 160, g_ps,
|
||||
"PS state", "Basin state", "{{msg.payload}}"))
|
||||
|
||||
# PS text widgets
|
||||
nodes.append(ui_text("ui_ps_direction", TAB_UI, LANE_X[2], y + 160, g_ps,
|
||||
"PS direction", "Direction", "{{msg.payload}}"))
|
||||
nodes.append(ui_text("ui_ps_level", TAB_UI, LANE_X[2], y + 200, g_ps,
|
||||
"PS level", "Basin level", "{{msg.payload}}"))
|
||||
nodes.append(ui_text("ui_ps_volume", TAB_UI, LANE_X[2], y + 240, g_ps,
|
||||
"PS volume","Basin volume", "{{msg.payload}}"))
|
||||
nodes.append(ui_text("ui_ps_qin", TAB_UI, LANE_X[2], y + 280, g_ps,
|
||||
"PS volume", "Basin volume", "{{msg.payload}}"))
|
||||
nodes.append(ui_text("ui_ps_fill", TAB_UI, LANE_X[2], y + 280, g_ps,
|
||||
"PS fill %", "Fill level", "{{msg.payload}}"))
|
||||
nodes.append(ui_text("ui_ps_netflow", TAB_UI, LANE_X[2], y + 320, g_ps,
|
||||
"PS net flow", "Net flow", "{{msg.payload}}"))
|
||||
nodes.append(ui_text("ui_ps_timeleft", TAB_UI, LANE_X[2], y + 360, g_ps,
|
||||
"PS time left", "Time to full/empty", "{{msg.payload}}"))
|
||||
nodes.append(ui_text("ui_ps_qin", TAB_UI, LANE_X[2], y + 400, g_ps,
|
||||
"PS Qin", "Inflow", "{{msg.payload}}"))
|
||||
nodes.append(ui_text("ui_ps_qout", TAB_UI, LANE_X[2], y + 320, g_ps,
|
||||
"PS Qout", "Pumped out", "{{msg.payload}}"))
|
||||
|
||||
# ===== SECTION: Per-pump panels =====
|
||||
y_pumps_start = 1000
|
||||
@@ -1046,6 +1086,26 @@ def build_ui_tab():
|
||||
order=1,
|
||||
))
|
||||
|
||||
# ===== Basin charts (fill %, level, net flow) =====
|
||||
# Short-term
|
||||
nodes.append(ui_chart(
|
||||
"trend_short_basin", TAB_UI, LANE_X[3], y_charts + 360,
|
||||
"ui_grp_trend_short_basin",
|
||||
"Basin — 10 min", "Basin metrics",
|
||||
width=12, height=8,
|
||||
remove_older="10", remove_older_unit="60", remove_older_points="300",
|
||||
y_axis_label="", order=1,
|
||||
))
|
||||
# Long-term
|
||||
nodes.append(ui_chart(
|
||||
"trend_long_basin", TAB_UI, LANE_X[3], y_charts + 440,
|
||||
"ui_grp_trend_long_basin",
|
||||
"Basin — 1 hour", "Basin metrics",
|
||||
width=12, height=8,
|
||||
remove_older="60", remove_older_unit="60", remove_older_points="1800",
|
||||
y_axis_label="", order=1,
|
||||
))
|
||||
|
||||
return nodes
|
||||
|
||||
|
||||
|
||||
@@ -881,7 +881,7 @@
|
||||
"type": "function",
|
||||
"z": "tab_process",
|
||||
"name": "format PS port 0",
|
||||
"func": "const p = msg.payload || {};\nconst c = context.get('c') || {};\nObject.assign(c, p);\ncontext.set('c', c);\nfunction find(prefix) {\n for (const k in c) { if (k.indexOf(prefix) === 0) return c[k]; }\n return null;\n}\nconst lvl = find('level.predicted.');\nconst vol = find('volume.predicted.');\nconst qIn = find('flow.measured.upstream.') || find('flow.measured.in.');\nconst qOut = find('flow.measured.downstream.') || find('flow.measured.out.');\nmsg.payload = {\n level: lvl != null ? Number(lvl).toFixed(2) + ' m' : 'n/a',\n volume: vol != null ? Number(vol).toFixed(1) + ' m\u00b3' : 'n/a',\n qIn: qIn != null ? (Number(qIn) * 3600).toFixed(0) + ' m\u00b3/h' : 'n/a',\n qOut: qOut != null ? (Number(qOut) * 3600).toFixed(0) + ' m\u00b3/h' : 'n/a',\n state: c.state || c.direction || 'idle',\n levelNum: lvl != null ? Number(lvl) : null,\n volumeNum: vol != null ? Number(vol) : null,\n};\nreturn msg;",
|
||||
"func": "const p = msg.payload || {};\nconst c = context.get('c') || {};\nObject.assign(c, p);\ncontext.set('c', c);\nfunction find(prefix) {\n for (const k in c) { if (k.indexOf(prefix) === 0) return c[k]; }\n return null;\n}\nconst lvl = find('level.predicted.');\nconst vol = find('volume.predicted.');\nconst qIn = find('flow.measured.upstream.') || find('flow.measured.in.');\nconst qOut = find('flow.measured.downstream.') || find('flow.measured.out.');\n// Compute derived metrics\nconst maxVol = 9.33; // must match basinVolume * basinHeight / basinHeight = basinVolume / surfaceArea * height\nconst fillPct = vol != null ? Math.round(Number(vol) / maxVol * 100) : null;\nconst netM3h = (c.netFlow != null) ? Number(c.netFlow) * 3600 : null;\nconst seconds = (c.seconds != null && Number.isFinite(Number(c.seconds))) ? Number(c.seconds) : null;\nconst timeStr = seconds != null ? (seconds > 60 ? Math.round(seconds/60) + ' min' : Math.round(seconds) + ' s') : 'n/a';\nmsg.payload = {\n direction: c.direction || 'steady',\n level: lvl != null ? Number(lvl).toFixed(2) + ' m' : 'n/a',\n volume: vol != null ? Number(vol).toFixed(1) + ' m\u00b3' : 'n/a',\n fillPct: fillPct != null ? fillPct + '%' : 'n/a',\n netFlow: netM3h != null ? netM3h.toFixed(0) + ' m\u00b3/h' : 'n/a',\n timeLeft: timeStr,\n qIn: qIn != null ? (Number(qIn) * 3600).toFixed(0) + ' m\u00b3/h' : 'n/a',\n qOut: qOut != null ? (Number(qOut) * 3600).toFixed(0) + ' m\u00b3/h' : 'n/a',\n // Numerics for trends\n levelNum: lvl != null ? Number(lvl) : null,\n volumeNum: vol != null ? Number(vol) : null,\n fillPctNum: fillPct,\n netFlowNum: netM3h,\n};\nreturn msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
@@ -1343,6 +1343,20 @@
|
||||
"disabled": false,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"id": "ui_grp_trend_short_basin",
|
||||
"type": "ui-group",
|
||||
"name": "Basin (10 min)",
|
||||
"page": "ui_page_short_trends",
|
||||
"width": "12",
|
||||
"height": "1",
|
||||
"order": 3,
|
||||
"showTitle": true,
|
||||
"className": "",
|
||||
"groupType": "default",
|
||||
"disabled": false,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"id": "ui_grp_trend_long_flow",
|
||||
"type": "ui-group",
|
||||
@@ -1371,6 +1385,20 @@
|
||||
"disabled": false,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"id": "ui_grp_trend_long_basin",
|
||||
"type": "ui-group",
|
||||
"name": "Basin (1 hour)",
|
||||
"page": "ui_page_long_trends",
|
||||
"width": "12",
|
||||
"height": "1",
|
||||
"order": 3,
|
||||
"showTitle": true,
|
||||
"className": "",
|
||||
"groupType": "default",
|
||||
"disabled": false,
|
||||
"visible": true
|
||||
},
|
||||
{
|
||||
"id": "c_ui_title",
|
||||
"type": "comment",
|
||||
@@ -1829,8 +1857,8 @@
|
||||
"type": "function",
|
||||
"z": "tab_ui",
|
||||
"name": "dispatch PS",
|
||||
"func": "const p = msg.payload || {};\nreturn [\n {payload: String(p.state || 'idle')},\n {payload: String(p.level || 'n/a')},\n {payload: String(p.volume || 'n/a')},\n {payload: String(p.qIn || 'n/a')},\n {payload: String(p.qOut || 'n/a')},\n];",
|
||||
"outputs": 5,
|
||||
"func": "const p = msg.payload || {};\nconst ts = Date.now();\nreturn [\n {payload: String(p.direction || 'steady')},\n {payload: String(p.level || 'n/a')},\n {payload: String(p.volume || 'n/a')},\n {payload: String(p.fillPct || 'n/a')},\n {payload: String(p.netFlow || 'n/a')},\n {payload: String(p.timeLeft || 'n/a')},\n {payload: String(p.qIn || 'n/a')},\n // Trend numerics\n p.fillPctNum != null ? {topic: 'Basin fill', payload: p.fillPctNum, timestamp: ts} : null,\n p.levelNum != null ? {topic: 'Basin level', payload: p.levelNum, timestamp: ts} : null,\n p.netFlowNum != null ? {topic: 'Net flow', payload: p.netFlowNum,timestamp: ts} : null,\n];",
|
||||
"outputs": 10,
|
||||
"noerr": 0,
|
||||
"initialize": "",
|
||||
"finalize": "",
|
||||
@@ -1839,7 +1867,7 @@
|
||||
"y": 760,
|
||||
"wires": [
|
||||
[
|
||||
"ui_ps_state"
|
||||
"ui_ps_direction"
|
||||
],
|
||||
[
|
||||
"ui_ps_level"
|
||||
@@ -1847,24 +1875,42 @@
|
||||
[
|
||||
"ui_ps_volume"
|
||||
],
|
||||
[
|
||||
"ui_ps_fill"
|
||||
],
|
||||
[
|
||||
"ui_ps_netflow"
|
||||
],
|
||||
[
|
||||
"ui_ps_timeleft"
|
||||
],
|
||||
[
|
||||
"ui_ps_qin"
|
||||
],
|
||||
[
|
||||
"ui_ps_qout"
|
||||
"trend_short_basin",
|
||||
"trend_long_basin"
|
||||
],
|
||||
[
|
||||
"trend_short_basin",
|
||||
"trend_long_basin"
|
||||
],
|
||||
[
|
||||
"trend_short_basin",
|
||||
"trend_long_basin"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ui_ps_state",
|
||||
"id": "ui_ps_direction",
|
||||
"type": "ui-text",
|
||||
"z": "tab_ui",
|
||||
"group": "ui_grp_ps",
|
||||
"order": 1,
|
||||
"width": "0",
|
||||
"height": "0",
|
||||
"name": "PS state",
|
||||
"label": "Basin state",
|
||||
"name": "PS direction",
|
||||
"label": "Direction",
|
||||
"format": "{{msg.payload}}",
|
||||
"layout": "row-left",
|
||||
"style": false,
|
||||
@@ -1915,6 +1961,66 @@
|
||||
"y": 840,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "ui_ps_fill",
|
||||
"type": "ui-text",
|
||||
"z": "tab_ui",
|
||||
"group": "ui_grp_ps",
|
||||
"order": 1,
|
||||
"width": "0",
|
||||
"height": "0",
|
||||
"name": "PS fill %",
|
||||
"label": "Fill level",
|
||||
"format": "{{msg.payload}}",
|
||||
"layout": "row-left",
|
||||
"style": false,
|
||||
"font": "",
|
||||
"fontSize": 14,
|
||||
"color": "#000000",
|
||||
"x": 640,
|
||||
"y": 880,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "ui_ps_netflow",
|
||||
"type": "ui-text",
|
||||
"z": "tab_ui",
|
||||
"group": "ui_grp_ps",
|
||||
"order": 1,
|
||||
"width": "0",
|
||||
"height": "0",
|
||||
"name": "PS net flow",
|
||||
"label": "Net flow",
|
||||
"format": "{{msg.payload}}",
|
||||
"layout": "row-left",
|
||||
"style": false,
|
||||
"font": "",
|
||||
"fontSize": 14,
|
||||
"color": "#000000",
|
||||
"x": 640,
|
||||
"y": 920,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "ui_ps_timeleft",
|
||||
"type": "ui-text",
|
||||
"z": "tab_ui",
|
||||
"group": "ui_grp_ps",
|
||||
"order": 1,
|
||||
"width": "0",
|
||||
"height": "0",
|
||||
"name": "PS time left",
|
||||
"label": "Time to full/empty",
|
||||
"format": "{{msg.payload}}",
|
||||
"layout": "row-left",
|
||||
"style": false,
|
||||
"font": "",
|
||||
"fontSize": 14,
|
||||
"color": "#000000",
|
||||
"x": 640,
|
||||
"y": 960,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "ui_ps_qin",
|
||||
"type": "ui-text",
|
||||
@@ -1932,27 +2038,7 @@
|
||||
"fontSize": 14,
|
||||
"color": "#000000",
|
||||
"x": 640,
|
||||
"y": 880,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "ui_ps_qout",
|
||||
"type": "ui-text",
|
||||
"z": "tab_ui",
|
||||
"group": "ui_grp_ps",
|
||||
"order": 1,
|
||||
"width": "0",
|
||||
"height": "0",
|
||||
"name": "PS Qout",
|
||||
"label": "Pumped out",
|
||||
"format": "{{msg.payload}}",
|
||||
"layout": "row-left",
|
||||
"style": false,
|
||||
"font": "",
|
||||
"fontSize": 14,
|
||||
"color": "#000000",
|
||||
"x": 640,
|
||||
"y": 920,
|
||||
"y": 1000,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
@@ -3311,6 +3397,130 @@
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "trend_short_basin",
|
||||
"type": "ui-chart",
|
||||
"z": "tab_ui",
|
||||
"group": "ui_grp_trend_short_basin",
|
||||
"name": "Basin \u2014 10 min",
|
||||
"label": "Basin metrics",
|
||||
"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": "",
|
||||
"ymax": "",
|
||||
"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": 12,
|
||||
"height": 8,
|
||||
"className": "",
|
||||
"x": 900,
|
||||
"y": 2640,
|
||||
"wires": [
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "trend_long_basin",
|
||||
"type": "ui-chart",
|
||||
"z": "tab_ui",
|
||||
"group": "ui_grp_trend_long_basin",
|
||||
"name": "Basin \u2014 1 hour",
|
||||
"label": "Basin metrics",
|
||||
"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": "",
|
||||
"ymax": "",
|
||||
"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": 12,
|
||||
"height": 8,
|
||||
"className": "",
|
||||
"x": 900,
|
||||
"y": 2720,
|
||||
"wires": [
|
||||
[]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "tab_drivers",
|
||||
"type": "tab",
|
||||
|
||||
Reference in New Issue
Block a user