Dashboard line chart color

Does anyone know if I can change the color of a line based on the number received? For example I have a line chart that goes above and below 0. When it goes below 0 I want it to turn green, when it goes above 0 I want it to turn red.

I tried using a topic to only send and it is above or below 0 but when the graph won't leave a gap between points.

It is not possible to change the line color on fly but you must draw two lines and make gaps by sending payload null where gaps needed. A bit hacking way but something like this:

[{"id":"85de7d2a.d10f5","type":"ui_slider","z":"d65d7a10.498788","name":"","label":"slider","tooltip":"","group":"a4448716.200ac8","order":2,"width":0,"height":0,"passthru":true,"outs":"all","topic":"","min":"-5","max":10,"step":"0.1","x":260,"y":500,"wires":[["d58bdaf1.d93048"]]},{"id":"d58bdaf1.d93048","type":"function","z":"d65d7a10.498788","name":"","func":"var last = context.get('last') || 'under'\nvar undermessage = {topic:'under',payload:null}\nvar overmessage = {topic:'over',payload:null}\n\nif(msg.payload > 0){\n    if(last == 'under'){\n        undermessage.payload = msg.payload\n    }\n   overmessage.payload = msg.payload\n   last = 'over'\n}\nelse{\n    if(last == 'over'){\n        overmessage.payload = msg.payload\n    }\n    undermessage.payload = msg.payload\n    last = 'under'\n}\ncontext.set('last',last)\n\nreturn [[overmessage,undermessage]]","outputs":1,"noerr":0,"x":430,"y":500,"wires":[["277cf5b5.1c633a"]]},{"id":"277cf5b5.1c633a","type":"ui_chart","z":"d65d7a10.498788","name":"","group":"a4448716.200ac8","order":2,"width":0,"height":0,"label":"chart","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"-5","ymax":"10","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"colors":["#1f77b4","#ff0000","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"outputs":1,"x":600,"y":500,"wires":[[]]},{"id":"125535f9.c93eca","type":"inject","z":"d65d7a10.498788","name":"","topic":"","payload":"[]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":430,"y":560,"wires":[["277cf5b5.1c633a"]]},{"id":"a4448716.200ac8","type":"ui_group","z":"","name":"LEVEL","tab":"6e01408.cda5dc","order":1,"disp":true,"width":"8","collapse":false},{"id":"6e01408.cda5dc","type":"ui_tab","z":"","name":"Home","icon":"track_changes","order":1,"disabled":false,"hidden":false}]

This is no full solution. Colors don't change always at your desired value
To get the thing working more precisely you'll probably need to send intermediate message for both lines with payload = your desired threshold value at the moment where threshold value is crossed.

EDIT:
I did some experiments to achieve better threshold crossing behavior and may be simplest way is to send threshold value for both topics and in addition send the original payload with slight delay back to function

image

[{"id":"85de7d2a.d10f5","type":"ui_slider","z":"d65d7a10.498788","name":"","label":"slider","tooltip":"","group":"a4448716.200ac8","order":2,"width":0,"height":0,"passthru":true,"outs":"all","topic":"","min":"-5","max":10,"step":"0.1","x":260,"y":500,"wires":[["d58bdaf1.d93048"]]},{"id":"d58bdaf1.d93048","type":"function","z":"d65d7a10.498788","name":"","func":"var last = context.get('last') || 'under'\nvar undermessage = {topic:'under',payload:null}\nvar overmessage = {topic:'over',payload:null}\nvar unsent = null\n\nif(msg.payload > 0){\n    if(last == 'under'){\n        undermessage.payload = 0\n        overmessage.payload = 0\n        unsent = msg\n    }\n    else{\n        overmessage.payload = msg.payload\n    }\n    last = 'over'\n}\nelse{\n    if(last == 'over'){\n        overmessage.payload = 0\n        undermessage.payload = 0\n        unsent = msg\n    }\n    else{\n        undermessage.payload = msg.payload\n    }\n    last = 'under'\n}\ncontext.set('last',last)\n\nreturn [[overmessage,undermessage],unsent]","outputs":2,"noerr":0,"x":430,"y":500,"wires":[["277cf5b5.1c633a"],["3679a19c.ac113e"]]},{"id":"277cf5b5.1c633a","type":"ui_chart","z":"d65d7a10.498788","name":"","group":"a4448716.200ac8","order":2,"width":0,"height":0,"label":"chart","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"-5","ymax":"10","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"colors":["#1f77b4","#ff0000","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"outputs":1,"x":600,"y":500,"wires":[[]]},{"id":"125535f9.c93eca","type":"inject","z":"d65d7a10.498788","name":"","topic":"","payload":"[]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":450,"y":440,"wires":[["277cf5b5.1c633a"]]},{"id":"3679a19c.ac113e","type":"delay","z":"d65d7a10.498788","name":"","pauseType":"delay","timeout":"20","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":440,"y":580,"wires":[["d58bdaf1.d93048"]]},{"id":"a4448716.200ac8","type":"ui_group","z":"","name":"LEVEL","tab":"6e01408.cda5dc","order":1,"disp":true,"width":"8","collapse":false},{"id":"6e01408.cda5dc","type":"ui_tab","z":"","name":"Home","icon":"track_changes","order":1,"disabled":false,"hidden":false}]
1 Like

Neat solution @hotnipi - worth putting on flows.nodered.org ?

Well for delay, if chart gets too many points in too short time frame it may cause some issues (rendering/performance) and this way it's kind of slightly prevented but of course, should work without delay also.

Edit: I see you removed the delay part ... but I leave my explanation anyway

For sharing, I think the docs/tutorial section should have dashboard section with such How To examples. There is many of such "not easy to find by just digging in documents and searching in forum" solutions and tricks for dashboard items. Gauge dynamic limits and look, Different icons (with dynamic color) in text widget, chart manipulations ... They come up time to time here in forum but every time different title and different wordings to ask basically same questions. And the forum search kind of works but only if you know how or what to search.

But for today and for community as always - sharing is our best friend :slight_smile:

Multicolor line with Dashboard Chart

Yes, a proper Dashboard introduction, examples, how-to docs has been on the to-do list for ever - By all means some pages as PR to the project would be a start and we can then link them from the README etc.

There is a flow for this already https://flows.nodered.org/flow/a78ac10821112eb07fb8be8957a9f7cb

I just discovered that too. And it is good to have them both as they do different things in different situations. :slight_smile:

[{"id":"d35fa016.a4e3c","type":"ui_slider","z":"7f6628bb.1a7bf8","name":"","label":"slider","tooltip":"","group":"e4b30ae.0ac5bf8","order":2,"width":0,"height":0,"passthru":true,"outs":"end","topic":"","min":"-1","max":"1","step":".1","x":630,"y":340,"wires":[["50b7ee76.5dc9b","540de43f.8b3b9c"]]},{"id":"532be957.f796b8","type":"ui_chart","z":"7f6628bb.1a7bf8","name":"threshold driven","group":"e4b30ae.0ac5bf8","order":2,"width":0,"height":0,"label":"threshold driven","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"-1","ymax":"1","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"colors":["#1f77b4","#0df2be","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"outputs":1,"x":1120,"y":380,"wires":[[]]},{"id":"63536e37.f08b3","type":"inject","z":"7f6628bb.1a7bf8","name":"Clear chart","topic":"","payload":"[]","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":920,"y":400,"wires":[["532be957.f796b8","bfdfa914.10fab8"]]},{"id":"e388eda7.fd5cd","type":"function","z":"7f6628bb.1a7bf8","name":"Sin","func":"msg.payload = Math.sin(msg.payload/4000)\nreturn msg;","outputs":1,"noerr":0,"x":630,"y":460,"wires":[["50b7ee76.5dc9b","540de43f.8b3b9c"]]},{"id":"d5bf32aa.f423a","type":"inject","z":"7f6628bb.1a7bf8","name":"0.5 sec timestamp","topic":"","payload":"","payloadType":"date","repeat":"0.5","crontab":"","once":false,"onceDelay":0.1,"x":650,"y":520,"wires":[["e388eda7.fd5cd"]]},{"id":"bfdfa914.10fab8","type":"ui_chart","z":"7f6628bb.1a7bf8","name":"occurrence driven","group":"e4b30ae.0ac5bf8","order":0,"width":0,"height":0,"label":"occurrence driven","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"useOldStyle":false,"outputs":1,"x":1120,"y":420,"wires":[[]]},{"id":"50b7ee76.5dc9b","type":"function","z":"7f6628bb.1a7bf8","name":"change by occurrence","func":"let threshold = 0.5\nlet lowTopic = \"low\"\nlet highTopic = \"high\"\nlet thisTopic;\nlet totherTopic\nif (msg.payload > threshold) {\n    thisTopic = highTopic\n    totherTopic = lowTopic\n} else {\n    thisTopic = lowTopic\n    totherTopic = highTopic\n}\nlet lastTopic = context.get('last') || thisTopic\nlet msg2 = null\nif (thisTopic != lastTopic) {\n    // just crossed the threshold, send to both lines\n    msg.topic = lastTopic\n    msg2 = {payload: msg.payload, topic: thisTopic}\n} else {\n    msg.topic = thisTopic\n    msg2 = {payload: null, topic: totherTopic} // leave payload null to stop the line\n}\ncontext.set('last', thisTopic)\nreturn [[msg, msg2]];","outputs":1,"noerr":0,"x":890,"y":460,"wires":[["bfdfa914.10fab8"]]},{"id":"540de43f.8b3b9c","type":"function","z":"7f6628bb.1a7bf8","name":"change by threshold","func":"var last = context.get('last') || 'under'\nvar threshold = 0.5\nvar undermessage = {topic:'under',payload:null}\nvar overmessage = {topic:'over',payload:null}\nvar unsentmessage = null\n\nif(msg.payload > threshold){\n    if(last == 'under'){\n        undermessage.payload = threshold\n        overmessage.payload = threshold\n        unsentmessage = msg\n    }\n    else{\n        overmessage.payload = msg.payload\n    }\n    last = 'over'\n}\nelse{\n    if(last == 'over'){\n        overmessage.payload = threshold\n        undermessage.payload = threshold\n        unsentmessage = msg\n    }\n    else{\n        undermessage.payload = msg.payload\n    }\n    last = 'under'\n}\ncontext.set('last',last)\n\nreturn [unsentmessage,[overmessage,undermessage]]","outputs":2,"noerr":0,"x":890,"y":340,"wires":[["7a0695bc.6dfb7c"],["532be957.f796b8"]]},{"id":"7a0695bc.6dfb7c","type":"delay","z":"7f6628bb.1a7bf8","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":900,"y":280,"wires":[["540de43f.8b3b9c"]]},{"id":"e4b30ae.0ac5bf8","type":"ui_group","z":"","name":"LEVEL","tab":"d628dd08.bd0b5","order":1,"disp":true,"width":"8","collapse":false},{"id":"d628dd08.bd0b5","type":"ui_tab","z":"","name":"Home","icon":"track_changes","order":1,"disabled":false,"hidden":false}]

Since I do but have access to my computer, which is which?

Lower is yours. And sure you'll need to try to see why and when the difference reveals pros and cons of each solution.

1 Like

Well this thread had lots of replies while I was away. Thanks everyone for the examples.