Plotly interactive Graph Card

Thank you so much!

It was not exactly what i was looking for, as I wanted the chart to have limits at 25-17 °C and if these limits are exceeded than I wanted to expand the range just enough so that all the data-points fit, but I managed to do it with your help!

    range: |
      $fn ({ getFromConfig }) => {
        const trackedEntities = getFromConfig("entities").filter(({ entity }) =>
          entity.startsWith("sensor.pokoj_teplota") || entity.startsWith("sensor.loznice_teplota")
        );
        const all = trackedEntities.flatMap(({ y }) => y.filter(val => !isNaN(val) && val !== null));
        return [
          Math.min(17, Math.min(...all)), // cap to 17 but if the value is lover cap to that
          Math.max(25, Math.max(...all)), // cap to 25 but if the value is higher cap to that
        ]
      }

Also an added benefit of my approach is that it filters all non-number or null values so that if the sensor was offline or something (In my case I monitor temperature through ESP32 and my router restarts every night, because of reasons) it will ignore them and will adjust by the real values. :smiley:

obrazek

Universal functions for the win!

You can simplify it a bit with:

Math.min(17, ...all); // from 17 or less
Math.max(25, ...all); // to 25 or more
1 Like

I’m attempting to migrate away from ApexCharts but I’m having trouble with one aspect.
In ApexCharts I can easily group by a time interval, then chart the difference between the first and last value in that group.

group_by:
  func: diff
  duration: 30min

I can almost replicate this in Plotly by using the below:
filters:
- resample: 30m
- delta

My issue is that I’d like to see the bar for the current 30 minute period updating live, whereas it doesn’t get displayed until the next 30 minute period starts.
Is there an easy approach I’m missing here?

It is possible but requires a bit of work
Something like this may work

filters:
  - fn: "({xs, ys}) => ({ xs: [...xs, xs[xs.length-1]+ 30*1000*60-1], ys: [...ys, ys[ys.length-1]})"
  - resample: 30m

The idea is to duplicate the last poit and move it 30 minutes into the future, so it falls in the next bar.

The minus 1 is there so that you don’t get a fake value if the plot is rendered on exactly the last millisecond of a 30 minutes block. It will be very funny if the whole thing doesn’t work but I was worried about something that has a 1 in 2 million chance of happening::joy:

Oh, and you may also want to use a global time_offset: 30m so the x axis reaches 30 mins into the future

I just noticed something weird on one of my basic weather graphs.

I did change it to be less ridged, but how is 66.9 > 74? Is there possibly some grouping option I missed?

Chris

You somehow have 2 yaxes (one on the right).

Paste your yaml

Right. The temp on the left and humidity in the right.

          - type: custom:plotly-graph
            entities:
              - entity: input_number.weather_forecast_low
                legendgroup: 1
                connectgaps: true
                name: Low
                line:
                  width: 3
                  color: rgba(59,117,175,1)
              - entity: input_number.weather_forecast_high
                legendgroup: 2
                connectgaps: true
                name: High
                line:
                  width: 3
                  color: rgba(239,134,54,1)
              - entity: sensor.relative_humidity
                legendgroup: 3
                connectedgaps: true
                name: Humidity
                fill: tozeroy
              - entity: sensor.temperature
                legendgroup: 4
                connectgaps: true
                name: Temperature
                line:
                  width: 3
            hours_to_show: 24
            refresh_interval: 3600
            layout:
              paper_bgcolor: transparent
              plot_bgcolor: transparent
            config:
              staticPlot: true
            defaults:
              entity:
                show_value: true
                line:
                  shape: spline

I think one of the temperatures landed on the right. Can you try adding “yaxis: y” to all temperature entities to confirm?

Ok, that seems like it got it straight.

Thanks!

This seems to ve a bug. Would you open an issue in GitHub so I don’t forget to take a look in a couple of days?

All set. Incorrect grouping · Issue #268 · dbuezas/lovelace-plotly-graph-card · GitHub

1 Like

v3.3.3 Released with a couple of minor corrections and a fix to the yaxis grouping reported by @DarthSebulba04 (thank you!).

Next a fun statistic. I’m tracking downloads of this card via a Github integration in Home Assistant and plotting it with… you guessed it! this very card!

  • v3.3.1 Was downloaded 6907 times.
  • After two months, there were still an average of ~10 downloads/h.
  • 2^7 stars ini total, but no new stars in the last month.

1 Like

Very cool! I find Plotly to produce by far the nicest looking graphs.

I can also confirm that v3.3.3 resolves the unit grouping problem.

Thanks for all the work you put into this!

1 Like

Is there anyway to autorange the y-axis to the data in the current view? I have a years worth of data but only show 1 week on load. I want the y-axis max to be the max of the data in view, not the whole data series.

edit:
Looks like I need to be using the “autorange_after_scroll: true” parameter however it doesn’t seem to adjust the max y axis for only data in view. I have a years worth of stacked bar charts and some data very far out pushes the max y axis high, then when I zoom into more recent data the max y axis doesn’t come down. Is this expected behavior?

My code is:

- type: custom:plotly-graph
  hours_to_show: 1M
  autorange_after_scroll: true
  layout:
    barmode: stack
  entities:
    - entity: sensor.yearly_downstairs_heat
      type: bar
      period: day
      filters:
        - fn: |-
            ({xs, ys, hass}) => {
              let xx = [];
              let yy = [];
              for (const [key, value] of Object.entries(hass.states["sensor.yearly_downstairs_heat"].attributes)) {
                  let d = new Date(key).getTime();
                  if (d > 0)
                      xx.push(d);
                      yy.push(value);
              }
              return {
                xs: xx,
                ys: yy
              };
            }
- type: custom:plotly-graph
  hours_to_show: 1M
  autorange_after_scroll: true
  layout:
    barmode: stack
    yaxis:
      fixedrange: true
    xaxis:
      rangeselector:
        "y": 1.2
        buttons:
          - count: 7
            step: day
          - count: 14
            step: day
          - count: 1
            step: month
          - count: 6
            step: month
          - count: 1
            step: year
  entities:
    - entity: sensor.yearly_downstairs_heat
      type: bar
      period: day
      filters:
        - fn: |-
            ({xs, ys, hass}) => {
              let xx = [];
              let yy = [];
              for (const [key, value] of Object.entries(hass.states["sensor.yearly_downstairs_heat"].attributes)) {
                  let d = new Date(key).getTime();
                  if (d > 0)
                      xx.push(d);
                      yy.push(value);
              }
              return {
                xs: xx,
                ys: yy
              };
            }
    - entity: sensor.yearly_downstairs_ac
      type: bar
      period: day
      filters:
        - fn: |-
            ({xs, ys, hass}) => {
              let xx = [];
              let yy = [];
              for (const [key, value] of Object.entries(hass.states["sensor.yearly_downstairs_ac"].attributes)) {
                  let d = new Date(key).getTime();
                  if (d > 0)
                      xx.push(d);
                      yy.push(value);
              }
              return {
                xs: xx,
                ys: yy
              };
            }

Yes that’s the right thing to use. I think the issue is that you have an error in your filter: the block inside the if is missing the curly braces (probably because you’ve been doing a lot of python lately :stuck_out_tongue: )

Wrong:

if (d > 0)
   xx.push(d);
   yy.push(value);

Right:

if (d > 0) {
   xx.push(d);
   yy.push(value);
}

What may be happening is that plotly is getting confused because the length of xx and yy differs.

Thanks for the help. It is true I have been using python a lot lately lol. I think my problem is between home and work I have to use too many different languages so I know just enough to be dangerous in all of them but it is hard to keep the syntaxs straight…

I fixed that and it doesn’t look like it solved the problem. Seems like maybe there is an issue with how I am using the filter block as when I remove the filter block and use another sensor it seems to work well.

Then I suggest you add another filter at the end
- fn: console.log # open the devtools console to see the data
maybe there is something wrong with the xs an ys arrays?
These tips on debugging may be of use too :slight_smile: