How to make an svg circle element on click, in a mustache template, output topic/payload?

Hi,
Screenshot from 2019-11-26 01-32-31
Without success I'm trying to get code in a template node to get working on click functionality svg circle elements that output msg.topic & msg.payload when clicked in the browser. The output msg will then into another node to toggle the relevant light.

Any help to point me to the relevant docs, hints that help we understand what is going on, or what I should be looking at will be most helpful.

Here is the current "on click" snippet of code that is not working. I have tried other syntaxes after reading various online solutions, but none had addressed this particular issue. I am a stranger to mustache syntax, Angular and javascript too! What a steep learning curve node red is if you want to get fancy with it! :slight_smile:

<circle id="lightEast" ng-click="send({topic:'east',payload:'toggle'})" cx="60" cy="90" r="57.2957795131" fill="transparent" stroke-dashoffset="45"></circle>

The flow
Screenshot from 2019-11-26 01-43-15

The code in the template (edit UI shown below)

<!DOCTYPE html>
<html>
<head>

<style>
    :root {
        --colourNorth: {{global.stateLightColourNorth}};
        --colourEast: {{global.stateLightColourEast}};
        --colourSouth: {{global.stateLightColourSouth}};
        --colourWest: {{global.stateLightColourWest}};
    }
    .chart-label {
        font-family: "FontAwesome";
        font-size: 60px;
    }
    
    #lightNorth {
        stroke: var(--colourNorth, "dimgray");
    }
    #lightEast {
        stroke: var(--colourEast, "dimgray");
    }
    #lightSouth {
        stroke: var(--colourSouth, "dimgray");
    }
    #lightWest {
        stroke: var(--colourWest, "dimgray");
    }
      
</style>

</head>

<body>
<svg width="70%" height="70%" viewBox="0 0 120 180" class="donut">
  <circle class="donut-hole" cx="60" cy="90" r="57.2957795131" fill="dimgray"></circle>
  <circle class="donut-centre" cx="60" cy="90" r="53" fill="dimgray"></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" ng-click="send({topic:'east',payload:'toggle'})" cx="60" cy="90" r="57.2957795131"  fill="transparent" stroke-dashoffset="45"></circle>
  <circle id="lightNorth" cx="60" cy="90" r="57.2957795131" fill="transparent" stroke-dashoffset="135"></circle>
  <circle id="lightWest" cx="60" cy="90" r="57.2957795131"  fill="transparent" stroke-dashoffset="225"></circle>
  <circle id="lightSouth" cx="60" cy="90" r="57.2957795131" fill="transparent" stroke-dashoffset="315"></circle>
</g>
<text x="40%" y="60%" class="chart-label" fill="white">&#xf0eb;</text>
  <g class="button-text" text-anchor="middle" font-family="Calibri,sans-serif" font-size="16" fill="#333333">
    <text id="lightNorthText" x="50%" y="22%">N</text>
    <text id="lightEastText" x="97%" y="54%">E</text>
    <text id="lightSouthText" x="50%" y="85%">S</text>
    <text id="lightWestText" x="3%" y="54%">W</text>
  </g>
</svg>
</body>
</html>

Have you tried the svg node extension for Dashboard?

If you mean node-red-contrib-ui-svg then yes. When I pasted ui_template code into it I could set the onclick events but could see no output when clicked, but on advice from a contributor here, for other use case requirements moved the code to the template mode inputting into ui_template. Whe I then tried the svg node in place of both (inputting the template code) I don't get any of the classes showing up in the Event tab. I am going to ask the ui-svg maintainer about this.

Independently I'd like to know how to add code to an svg element in template mode so that on click events can work in the dashboard via the ui_template mode. This I hope is a fairly common requirement, but finding this amongst all the relevant forum posts has been fruitless. I've probably searched-read-tested for we over 4 hours on this topic alone :frowning: - growing pains!

Fair enough, thought it best to check.

I can't really help with the Dashboard side of things as I don't use it for anything complex. But I do know that I can get this working with uibuilder and VueJS fairly easily as you will see from the thread I posted a while back.

Interesting. Your code is working for me without any modification. What browser you are using for the dashboard ? Here Chrome.

a-02

Edit: Just noted that it will not work when my click strikes exactly in the top of the letter "E". It works ok when i click elsewhere inside the East area.

I added the ng-click statement to the other areas and see that it works well. I find this svg code very smart. You created four circles with the very same parameters (cx, cy, radius) and still the code is smart enough to identify which circle has been clicked. Well done.

a-03

More that Dashboard gets very complex very quickly if you start pushing it outside its defaults.

2 Likes

I don't think that it is quite right. A quick test showed that the click works if you click far enough from the centre. But if you click further it - maybe inside the position of the text - it picks up the lightSouth circle not the north one.

With uibuilder

        <h2>Clickable Dial</h2>
        <div id="dial" style="position:relative; width:100%; height:50em;">
            <svg width="70%" height="70%" viewBox="0 0 120 180" class="donut">
                <circle class="donut-hole" cx="60" cy="90" r="57.2957795131" fill="red"></circle>
                <circle class="donut-centre" cx="60" cy="90" r="53" fill="dimgray"></circle>
                <circle class="donut-ring" cx="60" cy="90" r="57.2957795131" fill="transparent" stroke="#333333" stroke-width="32"></circle>
                <g transform="scale(.05) translate(700,1200)">
                    <path fill="#ffffff" d="M511.549861 803.293331H408.419043a73.232959 73.232959 0 0 1-67.1862-41.991375 59.795719 59.795719 0 0 1-6.71862-30.569722 207.60536 207.60536 0 0 0-33.593101-113.88061 196.519637 196.519637 0 0 0-27.882273-33.5931A463.248853 463.248853 0 0 1 217.274302 504.314738a399.086031 399.086031 0 0 1-36.95241-75.248544 242.542184 242.542184 0 0 1-15.116895-77.264131 349.032312 349.032312 0 0 1 8.062344-84.990544 314.76735 314.76735 0 0 1 51.733375-114.888403A367.172586 367.172586 0 0 1 361.724634 34.011334 327.532728 327.532728 0 0 1 433.949799 8.144647 369.524103 369.524103 0 0 1 528.682342 0.418234a333.579486 333.579486 0 0 1 126.310057 29.225997 326.860866 326.860866 0 0 1 70.881442 44.678824A382.625412 382.625412 0 0 1 808.848799 168.383736a314.095488 314.095488 0 0 1 41.991375 105.146403 312.751764 312.751764 0 0 1 6.382689 92.045095 275.799353 275.799353 0 0 1-20.15586 76.256338 449.139751 449.139751 0 0 1-61.139443 107.16199 497.513815 497.513815 0 0 1-33.5931 39.639858 160.575019 160.575019 0 0 0-31.241583 48.038134 215.331773 215.331773 0 0 0-18.812136 55.428615c-1.679655 11.757585 0 23.179239-2.687448 33.5931a171.660742 171.660742 0 0 1-3.695241 25.194826 69.873649 69.873649 0 0 1-33.593101 40.647651 74.576683 74.576683 0 0 1-39.639858 10.07793zM490.050277 88.768088c-11.085723 0-22.171446 2.351517-33.5931 4.031172a210.96467 210.96467 0 0 0-74.240752 26.538549 244.221839 244.221839 0 0 0-55.428616 44.342893 222.386324 222.386324 0 0 0-43.335099 63.82689 230.784599 230.784599 0 0 0-19.483998 94.732543 28.218204 28.218204 0 0 0 33.5931 28.890066 28.890066 28.890066 0 0 0 22.171446-26.202618v-13.773171a167.965501 167.965501 0 0 1 9.406068-49.045927 184.762052 184.762052 0 0 1 64.834684-83.98275 167.965501 167.965501 0 0 1 93.72475-33.593101 142.770676 142.770676 0 0 0 18.140274 0 23.851101 23.851101 0 0 0 19.148067-15.452826 33.5931 33.5931 0 0 0 0-19.483998 23.51517 23.51517 0 0 0-20.491791-18.140274 122.950747 122.950747 0 0 0-15.116895 0zM647.601917 943.040628a15.788757 15.788757 0 0 1-13.773171 15.116895H400.356699a17.468412 17.468412 0 0 1-16.460619-8.734206 18.812136 18.812136 0 0 1 0-20.15586 16.124688 16.124688 0 0 1 16.460619-4.703034h227.089358a19.148067 19.148067 0 0 1 19.148067 20.827722zM405.731595 899.369598a18.140274 18.140274 0 0 1-16.460619-12.765378 17.804343 17.804343 0 0 1 15.452826-23.851101H635.508401a18.812136 18.812136 0 0 1 17.804343 13.773171 19.819929 19.819929 0 0 1-10.749792 21.499584 24.187032 24.187032 0 0 1-8.734206 0H423.535938zM437.64504 1022.992207a17.132481 17.132481 0 0 1-15.452826-9.406068 18.140274 18.140274 0 0 1 15.116895-26.202618h139.411367a19.819929 19.819929 0 0 1 19.483998 17.804343 16.124688 16.124688 0 0 1-8.734206 15.788757 19.148067 19.148067 0 0 1-9.741999 3.023379H442.348074z" />
                </g>
                <g class="donut-segment" stroke-width="24" stroke-dasharray="86 274">
                    <circle id="lightEast" @click="doClickSegment" cx="60" cy="90" r="57.2957795131" fill="transparent" stroke-dashoffset="45"></circle>
                    <circle id="lightNorth" @click="doClickSegment" cx="60" cy="90" r="57.2957795131" fill="transparent" stroke-dashoffset="135"></circle>
                    <circle id="lightWest" @click="doClickSegment" cx="60" cy="90" r="57.2957795131" fill="transparent" stroke-dashoffset="225"></circle>
                    <circle id="lightSouth" @click="doClickSegment" class="on" cx="60" cy="90" r="57.2957795131" fill="transparent" stroke-dashoffset="315"></circle>
                </g>
                <g class="button-text" text-anchor="middle" font-family="Calibri,sans-serif" font-size="16" fill="#333333">
                    <text id="lightNorthText" x="50%" y="22%">N</text>
                    <text id="lightEastText" x="97%" y="54%">E</text>
                    <text id="lightSouthText" x="50%" y="85%">S</text>
                    <text id="lightWestText" x="3%" y="54%">W</text>
                </g>
            </svg>
        </div>
#lightNorth {
    stroke:dimgray; /*var(--colourNorth, "dimgray");*/
}
#lightEast {
    stroke: dimgray;
}
#lightSouth {
    stroke: dimgray;
}
#lightWest {
    stroke: dimgray;
}
.on {
    stroke: white !important;
}
    methods: {

        doClickSegment: function(event) {
            console.log('***EVENT***', event)
            
            uibuilder.send({
                'topic':'east',
                'payload':'toggle',
                'event':event,
            })
        },

    }, // --- End of methods --- //

Hi Julian, indeed. Doing additional testing (after your post) I confirm that the clicking is not consistent. How is it performing with the ui_builder example you provided ? Is it working better ?

@gr1nch, I still could not figure out the principle that allows to define four different areas using circles with same coordinates and radius. If I had to guess the behavior before running the flow I would say that clicking in any area would always lead to the same result , which is not the case. I would be happy to learn what is the idea behind the the area definition.

Nope, that's how I discovered the issue. It is something to do with the definition of the SVG but I'm not really good enough with SVG to work out a better way to write it (and I was supposed to be working after all :wink:)

Chrome - Version 78.0.3904.108 (Official Build) (64-bit) - on Linux (Fedora 31)

It would be very nice to accept your compliment @Andrei but I can't :wink:. It was reused code from this article Scratch-made SVG Donut & Pie Charts in HTML5 by Mark Caron c. 2016.

1 Like

A quick glance at that article suggests that the magic seems to be related to stroke-dasharray and stroke-dashoffset parameters. I will have a careful read tonight to understand better. Perhaps we can find a fix to make the code reliable.

1 Like

This might help https://www.smashingmagazine.com/2018/05/svg-interaction-pointer-events-property/

1 Like

Not sure that it will. I've done some more testing and it is clear that the segment circles do not detect pointer events very accurately at all. If you go too far into the circle, it picks up the last defined element and if you get too close to the last defined circle, it also picks it up.

You can only go about 1/2 way into any of the circles except that last one before it switches to the last one.

There is an alternative approach using arcs instead of circles. This does work correctly but working out the correct numbers is HARD!

        <h2><a href="http://xahlee.info/js/svg_circle_arc.html">Drawing Arcs</a></h2>
        <div>
            <svg viewBox="0 0 350 350" width="100%" height="50em" xmlns="http://www.w3.org/2000/svg" style="border:1px solid silver" version="1.1">
                <circle class="donut-bg" cx="130" cy="130" r="130" fill="transparent" stroke="dimgray" stroke-width="12"></circle>
                <g>
                    <path id="AA" @click="doClickSegment" d=" M 267 274 A 100 100 0 0 1 129 271" fill="none" stroke="blue" stroke-width="80"/>
                    <path id="BB" @click="doClickSegment" d=" M 126 267 A 100 100 90 0 1 129 129" fill="none" stroke="green" stroke-width="80"/>
                    <path id="CC" @click="doClickSegment" d=" M 133 126 A 100 100 180 0 1 271 129" fill="none" stroke="pink" stroke-width="80"/>
                    <path id="DD" @click="doClickSegment" d=" M 274 133 A 100 100 270 0 1 271 271" fill="none" stroke="brown" stroke-width="80"/>
                </g>
            </svg>
        </div>

As you can see from the example, arcs go along a path so you don't work from the centre like to do with a circle. So I haven't yet managed to get the circle background to line up and I'm too tired and annoyed with SVG now so I'm jacking it in.

I used the following site to help work out the correct numbers:

http://xahlee.info/js/svg_circle_arc.html

1 Like

Fully agree. I am pretty sure it will be much easier using D3.js as it has an arc generator function. I did a quick testing and it seems nice. I need however to polish the code before posting.

a-04

1 Like

@Andrei @TotallyInformation It is great to have a couple of Node-Red experts trying to solve a tricky problem. Thank you for your efforts so far and I hope you are successful :grinning:

I have installed uibuilder and added your "Clickable Dial" code @TotallyInformation , but for the life of me no element is clickable. Same with my code that get clickable events for you @Andrei which I have in a template node outputting to a ui_template node.

I'm wondering if it really is my Chrome version that is the problem or some config within Chrome (Tampermonkey?) that could be disabling onclicks though trying out test code from other webpages clicks just fine. It's perplexing.

@gr1nch, strange. May you try this "clicking" testing flow ?

a-05

[{"id":"71492edf.6a91b","type":"tab","label":"D3 - Four circles","disabled":false,"info":"![](/nri/d3-click-circle.png)"},{"id":"12f9f37d.163ebd","type":"ui_template","z":"71492edf.6a91b","group":"66ff152c.aabe1c","name":"","order":0,"width":"16","height":"5","format":"<!DOCTYPE html>\n\n<html>\n\n<head>\n  <style>\n\n\ncircle {\n  cursor: pointer;\n}\n\n</style>\n\n</head>\n\n\n<body>\n    \n    <div class=\"status\">Click on a circle</div>\n    \n  <svg width=\"760\" height=\"140\">\n    <g transform=\"translate(70, 70)\">\n      <circle r=\"40\" cx=\"120\" fill=\"red\"/>\n      <circle r=\"40\" cx=\"180\" fill=\"green\"/>\n      <circle r=\"40\" cx=\"240\" fill=\"blue\"/>\n      <circle r=\"40\" cx=\"300\" fill=\"yellow\"/>\n    </g>\n  </svg>\n\n\n\n  <script>\n\n    (function(scope){\n        d3.selectAll('circle').on('click', handleClick);\n        function handleClick(x, y) {\n            d3.select('.status').text('You clicked on circle ' + y);\n            scope.send({\"Circle number: \":y});\n        };\n    })(scope);\n\n  </script>\n  \n</body>\n</html>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":140,"y":120,"wires":[["d5de80c1.dc953"]]},{"id":"a7fe10c8.fc425","type":"comment","z":"71492edf.6a91b","name":"D3 on.click sending msg to the runtime","info":"Based on this post:\n\nhttps://stackoverflow.com/questions/50968952/node-red-ui-template-send-msg\n","x":230,"y":60,"wires":[]},{"id":"d5de80c1.dc953","type":"debug","z":"71492edf.6a91b","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":280,"y":120,"wires":[]},{"id":"66ff152c.aabe1c","type":"ui_group","z":"","name":"LAB","tab":"ce29ad0.9250a5","order":3,"disp":false,"width":"16","collapse":false},{"id":"ce29ad0.9250a5","type":"ui_tab","z":"","name":"Table","icon":"dashboard"}]

I would certainly recommend firing up a private browsing session so that you don't have any extensions loaded.

I'm using Edge Dev by the way - also Chromium based.

1 Like

Works perfectly for me! Nice test :slight_smile: I notice the flow contains the code in one node only (ui_template) and not the "template node feeding the ui_template node" in my set up.

1 Like

@gr1nch, I believe you. I already faced some kind of issue in a past project when trying to use the template node to generate the code for the ui_template. I just can´t remember what was the constraint.

I kept working on a different solution but could spend only three hours tonight. It was not enough to finish the flow. I will post the progress below and will resume working on it the day after tomorrow.

[{"id":"3a7650b3.10396","type":"tab","label":"Flow 3","disabled":false,"info":""},{"id":"450245a.f0d83bc","type":"comment","z":"3a7650b3.10396","name":"https://codepen.io/alsheuski/pen/BKpZpK","info":"","x":220,"y":300,"wires":[]},{"id":"9ce61744.5c2968","type":"comment","z":"3a7650b3.10396","name":"https://www.d3indepth.com/shapes/#arc-generator","info":"","x":250,"y":260,"wires":[]},{"id":"fd7e3e07.f804e","type":"debug","z":"3a7650b3.10396","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":630,"y":120,"wires":[]},{"id":"44d089e1.7266f8","type":"inject","z":"3a7650b3.10396","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":150,"y":120,"wires":[["48432f9b.7784e"]]},{"id":"48432f9b.7784e","type":"template","z":"3a7650b3.10396","name":"","field":"template","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<!DOCTYPE html>\n\n<html>\n\n<head>\n\n<style>\n\n    svg {\n      background-color: white;\n      margin-left: auto;\n      margin-right: auto;\n      display: block;\n    }\n\n    :root {\n        --colourNorth: {{global.stateLightColourNorth}};\n        --colourEast: {{global.stateLightColourEast}};\n        --colourSouth: {{global.stateLightColourSouth}};\n        --colourWest: {{global.stateLightColourWest}};\n    }\n    \n    .myicon {\n        font-family: \"FontAwesome\";\n        font-size: 30px;\n    }\n    \n    #lightNorth {\n        fill: var(--colourNorth, \"dimgray\");\n    }\n    \n    #lightEast {\n        fill: var(--colourEast, \"dimgray\");\n    }\n    \n    #lightSouth {\n        fill: var(--colourSouth, \"dimgray\");\n    }\n    \n    #lightWest {\n        fill: var(--colourWest, \"dimgray\");\n    }\n      \n</style>\n\n</head>\n\n<body>\n\n    <script>\n    let w = 200;\n    let h = 200;\n\n    var svg = d3.select(\"#T1_LAB\")\n        .append(\"svg\")\n        .attr(\"width\", w)\n        .attr(\"height\", h);\n    \nvar arc1 = d3.svg.arc()\n      .innerRadius(40)\n      .outerRadius(60)\n      .startAngle(45 * Math.PI/180) \n      .endAngle(-45 * Math.PI/180); \n  \n  var arc2 = d3.svg.arc()\n      .innerRadius(40)\n      .outerRadius(60)\n      .startAngle(45 * Math.PI/180) \n      .endAngle(135 * Math.PI/180);  \n  \n  var arc3 = d3.svg.arc()\n      .innerRadius(40)\n      .outerRadius(60)\n      .startAngle(135 * Math.PI/180) \n      .endAngle(225 * Math.PI/180);  \n  \n  var arc4 = d3.svg.arc()\n      .innerRadius(40)\n      .outerRadius(60)\n      .startAngle(-45 * Math.PI/180)  \n      .endAngle(-135 * Math.PI/180);    \n\nvar path1 = svg.append('path')\n      .attr('d', arc1)\n      .attr('stroke', 'black')\n      .attr('stroke-width', 3)\n      .attr('transform', 'translate('+ w/2 + ','+ h/2 +')')\n      .attr('id', 'lightNorth');\n  \n  var path2 = svg.append('path')\n      .attr('d', arc2)\n      .attr('stroke', 'black')\n      .attr('stroke-width', 3)\n      .attr('transform', 'translate('+ w/2 + ','+ h/2 +')')\n      .attr('id', 'lightEast');\n  \n  var path3 = svg.append('path')\n      .attr('d', arc3)\n      .attr('stroke', 'black')\n      .attr('stroke-width', 3)\n      .attr('transform', 'translate('+ w/2 + ','+ h/2 +')')\n      .attr('id', 'lightSouth');\n  \n  var path4 = svg.append('path')\n      .attr('d', arc4)\n      .attr('stroke', 'black')\n      .attr('stroke-width', 3)\n      .attr('transform', 'translate('+ w/2 + ','+ h/2 +')')\n      .attr('id', 'lightWest');\n  \n  var innerCircle = svg.append('circle')\n      .attr('cx', w/2)\n      .attr('cy', h/2)\n      .attr('r', 40)\n      .attr('fill', 'dimgray');\n  \n  var textCenter = svg.append('text')\n    .attr('y', 10 + h/2)\n    .attr('x', w/2)\n    .attr('text-anchor', 'middle')\n    .attr('class', 'myicon')\n    .attr('fill', 'yellow')\n    .text('\\uf0eb');\n  \n    </script>\n  \n    <script>\n        (function(scope){\n            d3.selectAll('path').on('click', handleClick);\n            function handleClick(x, y) {\n                //d3.select('.status').text('You clicked on element ' + y);\n                scope.send({\"Element number: \": y});\n            };\n        })(scope);\n  </script>\n  \n  </body>\n  </html>\n  ","output":"str","x":300,"y":120,"wires":[["588b77c7.af5168"]]},{"id":"588b77c7.af5168","type":"ui_template","z":"3a7650b3.10396","group":"66ff152c.aabe1c","name":"","order":2,"width":"0","height":"0","format":"","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":480,"y":120,"wires":[["fd7e3e07.f804e"]]},{"id":"15995eb0.d06221","type":"comment","z":"3a7650b3.10396","name":"https://fontawesome.com/cheatsheet?from=io","info":"","x":230,"y":220,"wires":[]},{"id":"66ff152c.aabe1c","type":"ui_group","z":"","name":"LAB","tab":"ce29ad0.9250a5","order":3,"disp":false,"width":"16","collapse":false},{"id":"ce29ad0.9250a5","type":"ui_tab","z":"","name":"T1","icon":"dashboard","disabled":false,"hidden":false}]

a-06