Tank component

Well… it doesn’t exist. Neither in Openhab nor in Framework 7 :roll_eyes:

A tank is a visual representation of a container containing a fluid. It could be used for a rainwater tank or more commonly for the ink levels in printer cartridges or tanks (e.g. Epson).

I have seen some widgets that use F7 progressbars for this. I even made one:


This looks nice, but it is not a correct representation of the real world: the ink leaks if the tanks are horizontal.

With some CSS learning, I made a more correct widget:

uid: Tank
tags: []
props:
  parameters:
    - default: Epson Printer
      description: Title
      name: title
      required: false
      type: TEXT
    - context: item
      default: EpsonStatus
      description: Printer Status item
      label: Item
      name: EpsonStatus
      required: false
      type: TEXT
    - context: item
      default: EpsonPaginas
      description: Totaal aantal afgedrukte pagina's
      label: Item
      name: EpsonPaginas
      required: false
      type: TEXT
      groupName: header
    - context: item
      default: EpsonZwart
      description: Epson inkt
      label: Item
      name: EpsonZwart
      required: false
      type: TEXT
    - context: item
      default: EpsonFotoZwart
      description: Epson inkt
      label: Item
      name: EpsonFotoZwart
      required: false
      type: TEXT
    - context: item
      default: EpsonCyaan
      description: Cyan Toner Level Item
      label: Item
      name: EpsonCyaan
      required: true
      type: TEXT
    - context: item
      default: EpsonMagenta
      description: Magenta Toner Level Item
      label: Item
      name: EpsonMagenta
      required: true
      type: TEXT
    - context: item
      default: EpsonGeel
      description: Yellow Toner Level Item
      label: Item
      name: EpsonGeel
      required: true
      type: TEXT
    - context: item
      default: EpsonGrijs
      description: Grey Toner Level Item
      label: Item
      name: EpsonGrijs
      required: true
      type: TEXT
    - context: item
      default: EpsonAfval
      description: Waste Level Item
      label: Item
      name: EpsonAfval
      required: true
      type: TEXT
timestamp: Feb 22, 2024, 7:36:49 PM
component: f7-card
config:
  noShadow: true
  outline: true
  title: =props.title
slots:
  content:
    - component: f7-block
      config:
        style:
          background-color: =themeOptions.dark=='dark'?'rgb(50,50,50)':'rgb(220,220,220)'
          display: grid
          grid-template-columns: repeat(7, 1fr)
          grid-template-rows: 5fr 1fr
          height: 200px
          margin: -10px
          padding-top: 15px
        stylesheet: >
          .level {
                align-self: end;  
                justify-self: center;
                text-align: center;
                width: 60%;    
          } .recipient {
                height: 100%;
                width: 60%;
                align-self: end;
                justify-self: center;
                border-style: solid;
                border-width: 2px;
                border-radius: 5px;
                color: =themeOptions.dark=='dark'?'white':'black';
          } .label {
                text-align: center;
          }
      slots:
        default:
          - component: Label
            config:
              class: ='level'
              style:
                background: black
                color: white
                grid-area: 1/1
                height: =Number.parseInt(items[props.EpsonZwart].state)+'%'
              text: =Number.parseInt(items[props.EpsonZwart].state)+'%'
          - component: Label
            config:
              class: ='recipient'
              style:
                grid-area: 1/1
          - component: Label
            config:
              class: ='label'
              text: Zwart
          - component: Label
            config:
              class: ='level'
              style:
                background: rgb(20, 20, 20)
                color: white
                grid-area: 1/2
                height: =Number.parseInt(items[props.EpsonFotoZwart].state)+'%'
              text: =Number.parseInt(items[props.EpsonFotoZwart].state)+'%'
          - component: Label
            config:
              class: ='recipient'
              style:
                grid-area: 1/2
          - component: Label
            config:
              class: ='label'
              text: FotoZwart
          - component: Label
            config:
              class: ='level'
              style:
                background: cyan
                color: black
                grid-area: 1/3
                height: =Number.parseInt(items[props.EpsonCyaan].state)+'%'
              text: =Number.parseInt(items[props.EpsonCyaan].state)+'%'
          - component: Label
            config:
              class: ='recipient'
              style:
                grid-area: 1/3
          - component: Label
            config:
              class: ='label'
              text: Cyaan
          - component: Label
            config:
              class: ='level'
              style:
                background: yellow
                color: black
                grid-area: 1/4
                height: =Number.parseInt(items[props.EpsonGeel].state)+'%'
              text: =Number.parseInt(items[props.EpsonGeel].state)+'%'
          - component: Label
            config:
              class: ='recipient'
              style:
                grid-area: 1/4
          - component: Label
            config:
              class: ='label'
              text: Geel
          - component: Label
            config:
              class: ='level'
              style:
                background: magenta
                color: black
                grid-area: 1/5
                height: =Number.parseInt(items[props.EpsonMagenta].state)+'%'
              text: =Number.parseInt(items[props.EpsonMagenta].state)+'%'
          - component: Label
            config:
              class: ='recipient'
              style:
                grid-area: 1/5
          - component: Label
            config:
              class: ='label'
              text: Magenta
          - component: Label
            config:
              class: ='level'
              style:
                background: rgb(20, 20, 20)
                color: white
                grid-area: 1/6
                height: =Number.parseInt(items[props.EpsonGrijs].state)+'%'
              text: =Number.parseInt(items[props.EpsonGrijs].state)+'%'
          - component: Label
            config:
              class: ='recipient'
              style:
                grid-area: 1/6
          - component: Label
            config:
              class: ='label'
              text: Grijs
          - component: Label
            config:
              class: ='level'
              style:
                background: black
                color: white
                grid-area: 1/7
                height: =Number.parseInt(items[props.EpsonAfval].state)+'%'
              text: =Number.parseInt(items[props.EpsonAfval].state)+'%'
          - component: Label
            config:
              class: ='recipient'
              style:
                grid-area: 1/7
          - component: Label
            config:
              class: ='label'
              text: Afval
    - component: f7-block
      config:
        outline: false
        style:
          display: flex
          gap: 10px
          height: 30px
          justify-content: center
          margin-top: 10px
          padding: 20px
      slots:
        default:
          - component: Label
            config:
              style:
                text-align: left
                width: 20%
              text: "Totale pagina's:"
          - component: Label
            config:
              style:
                text-align: left
                width: 20%
              text: =items[props.EpsonPaginas].state
    - component: f7-block
      config:
        outline: false
        style:
          display: flex
          gap: 10px
          justify-content: center
          padding: 20px
      slots:
        default:
          - component: Label
            config:
              style:
                text-align: left
                width: 20%
              text: "Status:"
          - component: Label
            config:
              style:
                text-align: left
                width: 20%
              text: =items[props.EpsonStatus].state

The ink levels are visualized in Label components. The tanks are also Label components. Both are positioned over each other with css-grid (explanation can be found in https://www.w3schools.com/css/css_grid.asp) .

Some other techniques can be used.
Sliders

They can be positioned vertically. But they are not really made to represent a tank.

Icons

I once made one for a vertical battery. Basically you make a svg file (e.g. with Inkscape). Then you make say 10 versions of the same, but with increasing fill levels.

The icons come in the icons/classic directory. They can be used in a component with ‘iconUseState: true’. The disadvantage is that there is a 10% gap between two levels. Not a problem for a single tank, but for a printer with a couple of tanks or cartridges, you would not see the difference between 61% and 69%.

Does anyone have better idea’s?

Edit: the widget has been reworked on 23 feb 2024:
• Positioning is now done with CSS-grid, which is a good technique to superpose the two components: tank and ink level.
• Stylesheets are used to minimize the repetitive typing of styles.
• The components for tank and ink level are now Label components instead of Buttons.

1 Like

Look at the code for my confirm button:

The sliding timer bar is accomplished just with css background parameters, and you could very easily re-purpose that same mechanic to show the tank fill state at any arbitrary %.

Also, if you’ve previously worked with svgs then that could be a good way to go here. Not by making svg based icon, but by using the the widget sytax to build a dynamic svg which would then give you control over the size of the fill element in the svg.

Here’s an example of how to make svgs in the widget editor, and even include them as icons in other widgets:

1 Like

Thank you Justin, I’ll try to elaborate both solutions and then post them.

@JustinG Here is a version that uses CSS gradients as was demonstrated in your Confirm Button. I learned a lot from it (stylesheets, linear-gradient, …).

uid: EpsonTestCss3
tags: []
props:
  parameters:
    - default: Epson Printer
      description: Title
      name: title
      required: false
      type: TEXT
    - context: item
      default: EpsonStatus
      description: Printer Status
      label: Item
      name: status
      required: false
      type: TEXT
    - context: item
      default: EpsonPaginas
      description: Total number of printed pages
      label: Item
      name: totalPages
      required: false
      type: TEXT
      groupName: header
    - context: item
      default: EpsonZwart
      description: Black inklevel
      label: Item
      name: blackLevel
      required: false
      type: TEXT
    - context: item
      default: EpsonFotoZwart
      description: Photo Black ink level
      label: Item
      name: photoBlackLevel
      required: false
      type: TEXT
    - context: item
      default: EpsonCyaan
      description: Cyan level
      label: Item
      name: cyanLevel
      required: true
      type: TEXT
    - context: item
      default: EpsonGeel
      description: Yellow level
      label: Item
      name: yellowLevel
      required: true
      type: TEXT    
    - context: item
      default: EpsonMagenta
      description: Magenta level
      label: Item
      name: magentaLevel
      required: true
      type: TEXT
    - context: item
      default: EpsonGrijs
      description: Grey level
      label: Item
      name: greyLevel
      required: true
      type: TEXT
    - context: item
      default: EpsonAfval
      description: Waste cassette level
      label: Item
      name: wasteLevel
      required: true
      type: TEXT
timestamp: Feb 23, 2024, 4:48:05 PM
component: f7-card
config:
  noShadow: true
  outline: true
  title: =props.title
slots:
  content:
    - component: f7-block
      config:
        style:
          background-color: =themeOptions.dark=='dark'?'rgb(50,50,50)':'rgb(220,220,220)'
          display: grid
          grid-template-columns: repeat(7, 1fr)
          grid-template-rows: 5fr 1fr          
          height: 200px
          margin: -10px
        stylesheet: >
          .ink {
            align-self: end;  
            justify-self: center;          
            height: 95%;
            width: 60%;
            background-size:100%;
            background-repeat: no-repeat;            
            border-width: 2px;
            border-style: solid;
            text-align: center;
            border-radius: 5px;
            background: linear-gradient(to top, var(--ink-color) var(--ink-level), rgba(0,0,0,0) 20%);  
          } 
          .inkName {
            text-align: center;          
          }
      slots:
        default:
          - component: Label
            config:
              class: ='ink'
              grid-area: 1/1              
              text: =items[props.blackLevel].state + '%'
              style:
                --ink-color: black
                --ink-level: =items[props.blackLevel].state + '%'              
          - component: Label
            config:
              class: ='ink'
              grid-area: 1/2              
              text: =items[props.photoBlackLevel].state + '%'
              style:
                --ink-color: rgb(20, 20, 20)
                --ink-level: =items[props.photoBlackLevel].state + '%'
          - component: Label
            config:
              class: ='ink'
              grid-area: 1/3
              text: =items[props.cyanLevel].state + '%'
              style:
                --ink-color: cyan
                --ink-level: =items[props.cyanLevel].state + '%'
          - component: Label
            config:
              class: ='ink'
              grid-area: 1/4
              text: =items[props.yellowLevel].state + '%'
              style:
                --ink-color: yellow
                --ink-level: =items[props.yellowLevel].state + '%'
          - component: Label
            config:
              class: ='ink'
              grid-area: 1/5              
              text: =items[props.magentaLevel].state + '%'
              style:
                --ink-color: magenta
                --ink-level: =items[props.magentaLevel].state + '%'
          - component: Label
            config:
              class: ='ink'
              grid-area: 1/6              
              text: =items[props.greyLevel].state + '%'
              style:
                --ink-color: rgb(20, 20, 20)
                --ink-level: =items[props.greyLevel].state + '%'
          - component: Label
            config:
              class: ='ink'
              grid-area: 1/7              
              text: =items[props.wasteLevel].state + '%'
              style:
                --ink-color: black
                --ink-level: =items[props.wasteLevel].state + '%'
          - component: Label
            config:
              class: ='inkName'
              grid-area: 2/1                            
              text: Zwart                
          - component: Label
            config:
              class: ='inkName'
              grid-area: 2/2                           
              text: FotoZwart   
          - component: Label
            config:
              class: ='inkName'
              grid-area: 2/3
              text: Cyaan                
          - component: Label
            config:
              class: ='inkName'
              grid-area: 2/4
              text: Geel                
          - component: Label
            config:
              class: ='inkName'
              grid-area: 2/5
              text: Magenta   
          - component: Label
            config:
              class: ='inkName'
              grid-area: 2/6
              text: Grijs   
          - component: Label
            config:
              class: ='inkName'
              grid-area: 2/7
              text: Afval              
    - component: f7-block
      config:
        outline: false
        style:
          display: flex
          gap: 10px
          height: 30px
          justify-content: center
          margin-top: 10px
          padding: 20px
      slots:
        default:
          - component: Label
            config:
              style:
                text-align: left
                width: 20%
              text: "Totale pagina's:"
          - component: Label
            config:
              style:
                text-align: left
                width: 20%
              text: =items[props.totalPages].state
    - component: f7-block
      config:
        outline: false
        style:
          display: flex
          gap: 10px
          justify-content: center
          padding: 20px
      slots:
        default:
          - component: Label
            config:
              style:
                text-align: left
                width: 20%
              text: "Status:"
          - component: Label
            config:
              style:
                text-align: left
                width: 20%
              text: =items[props.status].state

The trick with CSS gradients to create hard lines is explained here:

I didn’t find how to use the --ink-level variable in the ‘text’ field:

          - component: Label
            config:
              class: ='ink'
              grid-area: 1/1              
              text: =items[props.blackLevel].state + '%'
              style:
                --ink-color: black
                --ink-level: =items[props.blackLevel].state + '%'              

Looks great.

That’s correct. When you define the --ink-level variable under the style property, that is specifically a css variable. The only place you will be able to use that variable is in other styles.

You did it properly by just using the same expression for the text property and the css variable.

@JustinG Here is my last attempt: using SVG.

uid: EpsonSvgtest2
tags: []
props:
  parameters:
    - default: Epson Printer
      description: Title
      name: title
      required: false
      type: TEXT
    - context: item
      default: EpsonStatus
      description: Printer Status
      label: Item
      name: status
      required: false
      type: TEXT
    - context: item
      default: EpsonPaginas
      description: Total number of printed pages
      label: Item
      name: totalPages
      required: false
      type: TEXT
      groupName: header
    - context: item
      default: EpsonZwart
      description: Black inklevel
      label: Item
      name: blackLevel
      required: false
      type: TEXT
    - context: item
      default: EpsonFotoZwart
      description: Photo Black ink level
      label: Item
      name: photoBlackLevel
      required: false
      type: TEXT
    - context: item
      default: EpsonCyaan
      description: Cyan level
      label: Item
      name: cyanLevel
      required: true
      type: TEXT
    - context: item
      default: EpsonGeel
      description: Yellow level
      label: Item
      name: yellowLevel
      required: true
      type: TEXT
    - context: item
      default: EpsonMagenta
      description: Magenta level
      label: Item
      name: magentaLevel
      required: true
      type: TEXT
    - context: item
      default: EpsonGrijs
      description: Grey level
      label: Item
      name: greyLevel
      required: true
      type: TEXT
    - context: item
      default: EpsonAfval
      description: Waste cassette level
      label: Item
      name: wasteLevel
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Feb 26, 2024, 11:10:11 AM
component: f7-card
config:
  title: Epson printer
slots:
  default:
    - component: f7-block
      config:
        style:
          background-color: =themeOptions.dark=='dark'?'rgb(50,50,50)':'rgb(220,220,220)'
          display: grid
          grid-template-columns: repeat(7, 1fr)
          grid-template-rows: 5fr 1fr
          height: 250px
          margin: 0px
        stylesheet: >
          .inkText {
            justify-self: center;          
            text-align: center;
          } .ink {
            align-self: end;
            justify-self: center; 
          }
      slots:
        default:
          - component: Label
            config:
              class: ='inkText'
              style:
                grid-area: 1/1
              text: =items[props.blackLevel].state + '%'
          - component: f7-row
            config:
              class: ='ink'
              tag: svg
              xlmns: http://www.w3.org/2000/svg
              width: 90
              height: 200
              style:
                grid-area: 1/1
            slots:
              default:
                - component: f7-row
                  config:
                    tag: defs
                  slots:
                    default:
                      - component: f7-row
                        config:
                          tag: linearGradient
                          id: lGrad1
                          x1: 0%
                          x2: 0%
                          y1: 100%
                          y2: 0%
                        slots:
                          default:
                            - component: f7-row
                              config:
                                tag: stop
                                offset: =items[props.blackLevel].state + '%'
                                stop-color: rgb(20, 20, 20)
                            - component: f7-row
                              config:
                                tag: stop
                                offset: =items[props.blackLevel].state + '%'
                                stop-color: rgba(0,0,0,0)
                - component: f7-row
                  config:
                    tag: path
                    d: M 10,20 q 10,0 10,10 l 0 160 q 0,10 10,10  l 40,0 q 10,0 10,-10 l 0,-160 q 0,-10 10,-10 M 20,110 l 20,0 M 20,65 l 10,0 M 20,155 l 10,0
                    stroke: white
                    stroke-width: 2
                    id: path1
                    fill: url(#lGrad1)
          - component: Label
            config:
              class: ='inkText'
              style:
                grid-area: 2/1
              text: Zwart
          - component: Label
            config:
              class: ='inkText'
              style:
                grid-area: 1/2
              text: =items[props.blackLevel].state + '%'
          - component: f7-row
            config:
              class: ='ink'
              tag: svg
              xlmns: http://www.w3.org/2000/svg
              width: 90
              height: 200
              style:
                grid-area: 1/2
            slots:
              default:
                - component: f7-row
                  config:
                    tag: defs
                  slots:
                    default:
                      - component: f7-row
                        config:
                          tag: linearGradient
                          id: lGrad2
                          x1: 0%
                          x2: 0%
                          y1: 100%
                          y2: 0%
                        slots:
                          default:
                            - component: f7-row
                              config:
                                tag: stop
                                offset: =items[props.photoBlackLevel].state + '%'
                                stop-color: rgb(20, 20, 20)
                            - component: f7-row
                              config:
                                tag: stop
                                offset: =items[props.photoBlackLevel].state + '%'
                                stop-color: rgba(0,0,0,0)
                - component: f7-row
                  config:
                    tag: path
                    d: M 10,20 q 10,0 10,10 l 0 160 q 0,10 10,10  l 40,0 q 10,0 10,-10 l 0,-160 q 0,-10 10,-10 M 20,110 l 20,0 M 20,65 l 10,0 M 20,155 l 10,0
                    stroke: white
                    stroke-width: 2
                    id: path2
                    fill: url(#lGrad2)
          - component: Label
            config:
              class: ='inkText'
              style:
                grid-area: 2/2
              text: FotoZwart
          - component: Label
            config:
              class: ='inkText'
              style:
                grid-area: 1/3
              text: =items[props.cyanLevel].state + '%'
          - component: f7-row
            config:
              class: ='ink'
              tag: svg
              xlmns: http://www.w3.org/2000/svg
              width: 90
              height: 200
              style:
                grid-area: 1/3
            slots:
              default:
                - component: f7-row
                  config:
                    tag: defs
                  slots:
                    default:
                      - component: f7-row
                        config:
                          tag: linearGradient
                          id: lGrad3
                          x1: 0%
                          x2: 0%
                          y1: 100%
                          y2: 0%
                        slots:
                          default:
                            - component: f7-row
                              config:
                                tag: stop
                                offset: =items[props.cyanLevel].state + '%'
                                stop-color: cyan
                            - component: f7-row
                              config:
                                tag: stop
                                offset: =items[props.cyanLevel].state + '%'
                                stop-color: rgba(0,0,0,0)
                - component: f7-row
                  config:
                    tag: path
                    d: M 10,20 q 10,0 10,10 l 0 160 q 0,10 10,10  l 40,0 q 10,0 10,-10 l 0,-160 q 0,-10 10,-10 M 20,110 l 20,0 M 20,65 l 10,0 M 20,155 l 10,0
                    stroke: white
                    stroke-width: 2
                    id: path3
                    fill: url(#lGrad3)
          - component: Label
            config:
              class: ='inkText'
              style:
                grid-area: 2/3
              text: Cyaan
          - component: Label
            config:
              class: ='inkText'
              style:
                grid-area: 1/4
              text: =items[props.yellowLevel].state + '%'
          - component: f7-row
            config:
              class: ='ink'
              tag: svg
              xlmns: http://www.w3.org/2000/svg
              width: 90
              height: 200
              style:
                grid-area: 1/4
            slots:
              default:
                - component: f7-row
                  config:
                    tag: defs
                  slots:
                    default:
                      - component: f7-row
                        config:
                          tag: linearGradient
                          id: lGrad4
                          x1: 0%
                          x2: 0%
                          y1: 100%
                          y2: 0%
                        slots:
                          default:
                            - component: f7-row
                              config:
                                tag: stop
                                offset: =items[props.yellowLevel].state + '%'
                                stop-color: yellow
                            - component: f7-row
                              config:
                                tag: stop
                                offset: =items[props.yellowLevel].state + '%'
                                stop-color: rgba(0,0,0,0)
                - component: f7-row
                  config:
                    tag: path
                    d: M 10,20 q 10,0 10,10 l 0 160 q 0,10 10,10  l 40,0 q 10,0 10,-10 l 0,-160 q 0,-10 10,-10 M 20,110 l 20,0 M 20,65 l 10,0 M 20,155 l 10,0
                    stroke: white
                    stroke-width: 2
                    id: path4
                    fill: url(#lGrad4)
          - component: Label
            config:
              class: ='inkText'
              style:
                grid-area: 2/4
              text: Geel
          - component: Label
            config:
              class: ='inkText'
              style:
                grid-area: 1/5
              text: =items[props.magentaLevel].state + '%'
          - component: f7-row
            config:
              class: ='ink'
              tag: svg
              xlmns: http://www.w3.org/2000/svg
              width: 90
              height: 200
              style:
                grid-area: 1/5
            slots:
              default:
                - component: f7-row
                  config:
                    tag: defs
                  slots:
                    default:
                      - component: f7-row
                        config:
                          tag: linearGradient
                          id: lGrad5
                          x1: 0%
                          x2: 0%
                          y1: 100%
                          y2: 0%
                        slots:
                          default:
                            - component: f7-row
                              config:
                                tag: stop
                                offset: =items[props.magentaLevel].state + '%'
                                stop-color: magenta
                            - component: f7-row
                              config:
                                tag: stop
                                offset: =items[props.magentaLevel].state + '%'
                                stop-color: rgba(0,0,0,0)
                - component: f7-row
                  config:
                    tag: path
                    d: M 10,20 q 10,0 10,10 l 0 160 q 0,10 10,10  l 40,0 q 10,0 10,-10 l 0,-160 q 0,-10 10,-10 M 20,110 l 20,0 M 20,65 l 10,0 M 20,155 l 10,0
                    stroke: white
                    stroke-width: 2
                    id: path5
                    fill: url(#lGrad5)
          - component: Label
            config:
              class: ='inkText'
              style:
                grid-area: 2/5
              text: Magenta
          - component: Label
            config:
              class: ='inkText'
              style:
                grid-area: 1/6
              text: =items[props.greyLevel].state + '%'
          - component: f7-row
            config:
              class: ='ink'
              tag: svg
              xlmns: http://www.w3.org/2000/svg
              width: 90
              height: 200
              style:
                grid-area: 1/6
            slots:
              default:
                - component: f7-row
                  config:
                    tag: defs
                  slots:
                    default:
                      - component: f7-row
                        config:
                          tag: linearGradient
                          id: lGrad6
                          x1: 0%
                          x2: 0%
                          y1: 100%
                          y2: 0%
                        slots:
                          default:
                            - component: f7-row
                              config:
                                tag: stop
                                offset: =items[props.greyLevel].state + '%'
                                stop-color: rgb(20, 20, 20)
                            - component: f7-row
                              config:
                                tag: stop
                                offset: =items[props.greyLevel].state + '%'
                                stop-color: rgba(0,0,0,0)
                - component: f7-row
                  config:
                    tag: path
                    d: M 10,20 q 10,0 10,10 l 0 160 q 0,10 10,10  l 40,0 q 10,0 10,-10 l 0,-160 q 0,-10 10,-10 M 20,110 l 20,0 M 20,65 l 10,0 M 20,155 l 10,0
                    stroke: white
                    stroke-width: 2
                    id: path6
                    fill: url(#lGrad6)
          - component: Label
            config:
              class: ='inkText'
              style:
                grid-area: 2/6
              text: Grijs
          - component: Label
            config:
              class: ='inkText'
              style:
                grid-area: 1/7
              text: =items[props.wasteLevel].state + '%'
          - component: f7-row
            config:
              class: ='ink'
              tag: svg
              xlmns: http://www.w3.org/2000/svg
              width: 90
              height: 200
              style:
                grid-area: 1/7
            slots:
              default:
                - component: f7-row
                  config:
                    tag: defs
                  slots:
                    default:
                      - component: f7-row
                        config:
                          tag: linearGradient
                          id: lGrad7
                          x1: 0%
                          x2: 0%
                          y1: 100%
                          y2: 0%
                        slots:
                          default:
                            - component: f7-row
                              config:
                                tag: stop
                                offset: =items[props.wasteLevel].state + '%'
                                stop-color: black
                            - component: f7-row
                              config:
                                tag: stop
                                offset: =items[props.wasteLevel].state + '%'
                                stop-color: rgba(0,0,0,0)
                - component: f7-row
                  config:
                    tag: path
                    d: M 10,20 q 10,0 10,10 l 0 160 q 0,10 10,10  l 40,0 q 10,0 10,-10 l 0,-160 q 0,-10 10,-10 M 20,110 l 20,0 M 20,65 l 10,0 M 20,155 l 10,0
                    stroke: white
                    stroke-width: 2
                    id: path7
                    fill: url(#lGrad7)
          - component: Label
            config:
              class: ='inkText'
              style:
                grid-area: 2/7
              text: Afval
    - component: f7-block
      config:
        outline: false
        style:
          display: flex
          gap: 10px
          height: 30px
          justify-content: center
          margin-top: 10px
          padding: 20px
      slots:
        default:
          - component: Label
            config:
              style:
                text-align: left
                width: 20%
              text: "Totale pagina's:"
          - component: Label
            config:
              style:
                text-align: left
                width: 20%
              text: =items[props.totalPages].state
    - component: f7-block
      config:
        outline: false
        style:
          display: flex
          gap: 10px
          justify-content: center
          padding: 20px
      slots:
        default:
          - component: Label
            config:
              style:
                text-align: left
                width: 20%
              text: "Status:"
          - component: Label
            config:
              style:
                text-align: left
                width: 20%
              text: =items[props.status].state

The advantage is that you can design fancy symbols. But the price is high: lots of lines of error prone code. And the svg in is not ‘responsive’, meaning that it has a fixed size, that cannot expand or compress when the widget is resized.

Conclusion
This was an interesting experiment with 3 techniques:

  1. Superposed labels in a css-grid: simple
  2. Using CSS with a linear gradient to fill a label: a bit less lines of code
  3. SVG: nice, but too difficult, too much code, fixed size

I should have mentioned that the link I posted was for a much earlier version of MainUI. You no longer need to use f7-row and the tag property for everything, you can just use the tags directly for the component:

          - component: svg
            config:
              class: ='ink'
              xlmns: http://www.w3.org/2000/svg
              width: 90
              height: 200
              style:
                grid-area: 1/1

That helps make it a lot more readable.

From a quick glance at what you’ve posted it looks like you could easily use a repeater and just have the code for the svg once which would trim this down quite a bit.

The svgs would be responsive if you used the viewBox property with them and didn’t give them static height and width values. Here you can see an example of a responsive svg using viewBox:

@JustinG I have been an average OH user since 8 years. Now I am diving a bit deeper in the visualization techniques used in Openhab. Thank you for reading and replying to a lot of posts and for sharing your knowledge with all of us.

You made me discover the power of the oh-repeater component, now the code is drastically shorter. As every position in the array counts 3 variables (levelItem, label and color), I no longer use the props for this. So if anyone wants to use the widget, he or she has to adapt the oh-repeater values.

The svg part (path and texts) is scalable.

uid: testRepeater2
tags: []
props:
  parameters: 
    - default: Printer
      description: Title
      name: title
      required: false
      type: TEXT
    - context: item
      default: EpsonStatus
      description: Printer Status
      label: Item
      name: status
      required: false
      type: TEXT  
    - context: item
      default: EpsonPaginas
      description: Total number of printed pages
      label: Item
      name: totalPages
      required: false
      type: TEXT      
  parameterGroups: []
timestamp: Feb 28, 2024, 10:39:38 AM
component: f7-card
config:
  title: =props.title
  outline: true
slots:
  default:
    - component: f7-block
      config:
        style:
          background-color: =themeOptions.dark=='dark'?'rgb(50,50,50)':'rgb(220,220,220)'
          display: flex
          justify-content: space-evenly
          column-gap: 2%
          height: 200px
      slots:
        default:
          - component: oh-repeater
            config:
              comment: levelItem is the name of the item that contains the ink level
              for: tanks
              sourceType: array
              in:
                - levelItem: EpsonZwart
                  label: Zwart
                  color: black
                - levelItem: EpsonFotoZwart
                  label: FotoZwart
                  color: rgb(20,20,20)
                - levelItem: EpsonCyaan
                  label: Cyaan
                  color: cyan
                - levelItem: EpsonGeel
                  label: Geel
                  color: yellow
                - levelItem: EpsonMagenta
                  label: Magenta
                  color: magenta
                - levelItem: EpsonGrijs
                  label: Grijs
                  color: rgb(20,20,20)
                - levelItem: EpsonAfval
                  label: Afval
                  color: black
              fragment: true
            slots:
              default:
                - component: svg
                  config:
                    viewBox: 0 0 80 230
                    xlmns: http://www.w3.org/2000/svg             
                  slots:
                    default:
                      - component: text
                        config:
                          text-anchor: middle
                          x: 40
                          y: 15
                          fill: =themeOptions.dark=='dark'?'white':'black'
                          content: =items[loop.tanks.levelItem].state + '%'
                      - component: defs
                        slots:
                          default:
                            - component: linearGradient
                              config:
                                id: ='grad'+(loop.tanks_idx+1)
                                x1: 0%
                                x2: 0%
                                y1: 100%
                                y2: 0%
                              slots:
                                default:
                                  - component: stop
                                    config:
                                      offset: =items[loop.tanks.levelItem].state+'%'
                                      stop-color: =loop.tanks.color
                                  - component: stop
                                    config:
                                      offset: =items[loop.tanks.levelItem].state+'%'
                                      stop-color: rgba(0,0,0,0)
                      - component: path
                        config:
                          d: M 0,20 q 10,0 10,10 l 0 160 q 0,10 10,10  l 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'+(loop.tanks_idx+1)+')'
                          stroke: rgb(150,150,150)
                          stroke-width: 2
                      - component: text
                        config:
                          text-anchor: middle
                          x: 40
                          y: 220
                          fill: =themeOptions.dark=='dark'?'white':'black'
                          content: =loop.tanks.label
    - component: f7-block
      config:
        style:
          display: flex
          gap: 10px
          margin-top: 10px
          justify-content: center
          text-align: left
      slots:
        default:
          - component: Label
            config:
              style:
                width: 25%
              text: "Totale pagina's:"
          - component: Label
            config:
              style:
                width: 25%
              text: =items[props.totalPages].state
    - component: f7-block
      config:
        style:
          display: flex
          gap: 10px
          justify-content: center
          text-align: left
      slots:
        default:
          - component: Label
            config:
              style:
                width: 25%
              text: "Status:"
          - component: Label
            config:
              style:
                width: 25%
              text: =items[props.status].state

edit: added ‘height: 200px’ to the f7-block because it didn’t scale well in responsive layout.

2 Likes

That’s a very nice looking widget, now. Great job.

I encourage you to think about making a general widget version of just one of your tank svgs and posting it in the widget market-place. I think a lot of people would like it and it can be used not just for printers, but rain sensors, battery values, water tanks to just name a few uses.

2 Likes