I think the easiest way to build a donut chart in svg, is by putting a smaller circle on top of the pie chart that we created above:
That inner circle needs to have the same center point as the full cirlce, and the same color as your svg background. Which means we are cheating, by hiding a part of the pie chart behind the inner circle.
In the function node you can see a new property donutWidth
that is used to calculate the radius of the inner circle:
radius inner circle = radius full circle - donutWidth
Here is the update pie chart flow, to draw a donut chart:
[{"id":"cefc97a537a9df53","type":"ui_svg_graphics","z":"c53e793dd2f3be09","group":"6293cddec4b65c5f","order":1,"width":"9","height":"9","svgString":"<svg x=\"0\" y=\"0\" height=\"100%\" viewBox=\"0 0 200 200\" width=\"100%\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n <!-- Pie chart (consisting out of a full circle and a circle segment on top of it -->\n <path id=\"donut_full_circle\" style=\"fill:lightgreen\" />\n <path id=\"donut_segment\" style=\"fill:limegreen\" />\n <path id=\"donut_inner_circle\" style=\"fill:white\" />\n</svg>","clickableShapes":[],"javascriptHandlers":[],"smilAnimations":[],"bindings":[],"showCoordinates":false,"autoFormatAfterEdit":false,"showBrowserErrors":true,"showBrowserEvents":false,"enableJsDebugging":false,"sendMsgWhenLoaded":false,"noClickWhenDblClick":false,"outputField":"payload","editorUrl":"//drawsvg.org/drawsvg.html","directory":"","panning":"disabled","zooming":"disabled","panOnlyWhenZoomed":false,"doubleClickZoomEnabled":false,"mouseWheelZoomEnabled":false,"dblClickZoomPercentage":150,"cssString":"div.ui-svg svg{\n color: var(--nr-dashboard-widgetColor);\n fill: currentColor !important;\n}\ndiv.ui-svg path {\n fill: inherit;\n}","name":"","x":820,"y":120,"wires":[[]]},{"id":"96d685d613bae445","type":"function","z":"c53e793dd2f3be09","name":"Calculate pie chart paths","func":"// Define the center and radius\nconst centerX = 100;\nconst centerY = 100;\nconst radius = 50;\nconst donutWidth = 10;\n\n// Full Circle\nconst fullCirclePath = `M ${centerX - radius}, ${centerY} \n A ${radius}, ${radius} 0 1, 0 ${centerX + radius}, ${centerY} \n A ${radius}, ${radius} 0 1, 0 ${centerX - radius}, ${centerY} Z`\n\n// Inner Circle\nconst innerRadius = radius - donutWidth;\nconst innerCirclePath = `M ${centerX - innerRadius}, ${centerY} \n A ${innerRadius}, ${innerRadius} 0 1, 0 ${centerX + innerRadius}, ${centerY} \n A ${innerRadius}, ${innerRadius} 0 1, 0 ${centerX - innerRadius}, ${centerY} Z`\n\n// Segment\nconst segmentStartAngle = 0; // Specify the start angle for the segment\n\nlet segmentPercentage = msg.payload;\n\n// Calculate the segment stop angle based on the percentage from the input messag\nlet segmentStopAngle;\nif (segmentPercentage === 100) {\n // Handle the case when the percentage is 100%\n segmentStopAngle = 359.99; // Set it to 360 degrees for a full circle\n} else {\n segmentStopAngle = (segmentPercentage / 100) * 360;\n}\n\n// Convert angles to radians\nconst segmentStartAngleRad = (segmentStartAngle * Math.PI) / 180;\nconst segmentStopAngleRad = (segmentStopAngle * Math.PI) / 180;\n\n// Calculate the start and end points of the segment arc\nconst segmentStartX = centerX + radius * Math.cos(segmentStartAngleRad);\nconst segmentStartY = centerY + radius * Math.sin(segmentStartAngleRad);\nconst segmentEndX = centerX + radius * Math.cos(segmentStopAngleRad);\nconst segmentEndY = centerY + radius * Math.sin(segmentStopAngleRad);\n\n// Determine the large-arc-flag and sweep-flag for the segment\nconst segmentLargeArcFlag = segmentStopAngle - segmentStartAngle > 180 ? 1 : 0;\nconst segmentSweepFlag = 1;\n\n// Create the path data for the segment\nconst segmentPath = `M ${segmentStartX},${segmentStartY} A ${radius},${radius} 0 ${segmentLargeArcFlag},${segmentSweepFlag} ${segmentEndX},${segmentEndY} L ${centerX},${centerY} Z`;\n\nmsg.payload = [{\n \"command\": \"set_attribute\",\n \"selector\": \"#donut_full_circle\",\n \"attributeName\": \"d\",\n \"attributeValue\": fullCirclePath\n },\n {\n \"command\": \"set_attribute\",\n \"selector\": \"#donut_segment\",\n \"attributeName\": \"d\",\n \"attributeValue\": segmentPath\n },\n {\n \"command\": \"set_attribute\",\n \"selector\": \"#donut_inner_circle\",\n \"attributeName\": \"d\",\n \"attributeValue\": innerCirclePath\n }]\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":590,"y":120,"wires":[["cefc97a537a9df53"]]},{"id":"019aeda8ccb43e5a","type":"inject","z":"c53e793dd2f3be09","name":"0%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":370,"y":120,"wires":[["96d685d613bae445"]]},{"id":"1133f18615aefcad","type":"inject","z":"c53e793dd2f3be09","name":"25%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"25","payloadType":"num","x":370,"y":160,"wires":[["96d685d613bae445"]]},{"id":"e45cfc7c29d3bf30","type":"inject","z":"c53e793dd2f3be09","name":"50%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"50","payloadType":"num","x":370,"y":200,"wires":[["96d685d613bae445"]]},{"id":"5d1bb13331a00be0","type":"inject","z":"c53e793dd2f3be09","name":"75%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"75","payloadType":"num","x":370,"y":240,"wires":[["96d685d613bae445"]]},{"id":"93432edbf797e7ab","type":"inject","z":"c53e793dd2f3be09","name":"100%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"100","payloadType":"num","x":370,"y":280,"wires":[["96d685d613bae445"]]},{"id":"6293cddec4b65c5f","type":"ui_group","name":"Donut chart demo","tab":"db70d53e.369ce8","order":2,"disp":true,"width":"9","collapse":false,"className":""},{"id":"db70d53e.369ce8","type":"ui_tab","name":"Memory usage","icon":"dashboard","disabled":false,"hidden":false}]
Which looks like this:
I need to think about the gauge...