DB2 CSS - Change chart line thickness

Anyone know the CSS class to change a Dashboard 2 line chart - line thickness please?

Working from memory here but I'm pretty sure the graph is drawn inside a canvas. The canvas element is a low-level graphics API that operates on pixels. That's a decision out of our control. They changed this in chartjs.

If you remember there was no CSS involved even for DB1 Charts. Not much changed. As even the top layer of options is not supported for DB2 Chart node the only way is ui_template and build it as you like. Luckily with examples provided :slight_smile:

I was hoping to avoid that, but thanks both anyway.

I did a quick one for you with most basic options shown and commented. Obviously you have much higher needs :smiley: So modify and show us the outcome.

[{"id":"ea7c02fa77fe6efc","type":"ui-template","z":"6bf3e03fb58d61b1","group":"1c6f457dfe15977b","page":"","ui":"","name":"Custom Line Chart","order":1,"width":"6","height":"5","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        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: \"DATA\",\n                            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:2,//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            },\n            onInput (msg) {\n                this.chart.data.datasets[0].data.push({\n                    time: (new Date()).getTime(),\n                    value: msg.payload\n                }) \n                this.chart.update()      \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":530,"y":1300,"wires":[[]]},{"id":"caff24894c090d95","type":"ui-slider","z":"6bf3e03fb58d61b1","group":"1c6f457dfe15977b","name":"Slider 1","label":"Slider 1","tooltip":"","order":2,"width":0,"height":0,"passthru":false,"outs":"all","topic":"slider-1","topicType":"str","thumbLabel":"true","showTicks":"always","min":0,"max":10,"step":1,"className":"","color":"","colorTrack":"","colorThumb":"","x":360,"y":1300,"wires":[["ea7c02fa77fe6efc"]]},{"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":"538ceff32af2078f","path":"/charts","icon":"home","layout":"notebook","theme":"5075a7d8e4947586","order":3,"className":"","visible":"true","disabled":"false"},{"id":"538ceff32af2078f","type":"ui-base","name":"My Dashboard","path":"/dashboard","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"}}]
1 Like

Thanks @hotNipi - It's a chart formatted for viewing on a phone and the 'default ui-chart' node lines are too thick - look as though they have been drown with a felt tip marker pen!
I assume they are at the line width used for charts being displayed on a monitor - not a phone.

I've adjusted your flow to draw them at 1px instead, and they look much better, crisp & clean, however the reason that I didn't want to 'go down this road' was the other problems which I've hit previously when using a template node and never resolved, and are also present in your flow.

  1. After a refresh or first connection the chart is always blank, and does not load previous datapoints, so no historical data.

  2. I have 2 feeds into the chart (different topic names), and the template version draws every datapoint regardless of the topic, on one line, instead of two.

  3. There appears to be no limit to the number of datapoints displayed in the chart. ie not limited by for example previous hour, or 1000 points.

Paul

None of those points were mentioned before but of course very valid.
To make fully working chart on top of the library defaults it takes quite of many requirements which land on territory of "user wishes". Those can vary a lot. Can't be made as to support arbitrarily all of possible use cases so I don't find it very practical thing to do just for fun. It's a quite lot of coding.

But it is not impossible task. Except as for 1.st point it takes to keep the historical data at server side and feed it to the chart on page load. That is because of the ui_template can't reach the data storage. (That may have been changed but then I definitely haven't seen it)

More datasets and the limiting is just a bit of coding. Not that complicated at all.

EDIT:
Modified so that you can define multiple lines. Still sort of dedicated buildup cos for wider usage it should recognize incoming topics on fly an add series and stuff. I din't have time enough to go that deep.

And it doesn't contain example of sending historical data but has support and instructions.
Thus untested as you can imagine :smiley:

[{"id":"ea7c02fa77fe6efc","type":"ui-template","z":"6bf3e03fb58d61b1","group":"1c6f457dfe15977b","page":"","ui":"","name":"Custom Line Chart","order":1,"width":"6","height":"5","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:10000,  //keep data for 10 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: \"first\", data:[]},\n                            {label: \"second\",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:2,//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: value\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.addSinglePoint(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":650,"y":1200,"wires":[[]]},{"id":"caff24894c090d95","type":"ui-slider","z":"6bf3e03fb58d61b1","group":"1c6f457dfe15977b","name":"Slider 1","label":"Slider 1","tooltip":"","order":2,"width":0,"height":0,"passthru":false,"outs":"all","topic":"slider-1","topicType":"str","thumbLabel":"true","showTicks":"always","min":0,"max":10,"step":1,"className":"","color":"","colorTrack":"","colorThumb":"","x":200,"y":1200,"wires":[["7470f177a4852b22"]]},{"id":"7470f177a4852b22","type":"function","z":"6bf3e03fb58d61b1","name":"randomly choose topic","func":"const t = Math.random() > .5 ? \"second\" : \"first\"\nmsg.topic = t\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":420,"y":1200,"wires":[["ea7c02fa77fe6efc"]]},{"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":"538ceff32af2078f","path":"/charts","icon":"home","layout":"notebook","theme":"5075a7d8e4947586","order":3,"className":"","visible":"true","disabled":"false"},{"id":"538ceff32af2078f","type":"ui-base","name":"My Dashboard","path":"/dashboard","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"}}]
1 Like

Also, feature requests for this to be configurable are also welcome :grin:

I agree.

I was hoping that a quick CSS tweak would work on the ui-chart node, but obviously not.

I'll do a feature request when I get home.

But thanks for trying.

Feature issue - ui-chart - change line thickness · Issue #1372 · FlowFuse/node-red-dashboard · GitHub raised as suggested.

Is your example working for you @hotNipi ?
Doesn't seem to display any data for me.

Fixed

[{"id":"ea7c02fa77fe6efc","type":"ui-template","z":"a1ded7aeb5c2fa56","group":"1c6f457dfe15977b","page":"","ui":"","name":"Custom Line Chart","order":1,"width":"6","height":"5","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:10000,  //keep data for 10 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: \"first\", data:[]},\n                            {label: \"second\",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:2,//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":710,"y":500,"wires":[[]]},{"id":"caff24894c090d95","type":"ui-slider","z":"a1ded7aeb5c2fa56","group":"1c6f457dfe15977b","name":"Slider 1","label":"Slider 1","tooltip":"","order":2,"width":0,"height":0,"passthru":false,"outs":"all","topic":"slider-1","topicType":"str","thumbLabel":"true","showTicks":"always","min":0,"max":10,"step":1,"className":"","color":"","colorTrack":"","colorThumb":"","x":260,"y":500,"wires":[["7470f177a4852b22"]]},{"id":"7470f177a4852b22","type":"function","z":"a1ded7aeb5c2fa56","name":"randomly choose topic","func":"const t = Math.random() > .5 ? \"second\" : \"first\"\nmsg.topic = t\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":500,"wires":[["ea7c02fa77fe6efc"]]},{"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"}}]