Custom Widget ECharts Bar

I know that the bar chart has been asked for before, but I can’t quite find an answer.

I have figured out how to make gauge charts using oh-data-series, and I know that pie charts are also available. Since I consider a bar chart to be pretty much the most basic “chart”, I didn’t think to check if it was actually available.

My plan was to combine SVG components and a single bar to make a thermometer. I’ve done most of the SVG work (the scale is just an example and was meant to be drawn by the bar chart so that it could be changed/scaled):

Thermometer5_opt

(the SVG isn’t shown correctly here for some reason, but that’s not relevant to the issue - it works correctly as a widget)

But, to my surprise, when I tried to add the bar chart component, nothing happens - and when looking up oh-data-series it seems like only gauge and pie are available.

I know that there’s a question of size as to how much of ECharts to “embed” with OH, but as far as I can understand, bar, line, heatmap and scatter are already used in the “Charts type” Page. So, why aren’t these also available for “general” use via oh-data-series? Is it because only parts of these “components” are included?

There might be a way to use what’s already here for my simple need, but I can’t seem to figure it out. The charts pages seem to be “bound” to time series, and I don’t want a “grid”. I just want a single bar.

Actually, I could easily enough implement the “bar” itself using an SVG rect. What I really don’t want to make is the (y) scale, with all the lines and the labels. I’m pretty sure that you can configure an ECharts bar component to draw this pretty easily.

Are there any ways to achieve what I’m trying to do?

We are using a bar chart un the semanticHomeMenu_Rollershutter widget.
Perhaps you can find there what you are looking for.

If you already have the rest of the widget constructed out of svg components, what benefit do you get from incorporating the chart for this? For a thermometer, you only need one value which presumably is already stored in an item if you are also getting that information from a chart, and you can just use that item value to scale an appropriate object within the svg.

Here’s a widget that does something similar:

1 Like

I want the chart to draw the y-axis scale. I don’t want a fixed scale, but one with configurable min/max and which supports both Celsius and Fahrenheit. It would also be very convenient to not have to align the “bar value” with the axis scale myself (if the chart draws the bar, it’s already aligned).

edit: to clarify: The Celsius scale in the SVG at this time is just to “populate” it to see how it would look - it was never intended to be used (I just copied it from somewhere).

Thanks, but it seems that this widget uses oh-aggregate-series. I don’t know how that works, but I guess that if I only give it one piece of data, the “average” would equal the data. It might be workable, but I was ideally looking to use oh-data-series.

It also uses a “grid”, like the “Pages” does - but I’ll have to play with it to see how much I can “tweak” it. But, this is a good start for some experimentation :+1:

1 Like

It seems to me like oh-aggregate-series is fundamentally unsuitable for this because it is based on persisted data, not on current data. Whatever I try, it wants to show the persisted data for some period. I just want to show the current Item value - regardless of if the Item is stored in persistence or not.

I can’t find a way to make this work. As can be seen by this test:

uid: widget_test_bar
tags:
  - gauge
  - temperature
props:
  parameters:
    - context: item
      description: The current value
      label: Item
      name: item
      required: true
      type: TEXT
    - context: number
      description: The min range value
      label: MinRange
      name: minRange
      required: false
      type: TEXT
    - context: number
      description: The max range value
      label: MaxRange
      name: maxRange
      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: Dec 19, 2024, 6:16:17 PM
component: oh-chart
config:
  chartType: isoWeek
  height: =props.height
  periodVisible: false
slots:
  series:
    - component: oh-aggregate-series
      config:
        aggregationFunction: max
        backgroundStyle:
          borderRadius:
            - 50
            - 50
            - 50
            - 50
          color:
            colorStops:
              - color: yellow
                offset: 0
              - color: white
                offset: 1
            type: linear
            x: 0
            x2: 0
            y: 0
            y2: 1
        dimension1: minute
        gridIndex: 00
        item: =props.item
        itemStyle:
        showBackground: false
        type: bar
        xAxisIndex: 0
        yAxisIndex: 0
        data:
          - name: 1
            value: 30
  tooltip:
    - component: oh-chart-tooltip
      config:
        orient: vertical
        show: true
        trigger: axis
  xAxis:
    - component: oh-value-axis
      config:
        axisLabel:
          color: red
          show: true
        axisLine:
          show: true
        axisPointer:
          show: true
        axisTick:
          interval: 1
          lineStyle:
            opacity: 1
          show: true
        gridIndex: 0
        min: 1
        max: 1
        nameTextStyle:
          color: green
        show: true
  yAxis:
    - component: oh-value-axis
      config:
        gridIndex: 0
        inverse: false
        max: 30
        min: -30
        show: true
        splitNumber: 10
        axisTick:
          length: 40
        minorTick:
          show: true
          length: 20
        axisLabel:
          show: true
          margin: 23
          align: right
          verticalAlign: bottom

…the bar chart can be used to draw a quite reasonable axis scale (that can be changed to fit the user need). But, I can’t get the “bar” itself to show anything meaningful. I suspect that there is some “hardcoding” in oh-aggregate-series that overrides whatever data I try to set. I can probably hide everything except the axis scale, and as such use ECharts only to create the axis scale.

But, I can’t imagine how I would go about aligning the scale with the SVG coordinates, so that I could draw the “mercury”/bar with an SVG rectangle. I wouldn’t even know where to begin to try to achieve this, as far as I know, there’s no access to any form of “coordinates” output by ECharts. I could just manually place them so that they align, but it would never work when changing min/max, when the widget is scaled etc.

So, as far as I can tell, this is a dead-end without the ability to use oh-data-series and bar together.

What about this:

Something to play with…

uid: widget_d385fb64be
props:
  parameterGroups: []
  parameters:
    - name: prop1
      label: Prop 1
      type: TEXT
      description: A text prop
    - name: item
      label: Item
      type: TEXT
      context: item
      description: An item to control
tags: []
component: f7-card
config:
  title: '=(props.item) ? "State of " + props.item : "Set props to test!"'
  footer: =props.prop1
  content: =items[props.item].displayState || items[props.item].state
slots:
  default:
    - component: f7-block
      config:
        class:
          - no-margin
          - no-padding-left
          - no-padding-right
          - padding-top-half
        style:
          height: 120px
          width: 5%
      slots:
        default:
          - component: oh-chart
            config:
              chartType: day
              height: 80%
              periodVisible: false
            slots:
              grid:
                - component: oh-chart-grid
                  config:
                    borderWidth: 0
                    height: 100%
                    includeLabels: true
                    top: 0px
              series:
                - component: oh-aggregate-series
                  config:
                    aggregationFunction: last
                    backgroundStyle:
                      borderRadius:
                        - 50
                        - 50
                        - 50
                        - 50
                      color:
                        colorStops:
                          - color: yellow
                            offset: 0
                          - color: white
                            offset: 1
                        type: linear
                        x: 0
                        x2: 0
                        y: 0
                        y2: 1
                    dimension1: date
                    gridIndex: 0
                    item: OneCallAPIweatherandforecast_Current_Temperature
                    itemStyle:
                      borderRadius:
                        - 50
                        - 50
                        - 50
                        - 50
                      color: lightgray
                    showBackground: true
                    type: bar
                    xAxisIndex: 0
                    yAxisIndex: 0
              tooltip:
                - component: oh-chart-tooltip
                  config:
                    orient: vertical
                    show: false
                    trigger: axis
              xAxis:
                - component: oh-category-axis
                  config:
                    axisLabel:
                      color: red
                      show: false
                    axisLine:
                      show: false
                    axisPointer:
                      show: false
                    axisTick:
                      interval: 0
                      lineStyle:
                        opacity: 0.5
                      show: true
                    categoryType:
                    gridIndex: 0
                    nameTextStyle:
                      color: transparent
                    show: true
              yAxis:
                - component: oh-value-axis
                  config:
                    gridIndex: 0
                    inverse: false
                    max: 50
                    min: -50
                    show: false


It draws nothing for me. I’m still on 4.2.3 if there has been any changes to this in 4.3.0. Are you sure you pasted the same version that drew your screenshot?

edit: This probably does nothing here, since this is probably one of your Items: OneCallAPIweatherandforecast_Current_Temperature. I’ll try and replace that with a valid Item :wink:

edit2: That helped.

1 Like

Sorry, forgot to make it a prop…

1 Like

After looking a bit at the code, it seems that it’s as I thought, that bar, line etc. are also imported:

They are just not made available for oh-data-series:

I don’t know if there’s more that’s needed to make line, bar etc. available from the “data-series”, but if it’s only a question of specifying them as seriesTypeParameter, then I think it should be done.

It seems like I’m getting closer to what I want to do now:

uid: widget_test_bar
tags:
  - bar
  - temperature
props:
  parameters:
    - context: item
      description: The current value
      label: Item
      name: item
      required: true
      type: TEXT
    - context: number
      description: The min range value
      label: MinRange
      name: minRange
      required: false
      type: TEXT
    - context: number
      description: The max range value
      label: MaxRange
      name: maxRange
      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: Dec 19, 2024, 11:17:53 PM
component: oh-chart
config:
  chartType: day
  height: =props.height
  periodVisible: false
slots:
  grid:
    - component: oh-chart-grid
      config:
        #left: -90
        borderWidth: 0
        includeLabels: false
        width: 10
  series:
    - component: oh-aggregate-series
      config:
        aggregationFunction: last
        dimension1: date
        gridIndex: 0
        item: =props.item
        itemStyle:
          color: red
        showBackground: false
        type: bar
        xAxisIndex: 0
        yAxisIndex: 0
        offset: 5
        barWidth: 10
        barGap: 0
        barCategoryGap: 0
  tooltip:
    - component: oh-chart-tooltip
      config:
        orient: vertical
        show: true
        trigger: axis
  xAxis:
    - component: oh-value-axis
      config:
        axisLabel:
          color: red
          show: true
        axisLine:
          show: true
        axisPointer:
          show: true
        axisTick:
          interval: 1
          lineStyle:
            opacity: 1
          show: true
        gridIndex: 0
        show: false
  yAxis:
    - component: oh-value-axis
      config:
        startValue: -20
        scale: false
        offset: 0
        gridIndex: 0
        inverse: false
        max: 30
        min: -30
        show: true
        splitNumber: 10
        axisLine:
          show: true
          onZero: true
        axisTick:
          length: 40
        minorTick:
          show: true
          length: 20
        axisLabel:
          show: true
          margin: 23
          align: right
          verticalAlign: bottom

However, I can’t get “the fluid/mercury” to start from the bottom instead of from zero. There is an axis-parameter startValue which should allow just that, but it was introduced in ECharts 5.5.1 - which was first included in OH on August 12:

According to the GitHub tags, that commit is part of 4.3.0 though, so I guess I’ll have to upgrade my installation before trying to do anything more on this.

1 Like

Just to be able to test without upgrading my OH installation, I started a npm “dev session” of the latest MainUI main branch, which includes ECharts 5.5.1, and it seems to work fine there:

2 Likes

I made the following tiny change:

 .../org.openhab.ui/web/src/assets/definitions/widgets/chart/index.js    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/bundles/org.openhab.ui/web/src/assets/definitions/widgets/chart/index.js b/bundles/org.openhab.ui/web/src/assets/definitions/widgets/chart/index.js
index 27315258..36631ad3 100644
--- a/bundles/org.openhab.ui/web/src/assets/definitions/widgets/chart/index.js
+++ b/bundles/org.openhab.ui/web/src/assets/definitions/widgets/chart/index.js
@@ -286,7 +286,7 @@ export default {
     props: {
       parameterGroups: [actionGroup()],
       parameters: [
-        seriesTypeParameter('gauge', 'pie'),
+        seriesTypeParameter('gauge', 'pie', 'line', 'bar', 'heatmap', 'scatter'),
         ...actionParams()
       ]
     }

…and ran this modified version of MainUI locally. This change makes the following widget work:

uid: widget_test5xx_bar
tags:
  - bar
  - temperature
props:
  parameters:
    - context: item
      description: The current value
      label: Item
      name: item
      required: true
      type: TEXT
    - context: number
      description: The min range value
      label: MinRange
      name: minRange
      required: false
      type: TEXT
    - context: number
      description: The max range value
      label: MaxRange
      name: maxRange
      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: Dec 20, 2024, 7:56:37 PM
component: oh-chart
config:
  chartType: day
  height: =props.height
  periodVisible: false
slots:
  grid:
    - component: oh-chart-grid
      config:
        borderWidth: 0
        includeLabels: false
        width: 10
  series:
    - component: oh-data-series
      config:
        data:
          - name: "Name of data"
            value: =Number.parseFloat(items[props.item].state)
        gridIndex: 0
        itemStyle:
          color: red
        showBackground: false
        type: bar
        xAxisIndex: 0
        yAxisIndex: 0
        offset: 5
        barWidth: 10
        barGap: 0
        barCategoryGap: 0
  tooltip:
    - component: oh-chart-tooltip
      config:
        orient: vertical
        show: true
        trigger: axis
  xAxis:
    - component: oh-value-axis
      config:
        axisLabel:
          color: red
          show: true
        axisLine:
          show: true
        axisPointer:
          show: true
        axisTick:
          interval: 1
          lineStyle:
            opacity: 1
          show: true
        gridIndex: 0
        show: false
  yAxis:
    - component: oh-value-axis
      config:
        startValue: -30
        scale: false
        offset: 0
        gridIndex: 0
        inverse: false
        max: 30
        min: -30
        show: true
        splitNumber: 10
        axisLine:
          show: true
          onZero: true
        axisTick:
          length: 40
        minorTick:
          show: true
          length: 20
        axisLabel:
          show: true
          margin: 23
          align: right
          verticalAlign: bottom

That means that you avoid the “workaround” with using aggregate/last, potential reliance on the Item being persisted - and it also gives more freedom to manipulate the data directly, handle units etc., since you can set the value directly (value: =Number.parseFloat(items[props.item].state)). I also think it makes it easier to understand the widget, less code is involved, and that it’s generally a better way to do it when you don’t need the aggregate function.

I don’t know how to test if this has some other “negative impact” on the system though, so I’m a bit hesitant to straight forward suggest this change, but it does look tempting. I have tried to look at related commits, and it doesn’t seem like there’s anything more too it. Here is the commit that enabled the use of pie for example:

Is this something I should “suggest” at GitHub - or is it even a small enough change that I could make a PR and that it would fall into the “CLA exception” clause?

@florian-h05 Could you please check this one-liner….
Could be a candidate for the next patch release, if/when there is one….

1 Like

I think it might turn out to be “impossible” to achieve what I wanted to. I’ve managed to get it somewhat close to what I wanted (many parameters and details aren’t there yet, but the fundamentals).

However, I just can’t align the different elements correctly. To explain, the widget is essentially 3 layers - there’s a SVG layer in the bottom supplying the “background” of the widget. Then there’s the ECharts bar in the middle layer, which draws the “fluid”/mercury and the scale. On top there’s another SVG layer that makes up the semi-transparent “glass” that covers the fluid/mercury, to give it a somewhat more realistic look.

The problem is aligning these 3 layers so that they always are exactly on top of each other, also when scaled etc. I didn’t think that was going to be so difficult using CSS, but all I’ve tried until now has basically failed. I can get them to line up under some circumstances, but when inserting the widget in a “custom layout” page using Firefox, it goes completely crazy. It also doesn’t want to fit precisely.

I think some of the problem might be that I try to mix canvas and SVG layers. Canvases, being bitmaps that’s scaled by the browser and SVGs that is being rendered in the correct size just don’t want to behave the same.

There is an option to use a SVG renderer for ECharts, but it’s not imported by OH and even if it were, I have no idea how to tell ECharts what renderer to use. And, I don’t know if that would solve anything, it just sounds more likely that they would behave the same if all 3 layers were SVGs. But, it’s probably not that simple anyway, since the ECharts “layer” is several nested elements, while the SVGs are just a single element each.

Maybe some CSS guru could figure it out, but for now I really am out of ideas.

The post gets too long with the widget included, so I’ll have to try to post that as a separate post.

Nope, I can’t post the widget at all, since it’s 10400 something characters. I’m attaching a picture of it, though that won’t be of much help.

bilde

edit: I created a Gist with the widget:

1 Like

This one liner should be fine :+1:
We however have to think about or check the other types you added there, how well they are useable with data series.

BTW:
The file you edited is only the widget definition, I wonder that it does not work without changing the widget definition, because in general for the charts stuff you can do much more from the code tab than from the config UI.

My thought were simply to add what was already imported, if somebody can find some way to use them. line kind of have to work as I see it, as the data works “the same way” as a bar chart. I can’t quite imagine how it can be useful for a widget, but that’s a different matter - somebody might find some creative use that I can’t imagine.

heatmap and scatter I know less about, but when I look at the data source for a heatmap it seems to consist of an array of 3 data points x, y and “value”/color. That should be simple enough to use in a widget, where you want the color for something to change gradually with some value for example. This could arguably be solved much easier using CSS, but maybe it could make sense in some situation. The scatter data source seems to be even simpler, an array of just x and y coordinates. Again, I can’t quite see a use for it, but you never know what people come up with :wink:

If you are thinking of editing “the widget” after inserting it into a page and then edit the widget part, that might get around some restrictions, but it makes it hard to reuse the widget (copy/paste is of course a possibility, but it’s still somewhat “manual”) and to share it on the marketplace for others to use.

Agreed that these all can be used easily in widgets :+1:
Do you want to open a PR with the change you suggested above?
You may also need to regenerate the component docs, see openhab-webui/bundles/org.openhab.ui/CONTRIBUTING.md at main · openhab/openhab-webui · GitHub.

It in any way is not great, but it can still be shared on the marketplace with those additonal “undocumented” props.