# Retaining chart.js data after browser refresh

**URL:** <https://discourse.nodered.org/t/retaining-chart-js-data-after-browser-refresh/92525>\
**Category:** Dashboard\
**Tags:** dashboard-2\
**Created:** [20 October 2024 11:43 UTC](https://discourse.nodered.org/t/retaining-chart-js-data-after-browser-refresh/92525 "2024-10-20T11:43:04Z")\
**Posts on this page:** 14\
**Page:** 1

<div class="post-metadata">

**Author:** ![Paul-Reed](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/paul-reed/32/66906_2.png) [@Paul-Reed](https://discourse.nodered.org/u/Paul-Reed)\
**Post date:** [20 October 2024 11:43 UTC](https://discourse.nodered.org/t/retaining-chart-js-data-after-browser-refresh/92525/1 "2024-10-20T11:43:04Z")

</div>

As the current dashboard-2 chart node lacks many formatting options, I'm trying to use a dashboard template node to create a line chart, and thereby use other chart.js options to make my charts display better.  
I don't know how far I'll get with this 😉, but I've had [great help from hotnipi](https://discourse.nodered.org/t/db2-css-change-chart-line-thickness/92248/12) to establish a basic format.  
First task was to try and ensure that the chart data wasn't lost upon a browser refresh, and I'm almost there, however hit a snag...  
I'm currently passing data into the template, but also into a function node where the datapoints are added to an array and saved to context. The plan is if a `ui-event` node detects a browser connect, then it triggers the function node to feed the array into the template node, restoring the chart data, all good so far...  
However I've hit a problem where when the next datapoint arrives, it clears the chart again 🥵

I've attached the flow, along with fake data feeds, and the problem can be seen by letting the flow firstly store a number of data points, then F5 refresh the browser (which clears the chart), and inject the 'Simulate browser connect' node, which restores the chart. But when the next datapoint arrives is is all wiped again!

Any suggestions please.

```auto
[{"id":"ea7c02fa77fe6efc","type":"ui-template","z":"1326aadbacf36704","group":"2c34b9c7d6324531","page":"","ui":"","name":"Custom Line Chart","order":12,"width":"6","height":"6","head":"","format":"<template>\n <div class=\"base-container\">\n <div class=\"chart-container\" ><canvas ref=\"chart\" /></div>\n </div> \n</template>\n\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>\n\n<script>\n export default {\n data() { \n return {\n keep:1000000, // keep data for 1000 seconds \n }\n },\n \n mounted() {\n this.$socket.on('msg-input:' + this.id, this.onInput)\n\n // code here when the component is first loaded\n let interval = setInterval(() => {\n if (window.Chart) {\n // the chart.js is loaded, so we can now use it\n clearInterval(interval);\n this.draw()\n }\n }, 100);\n },\n methods: {\n draw () {\n const ctx = this.$refs.chart\n const datasets = []\n \n // Render the chart\n const chart = new Chart(ctx, {\n \n type: 'line',\n data: {\n datasets: [\n {label: \"dataA\", dataA:[]},\n {label: \"dataB\", dataB:[]}\n ]\n },\n options: {\n maintainAspectRatio: false,//let the chart take the shape of the container\n animation: false,// do not animate\n responsive: true,// and please be responisve\n scales: {\n x: {\n type: 'time',//x-axis is configured to be time\n time: {\n unit: 'second',//time resolution second\n displayFormats: {\n second: 'HH:mm:ss' // render ticks in that format\n }\n },\n ticks: {\n stepSize: 1, // by default render gridlaine and tick for defined step (1 here means every second)\n autoSkip: false, // turn off automatic tick count calculations\n maxTicksLimit:10, //limit total ticks amount (overrides setpSize obviously)\n maxRotation:0 //do not allow ticks to be rotated\n }\n }\n },\n elements:{\n line:{\n borderWidth:1,//line thickness\n tension:0.3 //line curvature \n },\n point:{\n pointRadius:0 //remove points (any positive number makes points at defined size)\n }\n },\n parsing: {\n xAxisKey: 'time',\n yAxisKey: 'value'\n },\n plugins: {\n legend: {\n position: 'top',\n },\n title: {\n display: true,\n text: 'Chart.js Line Chart. Basic Options'\n }\n } \n },\n });\n // make this available to all elements of the component\n this.chart = chart\n console.log(this.data)\n },\n clearOldData(){\n this.chart.data.datasets.forEach(dataset => {\n dataset.data = dataset.data.filter(point => point.time > Date.now() - this.keep)\n })\n },\n clearChart(){\n this.chart.data.datasets.forEach(dataset => {\n dataset.data = []\n })\n this.chart.update()\n },\n\n addDataPoint(topic,valueOrFulldata){\n let dataset = this.chart.data.datasets.find(ds => ds.label == topic)\n if(typeof valueOrFulldata === \"number\"){\n dataset.data.push({\n time: (new Date()).getTime(),\n value: valueOrFulldata\n })\n } \n else{\n dataset.data.push({\n time: valueOrFulldata.time,\n value: valueOrFulldata.value\n })\n } \n \n this.chart.update()\n },\n onInput (msg) {\n if(Array.isArray(msg.payload)){\n if(msg.payload.length == 0){\n this.clearChart()\n return\n }\n else{\n // historycal data. for each item in array add the data to proper dataset\n // the data has format [{topic:\"first\",time:123456789,value:120}, and many more of such ...]\n msg.payload.forEach(point => {\n this.addDataPoint(point.topic,point)\n }) \n }\n }\n else{\n this.clearOldData()\n this.addDataPoint(msg.topic,msg.payload)\n }\n }\n }\n }\n</script>\n<style>\n.base-container{\n width:100%;\n height:100%;\n display: flex;\n justify-content: center;\n align-items: center;\n container: chat / size;/*make this container available for container querys*/\n}\n.chart-container{\n position: relative;\n margin: auto;\n height: 100cqb;/*use container query units to give full height of the container (100% of container height) */\n width: 100cqi;/*use container query units to give full width of the container (100% of container height)*/\n display: flex;\n justify-content: center;\n align-items: center;\n}\n \n</style>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":810,"y":1260,"wires":[[]]},{"id":"e12c2fa23f25087e","type":"inject","z":"1326aadbacf36704","name":"clear chart","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[]","payloadType":"json","x":620,"y":1220,"wires":[["ea7c02fa77fe6efc"]]},{"id":"9b20d06596172275","type":"function","z":"1326aadbacf36704","name":"store data points","func":"// Simulate a 'browser connect' message from the UI-event node\nif (msg.payload !== 'redraw') {\n let data = flow.get('chartData')\n \n if (!Array.isArray(data)) {\n data = []\n }\n \n // add latest data points to data array\n const newdata = {\n topic: msg.topic,\n time: msg.time,\n value: msg.payload\n }\n data.push(newdata)\n\n // Limit the size of the data array\n while (data.length > 50) {\n data.shift()\n }\n\n flow.set('chartData', data)\n} else {\n // Clear existing chart and send the complete array\n let data = flow.get('chartData')\n node.send({ payload: [] })\n node.send({ payload: data })\n}\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":610,"y":1320,"wires":[["ea7c02fa77fe6efc"]]},{"id":"c5964592afa50a4c","type":"inject","z":"1326aadbacf36704","name":"Simulate browser connect","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"redraw","payloadType":"str","x":190,"y":1320,"wires":[["9b20d06596172275"]]},{"id":"257ec1778e051d96","type":"inject","z":"1326aadbacf36704","name":"Every 15 seconds","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"15","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"str","x":170,"y":1260,"wires":[["c5ab23508895112e"]]},{"id":"c5ab23508895112e","type":"function","z":"1326aadbacf36704","name":"Create dummy data!","func":"const date = new Date()\nconst time = date.toISOString().replace(/\\.\\d{3}Z$/, '.5Z')\n\nconst dataA = {\n payload: Math.floor(Math.random() * 10) + 1,\n topic: 'dataA',\n time: time,\n }\n\nconst dataB = {\n payload: Math.floor(Math.random() * 5) + 2,\n topic: 'dataB',\n time: time\n}\n\nreturn [[ dataA, dataB] ]\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":400,"y":1260,"wires":[["ea7c02fa77fe6efc","9b20d06596172275"]]},{"id":"2c34b9c7d6324531","type":"ui-group","name":"Energy","page":"7294756f31e17b81","width":"6","height":"1","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"7294756f31e17b81","type":"ui-page","name":"Home","ui":"ae3d4aeb3f977a90","path":"/home","icon":"home","layout":"tabs","theme":"52ba8a01d6eda628","breakpoints":[{"name":"Default","px":"0","cols":"3"},{"name":"Tablet","px":"576","cols":"6"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":1,"className":"","visible":"true","disabled":"false"},{"id":"ae3d4aeb3f977a90","type":"ui-base","name":"Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"showPageTitle":true,"navigationStyle":"temporary","titleBarStyle":"default"},{"id":"52ba8a01d6eda628","type":"ui-theme","name":"Mobile","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"density":"compact","pagePadding":"5px","groupGap":"5px","groupBorderRadius":"10px","widgetGap":"5px"}}]

```

---

<div class="post-metadata">

**Author:** ![Steve-Mcl](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/steve-mcl/32/4826_2.png) [@Steve-Mcl](https://discourse.nodered.org/u/Steve-Mcl)\
**Post date:** [20 October 2024 15:41 UTC](https://discourse.nodered.org/t/retaining-chart-js-data-after-browser-refresh/92525/2 "2024-10-20T15:41:29Z")

</div>

Try this

```auto
[{"id":"ea7c02fa77fe6efc","type":"ui-template","z":"3567e03e18126502","group":"2c34b9c7d6324531","page":"","ui":"","name":"Custom Line Chart","order":1,"width":"6","height":"6","head":"","format":"<template>\n <div class=\"base-container\">\n <div class=\"chart-container\"><canvas ref=\"chart\" /></div>\n </div>\n</template>\n\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>\n\n<script>\n export default {\n datasets: [],\n data() {\n return {\n maxAge: 1000000, // max age of data to keep 1000 seconds, can be set by msg.maxAge\n keepCount: 50 // can be set by msg.keepCount\n }\n },\n computed: {\n maxKeepAge: function () {\n if (typeof this.maxAge === \"number\" && this.maxAge > 0) {\n return this.maxAge\n }\n return null\n },\n maxKeepCount: function () {\n if (typeof this.keepCount === \"number\" && this.keepCount > 0) {\n return this.keepCount\n }\n return null\n }\n },\n mounted() {\n this.$socket.on('msg-input:' + this.id, this.onInput)\n\n // code here when the component is first loaded\n let interval = setInterval(() => {\n if (window.Chart) {\n // the chart.js is loaded, so we can now use it\n clearInterval(interval);\n this.draw()\n }\n }, 100);\n },\n methods: {\n draw () {\n const ctx = this.$refs.chart\n const datasets = []\n\n // Render the chart\n const chart = new Chart(ctx, {\n\n type: 'line',\n data: {\n datasets: [\n {label: \"dataA\", data:[]},\n {label: \"dataB\", data:[]}\n ]\n },\n options: {\n maintainAspectRatio: false,//let the chart take the shape of the container\n animation: false,// do not animate\n responsive: true,// and please be responisve\n scales: {\n x: {\n type: 'time',//x-axis is configured to be time\n time: {\n unit: 'second',//time resolution second\n displayFormats: {\n second: 'HH:mm:ss' // render ticks in that format\n }\n },\n ticks: {\n stepSize: 1, // by default render gridlaine and tick for defined step (1 here means every second)\n autoSkip: false, // turn off automatic tick count calculations\n maxTicksLimit:10, //limit total ticks amount (overrides setpSize obviously)\n maxRotation:0 //do not allow ticks to be rotated\n }\n }\n },\n elements:{\n line:{\n borderWidth:1,//line thickness\n tension:0.3 //line curvature\n },\n point:{\n pointRadius:0 //remove points (any positive number makes points at defined size)\n }\n },\n parsing: {\n xAxisKey: 'time',\n yAxisKey: 'value'\n },\n plugins: {\n legend: {\n position: 'top',\n },\n title: {\n display: true,\n text: 'Chart.js Line Chart. Basic Options'\n }\n }\n },\n });\n // make this available to all elements of the component\n this.chart = chart\n this.updateChart()\n console.log(this.data)\n },\n clearOldData() {\n console.log('clearing old data')\n if (this.datasets?.length) {\n let countPerSet = this.maxKeepCount ? Math.ceil(this.keepCount / this.datasets.length) : 0\n this.datasets.forEach(dataset => {\n if (countPerSet && dataset.data.length > countPerSet) {\n dataset.data.splice(0, dataset.data.length - countPerSet)\n }\n if (this.maxKeepAge) {\n const minTime = Date.now() - this.maxAge\n dataset.data = dataset.data.filter(point => {\n const pointTime = (new Date(point.time)).valueOf()\n return pointTime > minTime\n })\n }\n })\n }\n },\n clearChart() {\n this.datasets = []\n this.updateChart()\n },\n addDataPoint(topic, data, doUpdate = true) {\n if (!topic || !data) { return }\n this.datasets = this.datasets || []\n let dataset = this.datasets.find(e => e.label === topic)\n if (!dataset) {\n dataset = { label: topic, data: [] }\n this.datasets.push(dataset)\n }\n if (typeof data === \"number\") {\n dataset.data.push({\n time: (new Date()).getTime(),\n value: data\n })\n } else {\n dataset.data.push({\n time: data.time,\n value: data.value\n })\n }\n this.updateChart()\n },\n updateChart() {\n if (this.chart) {\n this.chart.data.datasets = this.datasets\n this.chart.update()\n }\n },\n onInput (msg) {\n console.log('got msg:', msg)\n // sync keepCount - count of points to keep\n if (typeof msg.keepCount === \"number\") {\n this.keepCount = msg.keepCount\n // should probably call this.clearOldData() when a change is detected\n }\n // sync maxAge - oldest age of points to keep (in ms)\n if (typeof msg.maxAge === \"number\") {\n this.maxAge = msg.maxAge\n }\n if (Array.isArray(msg.payload)) {\n if (msg.payload.length == 0) {\n this.clearChart()\n return\n }\n if (msg.topic === 'reload') {\n this.clearChart()\n }\n // historical data. for each item in array add the data to proper dataset\n // the data has format [{topic:\"first\",time:123456789,value:120}, and many more of such ...]\n msg.payload.forEach(point => {\n this.addDataPoint(point.topic, point, false) // add without update\n })\n this.updateChart() // now they are added, call update\n } else {\n this.clearOldData()\n this.addDataPoint(msg.topic, msg.payload, true) // add and update\n }\n }\n }\n }\n</script>\n<style>\n.base-container {\n width: 100%;\n height: 100%;\n display: flex;\n justify-content: center;\n align-items: center;\n container: chat / size;\n /*make this container available for container querys*/\n}\n\n.chart-container {\n position: relative;\n margin: auto;\n height: 100cqb;\n /*use container query units to give full height of the container (100% of container height) */\n width: 100cqi;\n /*use container query units to give full width of the container (100% of container height)*/\n display: flex;\n justify-content: center;\n align-items: center;\n}\n</style>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":1270,"y":640,"wires":[[]]},{"id":"e12c2fa23f25087e","type":"inject","z":"3567e03e18126502","name":"clear chart","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[]","payloadType":"json","x":520,"y":520,"wires":[["fb4b4ec11c331f8b"]]},{"id":"9b20d06596172275","type":"function","z":"3567e03e18126502","name":"data cache","func":"// identify if this is a browser landing on page \"Home\"\n// if not, default to \"a new data point\"\nconst isPageChange = msg._client && msg.payload === \"change\" && msg.name === \"Home\"\nmsg.keepCount = 50\n\nif (isPageChange) {\n node.status({fill:\"yellow\",shape:\"ring\",text:\"Sending reload + cache\"});\n const data = flow.get('chartData') || []\n msg.topic = 'reload'\n msg.payload = data\n return msg\n}\nif (msg.topic === 'clear') {\n node.status({fill:\"yellow\",shape:\"ring\",text:\"Cleared cache\"});\n flow.set('chartData', [])\n msg.topic = 'reload'\n msg.payload = []\n return msg\n}\n\n// if we get here, then assume its \"a new data point\"\nlet data = flow.get('chartData')\nif (!Array.isArray(data)) {\n data = []\n}\n\n// add latest data points to data array\nconst newdata = {\n topic: msg.topic,\n time: msg.time,\n value: msg.payload\n}\ndata.push(newdata)\n\n// Limit the size of the data array\nwhile (data.length > msg.keepCount) {\n data = data.slice(data.length - msg.keepCount)\n}\n\nflow.set('chartData', data)\nnode.status({fill:\"green\",shape:\"ring\",text:\"Sending new data point\"});\nreturn msg\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1030,"y":640,"wires":[["ea7c02fa77fe6efc","e662ce12a8a3b32f"]]},{"id":"257ec1778e051d96","type":"inject","z":"3567e03e18126502","name":"Every 5 seconds","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"5","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"str","x":550,"y":640,"wires":[["c5ab23508895112e"]]},{"id":"c5ab23508895112e","type":"function","z":"3567e03e18126502","name":"Create dummy data!","func":"const date = new Date()\nconst time = date.toISOString().replace(/\\.\\d{3}Z$/, '.5Z')\n\nconst dataA = {\n payload: Math.floor(Math.random() * 10) + 1,\n topic: 'dataA',\n time: time,\n}\n\nconst dataB = {\n payload: Math.floor(Math.random() * 5) + 2,\n topic: 'dataB',\n time: time\n}\n\nreturn [[ dataA, dataB] ]\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":780,"y":640,"wires":[["9b20d06596172275"]]},{"id":"04526ca43ba5f66b","type":"ui-control","z":"3567e03e18126502","name":"","ui":"22ea43815413e748","events":"change","x":700,"y":760,"wires":[["bc086024e8bde1a1"]]},{"id":"530a22a0fa932d73","type":"inject","z":"3567e03e18126502","name":"Simulate browser page change to Home","props":[{"p":"payload"},{"p":"tab","v":"1","vt":"num"},{"p":"name","v":"Home","vt":"str"},{"p":"_client","v":"{}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"change","payloadType":"str","x":610,"y":700,"wires":[["bc086024e8bde1a1"]]},{"id":"e662ce12a8a3b32f","type":"debug","z":"3567e03e18126502","name":"debug 172","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1250,"y":700,"wires":[]},{"id":"ac7e15db347084b0","type":"inject","z":"3567e03e18126502","name":"clear cache","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"clear","payload":"[]","payloadType":"json","x":530,"y":580,"wires":[["4eb81b9c157a38da"]]},{"id":"fb4b4ec11c331f8b","type":"junction","z":"3567e03e18126502","x":1100,"y":520,"wires":[["ea7c02fa77fe6efc"]]},{"id":"4eb81b9c157a38da","type":"junction","z":"3567e03e18126502","x":880,"y":580,"wires":[["9b20d06596172275"]]},{"id":"bc086024e8bde1a1","type":"junction","z":"3567e03e18126502","x":880,"y":720,"wires":[["9b20d06596172275"]]},{"id":"2c34b9c7d6324531","type":"ui-group","name":"Energy","page":"7294756f31e17b81","width":"6","height":"1","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"22ea43815413e748","type":"ui-base","name":"base","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"showPageTitle":true,"navigationStyle":"fixed","titleBarStyle":"default"},{"id":"7294756f31e17b81","type":"ui-page","name":"Home","ui":"22ea43815413e748","path":"/home","icon":"home","layout":"tabs","theme":"52ba8a01d6eda628","breakpoints":[{"name":"Default","px":"0","cols":"3"},{"name":"Tablet","px":"576","cols":"6"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":2,"className":"","visible":"true","disabled":"false"},{"id":"52ba8a01d6eda628","type":"ui-theme","name":"Mobile","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"density":"compact","pagePadding":"5px","groupGap":"5px","groupBorderRadius":"10px","widgetGap":"5px"}}]

```

Part of the issue was attempting to add points to a chart that was not yet initialised.

A handy way of figuring things out like this is to add `console.log` statements to your code and then inspect/debug/pause in the browser console.

PS: I also added a `keepCount` (that and `maxAge`) they can also be set in the `msg` too so that data is pruned at client side based similar to how your cache in the function prunes by max point count

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [20 October 2024 15:43 UTC](https://discourse.nodered.org/t/retaining-chart-js-data-after-browser-refresh/92525/3 "2024-10-20T15:43:25Z")

</div>

You do create timestamps like this

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/4/8/483b10fcf87a2feb9c6b0af7d55ff4c1553b83fc.png)

Fine.

But the clearing the old data in ui\_template tries to compare numbers to figure out the old data.  
Numbers because it expects the timestamps to be numbers to do math which is not directly possible with any kind of time related strings.

See the `point.time > Date.now() - this.keep`

```auto
 clearOldData(){
                this.chart.data.datasets.forEach(dataset => {
                    dataset.data = dataset.data.filter(point => point.time > Date.now() - this.keep)
                })
            },

```

So even this comparing function or your time creation must be changed to make comparing possible.

---

<div class="post-metadata">

**Author:** ![Paul-Reed](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/paul-reed/32/66906_2.png) [@Paul-Reed](https://discourse.nodered.org/u/Paul-Reed)\
**Post date:** [20 October 2024 18:40 UTC](https://discourse.nodered.org/t/retaining-chart-js-data-after-browser-refresh/92525/4 "2024-10-20T18:40:23Z")

</div>

> [@hotNipi](#):
>
> Numbers because it expects the timestamps to be numbers to do math which is not directly possible with any kind of time related strings

Aah OK, the reason for the timestamp being in that format is that the data stream was formatted some years ago for a legacy system when I used a TIG stack, but I can change it.  
I prefer to work with epoch timestamps anyway.  
Thanks

---

<div class="post-metadata">

**Author:** ![Paul-Reed](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/paul-reed/32/66906_2.png) [@Paul-Reed](https://discourse.nodered.org/u/Paul-Reed)\
**Post date:** [20 October 2024 19:32 UTC](https://discourse.nodered.org/t/retaining-chart-js-data-after-browser-refresh/92525/5 "2024-10-20T19:32:15Z")

</div>

> [@Steve-Mcl](#):
>
> Try this

Thanks Steve.

> [@Steve-Mcl](#):
>
> ... **they can also be set in the msg too** so that data is pruned at client side based similar to how your cache in the function prunes by max point count

That's exactly the way I want to introduce chart formatting changes, such as line type (dashed, solid, dotted, etc), line colour and others enhancements, by setting them in the message.  
It appears that the datasets are created dynamically, so have to get my head around how to specify different styles to different datasets.

---

<div class="post-metadata">

**Author:** ![Kundepuu](https://avatars.discourse-cdn.com/v4/letter/k/bcef8e/32.png) [@Kundepuu](https://discourse.nodered.org/u/Kundepuu)\
**Post date:** [21 October 2024 04:15 UTC](https://discourse.nodered.org/t/retaining-chart-js-data-after-browser-refresh/92525/6 "2024-10-21T04:15:02Z")

</div>

[custom-period-selector-on-xychart](https://www.amcharts.com/docs/v5/tutorials/custom-period-selector-on-xychart/)  
How about like this way by buttons?  
Not chart.js, but Amcharts.

---

<div class="post-metadata">

**Author:** ![Paul-Reed](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/paul-reed/32/66906_2.png) [@Paul-Reed](https://discourse.nodered.org/u/Paul-Reed)\
**Post date:** [21 October 2024 08:14 UTC](https://discourse.nodered.org/t/retaining-chart-js-data-after-browser-refresh/92525/7 "2024-10-21T08:14:14Z")

</div>

> [@Kundepuu](#):
>
> Not chart.js, but Amcharts

There are lots of different charting packages, each with their own strengths & weaknesses, but for the sake of this topic, let's stay with `chart.js`.

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [21 October 2024 14:33 UTC](https://discourse.nodered.org/t/retaining-chart-js-data-after-browser-refresh/92525/8 "2024-10-21T14:33:11Z")

</div>

The chartjs leaves all fancy stuff like zooming to plugins. You can create one if you need or use already built plugins if they do what you wanted. Mostly they are overkill or don't fulfill your desires.  
There is zoom plugin but I didn't get any results with it.  
And I'm not going to write any.

But for zooming (as long within current data) the one can use the "Poor Man Three Click Zoom" which works on options thus doesn't need to be plugin.

```auto
[{"id":"d185e71dbf6af008","type":"ui-template","z":"9ab81d258b54a577","group":"1c6f457dfe15977b","page":"","ui":"","name":"Custom Line Chart","order":1,"width":"6","height":"6","head":"","format":"<template>\n <div class=\"base-container\">\n <div class=\"chart-container\" ><canvas ref=\"chart\" /></div>\n </div> \n</template>\n\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>\n\n\n<script>\n export default {\n data() { \n return {\n keep:1000000, // keep data for 1000 seconds\n title:\"Custom chart\"\n \n }\n },\n \n mounted() {\n this.$socket.on('msg-input:' + this.id, this.onInput)\n\n // code here when the component is first loaded\n let interval = setInterval(() => {\n if (window.Chart) {\n // the chart.js is loaded, so we can now use it\n clearInterval(interval);\n this.draw()\n }\n }, 100);\n },\n methods: {\n draw () {\n const ctx = this.$refs.chart\n const datasets = []\n const temporaryLimits = [] \n // Render the chart\n const chart = new Chart(ctx, {\n \n type: 'line',\n data: {\n datasets: [\n {label: \"dataA\", dataA:[]},\n {label: \"dataB\", dataB:[]}\n ]\n },\n options: { \n onClick: (e) => {\n switch (temporaryLimits.length){\n case 0:{\n const canvasPosition = getRelativePosition(e, chart);\n const dataX = chart.scales.x.getValueForPixel(canvasPosition.x); \n temporaryLimits.push(dataX)\n break\n }\n case 1:{\n const canvasPosition = getRelativePosition(e, chart);\n const dataX = chart.scales.x.getValueForPixel(canvasPosition.x);\n temporaryLimits.push(dataX)\n temporaryLimits.sort((a, b) => a - b); \n chart.options.scales.x.min = temporaryLimits[0]\n chart.options.scales.x.max = temporaryLimits[1]\n chart.options.plugins.title.text = this.title +\" * zoomed in\" \n this.chart.update()\n break\n }\n case 2:{\n temporaryLimits.length = 0\n chart.options.scales.x.min = undefined\n chart.options.scales.x.max = undefined\n chart.options.plugins.title.text = this.title\n this.chart.update()\n break\n }\n\n }\n \n },\n maintainAspectRatio: false,//let the chart take the shape of the container\n animation: false,// do not animate\n responsive: true,// and please be responisve\n scales: {\n x: {\n type: 'time',//x-axis is configured to be time\n time: {\n unit: 'second',//time resolution second\n displayFormats: {\n second: 'HH:mm:ss' // render ticks in that format\n }\n },\n ticks: {\n stepSize: 1, // by default render gridlaine and tick for defined step (1 here means every second)\n autoSkip: false, // turn off automatic tick count calculations\n maxTicksLimit:10, //limit total ticks amount (overrides setpSize obviously)\n maxRotation:0 //do not allow ticks to be rotated\n }\n }\n },\n elements:{\n line:{\n borderWidth:1,//line thickness\n tension:0.3 //line curvature \n },\n point:{\n pointRadius:0 //remove points (any positive number makes points at defined size)\n }\n },\n parsing: {\n xAxisKey: 'time',\n yAxisKey: 'value'\n },\n plugins: {\n legend: {\n position: 'top',\n },\n title: {\n display: true,\n text: this.title\n }\n } \n },\n });\n // make this available to all elements of the component\n this.chart = chart \n },\n clearOldData(){\n this.chart?.data?.datasets.forEach(dataset => {\n dataset.data = dataset.data.filter(point => point.time > Date.now() - this.keep)\n })\n },\n clearChart(){\n this.chart?.data?.datasets.forEach(dataset => {\n dataset.data = []\n })\n this.chart.update()\n },\n\n addDataPoint(topic,valueOrFulldata){\n let dataset = this.chart?.data?.datasets.find(ds => ds.label == topic)\n if(typeof valueOrFulldata === \"number\"){\n dataset.data.push({\n time: (new Date()).getTime(),\n value: valueOrFulldata\n })\n } \n else{\n dataset.data.push({\n time: valueOrFulldata.time,\n value: valueOrFulldata.value\n })\n } \n \n this.chart.update()\n },\n onInput (msg) {\n if(Array.isArray(msg.payload)){\n if(msg.payload.length == 0){\n this.clearChart()\n return\n }\n else{\n // historycal data. for each item in array add the data to proper dataset\n // the data has format [{topic:\"first\",time:123456789,value:120}, and many more of such ...]\n msg.payload.forEach(point => {\n this.addDataPoint(point.topic,point)\n }) \n }\n }\n else{\n this.clearOldData()\n this.addDataPoint(msg.topic,msg.payload)\n }\n }\n }\n }\n</script>\n<style>\n.base-container{\n width:100%;\n height:100%;\n display: flex;\n justify-content: center;\n align-items: center;\n container: chat / size;/*make this container available for container querys*/\n}\n.chart-container{\n position: relative;\n margin: auto;\n height: 100cqb;/*use container query units to give full height of the container (100% of container height) */\n width: 100cqi;/*use container query units to give full width of the container (100% of container height)*/\n display: flex;\n justify-content: center;\n align-items: center;\n}\n \n</style>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":950,"y":1160,"wires":[[]]},{"id":"cb6afc5505802345","type":"inject","z":"9ab81d258b54a577","name":"clear chart","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[]","payloadType":"json","x":700,"y":1120,"wires":[["d185e71dbf6af008"]]},{"id":"add41d2d13967c9c","type":"function","z":"9ab81d258b54a577","name":"store data points","func":"// Simulate a 'browser connect' message from the UI-event node\nif (msg.payload !== 'redraw') {\n let data = flow.get('chartData')\n \n if (!Array.isArray(data)) {\n data = []\n }\n \n // add latest data points to data array\n const newdata = {\n topic: msg.topic,\n time: msg.time,\n value: msg.payload\n }\n data.push(newdata)\n\n // Limit the size of the data array\n while (data.length > 50) {\n data.shift()\n }\n\n flow.set('chartData', data)\n} else {\n // Clear existing chart and send the complete array\n let data = flow.get('chartData')\n node.send({ payload: [] })\n node.send({ payload: data })\n}\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":690,"y":1220,"wires":[["d185e71dbf6af008"]]},{"id":"8c72de4c76b53858","type":"inject","z":"9ab81d258b54a577","name":"Simulate browser connect","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"redraw","payloadType":"str","x":270,"y":1220,"wires":[["add41d2d13967c9c"]]},{"id":"dca7cb685805cc93","type":"inject","z":"9ab81d258b54a577","name":"Every 15 seconds","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"3","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"str","x":250,"y":1160,"wires":[["ce575d8959f48057"]]},{"id":"ce575d8959f48057","type":"function","z":"9ab81d258b54a577","name":"Create dummy data!","func":"const date = new Date()\nconst time = Date.now()//date.toISOString().replace(/\\.\\d{3}Z$/, '.5Z')\n\nconst dataA = {\n payload: Math.floor(Math.random() * 10) + 1,\n topic: 'dataA',\n time: time,\n }\n\nconst dataB = {\n payload: Math.floor(Math.random() * 5) + 2,\n topic: 'dataB',\n time: time\n}\n\nreturn [[ dataA, dataB] ]\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":1160,"wires":[["d185e71dbf6af008","add41d2d13967c9c"]]},{"id":"1c6f457dfe15977b","type":"ui-group","name":"Custom Bar Chart","page":"d0621b8f20aee671","width":"6","height":"1","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"d0621b8f20aee671","type":"ui-page","name":"Charts","ui":"29792df7d7b05e2e","path":"/charts","icon":"home","layout":"notebook","theme":"5075a7d8e4947586","order":2,"className":"","visible":"true","disabled":"false"},{"id":"29792df7d7b05e2e","type":"ui-base","name":"My Dashboard","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"showPageTitle":true,"navigationStyle":"default","titleBarStyle":"default"},{"id":"5075a7d8e4947586","type":"ui-theme","name":"Default Theme","colors":{"surface":"#ffffff","primary":"#0094CE","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}]

```

---

<div class="post-metadata">

**Author:** ![Paul-Reed](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/paul-reed/32/66906_2.png) [@Paul-Reed](https://discourse.nodered.org/u/Paul-Reed)\
**Post date:** [21 October 2024 19:32 UTC](https://discourse.nodered.org/t/retaining-chart-js-data-after-browser-refresh/92525/9 "2024-10-21T19:32:04Z")

</div>

Thanks, I've added that into the flow, and also changed the timestamp format to a epoch.  
This is the flow I'm using -

```auto
[{"id":"6c017fa405ce6b6e","type":"ui-template","z":"1326aadbacf36704","group":"2c34b9c7d6324531","page":"","ui":"","name":"Custom Line Chart","order":11,"width":"6","height":"6","head":"","format":"<template>\n <div class=\"base-container\">\n <div class=\"chart-container\"><canvas ref=\"chart\" /></div>\n </div>\n</template>\n\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js\"></script>\n\n<script>\n export default {\n datasets: [],\n data() {\n return {\n maxAge: 1000000, // max age of data to keep 1000 seconds, can be set by msg.maxAge\n keepCount: 50 // can be set by msg.keepCount\n }\n },\n computed: {\n maxKeepAge: function () {\n if (typeof this.maxAge === \"number\" && this.maxAge > 0) {\n return this.maxAge\n }\n return null\n },\n maxKeepCount: function () {\n if (typeof this.keepCount === \"number\" && this.keepCount > 0) {\n return this.keepCount\n }\n return null\n }\n },\n mounted() {\n this.$socket.on('msg-input:' + this.id, this.onInput)\n\n // code here when the component is first loaded\n let interval = setInterval(() => {\n if (window.Chart) {\n // the chart.js is loaded, so we can now use it\n clearInterval(interval);\n this.draw()\n }\n }, 100);\n },\n methods: {\n draw () {\n const ctx = this.$refs.chart\n const datasets = []\n const temporaryLimits = []\n\n // Render the chart\n const chart = new Chart(ctx, {\n\n type: 'line',\n data: {\n datasets: [\n {label: \"dataA\", data:[]},\n {label: \"dataB\", data:[]}\n ]\n },\n options: {\n onClick: (e) => {\n switch (temporaryLimits.length){\n case 0:{\n const canvasPosition = getRelativePosition(e, chart);\n const dataX = chart.scales.x.getValueForPixel(canvasPosition.x); \n temporaryLimits.push(dataX)\n break\n }\n case 1:{\n const canvasPosition = getRelativePosition(e, chart);\n const dataX = chart.scales.x.getValueForPixel(canvasPosition.x);\n temporaryLimits.push(dataX)\n temporaryLimits.sort((a, b) => a - b); \n chart.options.scales.x.min = temporaryLimits[0]\n chart.options.scales.x.max = temporaryLimits[1]\n chart.options.plugins.title.text = \" * zoomed in *\" \n this.chart.update()\n break\n }\n case 2:{\n temporaryLimits.length = 0\n chart.options.scales.x.min = undefined\n chart.options.scales.x.max = undefined\n chart.options.plugins.title.text = this.title\n this.chart.update()\n break\n }\n\n }\n \n },\n maintainAspectRatio: false,//let the chart take the shape of the container\n animation: false,// do not animate\n responsive: true,// and please be responisve\n scales: {\n x: {\n type: 'time',//x-axis is configured to be time\n time: {\n unit: 'second',//time resolution second\n displayFormats: {\n second: 'HH:mm:ss' // render ticks in that format\n }\n },\n ticks: {\n stepSize: 1, // by default render gridlaine and tick for defined step (1 here means every second)\n autoSkip: false, // turn off automatic tick count calculations\n maxTicksLimit:10, //limit total ticks amount (overrides setpSize obviously)\n maxRotation:0 //do not allow ticks to be rotated\n }\n }\n },\n elements:{\n line:{\n borderWidth:1,//line thickness\n tension:0.3 //line curvature\n },\n point:{\n pointRadius:0 //remove points (any positive number makes points at defined size)\n }\n },\n parsing: {\n xAxisKey: 'time',\n yAxisKey: 'value'\n },\n plugins: {\n legend: {\n position: 'top',\n },\n title: {\n display: true,\n text: 'Line chart test'\n }\n }\n },\n });\n // make this available to all elements of the component\n this.chart = chart\n this.updateChart()\n console.log(this.data)\n },\n clearOldData() {\n console.log('clearing old data')\n if (this.datasets?.length) {\n let countPerSet = this.maxKeepCount ? Math.ceil(this.keepCount / this.datasets.length) : 0\n this.datasets.forEach(dataset => {\n if (countPerSet && dataset.data.length > countPerSet) {\n dataset.data.splice(0, dataset.data.length - countPerSet)\n }\n if (this.maxKeepAge) {\n const minTime = Date.now() - this.maxAge\n dataset.data = dataset.data.filter(point => {\n const pointTime = (new Date(point.time)).valueOf()\n return pointTime > minTime\n })\n }\n })\n }\n },\n clearChart() {\n this.datasets = []\n this.updateChart()\n },\n addDataPoint(topic, data, doUpdate = true) {\n if (!topic || !data) { return }\n this.datasets = this.datasets || []\n let dataset = this.datasets.find(e => e.label === topic)\n if (!dataset) {\n dataset = { label: topic, data: [] }\n this.datasets.push(dataset)\n }\n if (typeof data === \"number\") {\n dataset.data.push({\n time: (new Date()).getTime(),\n value: data\n })\n } else {\n dataset.data.push({\n time: data.time,\n value: data.value\n })\n }\n this.updateChart()\n },\n updateChart() {\n if (this.chart) {\n this.chart.data.datasets = this.datasets\n this.chart.update()\n }\n },\n onInput (msg) {\n console.log('got msg:', msg)\n // sync keepCount - count of points to keep\n if (typeof msg.keepCount === \"number\") {\n this.keepCount = msg.keepCount\n // should probably call this.clearOldData() when a change is detected\n }\n // sync maxAge - oldest age of points to keep (in ms)\n if (typeof msg.maxAge === \"number\") {\n this.maxAge = msg.maxAge\n }\n if (Array.isArray(msg.payload)) {\n if (msg.payload.length == 0) {\n this.clearChart()\n return\n }\n if (msg.topic === 'reload') {\n this.clearChart()\n }\n // historical data. for each item in array add the data to proper dataset\n // the data has format [{topic:\"first\",time:123456789,value:120}, and many more of such ...]\n msg.payload.forEach(point => {\n this.addDataPoint(point.topic, point, false) // add without update\n })\n this.updateChart() // now they are added, call update\n } else {\n this.clearOldData()\n this.addDataPoint(msg.topic, msg.payload, true) // add and update\n }\n }\n }\n }\n</script>\n<style>\n.base-container {\n width: 100%;\n height: 100%;\n display: flex;\n justify-content: center;\n align-items: center;\n container: chat / size;\n /*make this container available for container querys*/\n}\n\n.chart-container {\n position: relative;\n margin: auto;\n height: 100cqb;\n /*use container query units to give full height of the container (100% of container height) */\n width: 100cqi;\n /*use container query units to give full width of the container (100% of container height)*/\n display: flex;\n justify-content: center;\n align-items: center;\n}\n</style>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":910,"y":1500,"wires":[[]]},{"id":"3f0db9aa605aad98","type":"inject","z":"1326aadbacf36704","name":"clear chart","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[]","payloadType":"json","x":160,"y":1380,"wires":[["6005ba1dd76acd77"]]},{"id":"87a3e6c101ac7cba","type":"function","z":"1326aadbacf36704","name":"data cache","func":"// identify if this is a browser landing on page \"Home\"\n// if not, default to \"a new data point\"\nconst isPageChange = msg._client && msg.payload === \"change\" && msg.name === \"Home\"\n\nif (isPageChange) {\n node.status({fill:\"yellow\",shape:\"ring\",text:\"Sending reload + cache\"});\n const data = flow.get('chartData') || []\n msg.topic = 'reload'\n msg.payload = data\n return msg\n}\nif (msg.topic === 'clear') {\n node.status({fill:\"yellow\",shape:\"ring\",text:\"Cleared cache\"});\n flow.set('chartData', [])\n msg.topic = 'reload'\n msg.payload = []\n return msg\n}\n\n// if we get here, then assume its \"a new data point\"\nlet data = flow.get('chartData')\nif (!Array.isArray(data)) {\n data = []\n}\n\n// add latest data points to data array\nconst newdata = {\n topic: msg.topic,\n time: msg.time,\n value: msg.payload\n}\ndata.push(newdata)\n\n// Limit the size of the data array\nwhile (data.length > msg.keepCount) {\n data = data.slice(data.length - msg.keepCount)\n}\n\nflow.set('chartData', data)\nnode.status({fill:\"green\",shape:\"ring\",text:\"Sending new data point\"});\nreturn msg\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":670,"y":1500,"wires":[["6c017fa405ce6b6e","c8a34e14a1583959"]]},{"id":"fa2f59aa9d5b5909","type":"inject","z":"1326aadbacf36704","name":"Every 5 seconds","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"5","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"str","x":190,"y":1500,"wires":[["3a8db11f7faeb9bf"]]},{"id":"3a8db11f7faeb9bf","type":"function","z":"1326aadbacf36704","name":"Create dummy data!","func":"const date = new Date()\nconst time = (new Date()).getTime()\n\nconst dataA = {\n payload: Math.floor(Math.random() * 10) + 1,\n topic: 'dataA',\n time: time,\n keepCount: 50\n}\n\nconst dataB = {\n payload: Math.floor(Math.random() * 5) + 2,\n topic: 'dataB',\n time: time,\n keepCount: 50\n}\n\nreturn [[ dataA, dataB] ]\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":420,"y":1500,"wires":[["87a3e6c101ac7cba"]]},{"id":"dbcb4d92e2577637","type":"ui-control","z":"1326aadbacf36704","name":"","ui":"ae3d4aeb3f977a90","events":"change","x":340,"y":1620,"wires":[["beda919d9504c788"]]},{"id":"ff4cb573a004472b","type":"inject","z":"1326aadbacf36704","name":"Simulate browser page change to Home","props":[{"p":"payload"},{"p":"tab","v":"1","vt":"num"},{"p":"name","v":"Home","vt":"str"},{"p":"_client","v":"{}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"change","payloadType":"str","x":250,"y":1560,"wires":[["beda919d9504c788"]]},{"id":"c8a34e14a1583959","type":"debug","z":"1326aadbacf36704","name":"debug 172","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":890,"y":1560,"wires":[]},{"id":"d2cfcde862d92ba3","type":"inject","z":"1326aadbacf36704","name":"clear cache","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"clear","payload":"[]","payloadType":"json","x":170,"y":1440,"wires":[["75ee8d2ec5b27239"]]},{"id":"6005ba1dd76acd77","type":"junction","z":"1326aadbacf36704","x":740,"y":1380,"wires":[["6c017fa405ce6b6e"]]},{"id":"75ee8d2ec5b27239","type":"junction","z":"1326aadbacf36704","x":520,"y":1440,"wires":[["87a3e6c101ac7cba"]]},{"id":"beda919d9504c788","type":"junction","z":"1326aadbacf36704","x":520,"y":1580,"wires":[["87a3e6c101ac7cba"]]},{"id":"2c34b9c7d6324531","type":"ui-group","name":"Energy","page":"7294756f31e17b81","width":"6","height":"1","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"ae3d4aeb3f977a90","type":"ui-base","name":"Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"showPageTitle":true,"navigationStyle":"temporary","titleBarStyle":"default"},{"id":"7294756f31e17b81","type":"ui-page","name":"Home","ui":"ae3d4aeb3f977a90","path":"/home","icon":"home","layout":"tabs","theme":"52ba8a01d6eda628","breakpoints":[{"name":"Default","px":"0","cols":"3"},{"name":"Tablet","px":"576","cols":"6"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":1,"className":"","visible":"true","disabled":"false"},{"id":"52ba8a01d6eda628","type":"ui-theme","name":"Mobile","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"density":"compact","pagePadding":"5px","groupGap":"5px","groupBorderRadius":"10px","widgetGap":"5px"}}]

```

I'm currently trying to implement more basic features, such as specifying line color for individual datasets.  
I can change both line colors together, for example to red;

```auto
 elements:{
         line:{
         borderWidth:1,//line thickness
         tension:0.3, //line curvature
         borderColor: 'red',
               },
               point:{
               pointRadius:0 //remove points
               }
            },

```

But I can't see how to change line colors individually, for example one dataset red, and the other blue.

Is it something like -

```auto
if (this.datasets = "dataA") {
    borderColor: 'red',
    } else if (this.datasets = "dataB") {
    borderColor: 'blue',
    }

```

(It doesn't work!)

---

<div class="post-metadata">

**Author:** ![Steve-Mcl](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/steve-mcl/32/4826_2.png) [@Steve-Mcl](https://discourse.nodered.org/u/Steve-Mcl)\
**Post date:** [21 October 2024 19:49 UTC](https://discourse.nodered.org/t/retaining-chart-js-data-after-browser-refresh/92525/10 "2024-10-21T19:49:09Z")

</div>

> [@Paul-Reed](#):
>
> `if (this.datasets = "dataA") {`

That will set `this.datasets` (single `=` is assignment)

What you probably want is a look up or send a color in a control message. Something that is controlled like sending a msg without a payload or a specific topic that you can use to set the specific property in the specified dataset. The chartjs has decent docs showing where the property goes.

Something like (untested, written on my phone)

```auto
const ds = this.datasets?.find(e => e.label === msg.label)
if (ds) {
  ds.borderColor = msg.borderColor ?? ds.borderColor
}

```

---

<div class="post-metadata">

**Author:** ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)\
**Post date:** [21 October 2024 19:57 UTC](https://discourse.nodered.org/t/retaining-chart-js-data-after-browser-refresh/92525/11 "2024-10-21T19:57:03Z")

</div>

I think at this point you'll need to make clear which kind of chart widget you are creating.

1. If the colors always need to be tied with the topic and you know how many different topics (== lines) the chart will show, there's no need to make dynamic datasets.

2. If you are creating universal thing - the widget level config should have some kind of mapping which you can use to bind color with topic.

And I'd guess you aren't happy with default behaviour where color is figured out by chart (kind of random, color for topic is not guaranteed)

---

<div class="post-metadata">

**Author:** ![Paul-Reed](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/paul-reed/32/66906_2.png) [@Paul-Reed](https://discourse.nodered.org/u/Paul-Reed)\
**Post date:** [21 October 2024 20:33 UTC](https://discourse.nodered.org/t/retaining-chart-js-data-after-browser-refresh/92525/12 "2024-10-21T20:33:35Z")

</div>

> [@hotNipi](#):
>
> And I'd guess you aren't happy with default behaviour where color is figured out by chart

I'm using line color as an example, because I will then be able to add/change other characteristics, (although yes, I do wish to control the colors - see the screenshot below, dataB can hardly be seen).

> [@hotNipi](#):
>
> If you are creating universal thing

No, not universal (above my paygrade!!), although I want to make the options easy for users to configure.

> [@hotNipi](#):
>
> there's no need to make dynamic datasets

I'm only using it because that was the example flow that you wrote, and which worked well.

 ![Screenshot_20241022-122439](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/0/2/02ea65a633c4006b5aaeabf8c32b6da3f6ac54d4.png)

---

<div class="post-metadata">

**Author:** ![Paul-Reed](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/paul-reed/32/66906_2.png) [@Paul-Reed](https://discourse.nodered.org/u/Paul-Reed)\
**Post date:** [21 October 2024 20:39 UTC](https://discourse.nodered.org/t/retaining-chart-js-data-after-browser-refresh/92525/13 "2024-10-21T20:39:11Z")

</div>

> [@Steve-Mcl](#):
>
> What you probably want is a look up or send a color in a control message

Yes I'm hoping to, and I'll look further at your code tomorrow, it's given me a headache trying to understand it (now where has my 'javascript for dummies' book gone 😉)

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/1X/d073cd938eafa2e558d7c2cd59003b3ef4963033.png) [@system](https://discourse.nodered.org/u/system)\
**Post date:** [20 November 2024 20:39 UTC](https://discourse.nodered.org/t/retaining-chart-js-data-after-browser-refresh/92525/14 "2024-11-20T20:39:39Z")

</div>

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