Plotly interactive Graph Card

Hey, thanks! That line did the trick.

So the issue is that reusing the statistics card pulls from the main theme, but I have numerous themes defined, and apply them individually to each card. There doesn’t seem to be a way to specify a theme to use on a Plotly card. So it’s pulling from a theme different from what all my other cards use. This means I’d have to specify each color for each card I make with Plotly. Not the worst thing.

But as you see in the comparison between what I had with Apexcharts, and Plotly, my labels and everything else need to specified…

Also, I noticed what Plotly is pulling is different from what I have in my themes… Plotly uses "
card-background-color", but defined in my theme is “ha-background-color”? My themes have been around forever, so maybe these are outdated labels that still work?

I looked at that reference you provided a link to earlier…

The problem for us (and likely what is creating many questions in this thread), is the translation between this…

var trace1 = {

  x: [1, 2, 3, 4],

  y: [10, 15, 13, 17],

  type: 'scatter'

};

Into the format for the Home Assistant card. Is it…?

    line:
      color: rgb(255, 152, 0)
      type: scatter

None of us know. We need a list… LOL

So I have some other elements, like labels, I need to figure out what to specify since the theme isn’t going to carry over. Any help on that would be appreciated. I’d help make the list, but I don’t know what the elements are.

Thanks for everything…

Hello,
I am really enjoying this card as I use it to display target data for two mmWave sensors.

grafik

grafik

These two screenshots show the coordinates of myself sitting at my desk to write this post, so they both show the same target at the same position.
Could I somehow combine the data from these two sensors to get one coordinate system for the whole room?

Best regards
Aaron Eisele

1 Like

When i first realized that HAs recorder data is not kept indefinitely i was like.: “HERESY! The mechanicum deletes nothing!” and that is even by default and not optional. They use STS and LTS as a second database in which they dare to alter the sample rate of MY DATA. They they say they do it to conserve on my memory (as if that needs to be conserved) and in the same move store features of the data (max mean min) together with the raw data instead of storing the function that creates them (redundant). Wth … So i had to flag all the data i want to keep at low resolution with state_class and did that.

Feature request: It would be great if the plotly card could use state, short or long term statistics data dynamically baaed on what is available at the currently displayed time. Afaik currently i can either define “statistic:” or not so that i either use the state or the statistics data source. If there would be a “use both dynamically” option so that the recent data which is still in the recorder can be used to display high resolution but the plot can still be scrolled back in time to see the long term statistics data that would imo be an improvement.

Also, and I’m not even sure if those values actually exist in the database, they talk about “sum_increasing” and other features of the LTS data here.: Sensor Entity | Home Assistant Developer Docs
Was that actually implemented? If so, wouldn’t it make sense to implement
statistic: state # state or sum or sum_increase or sum_decrease in plotly?

Thanks for your time! I love your plotly card implementation. Its simply the best data plot there is!

I was also considering to actually store all my data in some “professional” database instead and considered influxDB. I mean what is HA about in the first place? Collect data, act based on data and imo ofc. also store data. The current implementation to store data is just unprofessional. Its a core feature of the whole setup imo.

With influxDB and grafana and URLs pointing to its plots there are also hassles. I wonder what the best ways to access data from there are for the plotly card, and if that or a similar DB should be considered as a supported source for the plots. I guess you can currently access the data via a complex - fn: expression, but wouldnt it make sense to streamline that aa bit instead?

tbh the whole thing is a mess and the reason is that HA stores data not simply just raw but in arbitrary not optinal formats. That i even need to think about which plotting function can use which version with which properties of my data in which database. The integrity of raw data at all costs should be the core of such a system.

Could be. I don’t use custom themes, maybe some other user that does makes a PR for this :slight_smile:

The problem for us, is the translation between this … and this …

the translation is quite direct, only adding sound defaults (like using unit_of_measurement for the axes) and extra home assistant specific things like filters, $fn, etc.

  • Entities are translated into elements into traces for the data array, adding functionality for fetching the data and populating x and y.
  • Layout is Layout
  • Config is Config

Take a read at GitHub - dbuezas/lovelace-plotly-graph-card: Highly customisable Lovelace card to plot interactive graphs. Brings scrolling, zooming, and much more! again and maybe it clicks :slight_smile:

I cannot make a list because there are many thousands of configurations, and I would be just duplicating everything but changing json to yaml. I suggest you read the readme again and take a look at all the examples in the discussions section. It would be cool to have an index of plots with images, that’s for sure, maybe that’s a contribution you could make! You can make a PR without leaving the github website

Thanks for your kind words! Happy to hear you are enjoying it!

HERESY! The mechanicum deletes nothing!

:rofl: It happened to me that I had less than 50% of free disk and HA wasn’t able to run the cleanup. Let me tell you, the mechanicum gathered so much data so quickly that it became a nightmare! It is configurable though, you can configure the recorder to keep e.g 15 days if you have the space.

Also, and I’m not even sure if those values actually exist in the database, they talk about “sum_increasing” and other features of the LTS data here

Different statistic data will be stored depending on the entity’s state_class the details are here. To be honest that took me by surprise too.

If there would be a “use both dynamically” option

As you say that would require implementing the statistic types for the short term db too. It would be an interesting feature indeed.

I wonder what the best ways to access data from there (influxDB) are for the plotly card

I have no idea, this goes beyond the scope of this card, but isn’t the point of grafana that you can customise plots however you want? are you implying this card is better than grafana? :sunglasses: hehe

I think you would need to calibrate both sensors to a single point in the room (a corner for example). So the (0,0) reference is not at the sensor origin, but at a point in the room. Once that is done, you could define the blackout zone for each sensor, so only one sensor is reporting values for a given area.
An automation that triggers on either sensor changing, and assigning the updated value to a 3rd helper sensor. This would effectively be the combination of the two sensors, and the one you would plot.
Hope this helps, this is a pretty high level response, and thinking about the coordinate math is making my head hurt a bit ! :slight_smile:

I think you will need to capture the output of both sensors for you in 2 different places of the room. With that you can compute a transform to put the measurements of one system in the coordinate system of the other one. Something like this:

// first note down the output of each sensor for each of your 2 measured positions
P1 = {x: 10, y: 20}; // your position 1 according to the first sensor
P1_prime = {x: 15, y: 30}; // your position 1 according to the second sensor

P2 = {x: 20, y: 40}; // your position 2 according to 1st sensor
P2_prime = {x: 35, y: 70}; // same for 2nd sensor

// then compute scales and translations and note them down
scaleX = (P2_prime.x - P1_prime.x) / (P2.x - P1.x);
scaleY = (P2_prime.y - P1_prime.y) / (P2.y - P1.y);

translateX = P1_prime.x - P1.x * scaleX;
translateY = P1_prime.y - P1.y * scaleY;

Finally, you can put this code in a plotly filter to be able to plot the 2nd sensor in the plot of the 1st one:

xs = vars.second_sensor.xs.map(x => x * scaleX + translateX)
ys = vars.second_sensor.ys.map(y => y * scaleY + translateY)

Also, please post your results! preferrably a gif or video :slight_smile:

1 Like

Hello @mateine, thanks for your help!

How could I use this in my existing plotly-graph-card?
I currently have two cards:
Sensor 1:

type: custom:plotly-graph
refresh_interval: 1
hours_to_show: current_day
config:
  modeBarButtonsToRemove:
    - select2d
    - lasso2d
    - toImage
  displaylogo: false
layout:
  height: 240
  margin:
    l: 40
    r: 20
    t: 20
    b: 55
  showlegend: true
  xaxis:
    showticklabels: true
    dtick: 1000
    visible: true
    gridcolor: RGBA(200,200,200,0.15)
    zerolinecolor: RGBA(200,200,200,0.15)
    type: number
    fixedrange: true
    range:
      - 4000
      - -4000
  yaxis:
    showticklabels: true
    dtick: 1000
    visible: true
    gridcolor: RGBA(200,200,200,0.15)
    zerolinecolor: RGBA(200,200,200,0.15)
    scaleanchor: x
    scaleratio: 1
    fixedrange: true
    range:
      - 7500
      - 0
entities:
  - entity: ''
    name: Target1
    marker:
      size: 10
      symbol: star-diamond
    line:
      shape: spline
      width: 5
    x:
      - >-
        $ex { var x =
        hass.states["sensor.mmwavetest_d1mini32_02_s1_t1_x_position"].state*1000;
        var y =
        hass.states["sensor.mmwavetest_d1mini32_02_s1_t1_y_position"].state*1000;
        if (x == 0 && y == 0) { return -9999 }; if (vars.is_inch_unit) { x = x *
        25.4 }; return x; }
    'y':
      - >-
        $ex { var x =
        hass.states["sensor.mmwavetest_d1mini32_02_s1_t1_x_position"].state*1000;
        var
        y=hass.states["sensor.mmwavetest_d1mini32_02_s1_t1_y_position"].state*1000;
        if (x == 0 && y == 0) { return -9999 }; if (vars.is_inch_unit) { y = y *
        25.4 }; return y; }
  - entity: ''
    name: Target2
    marker:
      size: 10
      symbol: star-diamond
    line:
      shape: spline
      width: 5
    x:
      - >-
        $ex { var x =
        hass.states["sensor.mmwavetest_d1mini32_02_s1_t2_x_position"].state*1000;
        var y =
        hass.states["sensor.mmwavetest_d1mini32_02_s1_t2_y_position"].state*1000;
        if (x == 0 && y == 0) { return -9999 }; if (vars.is_inch_unit) { x = x *
        25.4 }; return x; }
    'y':
      - >-
        $ex { var x =
        hass.states["sensor.mmwavetest_d1mini32_02_s1_t2_x_position"].state*1000;
        var
        y=hass.states["sensor.mmwavetest_d1mini32_02_s1_t2_y_position"].state*1000;
        if (x == 0 && y == 0) { return -9999 }; if (vars.is_inch_unit) { y = y *
        25.4 }; return y; }
  - entity: ''
    name: Target3
    marker:
      size: 10
      symbol: star-diamond
    line:
      shape: spline
      width: 5
    x:
      - >-
        $ex { var x =
        hass.states["sensor.mmwavetest_d1mini32_02_s1_t3_x_position"].state*1000;
        var y =
        hass.states["sensor.mmwavetest_d1mini32_02_s1_t3_y_position"].state*1000;
        if (x == 0 && y == 0) { return -9999 }; if (vars.is_inch_unit) { x = x *
        25.4 }; return x; }
    'y':
      - >-
        $ex { var x =
        hass.states["sensor.mmwavetest_d1mini32_02_s1_t3_x_position"].state*1000;
        var
        y=hass.states["sensor.mmwavetest_d1mini32_02_s1_t3_y_position"].state*1000;
        if (x == 0 && y == 0) { return -9999 }; if (vars.is_inch_unit) { y = y *
        25.4 }; return y; }
  - entity: ''
    name: Coverage
    mode: lines
    fill: tonexty
    fillcolor: rgba(168, 216, 234, 0.15)
    hoverinfo: none
    line:
      shape: line
      width: 0.7
      dash: dot
    x:
      - 0
      - $ex 7500 * Math.sin((2 * Math.PI)/360 * 60)
      - 6500
      - 5500
      - 4500
      - 4000
      - 3000
      - 2000
      - 1000
      - 0
      - -1000
      - -2000
      - -3000
      - -4000
      - -4500
      - -5500
      - -6500
      - $ex -7500 * Math.sin((2 * Math.PI)/360 * 60)
      - 0
    'y':
      - 0
      - $ex 7500 * Math.cos((2 * Math.PI)/360 * 60)
      - $ex Math.sqrt( 7500**2 - 6500**2 )
      - $ex Math.sqrt( 7500**2 - 5500**2 )
      - $ex Math.sqrt( 7500**2 - 4500**2 )
      - $ex Math.sqrt( 7500**2 - 4000**2 )
      - $ex Math.sqrt( 7500**2 - 3000**2 )
      - $ex Math.sqrt( 7500**2 - 2000**2 )
      - $ex Math.sqrt( 7500**2 - 1000**2 )
      - 7500
      - $ex Math.sqrt( 7500**2 - 1000**2 )
      - $ex Math.sqrt( 7500**2 - 2000**2 )
      - $ex Math.sqrt( 7500**2 - 3000**2 )
      - $ex Math.sqrt( 7500**2 - 4000**2 )
      - $ex Math.sqrt( 7500**2 - 4500**2 )
      - $ex Math.sqrt( 7500**2 - 5500**2 )
      - $ex Math.sqrt( 7500**2 - 6500**2 )
      - $ex 7500 * Math.cos((2 * Math.PI)/360 * 60)
      - 0
raw_plotly_config: true
title: S1 Map

Sensor 2:

type: custom:plotly-graph
refresh_interval: 1
hours_to_show: current_day
config:
  modeBarButtonsToRemove:
    - select2d
    - lasso2d
    - toImage
  displaylogo: false
layout:
  height: 240
  margin:
    l: 40
    r: 20
    t: 20
    b: 55
  showlegend: true
  xaxis:
    showticklabels: true
    dtick: 1000
    visible: true
    gridcolor: RGBA(200,200,200,0.15)
    zerolinecolor: RGBA(200,200,200,0.15)
    type: number
    fixedrange: true
    range:
      - 4000
      - -4000
  yaxis:
    showticklabels: true
    dtick: 1000
    visible: true
    gridcolor: RGBA(200,200,200,0.15)
    zerolinecolor: RGBA(200,200,200,0.15)
    scaleanchor: x
    scaleratio: 1
    fixedrange: true
    range:
      - 7500
      - 0
entities:
  - entity: ''
    name: Target1
    marker:
      size: 10
      symbol: star-diamond
    line:
      shape: spline
      width: 5
    x:
      - >-
        $ex { var x =
        hass.states["sensor.mmwavetest_d1mini32_02_s2_t1_x_position"].state*1000;
        var y =
        hass.states["sensor.mmwavetest_d1mini32_02_s2_t1_y_position"].state*1000;
        if (x == 0 && y == 0) { return -9999 }; if (vars.is_inch_unit) { x = x *
        25.4 }; return x; }
    'y':
      - >-
        $ex { var x =
        hass.states["sensor.mmwavetest_d1mini32_02_s2_t1_x_position"].state*1000;
        var
        y=hass.states["sensor.mmwavetest_d1mini32_02_s2_t1_y_position"].state*1000;
        if (x == 0 && y == 0) { return -9999 }; if (vars.is_inch_unit) { y = y *
        25.4 }; return y; }
  - entity: ''
    name: Target2
    marker:
      size: 10
      symbol: star-diamond
    line:
      shape: spline
      width: 5
    x:
      - >-
        $ex { var x =
        hass.states["sensor.mmwavetest_d1mini32_02_s2_t2_x_position"].state*1000;
        var y =
        hass.states["sensor.mmwavetest_d1mini32_02_s2_t2_y_position"].state*1000;
        if (x == 0 && y == 0) { return -9999 }; if (vars.is_inch_unit) { x = x *
        25.4 }; return x; }
    'y':
      - >-
        $ex { var x =
        hass.states["sensor.mmwavetest_d1mini32_02_s2_t2_x_position"].state*1000;
        var
        y=hass.states["sensor.mmwavetest_d1mini32_02_s2_t2_y_position"].state*1000;
        if (x == 0 && y == 0) { return -9999 }; if (vars.is_inch_unit) { y = y *
        25.4 }; return y; }
  - entity: ''
    name: Target3
    marker:
      size: 10
      symbol: star-diamond
    line:
      shape: spline
      width: 5
    x:
      - >-
        $ex { var x =
        hass.states["sensor.mmwavetest_d1mini32_02_s2_t3_x_position"].state*1000;
        var y =
        hass.states["sensor.mmwavetest_d1mini32_02_s2_t3_y_position"].state*1000;
        if (x == 0 && y == 0) { return -9999 }; if (vars.is_inch_unit) { x = x *
        25.4 }; return x; }
    'y':
      - >-
        $ex { var x =
        hass.states["sensor.mmwavetest_d1mini32_02_s2_t3_x_position"].state*1000;
        var
        y=hass.states["sensor.mmwavetest_d1mini32_02_s2_t3_y_position"].state*1000;
        if (x == 0 && y == 0) { return -9999 }; if (vars.is_inch_unit) { y = y *
        25.4 }; return y; }
  - entity: ''
    name: Coverage
    mode: lines
    fill: tonexty
    fillcolor: rgba(168, 216, 234, 0.15)
    hoverinfo: none
    line:
      shape: line
      width: 0.7
      dash: dot
    x:
      - 0
      - $ex 7500 * Math.sin((2 * Math.PI)/360 * 60)
      - 6500
      - 5500
      - 4500
      - 4000
      - 3000
      - 2000
      - 1000
      - 0
      - -1000
      - -2000
      - -3000
      - -4000
      - -4500
      - -5500
      - -6500
      - $ex -7500 * Math.sin((2 * Math.PI)/360 * 60)
      - 0
    'y':
      - 0
      - $ex 7500 * Math.cos((2 * Math.PI)/360 * 60)
      - $ex Math.sqrt( 7500**2 - 6500**2 )
      - $ex Math.sqrt( 7500**2 - 5500**2 )
      - $ex Math.sqrt( 7500**2 - 4500**2 )
      - $ex Math.sqrt( 7500**2 - 4000**2 )
      - $ex Math.sqrt( 7500**2 - 3000**2 )
      - $ex Math.sqrt( 7500**2 - 2000**2 )
      - $ex Math.sqrt( 7500**2 - 1000**2 )
      - 7500
      - $ex Math.sqrt( 7500**2 - 1000**2 )
      - $ex Math.sqrt( 7500**2 - 2000**2 )
      - $ex Math.sqrt( 7500**2 - 3000**2 )
      - $ex Math.sqrt( 7500**2 - 4000**2 )
      - $ex Math.sqrt( 7500**2 - 4500**2 )
      - $ex Math.sqrt( 7500**2 - 5500**2 )
      - $ex Math.sqrt( 7500**2 - 6500**2 )
      - $ex 7500 * Math.cos((2 * Math.PI)/360 * 60)
      - 0
raw_plotly_config: true
title: S2 Map

How could I display the positions from both sensors in one card?
My test values are:

  • Position 1:
    • S1: X: -3.44, Y:3.20
    • S2: X: 1.73m, Y: 4.8
  • Position 2:
    • S1: X: -0.45, Y: 2.33
    • S2: X: 2.44, Y: 1.7

Wow, that’s a big one! You could reduce its size a bit with a for loop.
Just put both inside the same card, find out the scale and translate constants via an experiment and apply the math I described above before returning x and y.

BTW, I suggest you open a Q&A discussion in the repo when there are followups and big yamls :slight_smile:

const scaleX = 0.23745819397993312;
const scaleY = 3.5632183908045967;
const translateX = 2.54685618729097;
const translateY = -6.6022988505747096;

x = x * scaleX + translateX;
y = y * scaleY + translateY;

This should convert the coords of one sensor into the coordinate system of the other one.

Hello community!
I added an index of discussions with images so you can find examples more easily.

Click on the Index of examples with images in the readme

When you are done, would you make a Show&Tell post in the discussions?
I bet there’s other people trying to do the same thing that will thank you for it :slight_smile:

Hi !

I discovered Plotly recently. Very nice piece of software ! Thanks !

I wonder if there is any way to display values from all curves when hovering, as Solar Assistant graph has (see below). This is very convenient.

Thanks
Matt

Happy to hear!
Try

layout:
  hovermode: x unified

More Layout in JavaScript

Great thanks for this. That’s what I wanted :slight_smile:

Any clue how to make the labels less “verbose” and more compact by removing the date/time ?


oi

That should be hovertemplate, see here: Hover text and formatting in JavaScript

please remember to google first, e.g “plotlyjs how to change hover text”

Hi

i found the above link already - unfortantely, I am not skilled enough to understand/know how to apply this to plotly in HA (should have it been an option in the yaml definition, good, but jumping into JS things is too much for me ;)).

I’ll live with the full hover text as it is, maybe finding some explanations later on how to implement/modify it.

Thanks !

Just write the object as yaml:

e.g:

What in js is:

hovertemplate: '<i>Price</i>: $%{y:.2f}' +
                        '<br><b>X</b>: %{x}<br>' +
                        '<b>%{text}</b>',

in yaml is:

hovertemplate: "<i>Price</i>: $%{y:.2f}<br><b>X</b>: %{x}<br><b>%{text}</b>"

or

hovertemplate: >-
    <i>Price</i>: $%{y:.2f}
    <br><b>X</b>: %{x}<br>
    <b>%{text}</b>

Regarding how to format other things, they are just conventions so it is a matter of reading their docs more than skill, I also go and read it when I try to do something different :slight_smile:

1 Like