Plotly interactive Graph Card

Thanks. It took some time but statistics started to work after.

I am trying to display daily consumption. This is what it looks like:
image

Is there a way how can I move “total” line graph to the back, so that bars are in the front?

And this is the code:

type: custom:plotly-graph
entities:

  - entity: sensor.hourly_energy
    name: |
      $fn ({ ys,meta }) =>
        "Grid" + "(" +ys[ys.length - 1].toFixed(2)+"kWh)"
    type: bar
    filters:
      - filter: i>0
    statistic: state
    period: hour
    texttemplate: '%{y}'
    marker:
      color: red
  - entity: sensor.hourly_self_consumed_energy
    name: |
      $fn ({ ys,meta }) =>
        "Self" + "(" +ys[ys.length - 1].toFixed(2)+"kWh)"
    type: bar
    filters:
      - filter: i>0
    statistic: state
    period: hour
    texttemplate: '%{y}'
    marker:
      color: rgb(0, 204, 0)
  - entity: sensor.hourly_electricity_exported_to_grid_huawei
    name: |
      $fn ({ ys,meta }) =>
        "Export" + "(" +ys[ys.length - 1].toFixed(2)+"kWh)"
    type: bar
    filters:
      - filter: i>0
    statistic: state
    period: hour
    texttemplate: '%{y}'
    marker:
      color: rgb(230, 230, 0)
  - entity: sensor.hourly_house_total_consumed_energy
    name: |
      $fn ({ ys,meta }) =>
        "Total" + "(" +ys[ys.length - 1].toFixed(2)+"kWh)"
    type: line
    line:
      shape: spline
      color: rgb(255, 214, 204)
    fill: tozeroy
    statistic: state
    period: hour
    texttemplate: '%{y}'
    marker:
      color: rgb(255, 214, 204)
hours_to_show: current_day
layout:
  barmode: stack
  xaxis:
    rangeselector:
      'y': 1.2
  yaxis:
    fixedrange: true

One another question. For similar with my water usage, I have values in cubic meters. I display that on the bar, so I get values like “0.323”. How can I multiply it by 1000 to get “323” value instead? I am using texttemplate: '%{y}'

I have a simple plot but have been unable to disable the two-finger scroll. I’m on a MacBook and every time I scroll down on my dashboard, my plot zooms out when I scroll with two fingers with the mouse over the plot. I’ve set drag mode and scrollZoom both to false, but the behavior does not change. Any suggestions? I would like to not fully disable zooming (by fixing the axes).
Thanks

Is there a way how can I move “total” line graph to the back

Try changing the order of the entities array

How can I multiply it by 1000

try:

entity: sensor.xyz
texttemplate: '%{y:.1f}'
unit_of_measurement: L
filters:
  - multiply: 1000

disable the two-finger scroll

try:

config:
  scrollZoom: false

Filter worked, thanks.
But the other question regarding graphs - moving entites did not change anything. Line with fill is always in the foreground.

Make sure you’re wrapping the javascript inside of a $fn function that is used to add custom behavior.

Does that help?

on_dblclick: |-
  $fn ({ hass }) => () => {
     window.history.pushState(null,"","/lovelace/living_room")       
     window.dispatchEvent(new CustomEvent("location-changed"))
   }

This is a very cool card that I only just discovered from your reference to my my tap_action navigate post. I installed plotly and got it working using the code above.

2 Likes

Ok, I found out why changing the order of the traces/entities doesn’t affect what occludes what. It is because all your entities have the same unit_of_measurement and therefore the same yaxis. In this case it looks like Plotly decides what to do itself.

The trick is to set yaxis yourself:

  - entity: sensor.sensor_background
    yaxis: y
    type: bar
  - entity: climate.sensor_foreground
    yaxis: y2 # <---- here, this forces a new axis and it will be rendered on top.

You can then hide the y2 axis and force it to stay in sync with y

layout:
  yaxis2:
    visible: false
    matches: y

absolutely! :heart: Thanks
Javascript isn’t my forte…

1 Like

This totally worked! thanks.

Thank you for your help. I placed “scrollZoom: false” under a config: section as suggested, but this did not fix my problem. Let me describe the behavior more specifically (in case this helps):

  • If my mouse is not over the plot area (above or below the card) and I put my two fingers on the trackpad and scroll (up, down or back and fourth) the behavior is as expected (no zooming). I can scroll past the Plotly card up and down with no problems as long as I don’t take my two fingers off the trackpad and as long as they are initially placed on the trackpad when my mouse is NOT over the plot.

  • If the mouse is IN the plot area (so the mouse curser is a “plus”) and then I put my two fingers on the trackpad to scroll up or down, the plot will zoom in or out. The display also scrolls up or down even to the point where the mouse will no longer be over the plot. As long as I keep my two fingers on the trackpad, the plot will also zoom while I’m scrolling, even if my mouse has moved completely off of the graph.

Not sure if there’s any way to fix this, but here is my code:
type: custom:plotly-graph
disable_pinch_to_zoom: true
layout:
config:
scrollZoom: false
margin:
r: 75
dragmode: false
legend:
orientation: h
xanchor: center
x: 0.5
entities:

  • entity: sensor.deye_sunsynk_sol_ark_pv_power
    name: Solar
    fill: tozeroy
    line:
    color: gold
    filters:
    • sliding_window_moving_average:
      window_size: 10
      extended: false
  • entity: sensor.deye_sunsynk_sol_ark_load_power
    name: Load
    fill: tozeroy
    line:
    color: blue
    filters:
    • sliding_window_moving_average:
      window_size: 10
      extended: false
  • entity: sensor.deye_sunsynk_sol_ark_grid_power
    name: Grid
    fill: tozeroy
    line:
    color: red
    filters:
    • sliding_window_moving_average:
      window_size: 10
      extended: false
  • entity: sensor.deye_sunsynk_sol_ark_battery_state_of_charge
    name: Battery SOC
    line:
    color: black
    hours_to_show: 24
    refresh_interval: 10
    title: Energy Overview

Please fix the code block formatting and I’ll take a look.

type: custom:plotly-graph
disable_pinch_to_zoom: true
layout:
  config:
    scrollZoom: false
  margin:
    r: 75
  dragmode: false
  legend:
    orientation: h
    xanchor: center
    x: 0.5
entities:
  - entity: sensor.deye_sunsynk_sol_ark_pv_power
    name: Solar
    fill: tozeroy
    line:
      color: gold
    filters:
      - sliding_window_moving_average:
          window_size: 10
          extended: false
  - entity: sensor.deye_sunsynk_sol_ark_load_power
    name: Load
    fill: tozeroy
    line:
      color: blue
    filters:
      - sliding_window_moving_average:
          window_size: 10
          extended: false
  - entity: sensor.deye_sunsynk_sol_ark_grid_power
    name: Grid
    fill: tozeroy
    line:
      color: red
    filters:
      - sliding_window_moving_average:
          window_size: 10
          extended: false
  - entity: sensor.deye_sunsynk_sol_ark_battery_state_of_charge
    name: Battery SOC
    line:
      color: black
hours_to_show: 24
refresh_interval: 10
title: Energy Overview

Ahá, the config doesn’t go inside layout. it goes on the root level

I am using an expression to calculate a range for a y-axis, because I need to exclude a trace. I use getFromConfig(entities) to get the traces, then find min and max for the desired traces.

Can I somehow detect which traces are shown? (Enabled or disabled in the legend)

I want to make a chart with a overview of the energy used and produced in a year. So 2 plots for a month, 24 a year.

Is that possible ? And how do I make it happen ?

Can I somehow detect which traces are shown?

That’s unfortunately not supported at the moment. Feel free to open a feature request issue in the repo and I’ll look into it for the next release

yes, that’s possible, look into:

And while you are at it, you may want to read the whole readme and look around in the discussions page.

Hi, discovered this great Plotly extension, and created this power-energy history-graph using some example-code from some examples.
I added a function to add the the ‘live’ state info of the entity to this graph. As you can see when using this, the last column is continuously growing. (No energy-export here, due to sun-set :slight_smile:)

type: custom:plotly-graph
title: Plotly
hours_to_show: current_week
time_offset: 8h
stack: false
defaults:
  entity:
    unit_of_measurement: kWh
    texttemplate: '%{y}'
    type: bar
    statistic: state
    period:
      0s: 5minute
      2h: hour
      6d: day
      360d: month
entities:
  - entity: sensor.p1_meter_totale_energie_import
    filters:
      - delta
      - fn: |
          ({xs,ys,hass,statistics}) =>   {
            let statLast = statistics.length-1;
            let lastStatisticsDateTime = new Date(statistics[statLast].end);   
            let lastStateDateTime = new Date(hass.states["sensor.p1_meter_totale_energie_import"].last_changed);   
            let stateLastStatic = statistics[statLast].state; 
            let stateLastState = hass.states["sensor.p1_meter_totale_energie_import"].state
            let state = stateLastState-stateLastStatic; 
            if (state>0){   
              if (lastStateDateTime.getTime()>lastStatisticsDateTime.getTime()){ 
                xs.push(lastStatisticsDateTime);
                ys.push(state);
              }else{
                ys[statLast]+=state;
              }
              //debugger
            }
            return ({ xs,ys }) 
          }
      - store_var: import
      - fn: console.log
      - fn: |
          ({hass}) => {
            console.log(hass.states["sensor.p1_meter_totale_energie_export"])
          }
  - entity: sensor.p1_meter_totale_energie_export
    filters:
      - delta
      - fn: |
          ({xs,ys,hass,statistics}) =>   {
            let statLast = statistics.length-1;
            let lastStatisticsDateTime = new Date(statistics[statLast].end);   
            let lastStateDateTime = new Date(hass.states["sensor.p1_meter_totale_energie_export"].last_changed);   
            let stateLastStatic = statistics[statLast].state; 
            let stateLastState = hass.states["sensor.p1_meter_totale_energie_export"].state
            let state = stateLastState-stateLastStatic; 
            if (state>0){   
              if (lastStateDateTime.getTime()>lastStatisticsDateTime.getTime()){ 
                xs.push(lastStatisticsDateTime);
                ys.push(state);
              }else{
                ys[statLast]+=state;
              }
              //debugger
            }
            return ({ xs,ys }) 
          }
      - store_var: export
      - fn: console.log
      - fn: |
          ({hass}) => {
            console.log(hass.states["sensor.p1_meter_totale_energie_export"])
          }
refresh_interval: auto
fn: |
  $fn({getFromConfig, vars})=> {
    const range = getFromConfig("visible_range");
    const width = range[1] - range[0];
    vars.scroll = (label, p) => ({
      args: [
        {
          layout: {
            "xaxis.range": [range[0] + width*p, range[1] + width*p],
          }
        }, {
          transition: {
            duration: 150,
          }
        }
      ],
      label,
      method: "animate",
    })
    vars.zoom = (label, h) => ({
      args: [
        {
          layout: {
            "xaxis.range": [Date.now()-1000*60*60*h, Date.now()],
          }
        }
      ],
      label,
      method: "animate",
    })
  }
layout:
  barcornerradius: 15
  bargap: 0.1
  bargroupgap: 0.1
  height: 50%
  xaxis:
    tickangle: -45
  updatemenus:
    - buttons:
        - $fn({vars}) => vars.scroll( '<', -.5)
        - $fn({vars}) => vars.scroll( '>', .5)
      direction: right
      active: -1
      pad:
        r: 10
        t: 0
      type: buttons
      x: -0.1
      xanchor: left
      'y': -0.2
      yanchor: top
    - buttons:
        - $fn({vars}) => vars.zoom( '1y', 24*366)
        - $fn({vars}) => vars.zoom( '1m', 24*31)
        - $fn({vars}) => vars.zoom( '1w', 24*7)
        - $fn({vars}) => vars.zoom( '1d', 24)
        - $fn({vars}) => vars.zoom( '1h', 1)
      direction: right
      active: -1
      pad:
        r: 100
        t: 0
      type: buttons
      x: 0.1
      xanchor: left
      'y': -0.2
      yanchor: top

So far so good.

Now I have two questions, maybe some one can help, as I do not find a solution to the questions:

  1. As you can see I copied the code for the ‘live’ data for the last bar (the fn: code within the entity sections), and changed to entity-name in the hass.states[ ]. I did so, because I need the name of the entity to get the current state of the entity. My question here is:
    Is this name accessible as a variable, so I can make this one central code?

  2. See these two graphs, both showing the same page.

The upper one is opened at 18:52 hours, and the lower one is opened at 18:56 hours.
As you can see the upper one is continuing to increase in the 18:50 column, while the lower one has a new 18:55 column and is increased there.
I assume that the statistics are not updated while viewing the page. Is there a option to update the statistics within the view-time of this page?

Cool, I see you are really squeezing it really well already! :slight_smile:

Is this name accessible as a variable, so I can make this one central code?

Yes, use get('.entity'). The get function (or the alias getFromConfig) grabs stuff from other parts of the config, including defaults and stuff that gets populated as the yaml is interpreted. It can do both absolute (from the root) and relative paths (relative to wherever the function lyes). To do relative paths, just start with a dot.

This feature is designed it to enable the kind of advanced stuff like what you are doing :+1:

To reuse the function, you can:

  • define it at the top and put it in vars, or
  • make it part of the entity defaults.

Is there a option to update the statistics within the view-time of this page

refresh_interval: auto may not update statistics data. Try changing it to 10s.

Thanks for your reply!!
Using get() works, though I used let entity = get('entities[0].entity') as get('.entity') did not work.
This is my changed code:

type: custom:plotly-graph
title: Plotly
hours_to_show: current_week
time_offset: 8h
stack: false
entities:
  - entity: sensor.p1_meter_totale_energie_export
  - entity: sensor.p1_meter_totale_energie_import
defaults:
  entity:
    texttemplate: '%{y}'
    type: bar
    statistic: state
    period:
      0s: 5minute
      2h: hour
      6d: day
      360d: month
    filters:
      - delta
      - fn: |
          ({xs,ys,hass,statistics,get,meta}) =>   {   
            let statLast = statistics.length-1;
            let entity = get('entities[0].entity')  
            let lastStatisticsDateTime = new Date(statistics[statLast].end);   
            let lastStateDateTime = new Date(hass.states[entity].last_changed);   
            let stateLastStatic = statistics[statLast].state; 
            let stateLastState = hass.states[entity].state
            let state = stateLastState-stateLastStatic; 
            if (state>0){   
              if (lastStateDateTime.getTime()>lastStatisticsDateTime.getTime()){ 
                xs.push(lastStatisticsDateTime);
                ys.push(state);
              }else{
                ys[statLast]+=state; 
              }
            }
            return ({ xs,ys }) 
          }
refresh_interval: 5
fn: |
  $fn({getFromConfig, vars})=> {
    const range = getFromConfig("visible_range");
    const width = range[1] - range[0];
    vars.scroll = (label, p) => ({
      args: [
        {
          layout: {
            "xaxis.range": [range[0] + width*p, range[1] + width*p],
          }
        }, {
          transition: {
            duration: 150,
          }
        }
      ],
      label,
      method: "animate",
    })
    vars.zoom = (label, h) => ({
      args: [
        {
          layout: {
            "xaxis.range": [Date.now()-1000*60*60*h, Date.now()],
          }
        }
      ],
      label,
      method: "animate",
    })
  }
layout:
  barcornerradius: 15
  bargap: 0.1
  bargroupgap: 0.1
  height: 50%
  xaxis:
    tickangle: -45
  updatemenus:
    - buttons:
        - $fn({vars}) => vars.scroll( '<', -.5)
        - $fn({vars}) => vars.scroll( '>', .5)
      direction: right
      active: -1
      pad:
        r: 10
        t: 0
      type: buttons
      x: -0.1
      xanchor: left
      'y': -0.2
      yanchor: top
    - buttons:
        - $fn({vars}) => vars.zoom( '1y', 24*366)
        - $fn({vars}) => vars.zoom( '1m', 24*31)
        - $fn({vars}) => vars.zoom( '1w', 24*7)
        - $fn({vars}) => vars.zoom( '1d', 24)
        - $fn({vars}) => vars.zoom( '1h', 1)
      direction: right
      active: -1
      pad:
        r: 100
        t: 0
      type: buttons
      x: 0.1
      xanchor: left
      'y': -0.2
      yanchor: top

Sadly, changing refesh_interval: auto to refresh_interval: 5 did not work. A new bar is not created when the period is over within the view-time of the page. So statistics is not refreshed here also.