Dashboard line chart color

Well, I got slightly better results switching to the tri-colour option with this one :stuck_out_tongue: I guess I need to break the whole process down before I can wrangle out the rest.

image

let threshold = 0
let threshold2 = 0.5
let lowTopic = "low"
let midTopic = "mid"
let highTopic = "high"
let thisTopic;
let totherTopic
if (msg.payload > threshold && msg.payload < threshold2) {
    thisTopic = midTopic
    totherTopic = highTopic
} else if (msg.payload > threshold2) {
    thisTopic = lowTopic
    totherTopic = midTopic
}
let lastTopic = context.get('last') || thisTopic
let msg2 = null
if (thisTopic != lastTopic) {
    // just crossed the threshold, send to both lines
    msg.topic = lastTopic
    msg2 = {payload: msg.payload, topic: thisTopic}
} else {
    msg.topic = thisTopic
    msg2 = {payload: null, topic: totherTopic} // leave payload null to stop the line
}
context.set('last', thisTopic)
return [[msg, msg2]];

Make sure that it passes one of the tests if exactly equal to the threshold. Also it will need to output three messages, two null payloads and a real one, unless it crosses one of the boundaries in which case two of the messages must be non-null payload. It is significantly more complex than the single threshold as it will need to work out which two.

It would be good to generalise it to any number of thresholds by setting them up as an array at the start of the function.

I am not sure what it should do if the value skips right over an inner band in one go.

Can anyone help and check the below flow for modified function node. Am I doing it right ?

[{"id":"85de7d2a.d10f5","type":"ui_slider","z":"bc794abe.7829c8","name":"","label":"slider","tooltip":"","group":"a4448716.200ac8","order":2,"width":0,"height":0,"passthru":true,"outs":"all","topic":"","topicType":"str","min":"0","max":"100","step":"0.1","x":270,"y":2300,"wires":[["d58bdaf1.d93048"]]},{"id":"d58bdaf1.d93048","type":"function","z":"bc794abe.7829c8","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 > 50){\n    if(last == 'under'){\n        undermessage.payload = 50\n        overmessage.payload = 50\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,"initialize":"","finalize":"","libs":[],"x":440,"y":2300,"wires":[["277cf5b5.1c633a"],["3679a19c.ac113e"]]},{"id":"277cf5b5.1c633a","type":"ui_chart","z":"bc794abe.7829c8","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":"0","ymax":"100","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#ff0000","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"useDifferentColor":false,"x":610,"y":2300,"wires":[[]]},{"id":"125535f9.c93eca","type":"inject","z":"bc794abe.7829c8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[]","payloadType":"json","x":490,"y":2180,"wires":[["277cf5b5.1c633a"]]},{"id":"3679a19c.ac113e","type":"delay","z":"bc794abe.7829c8","name":"","pauseType":"delay","timeout":"20","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":470,"y":2420,"wires":[["d58bdaf1.d93048"]]},{"id":"c0275527.f46528","type":"function","z":"bc794abe.7829c8","name":"modified","func":"var last = context.get('last') || 'under'\nvar undermessage = {topic:'under',payload:null}\nvar overmessage = {topic:'over',payload:null}\nvar midmessage = {topic:'mid',payload:null}\nvar unsent = null\n\nif(msg.payload >= 80 && msg.payload < 100)\n{\n    if(last == 'under'){\n        undermessage.payload = 0\n        overmessage.payload = 0\n        midmessage.payload = 0\n        unsent = msg\n    }\n    else{\n        overmessage.payload = msg.payload\n    }\n    last = 'over'\n}\n\nelse if (msg.payload >= 50 && msg.payload < 79)\n{\n    if(last == 'over'){\n        overmessage.payload = 0\n        undermessage.payload = 0\n        midmessage.payload = 0\n        unsent = msg\n    }\n    else{\n        midmessage.payload = msg.payload\n    }\n    last = 'mid'\n}\n\n\nelse (msg.payload >= 0 && msg.payload < 49)\n{\n    if(last == 'mid'){\n        overmessage.payload = 0\n        undermessage.payload = 0\n        midmessage.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,midmessage],unsent]","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":440,"y":2260,"wires":[[],[]]},{"id":"a4448716.200ac8","type":"ui_group","name":"LEVEL","tab":"6e01408.cda5dc","order":1,"disp":true,"width":"8","collapse":false},{"id":"6e01408.cda5dc","type":"ui_tab","name":"Home","icon":"track_changes","order":1,"disabled":false,"hidden":false}]

image

I am unable to solve this. @hotNipi can you help? need to create chart with upper & lower limit. I tried but no result (check flow below). I'm poor with coding :neutral_face:

[{"id":"82b0136191a2a64d","type":"inject","z":"f2dbfb9b.9d8b08","name":"","props":[{"p":"payload"}],"repeat":"1","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":1070,"y":720,"wires":[["b4fbcf6cdc58ea46"]]},{"id":"b4fbcf6cdc58ea46","type":"random","z":"f2dbfb9b.9d8b08","name":"","low":1,"high":"100","inte":"true","property":"payload","x":1240,"y":720,"wires":[["3ef7a854f2406f91","a512984a4e041b62"]]},{"id":"3ef7a854f2406f91","type":"function","z":"f2dbfb9b.9d8b08","name":"","func":"if (msg.payload<=30)\n{\n    msg.payload =  [{topic:\"under\", payload:msg.payload},\n                    {topic:\"ok\", payload:null},\n                    {topic:\"over\", payload:null}]\n}\n\nelse if (msg.payload>=31 && msg.payload<=70)\n{\n    msg.payload =  [{topic:\"under\", payload:null},\n                    {topic:\"ok\", payload:msg.payload},\n                    {topic:\"over\", payload:null}]\n}\n\nelse if (msg.payload>=71)\n{\n    msg.payload =  [{topic:\"under\", payload:null},\n                    {topic:\"ok\", payload:null},\n                    {topic:\"over\", payload:msg.payload}]\n}\nnode.status({fill:\"green\",shape:\"ring\",text:msg.payload});\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1420,"y":680,"wires":[["89e47b605fe969ce"]]},{"id":"a512984a4e041b62","type":"function","z":"f2dbfb9b.9d8b08","name":"prepare","func":"var underspec = {};\nvar ok = {};\nvar overspec = {};\n\nif (msg.payload<=30)\n{\n        underspec.payload = {topic:'under',payload:msg.payload}\n        ok.payload = {topic:'ok',payload:null}\n        overspec.payload = {topic:'over',payload:null}\n}\nelse if (msg.payload>=31 && msg.payload<=70)\n{\n        underspec.payload = {topic:'under',payload:null}\n        ok.payload = {topic:'ok',payload:msg.payload}\n        overspec.payload = {topic:'over',payload:null}\n}\nelse if (msg.payload>=71)\n{\n        underspec.payload = {topic:'under',payload:null}\n        ok.payload = {topic:'ok',payload:null}\n        overspec.payload = {topic:'over',payload:msg.payload}\n}\nnode.status({fill:\"green\",shape:\"ring\",text:msg.payload});\nreturn [underspec,ok,overspec]","outputs":3,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1420,"y":760,"wires":[["b4df85e8b8cc4011"],["61752c840993b0e5"],["6f7f5df7cbe3e19a"]]},{"id":"99f00ab0.dbcfb8","type":"ui_chart","z":"f2dbfb9b.9d8b08","name":"","group":"39b33e51.922692","order":1,"width":0,"height":0,"label":"chart","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"useDifferentColor":false,"x":1870,"y":700,"wires":[[]]},{"id":"39b33e51.922692","type":"ui_group","name":"Default 1","tab":"55546119.d20b2","order":1,"disp":true,"width":"6","collapse":false},{"id":"55546119.d20b2","type":"ui_tab","name":"3","icon":"dashboard","order":5,"disabled":false,"hidden":false}]

May be easiest will be to use gradient but that is possible only if you have full control over the chart object, so not as easy as with chart node directly.

Modified an old example ..

[{"id":"8496abf.bdd4b58","type":"ui_template","z":"69a83901969741f7","group":"6d56c1dd.99aa2","name":"Line Chart","order":4,"width":"15","height":"10","format":"","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":830,"y":300,"wires":[[]]},{"id":"e91eab68.64fd68","type":"template","z":"69a83901969741f7","name":"","field":"template","fieldType":"msg","format":"html","syntax":"mustache","template":"<canvas id=\"myChart\" width=600 height=380></canvas>\n<script>\nvar textcolor = getComputedStyle(document.documentElement).getPropertyValue('--nr-dashboard-widgetTextColor');\nvar gridcolor = getComputedStyle(document.documentElement).getPropertyValue('--nr-dashboard-groupBorderColor');\n\nvar ctx = document.getElementById('myChart').getContext('2d');\nvar linecolors = {hi:\"#ff0000\",normal:\"#00ff00\",low:\"#0000ff\"}\n\nvar gradientStroke = ctx.createLinearGradient(0, 0, 0, 380);\ngradientStroke.addColorStop(0, linecolors.hi);\ngradientStroke.addColorStop(0.25, linecolors.hi);\ngradientStroke.addColorStop(0.25, linecolors.normal);\ngradientStroke.addColorStop(0.75, linecolors.normal);\ngradientStroke.addColorStop(0.75, linecolors.low);\ngradientStroke.addColorStop(1, linecolors.low);\n\nvar chart = new Chart(ctx, {\n    // The type of chart we want to create\n    type: 'line',\n\n    // The data for our dataset\n    data: {\n        labels: [],\n        datasets: [\n            {\n                label: 'first',\n                backgroundColor: gradientStroke,\n                borderColor: gradientStroke,\n                data: [],\n                yAxisID: 'left-y-axis',\n                steppedLine: false,\n                fill: false,\n                borderWidth: 1\n            }\n        ]\n    },\n\n    // Configuration options go here\n    options: {\n        scales: {\n            yAxes: [\n                {\n                    gridLines :{zeroLineColor:gridcolor,color:gridcolor,lineWidth:0.5},\n                    id: 'left-y-axis',\n                    type: 'linear',\n                    position: 'left',\n                    ticks: {\n                        fontColor: textcolor\n                    }\n                }\n            ],\n            xAxes: [\n                {\n                    gridLines :{zeroLineColor:gridcolor,color:gridcolor,lineWidth:0.5},\n                    type: 'time',\n                    distribution: 'series',\n                    time:{\n                        displayFormats: {\n                            quarter: 'MMM YYYY',\n                            millisecond:'h:mm:ss',\n                            second:\t'h:mm:ss',\n                            minute:\t'h:mm',\n                            hour:\t'h'                        \n                        }\n                    },\n                    \n                    ticks: {\n                        fontColor:textcolor\n                    }\n                }\n            ]\n        }\n    }\n});\nfunction addData(chart, data, label) {\n    // same calculation as for data storage at server side\n    // data.x is newest data so treated as now.\n    var old = data.x - 1000 * 60 * 2 \n    \n    chart.data.datasets.forEach((dataset) => {\n        if(dataset.label == label){\n            dataset.data.push(data);\n        }\n        dataset.data = dataset.data.filter(entry => entry.x > old)// filter out old data.\n        \n    });\n    chart.update(0);//0 means no animation\n}\nfunction restoreChart(chart, data) {\n    // overwrite datasets (but not clear all)\n    // based on incoming data\n    // if chart has datasets which then not found from icoming data, those remain\n    var dataset\n    Object.keys(data).forEach(label => {\n        dataset = chart.data.datasets.find(ds => {return ds.label === label })// find corresponding dataset\n        if(dataset){\n            dataset.data = data[label]// if found, replace with incoming data for that dataset\n        }\n    })\n    chart.update(0);//0 means no animation\n}\n(function(scope) {\n    scope.$watch('msg', function(msg) {\n        if(msg) {\n            if (msg.topic == \"restore\") {\n                // restore chart\n                restoreChart(chart, msg.payload)\n            }\n            else{\n                //add datapoints one by one\n                addData(chart, msg.payload, msg.topic)\n            }\n        }\n   \n  });\n})(scope);\n</script>\n","output":"str","x":640,"y":260,"wires":[["8496abf.bdd4b58"]]},{"id":"6d48838f.c861ac","type":"inject","z":"69a83901969741f7","name":"Initialize","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":400,"y":260,"wires":[["e91eab68.64fd68"]]},{"id":"15644e1e.7c2ab2","type":"inject","z":"69a83901969741f7","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"1","crontab":"","once":false,"onceDelay":"0.62","topic":"","payloadType":"date","x":355,"y":340,"wires":[["c3575dfd.f6964"]],"l":false},{"id":"c3575dfd.f6964","type":"function","z":"69a83901969741f7","name":"fake data","func":"msg.payload = Math.round(Math.random() * 100)\nmsg.topic = 'first'\n\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":460,"y":340,"wires":[["1735652c.72d0ab"]]},{"id":"63e9f633.a79e08","type":"ui_ui_control","z":"69a83901969741f7","name":"","events":"connect","x":300,"y":300,"wires":[["d4de9d72.8ce92"]]},{"id":"1735652c.72d0ab","type":"function","z":"69a83901969741f7","name":"store","func":"var storage = flow.get('chartData') || {first:[],second:[]} // data structure to match the chart datasets\n\nvar now = new Date().getTime() // the moment of datapoint creation\nvar old = now - 1000 * 60 * 2  // too old data will be 2 minutes\nvar datapoint = {x:now, y:msg.payload} // create the datapoint\n\nstorage[msg.topic].push(datapoint) // push datapoint into correct object's array in storage\n\nstorage[msg.topic] = storage[msg.topic].filter(entry => entry.x > old)// filter out any too old data\n\nflow.set('chartData',storage)// write storage to flow context\n\nmsg.payload = datapoint // send out the created datapoint (msg.topic remains same)\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":630,"y":340,"wires":[["8496abf.bdd4b58"]]},{"id":"99d40b3b.6ca708","type":"change","z":"69a83901969741f7","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"chartData","tot":"flow"},{"t":"set","p":"topic","pt":"msg","to":"restore","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":620,"y":300,"wires":[["8496abf.bdd4b58"]]},{"id":"d4de9d72.8ce92","type":"switch","z":"69a83901969741f7","name":"","property":"chartData","propertyType":"flow","rules":[{"t":"nnull"}],"checkall":"true","repair":false,"outputs":1,"x":450,"y":300,"wires":[["99d40b3b.6ca708"]]},{"id":"6d56c1dd.99aa2","type":"ui_group","name":"Chart","tab":"b7b17e22.6df42","order":3,"disp":true,"width":"15","collapse":false},{"id":"b7b17e22.6df42","type":"ui_tab","name":"Ovens","icon":"dashboard","order":1,"disabled":false,"hidden":false}]
1 Like

Thankyou :+1:. Its a great help even though this is too much code for me to understand. but will go through it.

wofür ist die delay Funktion?

If you click on the delay node and then click on the little book icon at the top of the right hand pane it will show you the help text for the node that describes what it does.

In future it would be helpful if you were to translate questions to English (as few here speak your language) and this would save each of us having to translate it.

[Edit] Why have you added this to the end of a old thread about the line chart?

what is the function of the delay node?

I answered that in my previous post.

finally took the time to edit and get what i needed except for one thing which I couldn't work out i.e. when the payload goes from

  • under lower spec to over upper spec or
  • over upper spec to under lower spec
    then entire line is red.

below is the edited code used from link for anyone who might need it

[{"id":"8e8eaefc25459c3f","type":"group","z":"eeb4b6f5c6a3c5d4","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["e0d0d51ff682f761","28ddd03ccaae24e7","c8b84e075024e905","4ba7915e5a7b3fba","34163f2da89b3c64"],"x":74,"y":39,"w":492,"h":182},{"id":"e0d0d51ff682f761","type":"ui_slider","z":"eeb4b6f5c6a3c5d4","g":"8e8eaefc25459c3f","name":"","label":"slider","tooltip":"","group":"a4448716.200ac8","order":1,"width":0,"height":0,"passthru":true,"outs":"all","topic":"","topicType":"str","min":"100","max":"500","step":"0.1","className":"","x":150,"y":140,"wires":[["28ddd03ccaae24e7"]]},{"id":"28ddd03ccaae24e7","type":"function","z":"eeb4b6f5c6a3c5d4","g":"8e8eaefc25459c3f","name":"","func":"var last = context.get('last') || 'under_lsl' || 'over_lsl'\nvar undermessage_lsl = {topic:'under_lsl',payload:null}\nvar overmessage_usl = {topic:'over_usl',payload:null}\nvar okmessage = {topic:'ok',payload:null}\n\nvar usl = 400 //upper specification limit\nvar lsl = 200 //lower specification limit\nvar unsent = null\n\nvar upper_limit = {topic:'usl',payload:usl}\nvar lower_limit = {topic:'lsl',payload:lsl}\n\nif(msg.payload >= lsl && msg.payload <= usl)\n    {\n        if(last == 'under_lsl')\n        {\n            undermessage_lsl.payload = lsl\n            okmessage.payload = lsl\n            overmessage_usl.payload = lsl\n            unsent = msg\n        }\n        else if(last == 'over_usl')\n        {\n            undermessage_lsl.payload = usl\n            okmessage.payload = usl\n            overmessage_usl.payload = usl\n            unsent = msg\n        }\n        else\n        {\n            okmessage.payload = msg.payload\n        }\n        last = 'ok' \n    }\n    \n    \nelse if (msg.payload < lsl)\n    {\n        if(last == 'ok')\n        {\n            undermessage_lsl.payload = lsl\n            okmessage.payload = lsl\n            overmessage_usl.payload = lsl\n            unsent = msg\n        }\n        else if(last == 'over_usl')\n        {\n            undermessage_lsl.payload = usl\n            okmessage.payload = usl\n            overmessage_usl.payload = usl\n            unsent = msg\n        }\n        else\n        {\n            undermessage_lsl.payload = msg.payload\n        }\n        last = 'under_lsl'\n    }\nelse if(msg.payload > usl)\n    {\n        if(last == 'ok')\n        {\n            undermessage_lsl.payload = usl\n            okmessage.payload = usl\n            overmessage_usl.payload = usl\n            unsent = msg\n        }\n        else if(last == 'under_lsl')\n        {\n            undermessage_lsl.payload = usl\n            okmessage.payload = usl\n            overmessage_usl.payload = usl\n            unsent = msg\n        }\n        else\n        {\n            overmessage_usl.payload = msg.payload\n        }\n        last = 'over_usl'\n    }\n\ncontext.set('last',last)\n\n\nreturn [[undermessage_lsl,okmessage,overmessage_usl,upper_limit,lower_limit],unsent]","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":315,"y":140,"wires":[["c8b84e075024e905"],["34163f2da89b3c64"]],"l":false},{"id":"c8b84e075024e905","type":"ui_chart","z":"eeb4b6f5c6a3c5d4","g":"8e8eaefc25459c3f","name":"","group":"a4448716.200ac8","order":2,"width":0,"height":0,"label":"chart","chartType":"line","legend":"false","xformat":"HH:mm:ss","interpolate":"bezier","nodata":"","dot":false,"ymin":"50","ymax":"550","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"60","cutout":0,"useOneColor":false,"useUTC":false,"colors":["#ff0000","#60c828","#ff0000","#a6a6a6","#a6a6a6","#ff0000","#ff9896","#9467bd","#c5b0d5"],"outputs":1,"useDifferentColor":false,"className":"","x":490,"y":140,"wires":[[]]},{"id":"4ba7915e5a7b3fba","type":"inject","z":"eeb4b6f5c6a3c5d4","g":"8e8eaefc25459c3f","name":"","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[]","payloadType":"json","x":340,"y":80,"wires":[["c8b84e075024e905"]]},{"id":"34163f2da89b3c64","type":"delay","z":"eeb4b6f5c6a3c5d4","g":"8e8eaefc25459c3f","name":"","pauseType":"delay","timeout":"50","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":315,"y":180,"wires":[["28ddd03ccaae24e7"]],"l":false},{"id":"a4448716.200ac8","type":"ui_group","name":"line chart","tab":"6e01408.cda5dc","order":1,"disp":true,"width":"17","collapse":false,"className":""},{"id":"6e01408.cda5dc","type":"ui_tab","name":"test 3","icon":"track_changes","order":12,"disabled":false,"hidden":false}]

Hi @sahsha, did you ever get this issue of going directly from under to over threshold sorted? I am equally programming illiterate and want to do the same thing you are! :slight_smile:

I stopped working on the issue as I was getting values from a temperature sensor of a furnace (from a GDC line) and it was very unlikely that temperature would go directly from under spec to over spec.