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

Partially filled shape (based on gradients)

bartbutenaers edited this page May 2, 2022 · 2 revisions

A common use case in visualization of IOT applications, are partially filled shapes.
Some examples:

  • Blinds that are partial open.
  • A tank partial filled with fluid.
  • A distance between an object and a sensor.
  • The sound volume of a speaker.
  • ...

In this tutorial we will explain how to visualize a partially filled circle-shaped oil tank. Of course any other shape can be filled.

Introduction to gradients

A gradient is a smooth transition from one color to another. SVG supports all kinds of gradients: linear, radial, ...

In the following example we create a linear gradient that goes from yellow to red, by specifying (stop) colors for position 0% and 100%:

<defs>
   <linearGradient id="my_gradient" x1="0%" y1="0%" x2="100%" y2="0%">
      <stop offset="0%" stop-color="yellow" />
      <stop offset="100%" stop-color="red" />
   </linearGradient>
</defs>
<rect x="10" y="10" width="400" height="100" fill="url(#my_gradient)"/>

By applying the gradient to the rectangle (via the fill url), we will get something like this:

image

Via the x1/y1/x2/y2 we can specify to which directions these percentages are related. In this case 0% means the left side and 100% means the right side, so we change the color from left to right. Since y1 does not differ from y2, there is only a color change in the X direction...

Create a partial filled shape

  1. Add a circle to the drawing:

    <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3"/>
    
  2. Now we will add a gradient to our drawing.

    <defs>
       <linearGradient id="circle_fill_gradient" x1="0%" x2="0%" y1="100%" y2="0%">
          <stop offset="0%" stop-color="dodgerblue" />
          <stop offset="50%" stop-color="dodgerblue" id="circle_offset_1" />
          <stop offset="50%" stop-color="white" id="circle_offset_2" />
          <stop offset="100%" stop-color="white" />
       </linearGradient>
    </defs>
    

    Via x1/y1/x2/y2 we specify that the 0% is at the bottom and 100% is at the top, which means we will fill the shape with the gradient from bottom to top. And we will use the blue color at the bottom (for the fluid), and the white color at the top (for the empty area above the fluid).

    Note that we have 4 different stop colors, instead of 2 colors. Indeed we don't want to have a smooth transition from blue to white. Instead this code snippet shows a blue color from 0% to 50%, and then white from 50% to 100%. Which means we will get an abrupt color change at 50%.

    The result will be a circle shape which is filled half:

    image

  3. We have specified an id ("circle_offset_1" and "circle_offset_2") for the stop colors at 50%, because that allows us to change the offset percentage of both stop colors very easily via a single input message. For example the following message payload will change the offset to 25%:

    [
       {
          command: "set_attribute",
          selector: "#circle_offset_1",
          attributeName: "offset",
          attributeValue: "25%"
       },
       {
          command: "set_attribute",
          selector: "#circle_offset_2",
          attributeName: "offset",
          attributeValue: "25%"
       }
    ]
    

    So you always need to change both offsets, to make sure there is no smooth transition between both colors.

Example flow

The following example flow shows how a number (between 0 and 100) can simulate a sensor value, to change the fluid level:

image

[{"id":"98d8ef2e2cd646d7","type":"ui_svg_graphics","z":"8fee6c7e9e28fe94","group":"f014eb03.a3c618","order":1,"width":"14","height":"10","svgString":"<svg version=\"1.1\" id=\"Capa_1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0px\" y=\"0px\" width=\"100%\" height=\"100%\" viewBox=\"0 0 463 463\" style=\"enable-background:new 0 0 463 463;\" xml:space=\"preserve\">\n  <defs>\n    <linearGradient id=\"circle_fill_gradient\" x1=\"0\" x2=\"0\" y1=\"1\" y2=\"0\">\n      <stop offset=\"0%\" stop-color=\"dodgerblue\" />\n      <stop offset=\"50%\" stop-color=\"dodgerblue\" id=\"circle_offset_1\" />\n      <stop offset=\"50%\" stop-color=\"white\" id=\"circle_offset_2\" />\n      <stop offset=\"100%\" stop-color=\"white\" />\n    </linearGradient>\n  </defs>\n  <circle cx=\"50\" cy=\"50\" r=\"40\" stroke=\"black\" stroke-width=\"3\" fill=\"url(#circle_fill_gradient)\" />\n</svg>","clickableShapes":[{"targetId":"#temp_living","action":"change","payload":"temperature_living","payloadType":"str","topic":"temperature_living"}],"javascriptHandlers":[{"selector":"#temp_living","action":"change","sourceCode":"if(evt.target.valueAsNumber < 10) {\n    $(\"#temp_living_label\")[0].style.color=\"blue\";\n}\nelse if(evt.target.valueAsNumber > 20) {\n    $(\"#temp_living_label\")[0].style.color=\"red\";\n}\nelse {\n    $(\"#temp_living_label\")[0].style.color=\"orange\";\n}"}],"smilAnimations":[],"bindings":[],"showCoordinates":false,"autoFormatAfterEdit":false,"showBrowserErrors":true,"showBrowserEvents":true,"enableJsDebugging":true,"sendMsgWhenLoaded":false,"noClickWhenDblClick":false,"outputField":"","editorUrl":"http://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":700,"y":320,"wires":[[]]},{"id":"3b736443b32a9749","type":"inject","z":"8fee6c7e9e28fe94","name":"Value 0","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":250,"y":320,"wires":[["b2ae01c2ad154562"]]},{"id":"b2ae01c2ad154562","type":"function","z":"8fee6c7e9e28fe94","name":"Change gradient offset","func":"msg.payload = [\n    {\n        command: \"set_attribute\",\n        selector: \"#circle_offset_1\",\n        attributeName: \"offset\",\n        attributeValue: msg.payload + \"%\"\n    },\n    {\n        command: \"set_attribute\",\n        selector: \"#circle_offset_2\",\n        attributeName: \"offset\",\n        attributeValue: msg.payload + \"%\"\n    }\n]\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":320,"wires":[["98d8ef2e2cd646d7"]]},{"id":"2b9ecbd548389816","type":"inject","z":"8fee6c7e9e28fe94","name":"Value 25","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"25","payloadType":"num","x":260,"y":360,"wires":[["b2ae01c2ad154562"]]},{"id":"41b7ebbd718019ab","type":"inject","z":"8fee6c7e9e28fe94","name":"Value 50","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"50","payloadType":"num","x":260,"y":400,"wires":[["b2ae01c2ad154562"]]},{"id":"aea6bf6a34253e3c","type":"inject","z":"8fee6c7e9e28fe94","name":"Value 75","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"75","payloadType":"num","x":260,"y":440,"wires":[["b2ae01c2ad154562"]]},{"id":"54e2adbe3704e9c3","type":"inject","z":"8fee6c7e9e28fe94","name":"Value 100","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"100","payloadType":"num","x":260,"y":480,"wires":[["b2ae01c2ad154562"]]},{"id":"f014eb03.a3c618","type":"ui_group","name":"Partial fill demo","tab":"80068970.6e2868","order":1,"disp":true,"width":"18","collapse":false,"className":""},{"id":"80068970.6e2868","type":"ui_tab","name":"SVG","icon":"dashboard","order":14,"disabled":false,"hidden":false}]

Which will result in this:

partial-fill-demo