Linear gauge as another gauge option


#1

For now I’m using some template as a linear gauge, a gauge style that for me is missing as standard on the palette.

Is very useful to monitor the level of something like for instance in my case light level and could be also great being able to adjust color for temperature or something like this.

The main point is that linear gauge is able to display same valuable information that any other kind of the available gauges but takes much less space on the dashboard.

As example:

image

Regards


#2

Sorry, just spotted this. Is there a helper library that will show this?


#3

There is this on the flows site

https://flows.nodered.org/flow/1538793825685bd8f08e4aea16ca2c74


#4

I started over to remake my home automation dashboard. I like gauges but as their graphics cant be customized so I decided to go for something similar to this topic. It's actually not that complicated thing to do (if targeted to dedicated task). It still "work in progress" so I don't want to share the flow in this stadium in public. But I'm happy to share basics and help to get started with.

Here's the visual outcome of mine as example.
image


#5

I especially like the look of the vertical elements on the right side, very neat.
Are you using a plug-in library, or drawn in the flow?


#6

ui_template and drawing with svg elements


#7

I'll look forward to seeing how you achieved it, if/when you do share :wink:


#8

There is couple of things to do for example I want them to be able to be rendered in different widget sizes without major modification needed. And as it rely on properly feed data, this part should be optimized and simplified also. So it takes some time but not long I hope :wink:


#9

As promised, a little example how to achieve it using default set nodes ("node_red_dashboard" is needed).
Feel free to use and modify it by your needs. The colors I used look fine on medium gray background.

I separated configuring part from data preparing part into different functions just for clarity of example.
Parameters can be configured using Change node also

You can get rid of shadows by just deleting the filter properties in ui_template :slight_smile: Shadows harm rendering for sure and not recommended for performance critical situations.

I added comments about basic stuff, I hope it makes this thing kind of clear.

[{"id":"66684c36.a1fb94","type":"inject","z":"828c92a8.6bb45","name":"","topic":"","payload":"","payloadType":"date","repeat":"1","crontab":"","once":false,"onceDelay":0.1,"x":370,"y":280,"wires":[["fdc73f50.1ac08"]]},{"id":"fdc73f50.1ac08","type":"function","z":"828c92a8.6bb45","name":"Generate some data","func":"var lastupdate = global.get(\"lastupdate\") || 0;\nif(lastupdate++ > 99){\n lastupdate = 0;\n}\nglobal.set(\"lastupdate\",lastupdate);\nmsg.payload = lastupdate\nreturn msg;","outputs":1,"noerr":0,"x":550,"y":280,"wires":[["313749d2.75fae6","818a6bf4.ddc6e8","4be2825.29c377c","85ebd007.f2145","c096a17b.058b7","5769d1e9.05358","b7f58518.7e0968"]]},{"id":"ae422917.1413b8","type":"ui_template","z":"828c92a8.6bb45","group":"2accfba2.147474","name":"svg level vertical 2x3","order":2,"width":"2","height":"3","format":"<svg width=100% height=100%>\n <defs>\n <filter id=\"level-filter\" x=\"0\" y=\"0\" width=\"200%\" height=\"200%\">\n <feOffset result=\"offOut\" in=\"SourceGraphic\" dx=\"1\" dy=\"1\" />\n <feColorMatrix result=\"matrixOut\" in=\"offOut\" type=\"matrix\"\n values=\"0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.1 0 0 0 0 0 1 0\" />\n <feGaussianBlur result=\"blurOut\" in=\"matrixOut\" stdDeviation=\"5\" />\n <feBlend in=\"SourceGraphic\" in2=\"blurOut\" mode=\"normal\" />\n </filter>\n </defs>\n <rect ng-repeat=\"obj in msg.colors track by $index\" \n x=\"0\" \n ng:attr:y=\"{{$index * 4}}px\"\n width=\"12\" \n height=\"2\" \n style=\"fill:{{msg.colors[$index]}}\" filter=\"url(#level-filter)\"\n />\n <text id=\"level-ver-value\"\n text-anchor=\"middle\" alignment-baseline=\"middle\"\n x=55%\n y=50%>\n {{msg.value}}{{msg.valuesuffix}}\n </text>\n <text id=\"level-minmax\"\n text-anchor=\"start\" alignment-baseline=\"ideographic\"\n x=15\n ng:attr:y={{msg.minpos}}>\n {{msg.min}}\n </text>\n <text id=\"level-minmax\"\n text-anchor=\"start\" alignment-baseline=\"hanging\"\n x=15\n y=1 >\n {{msg.max}}\n </text>\n</svg> ","storeOutMessages":false,"fwdInMessages":true,"templateScope":"local","x":1130,"y":320,"wires":[[]]},{"id":"81337426.09e378","type":"ui_template","z":"828c92a8.6bb45","group":"e19b4f66.fbbeb","name":"svg level horizontal 2x1","order":4,"width":"2","height":"1","format":"<svg width=100% height=100%>\n <defs>\n <filter id=\"level-filter\" x=\"0\" y=\"0\" width=\"200%\" height=\"200%\">\n <feOffset result=\"offOut\" in=\"SourceGraphic\" dx=\"1\" dy=\"1\" />\n <feColorMatrix result=\"matrixOut\" in=\"offOut\" type=\"matrix\"\n values=\"0.2 0 0 0 0 0 0.2 0 0 0 0 0 0.2 0 0 0 0 0 1 0\" />\n <feGaussianBlur result=\"blurOut\" in=\"matrixOut\" stdDeviation=\"5\" />\n <feBlend in=\"SourceGraphic\" in2=\"blurOut\" mode=\"normal\" />\n </filter>\n </defs>\n <rect ng-repeat=\"obj in msg.colors track by $index\" \n y=80% \n ng:attr:x=\"{{$index * 4}}px\"\n width=\"2\"\n height=\"6\" \n style=\"fill:{{msg.colors[$index]}}\" filter=\"url(#level-filter)\"\n />\n <text id=\"level-hor-value\"\n text-anchor=\"middle\" alignment-baseline=\"hanging\"\n x=50%\n y=0>\n {{msg.value}}{{msg.valuesuffix}}\n </text>\n <text id=\"level-minmax\"\n text-anchor=\"start\" alignment-baseline=\"ideographic\"\n x=0\n y=85%>\n {{msg.min}}\n </text>\n <text id=\"level-minmax\"\n text-anchor=\"end\" alignment-baseline=\"ideographic\"\n x=100%\n y=85%>\n {{msg.max}}\n </text>\n</svg> ","storeOutMessages":false,"fwdInMessages":true,"templateScope":"local","x":1140,"y":480,"wires":[[]]},{"id":"3d2df1f1.8c42fe","type":"ui_template","z":"828c92a8.6bb45","group":"efcad470.381fb8","name":"Custom element style","order":0,"width":0,"height":0,"format":"<style>\n #level-hor-value {\n font-size: 1.4em;\n fill:#d0d0d0;\n }\n #level-minmax {\n font-size: 60%;\n fill:#d0d0d0;\n }\n #level-3x3-stripe {\n fill: #404040;\n }\n #level-ver-value {\n font-size: 1.8em;\n fill:#d0d0d0;\n }\n #chart-hour {\n font-size: 60%;\n fill:#d0d0d0;\n }\n #chart-stripe {\n fill: #404040;\n }\n\n</style>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"global","x":850,"y":180,"wires":[[]]},{"id":"76e613ef.e1bb0c","type":"inject","z":"828c92a8.6bb45","name":"init","topic":"init","payload":"create","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":430,"y":180,"wires":[["8961d767.2bad38"]]},{"id":"8961d767.2bad38","type":"function","z":"828c92a8.6bb45","name":"set site properties","func":"var unit = {\"h\":48,w:48}; // site sizes 1x1 wdget size\nvar colormap = {on:\"#47d147\",off:\"dimgrey\",warn:\"#ff9933\", high:\"#ff5c33\"} // green, gray, orange, red \nvar site = {\"unit\":unit,\"widgetcolors\":colormap}; //merge to store to global as one variable\nglobal.set(\"site\",site);\nreturn msg;","outputs":1,"noerr":0,"x":610,"y":180,"wires":[[]]},{"id":"c1f35432.aeb748","type":"ui_template","z":"828c92a8.6bb45","group":"2accfba2.147474","name":"svg level vertical 2x4","order":3,"width":"2","height":"4","format":"<svg width=100% height=100%>\n <defs>\n <filter id=\"level-filter\" x=\"0\" y=\"0\" width=\"200%\" height=\"200%\">\n <feOffset result=\"offOut\" in=\"SourceGraphic\" dx=\"1\" dy=\"1\" />\n <feColorMatrix result=\"matrixOut\" in=\"offOut\" type=\"matrix\"\n values=\"0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.1 0 0 0 0 0 1 0\" />\n <feGaussianBlur result=\"blurOut\" in=\"matrixOut\" stdDeviation=\"5\" />\n <feBlend in=\"SourceGraphic\" in2=\"blurOut\" mode=\"normal\" />\n </filter>\n </defs>\n <rect ng-repeat=\"obj in msg.colors track by $index\" \n x=\"0\" \n ng:attr:y=\"{{$index * 4}}px\"\n width=\"12\" \n height=\"2\" \n style=\"fill:{{msg.colors[$index]}}\" filter=\"url(#level-filter)\"\n />\n <text id=\"level-ver-value\"\n text-anchor=\"middle\" alignment-baseline=\"middle\"\n x=55%\n y=50%>\n {{msg.value}}{{msg.valuesuffix}}\n </text>\n <text id=\"level-minmax\"\n text-anchor=\"start\" alignment-baseline=\"ideographic\"\n x=15\n ng:attr:y={{msg.minpos}}>\n {{msg.min}}\n </text>\n <text id=\"level-minmax\"\n text-anchor=\"start\" alignment-baseline=\"hanging\"\n x=15\n y=1 >\n {{msg.max}}\n </text>\n</svg> ","storeOutMessages":false,"fwdInMessages":true,"templateScope":"local","x":1130,"y":360,"wires":[[]]},{"id":"3978cc74.8f9694","type":"ui_template","z":"828c92a8.6bb45","group":"2accfba2.147474","name":"svg level vertical 2x5","order":4,"width":"2","height":"5","format":"<svg width=100% height=100%>\n <defs>\n <filter id=\"level-filter\" x=\"0\" y=\"0\" width=\"200%\" height=\"200%\">\n <feOffset result=\"offOut\" in=\"SourceGraphic\" dx=\"1\" dy=\"1\" />\n <feColorMatrix result=\"matrixOut\" in=\"offOut\" type=\"matrix\"\n values=\"0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.1 0 0 0 0 0 1 0\" />\n <feGaussianBlur result=\"blurOut\" in=\"matrixOut\" stdDeviation=\"5\" />\n <feBlend in=\"SourceGraphic\" in2=\"blurOut\" mode=\"normal\" />\n </filter>\n </defs>\n <rect ng-repeat=\"obj in msg.colors track by $index\" \n x=\"0\" \n ng:attr:y=\"{{$index * 4}}px\"\n width=\"12\" \n height=\"2\" \n style=\"fill:{{msg.colors[$index]}}\" filter=\"url(#level-filter)\"\n />\n <text id=\"level-ver-value\"\n text-anchor=\"middle\" alignment-baseline=\"middle\"\n x=55%\n y=50%>\n {{msg.value}}{{msg.valuesuffix}}\n </text>\n <text id=\"level-minmax\"\n text-anchor=\"start\" alignment-baseline=\"ideographic\"\n x=15\n ng:attr:y={{msg.minpos}}>\n {{msg.min}}\n </text>\n <text id=\"level-minmax\"\n text-anchor=\"start\" alignment-baseline=\"hanging\"\n x=15\n y=1 >\n {{msg.max}}\n </text>\n</svg> ","storeOutMessages":false,"fwdInMessages":true,"templateScope":"local","x":1130,"y":400,"wires":[[]]},{"id":"9845d23a.797d6","type":"ui_template","z":"828c92a8.6bb45","group":"2accfba2.147474","name":"svg level vertical 2x2","order":1,"width":"2","height":"2","format":"<svg width=100% height=100%>\n <defs>\n <filter id=\"level-filter\" x=\"0\" y=\"0\" width=\"200%\" height=\"200%\">\n <feOffset result=\"offOut\" in=\"SourceGraphic\" dx=\"1\" dy=\"1\" />\n <feColorMatrix result=\"matrixOut\" in=\"offOut\" type=\"matrix\"\n values=\"0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.1 0 0 0 0 0 1 0\" />\n <feGaussianBlur result=\"blurOut\" in=\"matrixOut\" stdDeviation=\"5\" />\n <feBlend in=\"SourceGraphic\" in2=\"blurOut\" mode=\"normal\" />\n </filter>\n </defs>\n <rect ng-repeat=\"obj in msg.colors track by $index\" \n x=\"0\" \n ng:attr:y=\"{{$index * 4}}px\"\n width=\"12\" \n height=\"2\" \n style=\"fill:{{msg.colors[$index]}}\" filter=\"url(#level-filter)\"\n />\n <text id=\"level-ver-value\"\n text-anchor=\"middle\" alignment-baseline=\"middle\"\n x=55%\n y=50%>\n {{msg.value}}{{msg.valuesuffix}}\n </text>\n <text id=\"level-minmax\"\n text-anchor=\"start\" alignment-baseline=\"ideographic\"\n x=15\n ng:attr:y={{msg.minpos}}>\n {{msg.min}}\n </text>\n <text id=\"level-minmax\"\n text-anchor=\"start\" alignment-baseline=\"hanging\"\n x=15\n y=1 >\n {{msg.max}}\n </text>\n</svg> ","storeOutMessages":false,"fwdInMessages":true,"templateScope":"local","x":1130,"y":280,"wires":[[]]},{"id":"313749d2.75fae6","type":"function","z":"828c92a8.6bb45","name":"Configure","func":"msg.value = msg.payload; //expected to be a number\nmsg.min = 0; //expected minimum value of incoming data\nmsg.max = 100; //expected maximum value of incoming data\nmsg.valuesuffix = \"%\" //kind of data \nmsg.direction = \"vertical\"; //widget look\nmsg.units = 2; //how many units widget takes in direction \n\nreturn msg;","outputs":1,"noerr":0,"x":820,"y":280,"wires":[["7925330e.07bb5c"]]},{"id":"818a6bf4.ddc6e8","type":"function","z":"828c92a8.6bb45","name":"Configure","func":"msg.value = msg.payload; //expected to be a number\nmsg.min = 0; //expected minimum value of incoming data\nmsg.max = 100; //expected maximum value of incoming data\nmsg.valuesuffix = \"%\" //kind of data \nmsg.direction = \"vertical\"; //widget look\nmsg.units = 3; //how many units widget takes in direction \n\nreturn msg;","outputs":1,"noerr":0,"x":820,"y":320,"wires":[["136cbf9c.efa6"]]},{"id":"4be2825.29c377c","type":"function","z":"828c92a8.6bb45","name":"Configure","func":"msg.value = msg.payload; //expected to be a number\nmsg.min = 0; //expected minimum value of incoming data\nmsg.max = 100; //expected maximum value of incoming data\nmsg.valuesuffix = \"%\" //kind of data \nmsg.direction = \"vertical\"; //widget look\nmsg.units = 4; //how many units widget takes in direction \n\nreturn msg;","outputs":1,"noerr":0,"x":820,"y":360,"wires":[["713cf384.d53dec"]]},{"id":"85ebd007.f2145","type":"function","z":"828c92a8.6bb45","name":"Configure","func":"msg.value = msg.payload; //expected to be a number\nmsg.min = 0; //expected minimum value of incoming data\nmsg.max = 100; //expected maximum value of incoming data\nmsg.valuesuffix = \"%\" //kind of data \nmsg.direction = \"vertical\"; //widget look\nmsg.units = 5; //how many units widget takes in direction \n\nreturn msg;","outputs":1,"noerr":0,"x":820,"y":400,"wires":[["1e353951.383ed7"]]},{"id":"c096a17b.058b7","type":"function","z":"828c92a8.6bb45","name":"Configure","func":"msg.value = msg.payload; //expected to be a number\nmsg.min = 0; //expected minimum value of incoming data\nmsg.max = 100; //expected maximum value of incoming data\nmsg.valuesuffix = \"%\" //kind of data \nmsg.direction = \"horizontal\"; //widget look\nmsg.units = 2; //how many units widget takes in direction \n\nreturn msg;","outputs":1,"noerr":0,"x":820,"y":480,"wires":[["d8cda4f7.3cf358"]]},{"id":"7925330e.07bb5c","type":"function","z":"828c92a8.6bb45","name":"Generate","func":"//drawing of stripes will be done on exact pixels to avoid blur\n//calculated count of stripes will not fill 100% of area.\n\n\n// get global variables\nvar unit = global.get(\"site\").unit.h;\nvar colormap = global.get(\"site\").widgetcolors;\n// declare local variables\nvar c;\nvar colors = [];\n\n// how many stripes should be drawn according to calculated widget size\nvar count = Math.floor(unit * msg.units / 4) - 2; \n\n// parameters for range incoming values into size of widget\nvar params = {minin:msg.min, maxin:msg.max+1, minout:0, maxout:count};\n\n// range\nfunction range(n,params){\n var divisor = params.maxin - params.minin;\n n = ((n - params.minin) % divisor + divisor) % divisor + params.minin;\n n = ((n - params.minin) / (params.maxin - params.minin) * (params.maxout - params.minout)) + params.minout;\n return n;\n}\nvar rangedvalue = Math.round(range(msg.value,params));\n\n// determine color for each stripe\nfor(var i=0; i<count; i++){\n c = rangedvalue <= i ? colormap.off : ( i < (count-5) ? colormap.on : ( i < (count-2) ? colormap.warn : colormap.high));\n colors.push(c);\n}\n//vertical starts drawing from top. colors needed to be in reverse order \nif(msg.direction === \"vertical\"){\n colors.reverse();\n}\n\n// set msg parameters\nmsg.colors = colors;\n\n// for vertical, the last stripe will not be at the bottom exactly.\n// for adjusting min value position.\nmsg.minpos = count*4; \nreturn msg;","outputs":1,"noerr":0,"x":960,"y":280,"wires":[["9845d23a.797d6"]]},{"id":"136cbf9c.efa6","type":"function","z":"828c92a8.6bb45","name":"Generate","func":"//drawing of stripes will be done on exact pixels to avoid blur\n//calculated count of stripes will not fill 100% of area.\n\n\n// get global variables\nvar unit = global.get(\"site\").unit.h;\nvar colormap = global.get(\"site\").widgetcolors;\n// declare local variables\nvar c;\nvar colors = [];\n\n// how many stripes should be drawn according to calculated widget size\nvar count = Math.floor(unit * msg.units / 4) - 2; \n\n// parameters for range incoming values into size of widget\nvar params = {minin:msg.min, maxin:msg.max+1, minout:0, maxout:count};\n\n// range\nfunction range(n,params){\n var divisor = params.maxin - params.minin;\n n = ((n - params.minin) % divisor + divisor) % divisor + params.minin;\n n = ((n - params.minin) / (params.maxin - params.minin) * (params.maxout - params.minout)) + params.minout;\n return n;\n}\nvar rangedvalue = Math.round(range(msg.value,params));\n\n// determine color for each stripe\nfor(var i=0; i<count; i++){\n c = rangedvalue <= i ? colormap.off : ( i < (count-5) ? colormap.on : ( i < (count-2) ? colormap.warn : colormap.high));\n colors.push(c);\n}\n//vertical starts drawing from top. colors needed to be in reverse order \nif(msg.direction === \"vertical\"){\n colors.reverse();\n}\n\n// set msg parameters\nmsg.colors = colors;\n\n// for vertical, the last stripe will not be at the bottom exactly.\n// for adjusting min value position.\nmsg.minpos = count*4; \nreturn msg;","outputs":1,"noerr":0,"x":960,"y":320,"wires":[["ae422917.1413b8"]]},{"id":"713cf384.d53dec","type":"function","z":"828c92a8.6bb45","name":"Generate","func":"//drawing of stripes will be done on exact pixels to avoid blur\n//calculated count of stripes will not fill 100% of area.\n\n\n// get global variables\nvar unit = global.get(\"site\").unit.h;\nvar colormap = global.get(\"site\").widgetcolors;\n// declare local variables\nvar c;\nvar colors = [];\n\n// how many stripes should be drawn according to calculated widget size\nvar count = Math.floor(unit * msg.units / 4) - 2; \n\n// parameters for range incoming values into size of widget\nvar params = {minin:msg.min, maxin:msg.max+1, minout:0, maxout:count};\n\n// range\nfunction range(n,params){\n var divisor = params.maxin - params.minin;\n n = ((n - params.minin) % divisor + divisor) % divisor + params.minin;\n n = ((n - params.minin) / (params.maxin - params.minin) * (params.maxout - params.minout)) + params.minout;\n return n;\n}\nvar rangedvalue = Math.round(range(msg.value,params));\n\n// determine color for each stripe\nfor(var i=0; i<count; i++){\n c = rangedvalue <= i ? colormap.off : ( i < (count-5) ? colormap.on : ( i < (count-2) ? colormap.warn : colormap.high));\n colors.push(c);\n}\n//vertical starts drawing from top. colors needed to be in reverse order \nif(msg.direction === \"vertical\"){\n colors.reverse();\n}\n\n// set msg parameters\nmsg.colors = colors;\n\n// for vertical, the last stripe will not be at the bottom exactly.\n// for adjusting min value position.\nmsg.minpos = count*4; \nreturn msg;","outputs":1,"noerr":0,"x":960,"y":360,"wires":[["c1f35432.aeb748"]]},{"id":"1e353951.383ed7","type":"function","z":"828c92a8.6bb45","name":"Generate","func":"//drawing of stripes will be done on exact pixels to avoid blur\n//calculated count of stripes will not fill 100% of area.\n\n\n// get global variables\nvar unit = global.get(\"site\").unit.h;\nvar colormap = global.get(\"site\").widgetcolors;\n// declare local variables\nvar c;\nvar colors = [];\n\n// how many stripes should be drawn according to calculated widget size\nvar count = Math.floor(unit * msg.units / 4) - 2; \n\n// parameters for range incoming values into size of widget\nvar params = {minin:msg.min, maxin:msg.max+1, minout:0, maxout:count};\n\n// range\nfunction range(n,params){\n var divisor = params.maxin - params.minin;\n n = ((n - params.minin) % divisor + divisor) % divisor + params.minin;\n n = ((n - params.minin) / (params.maxin - params.minin) * (params.maxout - params.minout)) + params.minout;\n return n;\n}\nvar rangedvalue = Math.round(range(msg.value,params));\n\n// determine color for each stripe\nfor(var i=0; i<count; i++){\n c = rangedvalue <= i ? colormap.off : ( i < (count-5) ? colormap.on : ( i < (count-2) ? colormap.warn : colormap.high));\n colors.push(c);\n}\n//vertical starts drawing from top. colors needed to be in reverse order \nif(msg.direction === \"vertical\"){\n colors.reverse();\n}\n\n// set msg parameters\nmsg.colors = colors;\n\n// for vertical, the last stripe will not be at the bottom exactly.\n// for adjusting min value position.\nmsg.minpos = count*4; \nreturn msg;","outputs":1,"noerr":0,"x":960,"y":400,"wires":[["3978cc74.8f9694"]]},{"id":"d8cda4f7.3cf358","type":"function","z":"828c92a8.6bb45","name":"Generate","func":"//drawing of stripes will be done on exact pixels to avoid blur\n//calculated count of stripes will not fill 100% of area.\n\n\n// get global variables\nvar unit = global.get(\"site\").unit.h;\nvar colormap = global.get(\"site\").widgetcolors;\n// declare local variables\nvar c;\nvar colors = [];\n\n// how many stripes should be drawn according to calculated widget size\nvar count = Math.floor(unit * msg.units / 4) - 2; \n\n// parameters for range incoming values into size of widget\nvar params = {minin:msg.min, maxin:msg.max+1, minout:0, maxout:count};\n\n// range\nfunction range(n,params){\n var divisor = params.maxin - params.minin;\n n = ((n - params.minin) % divisor + divisor) % divisor + params.minin;\n n = ((n - params.minin) / (params.maxin - params.minin) * (params.maxout - params.minout)) + params.minout;\n return n;\n}\nvar rangedvalue = Math.round(range(msg.value,params));\n\n// determine color for each stripe\nfor(var i=0; i<count; i++){\n c = rangedvalue <= i ? colormap.off : ( i < (count-5) ? colormap.on : ( i < (count-2) ? colormap.warn : colormap.high));\n colors.push(c);\n}\n//vertical starts drawing from top. colors needed to be in reverse order \nif(msg.direction === \"vertical\"){\n colors.reverse();\n}\n\n// set msg parameters\nmsg.colors = colors;\n\n// for vertical, the last stripe will not be at the bottom exactly.\n// for adjusting min value position.\nmsg.minpos = count*4; \nreturn msg;","outputs":1,"noerr":0,"x":960,"y":480,"wires":[["81337426.09e378"]]},{"id":"fb78f9a5.209888","type":"ui_template","z":"828c92a8.6bb45","group":"e19b4f66.fbbeb","name":"svg level horizontal 3x1","order":4,"width":"3","height":"1","format":"<svg width=100% height=100%>\n <defs>\n <filter id=\"level-filter\" x=\"0\" y=\"0\" width=\"200%\" height=\"200%\">\n <feOffset result=\"offOut\" in=\"SourceGraphic\" dx=\"1\" dy=\"1\" />\n <feColorMatrix result=\"matrixOut\" in=\"offOut\" type=\"matrix\"\n values=\"0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.1 0 0 0 0 0 1 0\" />\n <feGaussianBlur result=\"blurOut\" in=\"matrixOut\" stdDeviation=\"5\" />\n <feBlend in=\"SourceGraphic\" in2=\"blurOut\" mode=\"normal\" />\n </filter>\n </defs>\n <rect ng-repeat=\"obj in msg.colors track by $index\" \n y=80% \n ng:attr:x=\"{{$index * 4}}px\"\n width=\"2\"\n height=\"6\" \n style=\"fill:{{msg.colors[$index]}}\" filter=\"url(#level-filter)\"\n />\n <text id=\"level-hor-value\"\n text-anchor=\"middle\" alignment-baseline=\"hanging\"\n x=50%\n y=0>\n {{msg.value}}{{msg.valuesuffix}}\n </text>\n <text id=\"level-minmax\"\n text-anchor=\"start\" alignment-baseline=\"ideographic\"\n x=0\n y=85%>\n {{msg.min}}\n </text>\n <text id=\"level-minmax\"\n text-anchor=\"end\" alignment-baseline=\"ideographic\"\n x=100%\n y=85%>\n {{msg.max}}\n </text>\n</svg> ","storeOutMessages":false,"fwdInMessages":true,"templateScope":"local","x":1140,"y":520,"wires":[[]]},{"id":"5769d1e9.05358","type":"function","z":"828c92a8.6bb45","name":"Configure","func":"msg.value = msg.payload; //expected to be a number\nmsg.min = 0; //expected minimum value of incoming data\nmsg.max = 100; //expected maximum value of incoming data\nmsg.valuesuffix = \"%\" //kind of data \nmsg.direction = \"horizontal\"; //widget look\nmsg.units = 4; //how many units widget takes in direction \n\nreturn msg;","outputs":1,"noerr":0,"x":820,"y":520,"wires":[["bdd01ad3.6a39c8"]]},{"id":"bdd01ad3.6a39c8","type":"function","z":"828c92a8.6bb45","name":"Generate","func":"//drawing of stripes will be done on exact pixels to avoid blur\n//calculated count of stripes will not fill 100% of area.\n\n\n// get global variables\nvar unit = global.get(\"site\").unit.h;\nvar colormap = global.get(\"site\").widgetcolors;\n// declare local variables\nvar c;\nvar colors = [];\n\n// how many stripes should be drawn according to calculated widget size\nvar count = Math.floor(unit * msg.units / 4) - 2; \n\n// parameters for range incoming values into size of widget\nvar params = {minin:msg.min, maxin:msg.max+1, minout:0, maxout:count};\n\n// range\nfunction range(n,params){\n var divisor = params.maxin - params.minin;\n n = ((n - params.minin) % divisor + divisor) % divisor + params.minin;\n n = ((n - params.minin) / (params.maxin - params.minin) * (params.maxout - params.minout)) + params.minout;\n return n;\n}\nvar rangedvalue = Math.round(range(msg.value,params));\n\n// determine color for each stripe\nfor(var i=0; i<count; i++){\n c = rangedvalue <= i ? colormap.off : ( i < (count-5) ? colormap.on : ( i < (count-2) ? colormap.warn : colormap.high));\n colors.push(c);\n}\n//vertical starts drawing from top. colors needed to be in reverse order \nif(msg.direction === \"vertical\"){\n colors.reverse();\n}\n\n// set msg parameters\nmsg.colors = colors;\n\n// for vertical, the last stripe will not be at the bottom exactly.\n// for adjusting min value position.\nmsg.minpos = count*4; \nreturn msg;","outputs":1,"noerr":0,"x":960,"y":520,"wires":[["fb78f9a5.209888"]]},{"id":"f82fcdc6.76ca8","type":"ui_template","z":"828c92a8.6bb45","group":"e19b4f66.fbbeb","name":"svg level horizontal 4x1","order":4,"width":"4","height":"1","format":"<svg width=100% height=100%>\n <defs>\n <filter id=\"level-filter\" x=\"0\" y=\"0\" width=\"200%\" height=\"200%\">\n <feOffset result=\"offOut\" in=\"SourceGraphic\" dx=\"1\" dy=\"1\" />\n <feColorMatrix result=\"matrixOut\" in=\"offOut\" type=\"matrix\"\n values=\"0.1 0 0 0 0 0 0.1 0 0 0 0 0 0.1 0 0 0 0 0 1 0\" />\n <feGaussianBlur result=\"blurOut\" in=\"matrixOut\" stdDeviation=\"5\" />\n <feBlend in=\"SourceGraphic\" in2=\"blurOut\" mode=\"normal\" />\n </filter>\n </defs>\n <rect ng-repeat=\"obj in msg.colors track by $index\" \n y=80% \n ng:attr:x=\"{{$index * 4}}px\"\n width=\"2\"\n height=\"6\" \n style=\"fill:{{msg.colors[$index]}}\" filter=\"url(#level-filter)\"\n />\n <text id=\"level-hor-value\"\n text-anchor=\"middle\" alignment-baseline=\"hanging\"\n x=50%\n y=0>\n {{msg.value}}{{msg.valuesuffix}}\n </text>\n <text id=\"level-minmax\"\n text-anchor=\"start\" alignment-baseline=\"ideographic\"\n x=0\n y=85%>\n {{msg.min}}\n </text>\n <text id=\"level-minmax\"\n text-anchor=\"end\" alignment-baseline=\"ideographic\"\n x=100%\n y=85%>\n {{msg.max}}\n </text>\n</svg> ","storeOutMessages":false,"fwdInMessages":true,"templateScope":"local","x":1140,"y":560,"wires":[[]]},{"id":"b7f58518.7e0968","type":"function","z":"828c92a8.6bb45","name":"Configure","func":"msg.value = msg.payload; //expected to be a number\nmsg.min = 0; //expected minimum value of incoming data\nmsg.max = 100; //expected maximum value of incoming data\nmsg.valuesuffix = \"%\" //kind of data \nmsg.direction = \"horizontal\"; //widget look\nmsg.units = 4; //how many units widget takes in direction \n\nreturn msg;","outputs":1,"noerr":0,"x":820,"y":560,"wires":[["80d29778.56a648"]]},{"id":"80d29778.56a648","type":"function","z":"828c92a8.6bb45","name":"Generate","func":"//drawing of stripes will be done on exact pixels to avoid blur\n//calculated count of stripes will not fill 100% of area.\n\n\n// get global variables\nvar unit = global.get(\"site\").unit.h;\nvar colormap = global.get(\"site\").widgetcolors;\n// declare local variables\nvar c;\nvar colors = [];\n\n// how many stripes should be drawn according to calculated widget size\nvar count = Math.floor(unit * msg.units / 4) - 2; \n\n// parameters for range incoming values into size of widget\nvar params = {minin:msg.min, maxin:msg.max+1, minout:0, maxout:count};\n\n// range\nfunction range(n,params){\n var divisor = params.maxin - params.minin;\n n = ((n - params.minin) % divisor + divisor) % divisor + params.minin;\n n = ((n - params.minin) / (params.maxin - params.minin) * (params.maxout - params.minout)) + params.minout;\n return n;\n}\nvar rangedvalue = Math.round(range(msg.value,params));\n\n// determine color for each stripe\nfor(var i=0; i<count; i++){\n c = rangedvalue <= i ? colormap.off : ( i < (count-5) ? colormap.on : ( i < (count-2) ? colormap.warn : colormap.high));\n colors.push(c);\n}\n//vertical starts drawing from top. colors needed to be in reverse order \nif(msg.direction === \"vertical\"){\n colors.reverse();\n}\n\n// set msg parameters\nmsg.colors = colors;\n\n// for vertical, the last stripe will not be at the bottom exactly.\n// for adjusting min value position.\nmsg.minpos = count*4; \nreturn msg;","outputs":1,"noerr":0,"x":960,"y":560,"wires":[["f82fcdc6.76ca8"]]},{"id":"2accfba2.147474","type":"ui_group","z":"","name":"VERITCAL","tab":"ee954aaa.1cbb18","order":2,"disp":true,"width":"8","collapse":false},{"id":"e19b4f66.fbbeb","type":"ui_group","z":"","name":"HORIZONTAL","tab":"ee954aaa.1cbb18","disp":true,"width":"6","collapse":false},{"id":"efcad470.381fb8","type":"ui_group","z":"","name":"SITE","tab":"b5e9809.d91cc8","disp":true,"width":"6","collapse":false},{"id":"ee954aaa.1cbb18","type":"ui_tab","z":"","name":"WIDGETS","icon":"dashboard","order":1},{"id":"b5e9809.d91cc8","type":"ui_tab","z":"","name":"MAIN","icon":"dashboard","order":1}]


#10

Nice work!
The flow draws upon some of the more advanced features of node-RED/javascript, which unfortunately are above my pay grade :tired_face:
It would be really good if it could be wrapped up into a single library node, but I can see why that probably isn't possible.


#11

Well it is possible for sure. It's just matter of where to draw the line. If the "Thing" already does "This", then why it shouldn't do "that". And this way it is never ending story. That's why I try to do my "things" by myself and targeting them to do the dedicated tasks.

Well everyone starts from some point. Hunger is the best cook. Try, fail, ask, fail again and learn from every step. And never ever give up. There is nothing you can't achieve. There is only new and more advanced targets to reach in every step on the everlasting learning curve.


#12

linear


#13

Hi hotNipi,
Being new to NodeRed I was impressed with your Dashboard design skills. I like the image example you post and beg you if you can also share the code for the Graphics block, upper rigth side of this image. I'm just beggining with a personal project for a data logger at home, recording temperatures in several points and found this representation higly adecuate for this pourpose.
Thanks in advance
Javier


#14

Well I can share those things also. Takes a little time to prepare one to be shareable cos I have those things too deeply tied into my current solution. Let's see if I can do it tomorrow. Or if you are interested with specific thing - point it out :slight_smile: Less things for me to struggle with.

This is where I am right now with my dashboard design


#15

Thats a work of art - i want it !!! Looks great

You have to template this !!

Craig