How to change svg circle attribute based on msg.payload and msg.topic?

Hi,

I've been struggling with this for hours, trawling this forum and the internet generally and trying things which have not worked, so any help would be greatly appreciated!

I am trying to write code which will automatically change the attribute "stroke" of circle elements. The trigger is incoming msg.payload and msg.topic which I can see working with debug. What I can't get right is the correct syntax.

<circle id="lightEast" cx="60" cy="90" r="57.2957795131" stroke=({{msg.payload == "ON" ? "white" : "#999999"}}) fill="transparent" stroke-dashoffset="45"></circle>

I want to change stroke whenever msg.topic = "lightEast", then if msg.payload = "ON" set white as the colour and #999999 when "OFF".

This is the template flow

<svg width="70%" height="70%" viewBox="0 0 120 180" class="donut">
  <circle class="donut-hole" cx="60" cy="90" r="57.2957795131" fill="#333333"></circle>
  <circle class="donut-centre" cx="60" cy="90" r="53" fill="#999999"></circle>
  <circle class="donut-ring" cx="60" cy="90" r="57.2957795131" fill="transparent" stroke="#333333" stroke-width="32"></circle>
<g class="donut-segment" stroke-width="24" stroke-dasharray="86 274">
  <circle id="lightEast" cx="60" cy="90" r="57.2957795131" stroke=({{msg.payload == "ON" ? "white" : "#999999"}}) fill="transparent" stroke-dashoffset="45"></circle>
  <circle id="lightNorth" cx="60" cy="90" r="57.2957795131" stroke="#999999" fill="transparent" stroke-dashoffset="135"></circle>
  <circle id="lightWest" cx="60" cy="90" r="57.2957795131" stroke="#999999" fill="transparent" stroke-dashoffset="225"></circle>
  <circle id="lightSouth" cx="60" cy="90" r="57.2957795131" stroke="#999999" fill="transparent" stroke-dashoffset="315"></circle>
</g>
<text x="35%" y="60%" class="chart-label" font-size="60" fill="white">&#xf0eb;</text>
  <g class="button-text" text-anchor="middle" font-family="Calibri,sans-serif" font-size="16" fill="#333333">
    <text id="lightNorth" x="50%" y="22%">N</text>
    <text id="lightEast" x="97%" y="54%">E</text>
    <text id="lightSouth" x="50%" y="85%">S</text>
    <text id="lightWest" x="3%" y="54%">W</text>
  </g>
</svg>

Are you using the core template node, or the dashboard ui_template node?
(Just checking, because they are not interchangeable)

This code is in a ui_template node. Part if the flow and the dashboard widget created (East is broken).

Screenshot%20from%202019-11-24%2022-42-29 Screenshot%20from%202019-11-24%2022-42-15

I suspect that the values need to be enclosed in single/double quotes.

{{msg.payload == "ON" ? "'white'" : "'#999999'"}}

As far as I remember it is not possible to use mustache syntax inside the ui_template. Edit: The very first example code in the ui_template node shows the use of the ternary operator. That is not JavaScript neither Mustache syntax. I assume it is therefore angular (I am not acquainted with angular).

What you want can be achieved by using a core template node in front of the ui__template. You can use CSS variables for the dynamic part.

Below flow shows what I mean. I used colors white and lime for "ON" and "OFF".

[{"id":"d5558149.45357","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"83d7f84c.882178","type":"inject","z":"d5558149.45357","name":"","topic":"","payload":"ON","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":120,"wires":[["ee171f48.62ff5"]]},{"id":"5f56c187.17054","type":"inject","z":"d5558149.45357","name":"","topic":"","payload":"OFF","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":180,"wires":[["ee171f48.62ff5"]]},{"id":"d0543cef.6eded","type":"template","z":"d5558149.45357","name":"","field":"template","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<!DOCTYPE html>\n<html>\n<head>\n\n<style>\n      \n    :root {\n        --mystroke: {{payload}};\n    }\n    .chart-label {\n        font-family: \"FontAwesome\";\n        stroke: \"black\" ;\n        font-size: 35px;\n        stroke-width: 1;\n    }\n    \n    #lightEast {\n        stroke: var(--mystroke, white);\n    }\n      \n</style>\n\n</head>\n\n<body>\n<svg width=\"70%\" height=\"70%\" viewBox=\"0 0 120 180\" class=\"donut\">\n  <circle class=\"donut-hole\" cx=\"60\" cy=\"90\" r=\"57.2957795131\" fill=\"#333333\"></circle>\n  <circle class=\"donut-centre\" cx=\"60\" cy=\"90\" r=\"53\" fill=\"#999999\"></circle>\n  <circle class=\"donut-ring\" cx=\"60\" cy=\"90\" r=\"57.2957795131\" fill=\"transparent\" stroke=\"#333333\" stroke-width=\"32\"></circle>\n<g class=\"donut-segment\" stroke-width=\"24\" stroke-dasharray=\"86 274\">\n  <circle id=\"lightEast\" cx=\"60\" cy=\"90\" r=\"57.2957795131\"  fill=\"transparent\" stroke-dashoffset=\"45\"></circle>\n  <circle id=\"lightNorth\" cx=\"60\" cy=\"90\" r=\"57.2957795131\" stroke=\"#999999\" fill=\"transparent\" stroke-dashoffset=\"135\"></circle>\n  <circle id=\"lightWest\" cx=\"60\" cy=\"90\" r=\"57.2957795131\" stroke=\"#999999\" fill=\"transparent\" stroke-dashoffset=\"225\"></circle>\n  <circle id=\"lightSouth\" cx=\"60\" cy=\"90\" r=\"57.2957795131\" stroke=\"#999999\" fill=\"transparent\" stroke-dashoffset=\"315\"></circle>\n</g>\n<text x=\"35%\" y=\"60%\" class=\"chart-label\" font-size=\"60\" fill=\"white\">&#xf0eb;</text>\n  <g class=\"button-text\" text-anchor=\"middle\" font-family=\"Calibri,sans-serif\" font-size=\"16\" fill=\"#333333\">\n    <text id=\"lightNorth\" x=\"50%\" y=\"22%\">N</text>\n    <text id=\"lightEast\" x=\"97%\" y=\"54%\">E</text>\n    <text id=\"lightSouth\" x=\"50%\" y=\"85%\">S</text>\n    <text id=\"lightWest\" x=\"3%\" y=\"54%\">W</text>\n  </g>\n</svg>\n</body>\n</html>","output":"str","x":460,"y":160,"wires":[["7798d2c6.33436c"]]},{"id":"5f4cca83.07a184","type":"debug","z":"d5558149.45357","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":770,"y":160,"wires":[]},{"id":"ee171f48.62ff5","type":"change","z":"d5558149.45357","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload = \"ON\" ? \"white\" : \"lime\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":280,"y":160,"wires":[["d0543cef.6eded","4ba84199.d4975"]]},{"id":"4ba84199.d4975","type":"debug","z":"d5558149.45357","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":460,"y":220,"wires":[]},{"id":"7798d2c6.33436c","type":"ui_template","z":"d5558149.45357","group":"27ca2e6c.f90442","name":"","order":5,"width":0,"height":0,"format":"","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":620,"y":160,"wires":[["5f4cca83.07a184"]]},{"id":"27ca2e6c.f90442","type":"ui_group","z":"","name":"G1","tab":"2fc358ed.0a4b48","disp":false,"width":"12","collapse":false},{"id":"2fc358ed.0a4b48","type":"ui_tab","z":"","name":"T","icon":"dashboard","disabled":false,"hidden":false}]

a-1

a-2

2 Likes

@Andrei thanks so much for your example, especially as you took my code and made it work :wink:

I used your example and realised I needed more functionality, which needed a rethink in system design.

This is how the remote control disc is looking now:
Screenshot from 2019-11-26 01-32-31

Because of these extra features needed I expanded on your ideas and I'll tell you what they are in case its of interest.

  1. I have 4 security lights each powered by a Sonoff and 4 PIRs that each supply power to a Sonoff - all Sonoffs talk mqtt to my broker. So these 8 things are all decoupled and can act independently or, with logic, cooperatively. For example one PIR can switch one or more lights on, whilst also alerting me.
  2. I want a dashboard item (the disc) that has the current state of all four lights (on/off) and with the ability to react to "click" events so each arc is a button toggling the power to the respective light - this I am having problems with! (I just opened a separate post on that issue: How to make an svg circle element on click, in a mustache template, output topic/payload?

This is what I have so far and it all works swimmingly apart from the onclick stuff and that I can't seem to get the lightbulb in the centre try as I might!

Flow (first time I exported a bunch of wired nodes, hopefully all the wires stay in place)...

[{"id":"ed975584.ac23e8","type":"mqtt in","z":"e12bb044.9f8c","name":"","topic":"stat/security/light/north/POWER","qos":"0","datatype":"auto","broker":"7b5262ba.b63abc","x":650,"y":700,"wires":[["36a20f33.61d7","27962cfe.50c1e4"]]},{"id":"76487476.6a573c","type":"mqtt in","z":"e12bb044.9f8c","name":"","topic":"stat/security/light/west/POWER","qos":"0","datatype":"auto","broker":"7b5262ba.b63abc","x":650,"y":820,"wires":[["c90ffd67.4b98c","27962cfe.50c1e4"]]},{"id":"88c47026.ad73d","type":"mqtt in","z":"e12bb044.9f8c","name":"","topic":"stat/security/light/south/POWER","qos":"0","datatype":"auto","broker":"7b5262ba.b63abc","x":650,"y":780,"wires":[["30028040.92d3d","27962cfe.50c1e4"]]},{"id":"60ed2b3d.61b2e4","type":"mqtt in","z":"e12bb044.9f8c","name":"","topic":"stat/security/light/east/POWER","qos":"0","datatype":"auto","broker":"7b5262ba.b63abc","x":650,"y":740,"wires":[["4d55c146.de4b5","27962cfe.50c1e4"]]},{"id":"cfbc3ca.98ddec","type":"mqtt out","z":"e12bb044.9f8c","name":"","topic":"cmnd/security/light/east/power","qos":"0","retain":"true","broker":"7b5262ba.b63abc","x":1210,"y":740,"wires":[]},{"id":"e2bba51d.783c18","type":"ui_button","z":"e12bb044.9f8c","name":"","group":"e6654390.323a7","order":3,"width":3,"height":3,"passthru":false,"label":"East","tooltip":"","color":"{{payload}}","bgcolor":"","icon":"fa-lightbulb-o fa-x4","payload":"toggle","payloadType":"str","topic":"","x":1010,"y":740,"wires":[["cfbc3ca.98ddec"]]},{"id":"4d55c146.de4b5","type":"function","z":"e12bb044.9f8c","name":"On or OFF ?","func":"if (msg.payload === \"ON\"){\n    msg.payload = \"white\";\n} else {\n    msg.payload = \"red\";\n   } \n   return msg;\n","outputs":1,"noerr":0,"x":870,"y":740,"wires":[["e2bba51d.783c18"]]},{"id":"36a20f33.61d7","type":"function","z":"e12bb044.9f8c","name":"On or OFF ?","func":"if (msg.payload === \"ON\"){\n    msg.payload = \"white\";\n} else {\n    msg.payload = \"red\";\n   } \n   return msg;\n","outputs":1,"noerr":0,"x":870,"y":700,"wires":[["1617fedc.aa02b1"]]},{"id":"1617fedc.aa02b1","type":"ui_button","z":"e12bb044.9f8c","name":"","group":"e6654390.323a7","order":2,"width":3,"height":3,"passthru":false,"label":"North","tooltip":"","color":"{{payload}}","bgcolor":"","icon":"fa-lightbulb-o fa-x4","payload":"toggle","payloadType":"str","topic":"","x":1010,"y":700,"wires":[["39f40233.49d2fe"]]},{"id":"39f40233.49d2fe","type":"mqtt out","z":"e12bb044.9f8c","name":"","topic":"cmnd/security/light/north/power","qos":"0","retain":"true","broker":"7b5262ba.b63abc","x":1210,"y":700,"wires":[]},{"id":"30028040.92d3d","type":"function","z":"e12bb044.9f8c","name":"On or OFF ?","func":"if (msg.payload === \"ON\"){\n    msg.payload = \"white\";\n} else {\n    msg.payload = \"red\";\n   } \n   return msg;\n","outputs":1,"noerr":0,"x":870,"y":780,"wires":[["6181e0d1.8d4a7"]]},{"id":"6181e0d1.8d4a7","type":"ui_button","z":"e12bb044.9f8c","name":"","group":"e6654390.323a7","order":5,"width":3,"height":3,"passthru":false,"label":"South","tooltip":"","color":"{{payload}}","bgcolor":"","icon":"fa-lightbulb-o fa-x4","payload":"toggle","payloadType":"str","topic":"","x":1010,"y":780,"wires":[["1b047495.f374cb"]]},{"id":"1b047495.f374cb","type":"mqtt out","z":"e12bb044.9f8c","name":"","topic":"cmnd/security/light/south/power","qos":"0","retain":"true","broker":"7b5262ba.b63abc","x":1210,"y":780,"wires":[]},{"id":"c90ffd67.4b98c","type":"function","z":"e12bb044.9f8c","name":"On or OFF ?","func":"if (msg.payload === \"ON\"){\n    msg.payload = \"white\";\n} else {\n    msg.payload = \"red\";\n   } \n   return msg;\n","outputs":1,"noerr":0,"x":870,"y":820,"wires":[["b10446c6.6e47b8"]]},{"id":"b10446c6.6e47b8","type":"ui_button","z":"e12bb044.9f8c","name":"","group":"e6654390.323a7","order":4,"width":3,"height":3,"passthru":false,"label":"West","tooltip":"","color":"{{payload}}","bgcolor":"","icon":"fa-lightbulb-o fa-x4","payload":"toggle","payloadType":"str","topic":"","x":1010,"y":820,"wires":[["f3a31f17.5d8e1"]]},{"id":"f3a31f17.5d8e1","type":"mqtt out","z":"e12bb044.9f8c","name":"","topic":"cmnd/security/light/west/power","qos":"0","retain":"true","broker":"7b5262ba.b63abc","x":1210,"y":820,"wires":[]},{"id":"9fcbadbf.c7383","type":"comment","z":"e12bb044.9f8c","name":"Toggle Buttons with State","info":"","x":170,"y":640,"wires":[]},{"id":"8217a47b.e51678","type":"mqtt out","z":"e12bb044.9f8c","name":"","topic":"cmnd/security/light/north/power","qos":"0","retain":"true","broker":"7b5262ba.b63abc","x":370,"y":700,"wires":[]},{"id":"66729b86.f6f604","type":"mqtt out","z":"e12bb044.9f8c","name":"","topic":"cmnd/security/light/east/power","qos":"0","retain":"true","broker":"7b5262ba.b63abc","x":370,"y":740,"wires":[]},{"id":"d6cdc865.7a3d98","type":"mqtt out","z":"e12bb044.9f8c","name":"","topic":"cmnd/security/light/south/power","qos":"0","retain":"true","broker":"7b5262ba.b63abc","x":370,"y":780,"wires":[]},{"id":"259fa12b.1bb59e","type":"mqtt out","z":"e12bb044.9f8c","name":"","topic":"cmnd/security/light/west/power","qos":"0","retain":"true","broker":"7b5262ba.b63abc","x":370,"y":820,"wires":[]},{"id":"f1a4b005.fb9d2","type":"inject","z":"e12bb044.9f8c","name":"Initialize","topic":"","payload":"\"\"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":760,"wires":[["8217a47b.e51678","66729b86.f6f604","d6cdc865.7a3d98","259fa12b.1bb59e"]]},{"id":"e5c9e76c.eba358","type":"debug","z":"e12bb044.9f8c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1170,"y":900,"wires":[]},{"id":"973388ad.18b348","type":"template","z":"e12bb044.9f8c","name":"","field":"template","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<!DOCTYPE html>\n<html>\n<head>\n\n<style>\n    :root {\n        --colourNorth: {{global.stateLightColourNorth}};\n        --colourEast: {{global.stateLightColourEast}};\n        --colourSouth: {{global.stateLightColourSouth}};\n        --colourWest: {{global.stateLightColourWest}};\n    }\n    .chart-label {\n        font-family: \"FontAwesome\";\n        font-size: 60px;\n    }\n    \n    #lightNorth {\n        stroke: var(--colourNorth, \"dimgray\");\n    }\n    #lightEast {\n        stroke: var(--colourEast, \"dimgray\");\n    }\n    #lightSouth {\n        stroke: var(--colourSouth, \"dimgray\");\n    }\n    #lightWest {\n        stroke: var(--colourWest, \"dimgray\");\n    }\n      \n</style>\n\n</head>\n\n<body>\n<svg width=\"70%\" height=\"70%\" viewBox=\"0 0 120 180\" class=\"donut\">\n  <circle class=\"donut-hole\" cx=\"60\" cy=\"90\" r=\"57.2957795131\" fill=\"dimgray\"></circle>\n  <circle class=\"donut-centre\" cx=\"60\" cy=\"90\" r=\"53\" fill=\"dimgray\"></circle>\n  <circle class=\"donut-ring\" cx=\"60\" cy=\"90\" r=\"57.2957795131\" fill=\"transparent\" stroke=\"#333333\" stroke-width=\"32\"></circle>\n<g class=\"donut-segment\" stroke-width=\"24\" stroke-dasharray=\"86 274\">\n  <circle id=\"lightEast\" ng-click=\"send({topic:'east',payload:'toggle'})\" cx=\"60\" cy=\"90\" r=\"57.2957795131\"  fill=\"transparent\" stroke-dashoffset=\"45\"></circle>\n  <circle id=\"lightNorth\" cx=\"60\" cy=\"90\" r=\"57.2957795131\" fill=\"transparent\" stroke-dashoffset=\"135\"></circle>\n  <circle id=\"lightWest\" cx=\"60\" cy=\"90\" r=\"57.2957795131\"  fill=\"transparent\" stroke-dashoffset=\"225\"></circle>\n  <circle id=\"lightSouth\" cx=\"60\" cy=\"90\" r=\"57.2957795131\" fill=\"transparent\" stroke-dashoffset=\"315\"></circle>\n</g>\n<text x=\"40%\" y=\"60%\" class=\"chart-label\" fill=\"white\">&#xf0eb;</text>\n  <g class=\"button-text\" text-anchor=\"middle\" font-family=\"Calibri,sans-serif\" font-size=\"16\" fill=\"#333333\">\n    <text id=\"lightNorthText\" x=\"50%\" y=\"22%\">N</text>\n    <text id=\"lightEastText\" x=\"97%\" y=\"54%\">E</text>\n    <text id=\"lightSouthText\" x=\"50%\" y=\"85%\">S</text>\n    <text id=\"lightWestText\" x=\"3%\" y=\"54%\">W</text>\n  </g>\n</svg>\n</body>\n</html>","output":"str","x":820,"y":900,"wires":[["b3a47ca5.17f69"]]},{"id":"b3a47ca5.17f69","type":"ui_template","z":"e12bb044.9f8c","group":"e6654390.323a7","name":"Quadrants","order":7,"width":"4","height":"4","format":"","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":990,"y":900,"wires":[["e5c9e76c.eba358"]]},{"id":"27962cfe.50c1e4","type":"function","z":"e12bb044.9f8c","name":"update Light state","func":"// this takes an incoming mqtt stat command and \n// and uses the data to write out a global variable\n\n// capatilzation function\nfunction capitalizeFirstLetter(string) {\n    return string.charAt(0).toUpperCase() + string.slice(1);\n}\n\n// regex to get quadrant from msg.topic (mqtt topic)\nvar myRe = /.*light\\/(\\w+)\\/POWER/g;\n\n// set & get quadrant data\nvar rawquadrant = myRe.exec(msg.topic);\nvar quadrant = capitalizeFirstLetter(rawquadrant[1]);\n\n// set quadrant data\nvar quadrantState = [];\nquadrantState[0] = \"stateLight\".concat(quadrant);\nquadrantState[1] = msg.payload;\n\nvar quadrantStateColour = [];\nquadrantStateColour[0] = \"stateLightColour\".concat(quadrant);\nquadrantStateColour[1] = msg.payload === \"ON\" ? \"white\" : \"dimgray\";\n\n// set global variables\nglobal.set(quadrantState[0],quadrantState[1]);\nglobal.set(quadrantStateColour[0],quadrantStateColour[1]);\n\nreturn msg;","outputs":1,"noerr":0,"x":610,"y":900,"wires":[["973388ad.18b348"]]},{"id":"7b5262ba.b63abc","type":"mqtt-broker","z":"","name":"phoscon","broker":"192.168.1.8","port":"1883","clientid":"","usetls":false,"compatmode":false,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","willTopic":"","willQos":"0","willPayload":""},{"id":"e6654390.323a7","type":"ui_group","z":"","name":"Security","tab":"78f6af5d.516ad","order":1,"disp":true,"width":"6","collapse":false},{"id":"78f6af5d.516ad","type":"ui_tab","z":"","name":"Lighting","icon":"fa-lightbulb-o","order":1,"disabled":false,"hidden":false}]
1 Like

Hi @gr1nch, I am happy that I could help you to advance a little in your project. Surely I will have a look in your other post later today.

1 Like

Is this a duplicate thread because I could swear that I'd commented on this?

Anyway, my comment was: have you tried the SVG Dashboard extension node? I think this is the kind of thing that it was designed for?

Of course, I also have an example you could use as a basis using uibuilder if you don't actually need Dashboard :wink:

1 Like

Texts in svg aligned by using coordinates (pixels or percentages) and additionally baseline and anchor adjustments.

If you have center of your svg known, adjust text with icon to be x=50% and y=50% (or in pixels if needed)
then add dominant-baseline="middle" text-anchor="middle" inline and you should have centered icon.

See my reply in the other thread which includes a path that eliminates the need for FontAwesome.

@Andrei that would be much appreciated thank you. This help is all so encouraging that I already feel that sticking with Node-Red and trying to also contribute is definitely worthwhile.

Not really, but the project is the same. This thread is about processing incoming data into the UI to change UI elements - now solved thanks to you guys. The other thread is about making on click events work to send custom topic/payloads out.

Yes, I tried it but the code I have possibly (in its current form) confuses the svg node as no on click events work with it.

I see you like uibuilder :wink: . I installed it to try it out but currently your "Clickable Dial" code renders fine but I get not on click events.

Thanks @hotNipi. I read the dominant-baseline doc as you suggested but did not understand it at all as I do not have enough pre-knowledge to make sense of it. However I added your code snippet to my "clickable dial" and the lightbulb centres sweetly. I changed the x value back to 50% and adjusted the y value down to 47% to make it aesthetically pleasing to my eye.

I tried to find that but the only FontAwesome reference in the thread was in the OP - How to make an svg circle element on click, in a mustache template, output topic/payload?

Well, that's what happens when you invest a lot of time creating something :wink:

1 Like