Tank widget

TankBlue

A tank is a visual representation of a container containing a fluid. It could be used for a rainwater tank or for the ink levels in printer cartridges or tanks.

This widget is the result of some experiments here, where I tried different techniques to make a widget for my Epson printer. @JustinG encouraged me to try different approaches. This was the best result. It is a dynamic svg image. It contains a path for the outline of the tank and a linear gradient for the fill. The fill element is controlled by the level item.

As it is an svg image, it can be scaled, including the text for level and label. I must admit that the scaling is not completely as I expected.

In responsive layout it is the layout itself that decides how wide the columns or cells will be. So the widget height could be to high. Therefore a property has been added to control the height in px.

In fixed canvas layout or fixed grid layout there is more control. But the height property has an influence too: it seems to be an upper limit: you can scale down, but not up.

Configuration:

  • title: optional title for the card
  • levelItem: Item that contains the level in %
  • fillColor: Fill color: red, blue,… or rgb(200,10,65) or ‘#ff0066
  • tankLabel: optional text label under the tank
  • height: height in px (default 200px). This is needed for responsive layout, but also affects the canvas layout.
  • width: Width (no units, default: 80)

Screenshots

Examples in responsive layout. The columns have an equal width.

Examples in fixed canvas layout:

Changelog

Version 0.3

  • added a width property

Version 0.2

  • added animation (thanks to @JustinG)
  • added preserveAspectRatio for better looking scaling in fixed Canvas mode
  • explained the scaling in a bit more detail

Version 0.1

  • initial release

Resources

uid: tankWidget
tags: []
props:
  parameters:
    - description: Title
      label: Title
      name: title
      required: false
      type: TEXT
    - context: item
      description: Item that contains the level in %
      label: Level Item
      name: levelItem
      required: true
      type: TEXT
    - description: "Fill color: red, blue,... or rgb(200,10,65) or '#ff0066'"
      label: Fill color
      name: fillColor
      required: false
      type: TEXT
    - description: Text label under the tank
      label: Tank Label
      name: tankLabel
      required: false
      type: TEXT
    - default: 200px
      description: "Height in px (needed for responsive layout, default: 200px)"
      label: Height
      name: height
      required: false
      type: TEXT
    - default: 80
      description: "Width (no units, default: 80)"
      label: Width
      name: width
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Mar 3, 2024, 9:50:32 AM
component: f7-card
config:
  outline: true
  title: =props.title
slots:
  default:
    - component: f7-block
      config:
        style:
          display: flex
          height: =props.height
          justify-content: center
      slots:
        default:
          - component: svg
            config:
              preserveAspectRatio: xMidYMin meet
              viewBox: ='0 0 '+props.width+' 230'
              xlmns: http://www.w3.org/2000/svg
            slots:
              default:
                - component: text
                  config:
                    content: =items[props.levelItem].state+'%'
                    fill: =themeOptions.dark=='dark'?'white':'black'
                    text-anchor: middle
                    x: =props.width/2
                    y: 15
                - component: defs
                  slots:
                    default:
                      - component: linearGradient
                        config:
                          comment: the id can only be used once in a page; so we have to make it unique if we want to use the widget more than once
                          id: ='Grad'+props.levelItem
                          x1: 0%
                          x2: 0%
                          y1: 100%
                          y2: 0%
                        slots:
                          default:
                            - component: stop
                              config:
                                comment: in order to have a hard line, both stops must use the same offset
                                offset: =items[props.levelItem].state+'%'
                                stop-color: =props.fillColor
                            - component: stop
                              config:
                                comment: the upper part is transparent
                                offset: =items[props.levelItem].state+'%'
                                stop-color: rgba(0,0,0,0)
                            - component: animate
                              config:
                                attributeName: y2
                                dur: 1s
                                from: 100%
                                repeatCount: 1
                                to: 0%
                - component: path
                  config:
                    d: ='M 0,20 q 10,0 10,10 l 0 160 q 0,10 10,10  l '+(props.width-40)+',0 q 10,0 10,-10 l 0,-160 q 0,-10 10,-10 M 10,110 l 20,0 M 10,65 l 10,0 M 10,155 l 10,0'
                    fill: ='url(#Grad'+props.levelItem+')'
                    stroke: =themeOptions.dark=='dark'?'white':'black'
                    stroke-width: 2
                - component: text
                  config:
                    content: =props.tankLabel
                    fill: =themeOptions.dark=='dark'?'white':'black'
                    text-anchor: middle
                    x: =props.width/2
                    y: 220

5 Likes

Because you are using the svg structure, it’s really easy to add some fun little flourishes to this. For example, if you want the tank to “fill” each time you load the widget, you just need to add an animate tag to your gradient:

- component: linearGradient
  config:
    comment: the id can only be used once in a page; so we have to make it unique if we want to use the widget more than once
    id: ='Grad'+props.levelItem
    x1: 0%
    x2: 0%
    y1: 100%
    y2: 0%
  slots:
    default:
      - component: stop
        config:
          comment: in order to have a hard line, both stops must use the same offset 
          offset: =items[props.levelItem].state+'%'
          stop-color: =props.fillColor
      - component: stop
        config:
          comment: the upper part is transparent
          offset: =items[props.levelItem].state+'%'
          stop-color: rgba(0,0,0,0)
      - component: animate
        config:
          attributeName: "y2"
          dur: "1s"
          from: "100%"
          to: "0%"
          repeatCount: 1

And you get this:
tank-fill

4 Likes

Added the animation of @JustinG and also a width property to better represent a rainwater tank.