[Main UI] echarts gauge and pie POC

Tags: #<Tag:0x00007fc91005aa10>

What started as a search for a way to display some sort of gauge in order to model my heat pump, ended as a journey into main ui guts :slight_smile:

Long story short, after some digging it turned out echarts already include gauge - it took a while to get my head around the codebase, but finally was able to add support for a gauge:

uid: widget_18509ca60f
props:
  parameterGroups: []
tags: []
component: f7-card
config:
  title: Test Gauge
slots:
  default:
    - component: oh-chart
      slots:
        series:
          - component: oh-serie
            config:
              type: gauge
              data:
                - value: 55

and result

:partying_face:

Based on given echarts docs and examples ended with creating a custom gauge widget

id: widget_24dc21e636
tags: []
props:
  parameters:
    - context: item
      description: The value to show
      label: Item
      name: item
      required: true
      type: TEXT
    - context: item
      description: The min value in range
      label: MinItem
      name: minItem
      required: true
      type: TEXT
    - context: item
      description: The max value in range
      label: MaxItem
      name: maxItem
      required: true
      type: TEXT
    - context: number
      description: Min value
      label: Min
      name: min
      required: false
      type: TEXT
    - context: number
      description: Max value
      label: Max
      name: max
      required: false
      type: TEXT
    - context: string
      description: The name to display
      label: Name
      name: name
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Jan 12, 2021, 7:34:45 PM
component: f7-block
config:
  class:
    - no-padding
slots:
  default:
    - component: oh-chart
      slots:
        series:
          - component: oh-serie
            config:
              type: gauge
              radius: 95x%
              axisLine:
                lineStyle:
                  color:
                    - - 1
                      - "#fefefe"
                  width: 12
                  shadowBlur: 5
              startAngle: -90
              endAngle: -89.9999
              axisLabel:
                formatter: " "
              splitNumber: -1
              detail:
                formatter: " "
          - component: oh-serie
            config:
              type: gauge
              min: =props.min || 0
              max: =props.max || 100
              data:
                - value: =items[props.item].state.split(" ")[0]
                  name: =props.name
              splitNumber: 6
              radius: 85%
              pointer:
                length: 84%
              title:
                color: "#000"
                fontSize: 16
              axisLine:
                lineStyle:
                  color:
                    - - =(items[props.minItem].state.split(" ")[0] - props.min - 0.01)/(props.max - props.min)
                      - "#1e90ff"
                    - - =(items[props.maxItem].state.split(" ")[0] - props.min - 0.01)/(props.max - props.min)
                      - lime
                    - - 1
                      - "#ff4500"
                  width: 18
              splitLine:
                length: 18
                lineStyle:
                  color: "#000"
              axisTick:
                length: 10
                lineStyle:
                  color: "#000"
              axisLabel:
                distance: 8
                fontSize: 18
                color: "#000"
              detail:
                backgroundColor: rgb(137,150,96)
                borderWidth: 1
                borderColor: "#000"
                fontSize: 18
                fontFace: Bold
                color: "#000"
                fontFamily: Courier New

and using widget to display DHW status

- component: widget:widget_24dc21e636
    config:
    item: gBasement_BoilerRoom_HP_DHW_Current
    minItem: gBasement_BoilerRoom_HP_DHW_TargetOn
    maxItem: gBasement_BoilerRoom_HP_DHW_TargetOff
    min: 30
    max: 60
    name: DHW

renders as

Beside DHW, floor heating return and forward channels are also visible. Green color indicated (dynamic!) expected temperature range, as reported by the HP (based on outside temperature and other conditions).

After gauge was working, decided to try out pie as well - using example as a base, I was able to add a nice looking overview of how much time HP spends producing heat for DHW and how much for floor heating - and what percentage only compressor is producing heat and how much additional heat (electrical heater) is needed. Pie chart yaml as seen on above screenshot:

- component: oh-chart
    slots:
    series:
        - component: oh-serie
        config:
            type: pie
            radius:
            - 0
            - 45%
            data:
            - value: =(items.gBasement_BoilerRoom_HP_DHW_InOperation_HP.state.split(" ")[0] - 0) + (items.gBasement_BoilerRoom_HP_DHW_InOperation_AddHeat.state.split(" ")[0] - 0)
                name: DHW
                selected: true
            - value: =(items.gBasement_BoilerRoom_HP_RAD_InOperation_HP.state.split(" ")[0] - 0) + (items.gBasement_BoilerRoom_HP_RAD_InOperation_AddHeat.state.split(" ")[0] - 0)
                name: HEATING
            label:
            position: inner
        - component: oh-serie
        config:
            type: pie
            radius:
            - 52%
            - 70%
            data:
            - value: =items.gBasement_BoilerRoom_HP_DHW_InOperation_HP.state.split(" ")[0]
                name: DHW HP
            - value: =items.gBasement_BoilerRoom_HP_DHW_InOperation_AddHeat.state.split(" ")[0]
                name: DHW ADD
            - value: =items.gBasement_BoilerRoom_HP_RAD_InOperation_HP.state.split(" ")[0]
                name: HEATING HP
            - value: =items.gBasement_BoilerRoom_HP_RAD_InOperation_AddHeat.state.split(" ")[0]
                name: HEATING ADD
            label:
            formatter: "  {b|{b}: }{c}h  {per|{d}%}  "
            backgroundColor: "#eee"
            borderColor: "#aaa"
            borderWidth: 1
            borderRadius: 4
            rich:
                b:
                fontSize: 16
                lineHeight: 33
                per:
                color: "#eee"
                backgroundColor: "#334455"
                padding:
                    - 2
                    - 4
                borderRadius: 2

For the curious ones, code can be found here. Would need to think about how this POC can make its way to a real PR, if it make sense to others as well ofc.

Feedback welcome!

9 Likes

Really appreciate to see a PR for this great poc. :+1:

Really cool @crnjan!
I didn’t want to add all ECharts modules at first because they’re quite heavy, but at least I was on the fence on the gauge, pies I saw less use of them at first.
But the charts code is not loaded until you have to display a chart page, so if there’s some demand for them and you submit a PR it’ll be accepted for sure.

1 Like

Also note that ECharts 5.0 was released last month, it has a dedicated page for examples (there are more examples of gauges notably) and configuration.

openHAB includes version 4.9 so I think an upgrade down the line should be made. We’ll have to see about braking changes.

Yes, ECharts 5.0 brings quite some improvements, specially for gauges - I actually used 5.0 examples to model my (ofc with limitations since we don’t yet have 5.0).

I will try to create a PR ASAP, but since I’m new to web dev + need to find the best way to conceptually fit those pie/gauge elements in (elements that don’t need time range) - so there most certainly will be some back and forth - but lets discuss this than within PR on github.

PR was merged - so latest snapshots (2166+ AFAIK) should contain support for gauges and embedding charts in layout pages + upgrade to ECharts 5 which brings a lot of enhancements on its own.

While inline help might not be the best (yet), please check ECharts docs for reference, i.e. for gauges and examples.

Widget I’ve been using for testing

uid: widget_24dc21e636
tags: []
props:
  parameters:
    - context: item
      description: The current value
      label: Item
      name: item
      required: true
      type: TEXT
    - context: item
      description: The min range
      label: MinItem
      name: minItem
      required: false
      type: TEXT
    - context: item
      description: The max range
      label: MaxItem
      name: maxItem
      required: false
      type: TEXT
    - context: number
      description: The min value
      label: Min
      name: min
      required: false
      type: TEXT
    - context: number
      description: The max value
      label: Max
      name: max
      required: false
      type: TEXT
    - context: string
      description: The name
      label: Name
      name: name
      required: false
      type: TEXT
    - context: number
      description: The height of widget
      label: Height
      name: height
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Jan 21, 2021, 11:01:01 PM
component: oh-chart
config:
  height: =props.height
slots:
  series:
    - component: oh-data-series
      config:
        radius: 92%
        type: gauge
        startAngle: 90
        endAngle: -270
        axisLine:
          lineStyle:
            width: 10
            color:
              - - 1
                - "#fff"
            shadowColor: rgba(0, 0, 0, 0.5)
            shadowBlur: 10
        axisTick:
          show: false
        splitLine:
          show: false
        axisLabel:
          show: false
        pointer:
          show: false
        title:
          show: false
        anchor:
          show: true
          size: 20
          itemStyle:
            color: "#000"
    - component: oh-data-series
      config:
        radius: 82%
        splitNumber: 6
        type: gauge
        min: =props.min || 0
        max: =props.max || 100
        data:
          - value: =Number.parseFloat(items[props.item].state)
            name: =props.name
        axisLine:
          lineStyle:
            width: 10
            color:
              - - =(Number.parseFloat(items[props.minItem].state) - props.min)/(props.max - props.min)
                - "#58D9F9"
              - - =(Number.parseFloat(items[props.maxItem].state) - props.min)/(props.max - props.min)
                - "#7CFFB2"
              - - 1
                - "#FF6E76"
        axisTick:
          distance: -10
          length: 10
        splitLine:
          distance: -10
          length: 20
        axisLabel:
          distance: 18
          fontSize: 18
        detail:
          valueAnimation: true
          formatter: ="{value}" + "\n" + items[props.item].state.split(" ")[1]
          fontSize: 18
          offsetCenter:
            - 0
            - 45%
        title:
          offsetCenter:
            - 0
            - -35%
        pointer:
          offsetCenter:
            - 0
            - 10%
          icon: path://M2090.36389,615.30999 L2090.36389,615.30999 C2091.48372,615.30999 2092.40383,616.194028 2092.44859,617.312956 L2096.90698,728.755929 C2097.05155,732.369577 2094.2393,735.416212 2090.62566,735.56078 C2090.53845,735.564269 2090.45117,735.566014 2090.36389,735.566014 L2090.36389,735.566014 C2086.74736,735.566014 2083.81557,732.63423 2083.81557,729.017692 C2083.81557,728.930412 2083.81732,728.84314 2083.82081,728.755929 L2088.2792,617.312956 C2088.32396,616.194028 2089.24407,615.30999 2090.36389,615.30999 Z
          length: 100%
          itemStyle:
            color: "#f00"
        anchor:
          show: true
          size: 14
          itemStyle:
            borderColor: "#000"
            borderWidth: 2

usage

- component: widget:widget_24dc21e636
    config:
    item: gBasement_BoilerRoom_HP_DHW_Current
    minItem: gBasement_BoilerRoom_HP_DHW_TargetOn
    maxItem: gBasement_BoilerRoom_HP_DHW_TargetOff
    min: 30
    max: 60
    name: DHW

example screen:

As seen, charts can also be part of layout, for example

- component: oh-chart
    config:
    height: 350px
    slots:
        grid:
            - component: oh-chart-grid
            config: {}
        xAxis:
            - component: oh-time-axis
            config:
                gridIndex: 0
        yAxis:
            - component: oh-value-axis
            config:
                gridIndex: 0
        series:
            - component: oh-time-series
            config:
                name: DHW
                gridIndex: 0
                xAxisIndex: 0
                yAxisIndex: 0
                type: line
                item: gBasement_BoilerRoom_HP_DHW_Current
            - component: oh-time-series
            config:
                name: Return
                gridIndex: 0
                xAxisIndex: 0
                yAxisIndex: 0
                type: line
                item: gBasement_BoilerRoom_HP_RAD_Return
            - component: oh-time-series
            config:
                name: Forward
                gridIndex: 0
                xAxisIndex: 0
                yAxisIndex: 0
                type: line
                item: gBasement_BoilerRoom_HP_RAD_Forward
        legend:
            - component: oh-chart-legend
            config:
                show: true
                orient: horizontal
                bottom: "8"

would render line chart as seen on above screenshot.

Happy coding!

4 Likes

Amazing! I’ll give it a spin for sure! Just out of curiosity: The pie chart is not on there, yet, or is it?

Thank you so much for your awesome contribution!

Hey! Glad you like it! Pie support was not added since, as also @ysc mentioned, I did not see a direct need for it so I did not include it. But in order to add support, just a single import needs to be added …

I tried adding it and it adds ~5000 bytes to resulting jar

3.998.791 bytes vs. 4.003.852 bytes

With pie included and a bit modified yaml

- component: oh-chart
  config:
    height: 450px
  slots:
    series:
      - component: oh-data-series
        config:
          type: pie
          radius:
            - 0
            - 30%
          selectedMode: single
          label:
            position: inner
            fontSize: 14
          data:
            - value: =Number.parseInt(items.gBasement_BoilerRoom_HP_DHW_InOperation_HP.state)
                +
                Number.parseInt(items.gBasement_BoilerRoom_HP_DHW_InOperation_AddHeat.state)
              selected: true
              name: DHW
            - value: =Number.parseInt(items.gBasement_BoilerRoom_HP_RAD_InOperation_HP.state)
                +
                Number.parseInt(items.gBasement_BoilerRoom_HP_RAD_InOperation_AddHeat.state)
              name: HEATING
      - component: oh-data-series
        config:
          type: pie
          radius:
            - 45%
            - 60%
          data:
            - value: =Number.parseInt(items.gBasement_BoilerRoom_HP_DHW_InOperation_HP.state)
              name: DHW HP
            - value: =Number.parseInt(items.gBasement_BoilerRoom_HP_DHW_InOperation_AddHeat.state)
              name: DHW ADD
            - value: =Number.parseInt(items.gBasement_BoilerRoom_HP_RAD_InOperation_HP.state)
              name: HEATING HP
            - value: =Number.parseInt(items.gBasement_BoilerRoom_HP_RAD_InOperation_AddHeat.state)
              name: HEATING ADD
          label:
            formatter: "  {b|{b}: }{c}h  {per|{d}%}  "
            backgroundColor: "#eee"
            borderColor: "#aaa"
            borderWidth: 1
            borderRadius: 4
            rich:
              b:
                fontSize: 16
                lineHeight: 33
              per:
                color: "#eee"
                backgroundColor: "#334455"
                padding:
                  - 3
                  - 4
                borderRadius: 4

we get

Please let me know what you think …

1 Like

No problem to add it as well, I checked the gauge before merging, it was also a ~10kB overhead. We don’t want to add everything but pies make sense now that we have a 1-dimensional series component.

2 Likes

Added PR - support for pie.

1 Like

Wow! I think pies make sense for sure!
Of course I am a data lover and can’t get enough, but I can see pie charts have a real application for their broad user community.
I’m thinking of:

  • Home vs. Away
  • Heating vs. None vs. Cooling
  • Energy statistics for PV
    … And so on.

Thank you so much for adding it!

Hey @crnjan

thanks for your work and the possibility using some powerful charting features inside of widgets.

I tried to dive into the documentation today and slowly getting a felling of the very extensive customization options. But one thing I couldn’t find a hint for in the docs is, how to get rid of the floating menu popup for the date selection in the top-right?

Is it possible to disable this element?

Thanks!

Totally forgot to mention this, thank you for the reminder. There is a periodVisible parameter you can use:

- component: oh-chart
  config:
    periodVisible: false

should do the trick. If not specified, it will default to false if you use oh-data-series and true for others.

It’s a custom property, not related to ECharts so sorry for not mentioning it before.

1 Like

Hi,

Is it already possible to add a “oh-chart” (line chart) component to a widget in OH 3.0.1 ?
If yes, can someone tell me how? Cause it’s not working with my thermostat widget :upside_down_face:

This will be part of 3.1 release - you can use 3.1 snapshot version if you want to try it out (@BobMiles latest snapshot 2169 also includes pie support).

1 Like

Thx, the Gauge are now working with the examples above.
But i don’t get the line charts working in a widget…

widget code
uid: ChartTestWidget
props:
  parameters:
    - context: item
      description: Set an item which you want to control
      label: Item
      name: item
      required: true
      type: TEXT
  parameterGroups: []
tags: []
component: f7-card
config:
  title: Test Chart
slots:
  default:
    - component: oh-chart
      slots:
        series:
          - component: oh-serie
            config:
                type: line
                areaStyle: {}
            slots:
              grid:
                - component: oh-chart-grid
                  config: {}
              xAxis:
                - component: oh-time-axis
                  config:
                    gridIndex: 0
              yAxis:
                - component: oh-value-axis
                  config:
                    gridIndex: 0
              series:
                - component: oh-time-series
                  config:
                    name: Test
                    gridIndex: 0
                    xAxisIndex: 0
                    yAxisIndex: 0
                    type: line
                    item: props.item
              legend:
                - component: oh-chart-legend
                  config:
                    show: true
                    orient: horizontal
                    bottom: "8"
widget pic

Theres the = missing at the item prop inside the ‘oh-time-series’, so no item gets selected.

There are a couple of issues with your yaml (i.e. there is no oh-serie), something like below should give you a line chart:

uid: ChartTestWidget
props:
  parameters:
    - context: item
      description: Set an item which you want to control
      label: Item
      name: item
      required: true
      type: TEXT
  parameterGroups: []
tags: []
component: f7-card
config:
  title: Test Chart
slots:
  default:
    - component: oh-chart
      config:
        periodVisible: false
        height: 300px
      slots:
        grid:
          - component: oh-chart-grid
            config: {}
        xAxis:
          - component: oh-time-axis
            config:
              gridIndex: 0
        yAxis:
          - component: oh-value-axis
            config:
              gridIndex: 0
        series:
          - component: oh-time-series
            config:
              name: Outdoor
              gridIndex: 0
              xAxisIndex: 0
              yAxisIndex: 0
              type: line
              item: gBasement_BoilerRoom_HP_Outdoor
        legend:
          - component: oh-chart-legend
            config:
              show: true
              orient: horizontal
              bottom: "8"

There is currently one limitation - oh-time-series properties are not evaluated, meaning you cannot do

 item: =props.item

but only specifying explicit item will work. I guess it makes sense to support this use case too, specially in the context of custom widgets …

Awesome, now it’s working :smile:
Thank you very much

Is there already Git issue for this? Or should i create one?

Hey again @crnjan

Thanks for your quick answer and the easy trick. :wink:

This would be very helpful to have, yes!


Some things I recognized while fiddling around with the ECharts:

  • Line charts (and maybe other chart types) will set its own background in dark mode, instead of being transparent as in light mode - so that custom styling on the enclosing component gets hidden behind.

  • You mentioned it already, but there are some missing components & props within the inline help (like oh-chart-title, oh-chart-toolbox, oh-chart-tooltip and maybe more…)

  • Somehow connected to the above point - it’s hard to abstract the configuration of the charts from the ECharts example page to the YAML configuration in OH.

    Understanding what is a component and what is a setting took me some time and I’m still struggling on some of them…

    Example #1: Assigning a background gradient to a line-chart.

    • How would I translate this part
      new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
      to usable YAML?

      See full ECharts example (linear gradient)
         areaStyle: {
             color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
                 offset: 0,
                 color: 'rgb(255, 158, 68)'
             }, {
                 offset: 1,
                 color: 'rgb(255, 70, 131)'
             }])
         },
      
    • Or is it even necessary to work with these custom echart graphic classes (if this is the right term here…)?

    Example #2: Changing the timestamp-format in the tooltip.

    • I couldn’t find anything obvious about changing the tooltip-timestamp to a local format (like dd. MM yyyy) in the docs - a google search brought me to this function (which don’t work):

      Example changing timestamp in a tootltip (via google search)
        tooltip: {
         trigger: 'axis',
         formatter: function(params) {
             params = params[0];
             var chartdate = echarts.format.formatTime('dd-MM-yyyy', params.value[0]);
             var val = '<li style="list-style:none">' + params.marker +
                 params.seriesName + '&nbsp;&nbsp;' + params.value[1] + '</li>';
             return chartdate + val;
        }
      },
      
    • I could imagine that executing js functions isn’t allowed for security reasons?! Or did I miss anything here?

  • I think adding the single axis (timeline)-chart could also be helpful in the future, to achieve something like the BasicUI timeline inside the MainUI without have to fiddle something similar with f7-components:
    See → Timeline for Basic UI - #15 by Mihai_Badea

    The single axis type, looks like it would fit that use-case?!

Maybe you’ll find time give me some advice here or at least use this for future development of the implementation. :slight_smile:

Thank you!