Skip to content
This repository has been archived by the owner on Aug 10, 2024. It is now read-only.

Display partially filled shapes

bartbutenaers edited this page Sep 13, 2023 · 2 revisions

Lots of IOT systems measure liquid levels, so it is useful to learn how to display this in an SVG drawing in Node-RED.

On the internet you will find a lot of workarounds: they duplicate a shape, put them on top of each other with different colors, and then start modifying these shapes to get the required look. However duplicating shapes makes it very hard to maintain drawings. Therefore this tutorial shows how to apply a gradient to an existing shape, to achieve a similar effect without needing to duplicate shapes.

The basics - partially filled rectangle

Let's start with filling a rectangle. Instead of filling it with a solid color, we are going to fill it with a linear gradient (by specifying the gradient id):

<linearGradient x1="0%" y1="100%" x2="0%" y2="0%" id="rectangle_gradient">
   <stop stop-color="DodgerBlue" offset="0%" class="rectangle_gradient_stop"/>
   <stop stop-color="White" offset="0%" class="rectangle_gradient_stop"/>
</linearGradient>
<rect x="50" y="50" width="100" height="150" style="stroke:black; fill:url(#rectangle_gradient)"></rect>

Let's analyze how this works:

  • A gradient can be used to create a smooth transition from one color to another.

  • Specify where the linear gradient starts (x1,y1) and where it ends (x2,y2). Since we want to keep the color gradient within our rectangle shape, we will use percentages (of the shape to which this gradient is being applied) instead of absolute coordinate values. In the above example, the linear gradient starts at the left-bottom corner of the rectangle (0%, 100%). And the gradient ends at the left-top corner (0%, 0%) of the rectangle, which means the direction is vertically upwards. We need to do it like that, because 0% means no water in our case:

    image

  • It is possible to have more than one color transition, by adding a stop tag for every color that we need. For each stop element you need to specify the corresponding color and offset. The offset is the position where the gradient needs to become this color, which again is a percentage of the rectangle. In our case we only need 2 colors:

    • White for the air above the water.
    • Blue for the water.
  • In our case we don't need a smooth transition between white a blue, but instead we want a very abrupt transition (between the water and the air above it). To accomplish this, we will give both stop elements the same offset value. We start with two default offset values of 0%, to start with an empty rectangle without water:

    • The blue color runs until 0% of the height of the rectangle.
    • The white color starts at 0% of the height of the rectangle.
  • Now we can inject a message to adjust the offset attribute of both stop elements in the gradient (via the gradient class). For example the rectangle can be filled with water 30% via this input message:

    {
       "payload": {
          "command": "update_attribute",
          "selector": ".rectangle_gradient_stop",
          "attributeName": "offset",
          "attributeValue": "30%"
       }
    }
    

The following example flow demonstrates this:

[{"id":"030150f711975909","type":"ui_svg_graphics","z":"f6f2187d.f17ca8","group":"eac9211dd6c76f9b","order":1,"width":0,"height":0,"svgString":"<svg width=\"200\" height=\"200\" viewBox=\"0 0 200 200\" xmlns=\"http://www.w3.org/2000/svg\">\n    <linearGradient x1=\"0%\" y1=\"100%\" x2=\"0%\" y2=\"0%\" id=\"rectangle_gradient\">\n        <stop stop-color=\"DodgerBlue\" offset=\"0%\" class=\"rectangle_gradient_stop\"/>\n        <stop stop-color=\"White\" offset=\"0%\" class=\"rectangle_gradient_stop\"/>\n    </linearGradient>\n    <rect x=\"50\" y=\"50\" width=\"100\" height=\"150\" style=\"stroke:black; fill:url(#rectangle_gradient)\"></rect>\n</svg>\n","clickableShapes":[],"javascriptHandlers":[],"smilAnimations":[],"bindings":[],"showCoordinates":false,"autoFormatAfterEdit":false,"showBrowserErrors":false,"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":"Partial filled rectangle","x":1680,"y":340,"wires":[[]]},{"id":"98fc07e73c2e0292","type":"inject","z":"f6f2187d.f17ca8","name":"20%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"20","payloadType":"num","x":1250,"y":420,"wires":[["c42bf2979a800243"]]},{"id":"c42bf2979a800243","type":"function","z":"f6f2187d.f17ca8","name":"compose command","func":"return {\n    \"payload\": {\n        \"command\": \"update_attribute\",\n        \"selector\": \".rectangle_gradient_stop\",\n        \"attributeName\": \"offset\",\n        \"attributeValue\": msg.payload + \"%\"\n    }\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1450,"y":340,"wires":[["030150f711975909"]]},{"id":"8adbdca0f5da2086","type":"inject","z":"f6f2187d.f17ca8","name":"0%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":1250,"y":460,"wires":[["c42bf2979a800243"]]},{"id":"81ccc2dc9746897f","type":"inject","z":"f6f2187d.f17ca8","name":"100%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"100","payloadType":"num","x":1250,"y":340,"wires":[["c42bf2979a800243"]]},{"id":"d4243492b493fcb0","type":"inject","z":"f6f2187d.f17ca8","name":"60%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"60","payloadType":"num","x":1250,"y":380,"wires":[["c42bf2979a800243"]]},{"id":"eac9211dd6c76f9b","type":"ui_group","name":"Rectangle animation demo","tab":"b7631828414c3b00","order":1,"disp":true,"width":"6","collapse":false,"className":""},{"id":"b7631828414c3b00","type":"ui_tab","name":"SVG stuff","icon":"dashboard","disabled":false,"hidden":false}]

fill_rectangle

Filling partially any shape

Such a linear gradient can be used to partially fill any SVG shape. The following example flow demonstrates how to fill an SVG path element:

[{"id":"632419222a143f9e","type":"ui_svg_graphics","z":"f6f2187d.f17ca8","group":"eac9211dd6c76f9b","order":1,"width":"6","height":"7","svgString":"<svg version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\" viewBox=\"0 0 256 256\" enable-background=\"new 0 0 256 256\" xml:space=\"preserve\">\n  <linearGradient x1=\"0%\" y1=\"100%\" x2=\"0%\" y2=\"0%\" id=\"tank_gradient\">\n    <stop stop-color=\"DodgerBlue\" offset=\"0%\" class=\"tank_gradient_stop\" />\n    <stop stop-color=\"White\" offset=\"0%\" class=\"tank_gradient_stop\" />\n  </linearGradient>\n  <path style=\"stroke:black; fill:url(#tank_gradient)\" d=\"M122.8,3.3c-14.2,1.8-27.2,9.7-34.9,21.4c-3,4.4-5.6,10.5-6.8,15.3l-1,4.2l-2.7,0.2c-3.6,0.2-4.6,1.2-4.6,4.6v2.6h-8.1c-9.9,0-12.7,0.5-18.3,3.3C39.5,58.4,33.9,65,32,72.5c-0.7,2.6-0.8,6.9-0.8,22.9v19.7l-1.5,0.6c-6,2.4-8.7,4-11.3,6.5c-3.5,3.3-5.3,6.1-6.9,10.9c-1.1,3.3-1.1,4.5-1.4,37.3c-0.2,38.1-0.1,40.7,2.9,46.6c2.6,5.2,7.5,9.8,13.5,12.6l4.7,2.2l-0.2,3.6l-0.2,3.6l-10.4,0.2L10,239.4v6.8v6.8h118h118v-6.8v-6.7l-8.2-0.2l-8.3-0.2l-0.2-3.9l-0.2-3.9l3.4-1.7c4.6-2.3,8.7-6.5,11.2-11.4l2-4.1l0.2-59C246,99,245.9,96.1,245,93c-4.6-14.6-21.3-21.3-34.9-13.9c-3.1,1.7-8,6.7-9.6,9.8c-2.7,5.4-2.8,5.9-2.8,44.7v36.2h-7.1h-7.1v-2.6c0-3.4-1.1-4.4-4.5-4.4h-2.5v-58.5c0-49.9-0.1-59.1-0.8-62.4c-3.9-18.3-18.4-33.3-36.2-37.4C134,3.2,127.6,2.8,122.8,3.3z M72.9,68.3c0,1.4,0.3,2.9,0.7,3.3c0.4,0.4,1.8,0.7,3.3,0.7h2.5l0.2,59c0.2,65,0,61.4,3.6,70.5c4.7,11.7,15.2,22.3,26.9,27c1.8,0.7,5,1.7,7.2,2.2l3.9,1v3.7v3.7H83H44.7l0.2-4.1l0.2-4.1l3.3-1.3c4.4-1.8,10.4-6.3,12.9-10c1.1-1.6,2.6-4.7,3.3-6.8c1.2-3.8,1.2-4,1.2-39.6c0-38.5,0-38.8-2.9-44.5c-2-4-6.5-8-11.7-10.5c-2.4-1.1-4.9-2.2-5.4-2.4c-0.9-0.3-1-1.7-1-19c0-20.9,0.2-22.4,3.6-26.4c1.1-1.3,3.3-2.9,4.7-3.6c2.5-1.2,3.5-1.3,11.3-1.3h8.5L72.9,68.3L72.9,68.3z M197.7,197.2c0,15,0.4,18,3.1,22.6c2.9,4.9,7.9,9.3,12.8,11l2,0.7v4v4h-40.1h-40.1v-3.7v-3.7l3.9-0.9c14.3-3.5,26.6-13.4,32.7-26.4c1.6-3.4,2.5-6.2,4.4-13.4c0.1-0.4,1.2-0.7,2.9-0.7c3.4,0,4.4-0.9,4.4-4c0-1.2,0.2-2.5,0.4-2.7c0.2-0.2,3.4-0.4,7.1-0.4h6.7L197.7,197.2L197.7,197.2z\" />\n</svg>","clickableShapes":[],"javascriptHandlers":[],"smilAnimations":[],"bindings":[],"showCoordinates":false,"autoFormatAfterEdit":false,"showBrowserErrors":false,"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":"Partial filled tank","x":2360,"y":460,"wires":[[]]},{"id":"f1a2bc2f52c536d5","type":"inject","z":"f6f2187d.f17ca8","name":"20%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"20","payloadType":"num","x":1950,"y":540,"wires":[["49f8dd5a7646c714"]]},{"id":"49f8dd5a7646c714","type":"function","z":"f6f2187d.f17ca8","name":"compose command","func":"return {\n    \"payload\": {\n        \"command\": \"update_attribute\",\n        \"selector\": \".tank_gradient_stop\",\n        \"attributeName\": \"offset\",\n        \"attributeValue\": msg.payload + \"%\"\n    }\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":2150,"y":460,"wires":[["632419222a143f9e"]]},{"id":"d3e2e8ee1d6defb9","type":"inject","z":"f6f2187d.f17ca8","name":"0%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":1950,"y":580,"wires":[["49f8dd5a7646c714"]]},{"id":"38210c81d312c053","type":"inject","z":"f6f2187d.f17ca8","name":"100%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"100","payloadType":"num","x":1950,"y":460,"wires":[["49f8dd5a7646c714"]]},{"id":"b85e4cb8c98ff1a1","type":"inject","z":"f6f2187d.f17ca8","name":"60%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"60","payloadType":"num","x":1950,"y":500,"wires":[["49f8dd5a7646c714"]]},{"id":"eac9211dd6c76f9b","type":"ui_group","name":"Rectangle animation demo","tab":"b7631828414c3b00","order":1,"disp":true,"width":"6","collapse":false,"className":""},{"id":"b7631828414c3b00","type":"ui_tab","name":"SVG stuff","icon":"dashboard","disabled":false,"hidden":false}]

svg_fill_path

Fill direction - horizontally

As explained above the (x1,y1) and (x2,y2) values can be used to specify the gradient direction, i.e. in which direction the shape is being filled with the gradient.

The following gradient:

<linearGradient x1="0%" y1="0%" x2="100%" y2="0%" id="battery_gradient">

Will result in a horizontal gradient, from left to right:

image

As the following example flow demonstrates:

[{"id":"e5f0432a3e1c2eeb","type":"function","z":"f6f2187d.f17ca8","name":"compose command","func":"return {\n    \"payload\": {\n        \"command\": \"update_attribute\",\n        \"selector\": \".battery_gradient_stop\",\n        \"attributeName\": \"offset\",\n        \"attributeValue\": msg.payload + \"%\"\n    }\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":2110,"y":420,"wires":[["cdefb4c210fda5ff"]]},{"id":"cdefb4c210fda5ff","type":"ui_svg_graphics","z":"f6f2187d.f17ca8","group":"eac9211dd6c76f9b","order":1,"width":"6","height":"7","svgString":"<svg version=\"1.1\" id=\"Layer_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\" viewBox=\"0 0 122.88 70.13\" style=\"enable-background:new 0 0 122.88 70.13\" xml:space=\"preserve\">\n    <linearGradient x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\" id=\"battery_gradient\">\n    <stop stop-color=\"OrangeRed\" offset=\"0%\" class=\"battery_gradient_stop\" />\n    <stop stop-color=\"White\" offset=\"0%\" class=\"battery_gradient_stop\" />\n  </linearGradient>\n<polygon style=\"stroke:black; fill:url(#battery_gradient)\" points=\"0.5,31.5 0.5,8.5 37.5,8.5 37.5,13.5 39.5,13.5 39.5,26.5 37.5,26.5 37.5,31.5   \"/>\n\n</svg>","clickableShapes":[],"javascriptHandlers":[],"smilAnimations":[],"bindings":[],"showCoordinates":false,"autoFormatAfterEdit":false,"showBrowserErrors":false,"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":"Partial filled battery","x":2330,"y":420,"wires":[[]]},{"id":"c55feb44bbb1ea2f","type":"inject","z":"f6f2187d.f17ca8","name":"20%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"20","payloadType":"num","x":1910,"y":500,"wires":[["e5f0432a3e1c2eeb"]]},{"id":"0e7f408f167ceed4","type":"inject","z":"f6f2187d.f17ca8","name":"0%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":1910,"y":540,"wires":[["e5f0432a3e1c2eeb"]]},{"id":"14205f1cfd5fc2ae","type":"inject","z":"f6f2187d.f17ca8","name":"100%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"100","payloadType":"num","x":1910,"y":420,"wires":[["e5f0432a3e1c2eeb"]]},{"id":"3c37d45827857f41","type":"inject","z":"f6f2187d.f17ca8","name":"60%","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"60","payloadType":"num","x":1910,"y":460,"wires":[["e5f0432a3e1c2eeb"]]},{"id":"eac9211dd6c76f9b","type":"ui_group","name":"Rectangle animation demo","tab":"b7631828414c3b00","order":1,"disp":true,"width":"6","collapse":false,"className":""},{"id":"b7631828414c3b00","type":"ui_tab","name":"SVG stuff","icon":"dashboard","disabled":false,"hidden":false}]

svg_battery_fill