DB V2 - bar chart to track daily number of starts?

Glad it’s working for you!

msg.payload is undefined but there are other items that are defined and used in the flow. If you change the output of your debug to “complete msg object” you will see the other data.

You can send the data as msg.payload (or any other name) and make the appropriate changes in this section of the function node where the incoming data is used.

This is system wide so yes, it will be used by all flows. Here is the documentation on context stores ( Working with context : Node-RED )

1 Like

You're a star @rakgupta thanks so very much!

1 Like

Dear @rakgupta
One final follow-up if you have a minute?
We have been looking for a way to add persistence between reboots to any chart with any data so that we suddenly have that with this chart from your function made me think :wink:

Is there any chance that this flow could be adapted/partially repurposed to do just that, and only that?
Basically make any V2 chart persistent between reboots if you have context storage enabled?
I looked at your code and while I am no coder it looks like some of your code could be used to do this. A comment if you have time and interest would be appreciated. Thanks again!

I think the only thing that the flow is doing is storing the chart data as a flow variable. and the persistence comes from the context data storage section in settings.js.

You could try adding a change node to the output of the chart node and storing the _datapoint object into a flow variable.

That way, you are not modifying any existing flows but you would need to add logic to send that data back in case of a restart (since it is not actually the raw data that creates the chart). The other option is to modify existing flows to take a similar approach (and store the raw data that creates the chart in flow variables).

1 Like

Thanks for this @rakgupta , it is presently well above my skillset to figure that out, but will have it in mind.

Two smaller things that came up if you have time:

  1. Is there a way to have the today count also as a text field with this function?
    As it changes every day chartData[0].starts chartData[1].starts etc
    I can so far not easily use a change node.
    Or maybe change it so that today is always the same number?

  2. Is there a payload to delete only the first object when we get to thirty days
    to keep only 30 days of tabs?
    Many thanks again!

See if this works for you. I added in a new property (msg.todayCount) that is the output of the function node. However, it assumes that there is only one machine (could probably be enhanced to do multiple machines)

[{"id":"f93aa849d2691722","type":"function","z":"d84b176e12749a08","name":"Get Chart Data","func":"// --- Retrieve or initialize stored data ---\nlet chartData = flow.get('chartdata') || [];\n\n// Optional reset\nif (msg.reset || msg.topic === 'reset') {\n    chartData = [];\n}\n\n// Extract incoming message\nconst date = String(msg.date);\nconst machine = String(msg.topic);  // machine name\nconst count = Number(msg.count) || 0;\n\n//Track today's starts\nlet todayCount = '';\n\n// --- Update or insert record ---\nlet found = false;\nfor (let row of chartData) {\n    if (row.date === date && row.machine === machine) {\n        row.starts += count;  // accumulate count\n        todayCount = row.starts.toString(); // todya's count\n        found = true;\n        break;\n    }\n}\n\nif (!found) {\n    chartData.push({ date: date, machine: machine, starts: count });\n    todayCount = count.toString(); // todya's count\n}\n\n// Save updated data in flow\nflow.set('chartdata', chartData);\n\n// --- Output flat array for Dashboard 2 ---\nmsg.payload = chartData;\nmsg.todayCount = todayCount;\n\n// Optional debug\nmsg.chartData = chartData;\n\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is started.\nvar count = flow.get('count1');","finalize":"","libs":[],"x":808.0000915527344,"y":291.00000286102295,"wires":[["c7d0f3708a5187da","d66e4f7c17fc116d"]]}]

Bit confused by this - do you mean 30 days of bars? If so, you could have an inject node reset the flow variables (like in the clear data ). See if the following works for you - the Cronplus node will send msg.reset = true (could be anything - it just needs to trigger the change node) at midnight on the first day of the month and clear the previous month’s chart (and the regular flow will build it back)

[{"id":"9f23ac06dfb8bc57","type":"cronplus","z":"d84b176e12749a08","name":"Fist of every month","outputField":"reset","timeZone":"","storeName":"","commandResponseMsgOutput":"output1","defaultLocation":"","defaultLocationType":"default","outputs":1,"options":[{"name":"schedule1","topic":"topic1","payloadType":"bool","payload":"true","expressionType":"cron","expression":"0 0 1 * *","location":"","offset":"0","solarType":"all","solarEvents":"sunrise,sunset"}],"x":927.6582336425781,"y":500.96502685546875,"wires":[["9829b8e750d335a0"]]},{"id":"9829b8e750d335a0","type":"change","z":"d84b176e12749a08","name":"Delete Last Month","rules":[{"t":"delete","p":"chartdata","pt":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":1144.5403747558594,"y":500.82720947265625,"wires":[[]]},{"id":"c95b5fe6662fa2b1","type":"global-config","env":[],"modules":{"node-red-contrib-cron-plus":"2.1.0"}}]

Hopefully this works for you.

Thanks for this! Will try the new function ASAP!

I was unclear on the thirty day bar count. Apologies for that,
I meant when it gets to 30 days it should delete the oldest bar daily, keeping the bar count at 30 from there on. So always 30 bars in the chart when it reaches that number.
Am I making sense?

Yeah - so 30 days running totals… this will require some more thought. Give me a day or so to figure it out.

1 Like

Okidoki. All ears!
The first function you gave for a new property (msg.todayCount) appears to be working great so far.
Fab!

Try this flow - I tested this with a test of 5 bars. On the 6th, it drops the first one and add the last one. You can change it to 30 to keep a running set of 30 days data.

[{"id":"cdcb31eb2f25ce22","type":"inject","z":"d84b176e12749a08","name":"Machine 1 Day 1","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 1","x":204.83456420898438,"y":2749.591796875,"wires":[["a778b74aafec4062"]]},{"id":"a778b74aafec4062","type":"change","z":"d84b176e12749a08","name":"Day 1","rules":[{"t":"set","p":"date","pt":"msg","to":"$moment(timestamp).tz(\"America/Chicago\").format(\"MM-DD\")","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":418.1286926269531,"y":2793.477828979492,"wires":[["bb29bd8a8c4f8516"]]},{"id":"d0cd9e4acb65a21f","type":"inject","z":"d84b176e12749a08","name":"Machine 2 Day 1","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 2","x":204.83456420898438,"y":2790.536651611328,"wires":[["a778b74aafec4062"]]},{"id":"525c671130d800e0","type":"inject","z":"d84b176e12749a08","name":"Machine 3 Day 1","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 3","x":204.83456420898438,"y":2836.536651611328,"wires":[["a778b74aafec4062"]]},{"id":"bb29bd8a8c4f8516","type":"junction","z":"d84b176e12749a08","x":577.5800737738609,"y":3163.905901223421,"wires":[["f93aa849d2691722","95529f58c0531a26"]]},{"id":"dd40a0c12802d59f","type":"change","z":"d84b176e12749a08","name":"Day 2","rules":[{"t":"set","p":"date","pt":"msg","to":"$moment(timestamp).tz(\"America/Chicago\").add(1, \"days\").format(\"MM-DD\")","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":418.1286926269531,"y":2926.3638610839844,"wires":[["bb29bd8a8c4f8516"]]},{"id":"dcc2741547a1f307","type":"change","z":"d84b176e12749a08","name":"Day 3","rules":[{"t":"set","p":"date","pt":"msg","to":"$moment(timestamp).tz(\"America/Chicago\").add(2, \"days\").format(\"MM-DD\")","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":418.1286926269531,"y":3065.3638439178467,"wires":[["bb29bd8a8c4f8516"]]},{"id":"d623e86676f4793e","type":"change","z":"d84b176e12749a08","name":"Day 4","rules":[{"t":"set","p":"date","pt":"msg","to":"$moment(timestamp).tz(\"America/Chicago\").add(3, \"days\").format(\"MM-DD\")","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":404.8345642089844,"y":3224.591796875,"wires":[["bb29bd8a8c4f8516"]]},{"id":"7c0e25b754dea6bf","type":"change","z":"d84b176e12749a08","name":"Day 5","rules":[{"t":"set","p":"date","pt":"msg","to":"$moment(timestamp).tz(\"America/Chicago\").add(4, \"days\").format(\"MM-DD\")","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":410.8345642089844,"y":3377.591796875,"wires":[["bb29bd8a8c4f8516"]]},{"id":"613f4487bb1aca73","type":"change","z":"d84b176e12749a08","name":"Day 6","rules":[{"t":"set","p":"date","pt":"msg","to":"$moment(timestamp).tz(\"America/Chicago\").add(6, \"days\").format(\"MM-DD\")","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":413.8345642089844,"y":3532.47412109375,"wires":[["bb29bd8a8c4f8516"]]},{"id":"f93aa849d2691722","type":"function","z":"d84b176e12749a08","d":true,"name":"Get Chart Data","func":"// --- Retrieve or initialize stored data ---\nlet chartData = flow.get('chartdata') || [];\n\n// Optional reset\nif (msg.reset || msg.topic === 'reset') {\n    chartData = [];\n}\n\n// Extract incoming message\nconst date = String(msg.date);\nconst machine = String(msg.topic);  // machine name\nconst count = Number(msg.count) || 0;\n\n//Track today's starts\nlet todayCount = '';\n\n// --- Update or insert record ---\nlet found = false;\nfor (let row of chartData) {\n    if (row.date === date && row.machine === machine) {\n        row.starts += count;  // accumulate count\n        todayCount = row.starts.toString(); // todya's count\n        found = true;\n        break;\n    }\n}\n\nif (!found) {\n    chartData.push({ date: date, machine: machine, starts: count });\n    todayCount = count.toString(); // todya's count\n}\n\n// Save updated data in flow\nflow.set('chartdata', chartData);\n\n// --- Output flat array for Dashboard 2 ---\nmsg.payload = chartData;\nmsg.todayCount = todayCount;\n\n// Optional debug\nmsg.chartData = chartData;\n\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is started.\nvar count = flow.get('count1');","finalize":"","libs":[],"x":715.0001525878906,"y":3094.591796875,"wires":[["c7d0f3708a5187da","d66e4f7c17fc116d"]]},{"id":"95529f58c0531a26","type":"function","z":"d84b176e12749a08","name":"Get Chart Data 30 days","func":"// --- Retrieve or initialize stored data ---\nlet chartData = flow.get('chartdata') || [];\n\n// Optional reset\nif (msg.reset || msg.topic === 'reset') {\n    chartData = [];\n}\n\n// Extract incoming message\nconst date = String(msg.date);\nconst machine = String(msg.topic);  // machine name\nconst count = Number(msg.count) || 0;\n\n// Track today's starts\nlet todayCount = '';\n\n// --- Update or insert record ---\nlet found = false;\nfor (let row of chartData) {\n    if (row.date === date && row.machine === machine) {\n        row.starts += count;  // accumulate count\n        todayCount = row.machine + ': ' + row.starts.toString(); // today's count\n        found = true;\n        break;\n    }\n}\n\nif (!found) {\n    if (chartData.length >= 5) {    // Change 5 to 30 - this is for testing\n        const firstRow = chartData.shift(); // drop the first element\n    }\n    chartData.push({ date: date, machine: machine, starts: count });\n    todayCount = machine + ': ' + count.toString(); // today's count\n}\n\n// Save updated data in flow\nflow.set('chartdata', chartData);\n\n// --- Output flat array for Dashboard 2 ---\nmsg.payload = chartData;\nmsg.todayCount = todayCount;\n\n// Optional debug\nmsg.chartData = chartData;\n\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is started.\nvar count = flow.get('count1');","finalize":"","libs":[],"x":770.8346252441406,"y":3164.4189453125,"wires":[["d66e4f7c17fc116d"]]},{"id":"407676baf1fbb4af","type":"inject","z":"d84b176e12749a08","name":"Machine 1 (Day 2)","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 1","x":214.83456420898438,"y":2887.477847099304,"wires":[["dd40a0c12802d59f"]]},{"id":"a2fe8cb06e81a09b","type":"inject","z":"d84b176e12749a08","name":"Machine 2 (Day 2)","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 2","x":214.83456420898438,"y":2925.536651611328,"wires":[["dd40a0c12802d59f"]]},{"id":"92e078556e16bb49","type":"inject","z":"d84b176e12749a08","name":"Machine 3 (Day 2)","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 2","x":214.83456420898438,"y":2961.536651611328,"wires":[["dd40a0c12802d59f"]]},{"id":"e027ea0db2f5ae23","type":"inject","z":"d84b176e12749a08","name":"Machine 1 (Day 3)","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 1","x":214.83456420898438,"y":3022.536651611328,"wires":[["dcc2741547a1f307"]]},{"id":"dfe39f75f1fddffe","type":"inject","z":"d84b176e12749a08","name":"Machine 2 (Day 3)","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 2","x":214.83456420898438,"y":3063.5954599380493,"wires":[["dcc2741547a1f307"]]},{"id":"8fb8c001a9735507","type":"inject","z":"d84b176e12749a08","name":"Machine 3 (Day 3)","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 2","x":214.83456420898438,"y":3105.595458984375,"wires":[["dcc2741547a1f307"]]},{"id":"aac28d3dbe0e5e26","type":"inject","z":"d84b176e12749a08","name":"Machine 1 (Day 4)","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 1","x":201.54043579101562,"y":3181.7646045684814,"wires":[["d623e86676f4793e"]]},{"id":"5e36c4db93fbf364","type":"inject","z":"d84b176e12749a08","name":"Machine 2 (Day 4)","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 2","x":201.54043579101562,"y":3222.8234128952026,"wires":[["d623e86676f4793e"]]},{"id":"7214f2169084fb07","type":"inject","z":"d84b176e12749a08","name":"Machine 3 (Day 4)","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 2","x":201.54043579101562,"y":3264.8234119415283,"wires":[["d623e86676f4793e"]]},{"id":"2c1227c4e88d1970","type":"inject","z":"d84b176e12749a08","name":"Machine 1 (Day 5)","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 1","x":207.54043579101562,"y":3334.7646045684814,"wires":[["7c0e25b754dea6bf"]]},{"id":"ef5839bcadc33f45","type":"inject","z":"d84b176e12749a08","name":"Machine 2 (Day 5)","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 2","x":207.54043579101562,"y":3375.8234128952026,"wires":[["7c0e25b754dea6bf"]]},{"id":"3d018c74499001d6","type":"inject","z":"d84b176e12749a08","name":"Machine 3 (Day 5)","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 2","x":207.54043579101562,"y":3417.8234119415283,"wires":[["7c0e25b754dea6bf"]]},{"id":"b040c9eb15d938aa","type":"inject","z":"d84b176e12749a08","name":"Machine 1 (Day 6)","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 1","x":210.54043579101562,"y":3489.6469287872314,"wires":[["613f4487bb1aca73"]]},{"id":"1e5207b2bc9c8aa4","type":"inject","z":"d84b176e12749a08","name":"Machine 2 (Day 6)","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 2","x":210.54043579101562,"y":3530.7057371139526,"wires":[["613f4487bb1aca73"]]},{"id":"35fa29c5bc437878","type":"inject","z":"d84b176e12749a08","name":"Machine 3 (Day 6)","props":[{"p":"timestamp","v":"","vt":"date"},{"p":"topic","vt":"str"},{"p":"count","v":"10","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Machine 2","x":210.54043579101562,"y":3572.7057361602783,"wires":[["613f4487bb1aca73"]]},{"id":"c7d0f3708a5187da","type":"debug","z":"d84b176e12749a08","name":"Debug Chart Data","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":714.6546936035156,"y":3041.75537109375,"wires":[]},{"id":"d66e4f7c17fc116d","type":"ui-chart","z":"d84b176e12749a08","group":"fd6045295841c382","name":"Count STarts","label":"Machine Starts","order":3,"chartType":"bar","category":"machine","categoryType":"property","xAxisLabel":"Date","xAxisProperty":"date","xAxisPropertyType":"property","xAxisType":"category","xAxisFormat":"","xAxisFormatType":"auto","xmin":"","xmax":"","yAxisLabel":"Starts","yAxisProperty":"starts","yAxisPropertyType":"property","ymin":"","ymax":"","bins":"","action":"replace","stackSeries":false,"pointShape":"circle","pointRadius":4,"showLegend":true,"removeOlder":1,"removeOlderUnit":"3600","removeOlderPoints":"","colors":["#0095ff","#ff0000","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"textColor":["#ffffff"],"textColorDefault":false,"gridColor":["#e5e5e5"],"gridColorDefault":true,"width":6,"height":8,"className":"","interpolation":"linear","x":999.1286888122559,"y":3162.477783203125,"wires":[["f4c7cc94749700f8"]]},{"id":"1fc05c88906e8a89","type":"change","z":"d84b176e12749a08","name":"Delete Flow Variables","rules":[{"t":"delete","p":"oldCount","pt":"flow"},{"t":"delete","p":"oldDate","pt":"flow"},{"t":"delete","p":"chartdata","pt":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":951.6582336425781,"y":2965.7294921875,"wires":[["d66e4f7c17fc116d"]]},{"id":"f4c7cc94749700f8","type":"debug","z":"d84b176e12749a08","name":"debug 23","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1167.6433410644531,"y":3160.748046875,"wires":[]},{"id":"dcd1f46bcbe83c0f","type":"inject","z":"d84b176e12749a08","name":"Clear Data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[]","payloadType":"json","x":750.7170677185059,"y":2966.00732421875,"wires":[["1fc05c88906e8a89"]]},{"id":"fd6045295841c382","type":"ui-group","name":"db2 bar wip share","page":"ccc312f7c9d7a917","width":"30","height":1,"order":1,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"ccc312f7c9d7a917","type":"ui-page","name":"db2 bar wip share","ui":"de5759a313e7ad79","path":"/page26","icon":" ","layout":"tabs","theme":"682b37bffc90cac5","breakpoints":[{"name":"Default","px":"0","cols":"9"},{"name":"Tablet","px":"576","cols":"9"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":23,"className":"","visible":"true","disabled":"false"},{"id":"de5759a313e7ad79","type":"ui-base","name":"Node-RED Dashboard DB2","path":"/dashboard","appIcon":"","includeClientData":false,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"headerContent":"dashboard","navigationStyle":"fixed","titleBarStyle":"default","showReconnectNotification":true,"notificationDisplayTime":5,"showDisconnectNotification":true,"allowInstall":true},{"id":"682b37bffc90cac5","type":"ui-theme","name":"Rakesh Dark","colors":{"surface":"#097479","primary":"#337278","bgPage":"#000000","groupBg":"#000000","groupOutline":"#337278"},"sizes":{"density":"comfortable","pagePadding":"6px","groupGap":"5px","groupBorderRadius":"1px","widgetGap":"6px"}},{"id":"fe38d4a254586020","type":"global-config","env":[],"modules":{"@flowfuse/node-red-dashboard":"1.25.0"}}]

Here is where you need to change it to 30…

if (!found) {
    if (chartData.length >= 5) {    // Change 5 to 30 - this is for testing
        const firstRow = chartData.shift(); // drop the first element
    }
    chartData.push({ date: date, machine: machine, starts: count });
    todayCount = machine + ': ' + count.toString(); // today's count
}

I also added in the Machine name to the today’s count so it should take into account different machines.

UPDATE: This does not work for multiple machines on the same chart (it will work for a single machine).

If you had used a database, a query like this would retrieve the most recent 30 days' data to feed to the chart at intervals:

WITH recent_days AS (
    SELECT DISTINCT DATE(timestamp) AS day
    FROM your_table_name
    ORDER BY day DESC
    LIMIT 30
)
SELECT DATE(t.timestamp) AS day, COUNT(*) AS count
FROM your_table_name t
JOIN recent_days d ON DATE(t.timestamp) = d.day
GROUP BY day
ORDER BY day ASC;

No need for code to make context data survive reboots & deploys.
No need for appending new context data elements and deleting them after 30 days.

Admittedly it does require sqlite to be installed and records inserted when a machine is restarted.

@houser Here is a function node that works for many machines and the last 5 days (change 5 to 30 for your use case). I can’t take credit for this as I asked ChartGPT to modify it for me :wink:

[{"id":"69b29ba88009ad51","type":"function","z":"d84b176e12749a08","name":"30 days multi machine","func":"// --- Retrieve or initialize stored data ---\nlet chartData = flow.get('chartdata') || [];\n\n// Optional reset\nif (msg.reset || msg.topic === 'reset') {\n    chartData = [];\n}\n\n// Extract incoming message\nconst date = String(msg.date);         // e.g. \"2025-08-24\"\nconst machine = String(msg.topic);     // e.g. \"Machine 1\"\nconst count = Number(msg.count) || 0;\n\nlet todayCount = '';\n\n// --- Update or insert record ---\nlet found = false;\nfor (let row of chartData) {\n    if (row.date === date && row.machine === machine) {\n        row.starts += count;  // accumulate count\n        todayCount = row.machine + ': ' + row.starts.toString();\n        found = true;\n        break;\n    }\n}\n\nif (!found) {\n    // Add a new row\n    chartData.push({ date: date, machine: machine, starts: count });\n    todayCount = machine + ': ' + count.toString();\n}\n\n// --- Ensure only last 5 unique dates are kept ---\nlet uniqueDates = [...new Set(chartData.map(r => r.date))].sort(); // oldest to newest\nif (uniqueDates.length > 5) {\n    const oldestDate = uniqueDates[0];\n    // Remove all rows with the oldest date\n    chartData = chartData.filter(r => r.date !== oldestDate);\n}\n\n// Save updated data in flow\nflow.set('chartdata', chartData);\n\n// --- Output flat array for Dashboard 2 ---\nmsg.payload = chartData;\nmsg.todayCount = todayCount;\n\n// Optional debug\nmsg.chartData = chartData;\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":760.6544494628906,"y":3266.965087890625,"wires":[["d66e4f7c17fc116d"]]}]

Posting as flow code (just the one node)

Here is where to change 5 to 30

// --- Ensure only last 5 unique dates are kept ---
let uniqueDates = [...new Set(chartData.map(r => r.date))].sort(); // oldest to newest
if (uniqueDates.length > 5) {
    const oldestDate = uniqueDates[0];
    // Remove all rows with the oldest date
    chartData = chartData.filter(r => r.date !== oldestDate);
}

I implemented this straight away on my main install.
I guess I will know in a month if it works :wink:
A billion thanks for all this! Will try to shut up now :wink:

1 Like

Dear @rakgupta,

The dropping of days also appears to work great. Thanks!

I am worried I am wearing out my welcome, but I have a few questions if/when you have time? Mostly maintenance. If not I will survive :wink:

  1. If we want to use more than one of this excellent function in an install, what values do we need to change in the function to not duplicate the context-files and unwillingly connect the multiple functions to each other via the context flows?
  2. Is it possible to get the todayCount for each machine?
  3. Is there a payload that only deletes one machine, and if not, can we delete specific context flowflies from disk, and if so how do we identify them?
  4. I am working on another copy of this function that basically does the same thing but with counting operating time for the machine. It works. I am now using a node that gives me the milliseconds for each run, which I can convert to minutes or seconds and this works as-is with your function, but can we change the function to give us mins, secs In the bar charts? If possible that would be nice.

These are stored as “flow” variables (specific to a single flow/tab), so as long as the function is used in different flows/tabs, it should not be duplicated.

I thought I had added that in the last version that I sent? Maybe I forgot to indicate that it was included. Can you check?

Are you plotting different machines on the same chart or single machine per chart? If multiple per chart, then it will get a bit tricky. I don’t know of a way to delete it from disk. The one thing to be careful about is if there are multiple flows on the same tab - I believe they share the same “flow” context.

[
    {
        "id": "f366e64f4add3510",
        "type": "function",
        "z": "c1b9d37f.b6443",
        "g": "e478940ab1892d2c",
        "name": "Calculate Uptime",
        "func": "//Copy attributes to variables\n\nvar currentTime =Date.now();\n//var currentTime = msg.currentTime*1000;\n//var lastRestart = msg.lastRestart*1000;\n\nvar lastRestart = msg.payload.value;\n\n//node.warn(\"currentTime=\"+currentTime);\n//node.warn(\"lastRestarrt=\"+lastRestart);\n\n\n\n//Conversion factors\nvar millisInDays = 86400000;\nvar millisInHrs =3600000;\nvar millisInMins = 60000;\nvar millisInSecs = 1000;\n\n//Text for days, hours, minutes and seconds\nvar daysText ='d';\nvar hrsText ='h';\nvar minText ='m';\nvar secText ='s';\n\n//Calc differene in milliseconds\nvar ut = (currentTime) - (lastRestart);\n//node.warn(\"ut=\"+ut);\nvar utBalance = ut;\n\n//Calculate days, hous, months and seconds\nvar days = Math.trunc((utBalance/(millisInDays)));\nutBalance = utBalance - (days*millisInDays);\nvar utBalanceD = utBalance;\n\nvar hrs = Math.trunc(utBalance/(millisInHrs));\nutBalance = utBalance - (hrs*millisInHrs);\nvar utBalanceH = utBalance;\n\nvar min = Math.trunc(utBalance/(millisInMins));\nutBalance = utBalance - (min*millisInMins);\nvar utBalanceM = utBalance;\n\nvar sec = Math.trunc(utBalance/(millisInSecs));\n\n\nmsg.days = days;\nmsg.hrs = hrs;\nmsg.min = min;\nmsg.sec = sec;\n\n\nif (days !=1) {\n    daysText = 'days';\n    } else {\n    daysText = 'day';\n}\n\nif (hrs !=1) {\n    hrsText = 'hours';\n    } else {\n    hrsText = 'hour';\n}\n\nif (min !=1) {\n    minText = 'minutes';\n    } else {\n    minText = 'minute';\n}\n\nif (sec !=1) {\n    secText = 'seconds';\n    } else {\n    secText = 'second';\n}\n\nmsg.payload = null;\nmsg.payload = days+\" \"+daysText+\", \"+hrs+\" \"+hrsText+\", \"+min+\" \"+minText+\", \"+sec+\" \"+secText;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1055.6666412353516,
        "y": 1498.3333740234375,
        "wires": [
            [
                "73ab0136.77ba08"
            ]
        ]
    }
]

Above is a function I use to calculate uptime. Maybe it can be adapted for your requirements?

Hope this helps.

1 Like

It all helps, thanks @rakgupta !
I did look for multiple todayCount and as far as I can tell there is just the one.
Edit: Apologies, it is there if I switch on the different names, I got confused.

I will work with all this.
I was also unaware of the fact about shared flow on same tabs. Very useful info.

Yes - the output is just one since it is triggered by a single “start”. However, the output contains the name of the machine and the count.

You can modify the output (eg. turn it in todayCount.machne, todayCount.count) etc. and then use it the way you want to.

1 Like

Understood!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.