Echart with dynamic oh-data-series

Hello Everyone,

I try to create a widget to display the realtime power consumption in my house.
The result I would like to achieve is something like that:

image

But what I would also like, is to dynamically construct the oh-data-series so configuration of the widget is simple.

I first give a try with oh-repeater, but hit the problems that oh-repeater don’t work with echart (Oh-repeater not working for oh-time-series in oh-chart).
The idea was to pass a group of items as a parameter, and iterate it with oh-repeater.

I also try by feeding directly JSON from an item to the oh-data-series. The idea was to construct the Json from a rule.

The following code is working well, giving the result show above.

uid: widget_1dcd9ac689
tags: []
props:
timestamp: May 8, 2024, 7:17:40 PM
component: f7-card
config:  
  title: "TestJSon"
  content: =@@'TestJSon'
slots:
  default:
    - component: oh-list
      slots:
        default:
          - component: oh-chart
            config:
              label: Power
              legend:
                top: 5%
                left: center
                show: true
            slots:
              series:
                - component: oh-data-series
                  config:
                    type: pie
                    avoidLabelOverlap: false,
                    labelLine:
                      length: 30
                    radius:
                      - 40%
                      - 60%
                    label:
                      show: true
                      formatter: "{b} {c}"
                      backgroundColor: "#F6F8FC"
                      borderColor: "#8C8D8E"
                      borderWidth: 1
                      borderRadius: 4
                      padding: 20
                    data:
                      [{'value':'20','name':'Hoven'},{'value':'30','name':'Dishwasher'},{'value':'50','name':'Light'}]                      

But if I replace the code by an item state:

uid: widget_1dcd9ac689
...
                    data:
                      =item.TestJSon.state        

I’ve got the following results:
image

It seems because oh-data-series in this case interprete the data as a string, and not as a Json.
I get the same results if I put into data the following code:

                    data:
                      "[{'value':'20','name':'Hoven'},{'value':'30','name':'Dishwasher'},{'value':'50','name':'Light'}]"                  

Does someone has any idea how to make this working ?

Thanks,
Laurent.

If it’s coming in as an item state, you need to parse that string:

data: =JSON.parse(item.TestJSon.state)

If you want to build the data dynamically and not use an item state, you can also do so. If you have a fixed number of elements then it’s trivial:

data:
  - name: 'Name 1'
    value: =items.Item1.state
  - name: 'Name 2'
    value: =items.Item2.state
...

If you have a dynamic number of elements, or want to use your group, it gets a little more difficult. You have to have the repeater outside the chart to create the array of data that you want. This means that 1) you need to then restrict the repeater’s children to only be visible during the first repeater iteration and 2) modify the repeater’s source array so that it matches the array that data expects.

- component: oh-repeater
  config:
    for: itemArray
    sourceType: itemsInGroup
    groupItem: youGroupItem
    map:  |
      loop.itemArray_source.map( x =>
        {
          name: x.label,
          value: x.stat
        }
      )
    fragment: true
  slots:
    default:
      - component: oh-chart
        config:
          visible: =loop.itemArray_idx == 0
          label: Power
          legend:
            top: 5%
            left: center
            show: true
        slots:
          series:
            - component: oh-data-series
              config:
                type: pie
                avoidLabelOverlap: false,
                labelLine:
                  length: 30
                radius:
                  - 40%
                  - 60%
                label:
                  show: true
                  formatter: "{b} {c}"
                  backgroundColor: "#F6F8FC"
                  borderColor: "#8C8D8E"
                  borderWidth: 1
                  borderRadius: 4
                  padding: 20
                data: =loop.itemArray

Hello,

Thanks a lot for your quick answer.
Solution 2 is great; I wasn’t knowing about the mapping functionality of the oh-repeater!

After some tries, i get the following result with this solution:
image

For some unknown reason, I wasn’t able to make the solution 1 with JSON.parse work.
When testing with this code, the chart stays empty.

uid: widget_1dcd9ac689
tags: []
props:
  parameters: []
  parameterGroups: []
timestamp: May 10, 2024, 11:00:35 AM
component: f7-card
config:
  title: TestJSon
slots:
  default:
      - component: oh-label-item
        config:
          title: =items.TestJSon.state
          item: TestJSon
      - component: oh-chart
        config:
          visible: =loop.itemArray_idx == 0
          label: Power
          legend:
            top: 5%
            left: center
            show: true
        slots:
          series:
            - component: oh-data-series
              config:
                type: pie
                avoidLabelOverlap: false,
                labelLine:
                  length: 30
                radius:
                  - 60%
                  - 80%
                label:
                  show: true
                  formatter: "{b} {c}W"
                  backgroundColor: "#F6F8FC"
                  borderColor: "#8C8D8E"
                  borderWidth: 1
                  borderRadius: 4
                  padding: 5
                data: =JSON.parse(items.TestJSon.state)

image

I don’t know if I miss something, or if the JSON.parse don’t work correctly in oh-chart context.

Thanks,
Laurent.

Forget about It, I finally manage to make solution 1 work as well.
I realize that my test Json in the items was not correctly formatted :frowning:

Thanks a lot for your help.

Laurent.

Hello OpenHabbers,

I am really happy about the community posts that help me to learn and thought I’d give something back here. I have created a 3-series pie chart widget (with some default series items) that created “real-time” updates based on the original version from here. I am using now for each of my widgets an overarching context component that allows me to define global variables (=vars. …) and functions (fn. …) anywhere in the widget. If I could ask one question: How can one build nested pie charts, i.e. concentric pies with different sets of series? I am experimenting with the stack: keyworkd but have not found the solution yet.

uid: widget_chart_pie3
tags:
  - chart
  - tested
  - parameterized
props:
  parameters:
    - default: 3 Element Pie Chart with Energy Mix
      description: Widget Title
      label: Title
      name: title
      required: false
      type: TEXT
    - label: Footer
      name: footer
      required: false
      type: TEXT
    - default: Solar
      description: Description1
      name: nameData1
      required: false
      type: TEXT
    - default: Grid
      description: Description2
      name: nameData2
      required: false
      type: TEXT
    - default: Heat
      description: Description3
      name: nameData3
      required: false
      type: TEXT
    - context: item
      default: Shelly_Pro_3EM_Photovoltaik_HAR_Kumulierter_Verbrauch
      description: Data Item1
      name: itemData1
      required: false
      type: TEXT
    - context: item
      default: Shelly_Pro_3EM_Grid_HAR_Kumulierter_Verbrauch
      description: Data Item2
      name: itemData2
      required: false
      type: TEXT
    - context: item
      default: ShellyEM3WaermepumpeHAR_KumulierterVerbrauch
      description: Data Item3
      name: itemData3
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Jul 11, 2025, 2:53:26 PM
component: oh-context
config:
  functions:
    addition: =(x,y) => (Number.parseFloat(x)+Number.parseFloat(y))
    naked: =(x) => Number.parseFloat(x)
    nearzero: =(x) => (Math.abs(Number.parseFloat(x)) < 0.01)
    positive: =(x) => (Number.parseFloat(x) > 0)
    product: =(x,y) => (Number.parseFloat(x)*Number.parseFloat(y))
    rounding: =(x,d) => Number.parseFloat(x).toFixed(d)
    subtract: =(x,y) => (Number.parseFloat(x)-Number.parseFloat(y))
  variables:
    colorData1: rgb(255,200,0)
    colorData2: rgb(120,120,120)
    colorData3: rgb(180,180,180)
    colarLblBack: "#F6F8FC"
    colarLblLine: "#8C8D8E"
    radiusInner: 40%
    radiusOuter: 60%
slots:
  default:
    - component: f7-card
      config:
        title: '=(props.title) ?  props.title : ""'
        footer: '=(props.footer) ?  props.footer : ""'
      slots:
        default:
          - component: oh-list
            slots:
              default:
                - component: oh-chart
                  config:
                    label: This is not recognized
                    legend:
                      top: 25%
                      left: center
                      show: true
                  slots:
                    series:
                      - component: oh-data-series
                        config:
                          type: pie
                          avoidLabelOverlap: false
                          labelLine:
                            length: 20
                          radius:
                            - =vars.radiusInner
                            - =vars.radiusOuter
                          label:
                            show: true
                            formatter: "{b} {c}W, {d}%"
                            position: outer
                            backgroundColor: =vars.colarLblBack
                            borderColor: =vars.colarLblLine
                            borderWidth: 1
                            borderRadius: 5
                            padding: 10
                          color:
                            - =vars.colorData1
                            - =vars.colorData2
                            - =vars.colorData3
                          data:
                            - value: =fn.rounding(#props.itemData1,1)
                              name: =props.nameData1
                            - value: =fn.rounding(#props.itemData2,1)
                              name: =props.nameData2
                            - value: =fn.rounding(#props.itemData3,1)
                              name: =props.nameData3

Screenshot 2025-07-11 151230

‘stack’ is not a property that applies to the pie chart series. For a nested pie chart, you should just be able to create multiple series and give each one a different radius array:

Hello,

I’m not certainly sure to understand what you want to achieve.
But one solution I experiment with is to change the datasource when you click on the pie section.
This will allow to drill down in your series to have the details of this series.

If you interest in this solution, I would need to check against it because it is not working anymore on my setup.

But as far a I remember, the idea was globally:

  1. Have you oh-chart series base on the state of the openhab items.

             - component: oh-chart
                 ...
                 series:
                   - component: oh-data-series
                     config:
                       avoidLabelOverlap: false,
                       data: =JSON.parse(items.TestJSon.state)
    
  2. Have a rule that update the item base on your values
    const tabConso2 = tabConso.slice(0, 20);
    for (var consoIdx in tabConso2) {
    var conso = tabConso[consoIdx];
    log.info(“testpower2 > tabConso:” + conso.name + " " + conso.value + “”);
    }

     json = JSON.stringify(tabConso2);
     log.info("testpower2 > json:" + json);
     items.TestJSon.postUpdate(json)
    
  3. Have the rule change the way it construct data base on selection from you UI.

Laurent.

Thanks JustinG. That seems to be an easy solution; on second thought: Wouldn’t this just expand the entire pie? What I want is to show my actual, real-time energy mix in one pie and then, concentric to that the daily energy mix, so two time 100% pies. If I just change the radius, I get a pie with 2x100% pieces.

No, you set one series radius values to something like 70% and 100% and the other series to radius values of 0% and 60% and all those values are relative to the overall size which is controlled by the chart.

    - component: oh-chart
      config: {}
      slots:
        series:
          - component: oh-data-series
            config:
              type: pie
              radius:
                - 70%
                - 100%
              label:
                show: false
              data:
                - value: 25
                - value: 25
                - value: 50
          - component: oh-data-series
            config:
              type: pie
              radius:
                - 30%
                - 60%
              label:
                show: false
              data:
                - value: 80
                - value: 10
                - value: 10

image

The two series are completely independent of each other. You could even make them overlap if you like just by setting overlapping ranges for the radius values:
image

1 Like