[Tutorial] Plotly Graph Card (ausführliche Charts direkt in Home Assistant)

:bar_chart: Plotly Graph Card – Stromverbrauch & PV-Übersicht

Ich habe eine Weile gesucht, um die gestapelte Balkendiagramme zu realisieren. Mit der apex-chart card habe ich keinen Weg gefunden, daher bin ich jetzt auf die ploty-graph card gewechselt. Das kommt dem Energie Dashboard von HA schon sehr nah, wie ich finde. :slight_smile:

Die custom:plotly-graph-Karte kann dir übersichtlich zeigen, wie sich dein täglicher Stromverbrauch zusammensetzt – inklusive PV-Erzeugung, Netzbezug und Batterienutzung. Ideal zur Visualisierung von Energieflüssen!

:wrench: Voraussetzungen

  • HACS installiert
  • plotly-graph Custom Card installiert
  • Sensoren mit täglichen Energie-Werten

Die meisten werden keine Sensoren für den realen Solarverbrauch (abzüglich Einspeisung und Batterieladung) haben. Genauso beim Verbrauch, hier wird der Netzbezug plus Batterie plus Erzeugung (erster Sensor) gerechnet, daher habe ich mir dafür zwei Sensoren angelegt. (danke nochmal @dreckfresse für den Gedankenanstoß.)

# Verrechnung für Diagramm Verbrauch und Erzeugung
- sensor:
  - name: "Erzeugzung taglich nach Abzug Einspeisung"
    unique_id: erzeugung_taglich_kWh
    unit_of_measurement: "kWh"
    state_class: total_increasing
    device_class: energy
    state: >
      {% set solar = states('sensor.growatt_todaygenerateenergy') | float(0) %}
      {% set batterie = states('sensor.acpowerzubatterie_energy_today') | float(0) %}
      {% set einspeisung = states('sensor.solar_netzeinspeisung_kwh_taglich') | float(0) %}
      {{ (solar - einspeisung - batterie) | round(2) }}
- sensor:
  - name: "Verbrauch taglich nach Abzug"
    unique_id: verbrauch_taglich_kWh
    unit_of_measurement: "kWh"
    state_class: total_increasing
    device_class: energy
    state: >
      {% set stromverbrauch = states('sensor.stromverbrauch_taglich') | float(0) %}
      {% set batterie = states('sensor.acpowervonbatterie_energy_today') | float(0) %}
      {% set erzeugung = states('sensor.erzeugzung_taglich_nach_abzug_einspeisung') | float(0) %}
      {% set batterieent = states('sensor.acpowerzubatterie_energy_today') | float(0) %}
      {% set einspeisung = states('sensor.solar_netzeinspeisung_kwh_taglich') | float(0) %}
      {{ (stromverbrauch + batterie + erzeugung - einspeisung - batterieent) | round(2) }}


Aufbau der Card im Detail

type: custom:plotly-graph
title: Aufteilung
hours_to_show: 7d
refresh_interval: 10
  • title: Titel der Karte.
  • hours_to_show: Zeitraum der Anzeige – hier 7 Tage (1m für ein Monat)
  • refresh_interval: Daten werden alle 10 Sekunden aktualisiert.

Layout & Design

layout:
  paper_bgcolor: rgba(0,0,0,0)
  plot_bgcolor: rgba(0,0,0,0)
  barmode: relative
  bargap: 0.2
  barcornerradius: 8
  uniformtext:
    minsize: 10
    mode: show
  modebar:
    orientation: v
    remove: zoom
  margin:
    t: 15
    l: 45
    r: 20
  • paper_bgcolor / plot_bgcolor: Transparenter Hintergrund.
  • barmode: relative: Balken werden übereinandergelegt (stacked).
  • bargap: Abstand zwischen Balken (0–1).
  • barcornerradius: Abgerundete Balken.
  • uniformtext: Einheitliche Textgröße.
  • modebar: Werkzeugleiste – hier wird Zoom ausgeblendet.
  • margin: Randabstände (top, left, right).

Globale Einstellungen (defaults)

defaults:
  entity:
    period: day
    type: bar
    statistic: state
    filters:
      - filter: i>0
  • period: day: Zeigt Tageswerte.
  • type: bar: Balkendiagramm.
  • statistic: state: Nutzt den Zustand des Sensors (z. B. kWh).
  • filter: i>0: Nur Werte größer 0 werden dargestellt (Filter gegen Nullwerte).

Eingebundene Sensoren

entities:
  - entity: sensor.erzeugzung_taglich_nach_abzug_einspeisung
    name: PV
    marker:
      color: green
      opacity: 0.7

  - entity: sensor.stromverbrauch_taglich
    name: Netzbezug
    marker:
      color: darkorange
      opacity: 0.7

  - entity: sensor.acpowervonbatterie_energy_today
    name: Batterie
    marker:
      color: steelblue
      opacity: 0.7

Jede Entität wird:

  • Benannt (name)
  • Farblich dargestellt (color)
  • optional mit Transparenz versehen (opacity)

:white_check_mark: Ergebnis

Modernes, kompaktes und aussagekräftiges Balkendiagramm, das zeigt:

  • Wieviel Strom täglich aus dem Netz bezogen wurde
  • Wieviel aus der Batterie kam
  • Wieviel PV selbst verbraucht wurde (nach Abzug der Einspeisung)

So und nun zu den Beispielen:

:pushpin: einfache Darstellung sehr ähnlich zum Energie-Dashboard

type: custom:plotly-graph
card_mod:
  style: |
    ha-card {
        border-width: 0px;
       }
layout:
  paper_bgcolor: rgba(0,0,0,0)
  plot_bgcolor: rgba(0,0,0,0)
  barmode: relative
  bargap: 0.2
  barcornerradius: 8
  uniformtext:
    minsize: 10
    mode: show
  modebar:
    orientation: v
    remove: zoom
  margin:
    t: 15
    l: 45
    r: 20
defaults:
  entity:
    period: day
    type: bar
    statistic: state
    filters:
      - filter: i>0
entities:
  - entity: sensor.erzeugzung_taglich_nach_abzug_einspeisung
    name: PV
    marker:
      color: green
      opacity: 0.7
  - entity: sensor.stromverbrauch_taglich
    name: Netzbezug
    marker:
      color: darkorange
      opacity: 0.7
  - entity: sensor.acpowervonbatterie_energy_today
    name: Batterie
    marker:
      color: steelblue
      opacity: 0.7
hours_to_show: 7d
refresh_interval: 10
grid_options:
  columns: 24
  rows: 5
title: Aufteilung

:pushpin: Das selbe Bespiel etwas erweitert und sortierter. Zusätzlich Zeitauswahl hinzugefügt.

type: custom:plotly-graph
defaults:
  yaxes:
    fixedrange: true
  entity:
    period: day
    type: bar
    statistic: state
    texttemplate: "%{y:.1f}"
    textposition: auto
    filters:
      - filter: i>0
entities:
  - entity: sensor.erzeugzung_taglich_nach_abzug_einspeisung
    name: Erzeugung
    marker:
      color: green
      opacity: 2
  - entity: sensor.acpowervonbatterie_energy_today
    name: Akku Entladung
    marker:
      color: "#6C9BC4"
      opacity: 2
  - entity: sensor.stromverbrauch_taglich
    name: Bezug
    marker:
      color: "#EE7A06"
      opacity: 2
layout:
  paper_bgcolor: rgba(0,0,0,0)
  plot_bgcolor: rgba(0,0,0,0)
  barmode: stack
  bargap: 0.2
  barcornerradius: 5
  uniformtext:
    minsize: 10
    mode: show
  modebar:
    orientation: v
    remove: zoom
  margin:
    t: 15
    l: 45
    r: 20
  height: 500
  legend:
    "y": -0.25
    x: 0.02
  xaxis:
    rangeselector:
      "y": 1.15
      buttons:
        - count: 1
          step: day
        - count: 7
          step: day
        - count: 10
          step: day
        - count: 30
          step: day
    gridcolor: rgba(238,235,235,0.3)
    autorange: true
    showgrid: true
  yaxis:
    visible: true
    autorange: true
hours_to_show: 7d
refresh_interval: 15
card_mod:
  style: |
    ha-card {
        border-width: 0px;
       }
view_layout:
  position: main
grid_options:
  columns: full

:pushpin: Nun kommt das worauf es mir persönlich ankommt. Hier findet eine Aufteilung statt. Man sieht die Solarprognose für den laufenden Tag, im zweiten Balken wie sich der Strom aus Netzbezug, Solar und Batterie zusammensetzt und im dritten Balken wie der Verbrauch sich aufteilt, in Netzeinspeisung, Batterieladung und restlicher Verbrauch.

type: custom:plotly-graph
defaults:
  yaxes:
    fixedrange: true
  entity:
    period: day
    type: bar
    statistic: state
    texttemplate: "%{y:.1f}"
    textposition: auto
entities:
  - entity: sensor.erzeugzung_taglich_nach_abzug_einspeisung
    name: Erzeugung
    marker:
      color: green
      opacity: 1
    textfont:
      color: black
    filters:
      - filter: i>0 && i < xs.length - 1
      - fn: |
          ({ys,xs,hass}) => ({
            xs: [...xs, new Date().setHours(0,0,0)],
            ys: [...ys, hass.states['sensor.erzeugzung_taglich_nach_abzug_einspeisung'].state],
          })
  - entity: sensor.acpowervonbatterie_energy_today
    name: Akku Entladung
    marker:
      color: "#6C9BC4"
      opacity: 1
    filters:
      - filter: i>0 && i < xs.length - 1
      - fn: |
          ({ys,xs,hass}) => ({
            xs: [...xs, new Date().setHours(0,0,0)],
            ys: [...ys, hass.states['sensor.acpowervonbatterie_energy_today'].state],
          })
  - entity: sensor.stromverbrauch_taglich
    name: Bezug
    marker:
      color: darkorange
      opacity: 1
    textfont:
      color: black
    filters:
      - filter: i>0 && i < xs.length - 1
      - fn: |
          ({ys,xs,hass}) => ({
            xs: [...xs, new Date().setHours(0,0,0)],
            ys: [...ys, hass.states['sensor.stromverbrauch_taglich'].state],
          })
  - entity: sensor.verbrauch_taglich_nach_abzug
    name: Verbrauch
    marker:
      color: "#CAD97D"
      opacity: 0.5
    filters:
      - filter: i>0 && i < xs.length - 1
      - fn: |
          ({ys,xs,hass}) => ({
            xs: [...xs, new Date().setHours(6,0,0)],
            ys: [...ys, hass.states['sensor.verbrauch_taglich_nach_abzug'].state],
          })
    time_offset: 6h
  - entity: sensor.acpowerzubatterie_energy_today
    name: Akku Beladung
    marker:
      color: "#AAE1FC"
      opacity: 0.5
    filters:
      - filter: i>0 && i < xs.length - 1
      - fn: |
          ({ys,xs,hass}) => ({
            xs: [...xs, new Date().setHours(6,0,0)],
            ys: [...ys, hass.states['sensor.acpowerzubatterie_energy_today'].state],
          })
    time_offset: 6h
  - entity: sensor.solar_netzeinspeisung_kwh_taglich
    name: Netzeinspeisung
    marker:
      color: red
      opacity: 0.5
    textfont:
      color: grey
    filters:
      - filter: i>0 && i < xs.length - 1
      - fn: |
          ({ys,xs,hass}) => ({
            xs: [...xs, new Date().setHours(6,0,0)],
            ys: [...ys, hass.states['sensor.solar_netzeinspeisung_kwh_taglich'].state],
          })
    time_offset: 6h
  - entity: sensor.solcast_pv_forecast_prognose_heute
    name: Prognose
    marker:
      color: "#45664a"
      opacity: 0.2
    textfont:
      color: white
    filters:
      - filter: i>0 && i < xs.length - 1
      - fn: |
          ({ys,xs,hass}) => ({
            xs: [...xs, new Date().setHours(-6,0,0)],
            ys: [...ys, hass.states['sensor.solcast_pv_forecast_prognose_heute'].state],
          })
    time_offset: "-6h"
layout:
  paper_bgcolor: rgba(0,0,0,0)
  plot_bgcolor: rgba(0,0,0,0)
  barmode: stack
  bargap: 0.05
  barcornerradius: 3
  uniformtext:
    minsize: 10
    mode: show
  modebar:
    orientation: v
    remove: zoom
  margin:
    t: 5
    l: 45
    r: 20
  height: 400
  legend:
    "y": -0.22
    x: 0.1
  xaxis:
    rangeselector:
      "y": 1.05
      buttons:
        - count: 1
          step: day
        - count: 7
          step: day
        - count: 10
          step: day
        - count: 30
          step: day
        - count: 6
          step: month
    gridcolor: rgba(238,235,235,0.3)
    autorange: true
    showgrid: true
  yaxis:
    visible: true
    autorange: true
hours_to_show: 7d
refresh_interval: 360
card_mod:
  style: |
    ha-card {
        border-width: 0px;
       }
view_layout:
  position: main
grid_options:
  columns: full

:mag: Funktion = fn?

- fn: |
    ({ys,xs,hass}) => ({
      xs: [...xs, new Date().setHours(0,0,0)],
      ys: [...ys, hass.states['sensor.acpowervonbatterie_energy_today'].state],
    })

Die Funktion dient zur manuellen Ergänzung der Datenpunkte im Diagramm. Sie ist sozusagen ein Code, der individuell zusätzliche Werte zu den Achsen (xs = X-Achse, ys = Y-Achse) hinzufügen kann.

:wrench: Was passiert genau?

  • ({ys, xs, hass}) => ...
    Das ist eine Funktion, die entsprechend Dinge aus dem Plotly-Kontext filtert:
    • xs: Die bisherigen Zeitstempel (X-Achse)
    • ys: Die bisherigen Werte (Y-Achse)
    • hass: Zugriff auf den gesamten Home Assistant-Status (hass.states)
  • new Date().setHours(0, 0, 0)
    Gibt den Zeitpunkt heute um Mitternacht zurück, als Millisekunden-Zeitstempel. X-Wert (Zeitachse).
  • hass.states['sensor.acpowervonbatterie_energy_today'].state
    Liest den aktuellen Wert dieses Sensors direkt aus Home Assistant aus. Y-Wert (Datenwert für die Y-Achse).
  • xs: [...xs, ...] und ys: [...ys, ...]
    Fügt jeweils einen neuen Eintrag zu den vorhandenen Datenlisten hinzu.
    Hieran habe ich ewig gesessen, aber es geht jetzt wie gewünscht.

:pushpin: Zur Ergänzung noch ein Liniendiagramm mit Aufteilung des aktuellen Strombezuges inkl. Batterieladung und Vorhersage. Das Diagramm ist immer auf einen Tag fixiert und baut sich über den Tag hinweg auf.

type: custom:plotly-graph
defaults:
  yaxes:
    fixedrange: true
    range:
      - 0
      - null
card_mod:
  style: |
    ha-card {
      border: none;
    }
entities:
  - entity: sensor.acpowervonbatterie_energy_power
    name: Batterieentladung
    yaxis: y1
    fill: tozeroy
    line:
      shape: spline
      color: "#6C9BC4"
  - entity: sensor.growatt_outpowerwatt
    name: Solarertrag
    yaxis: y1
    fill: tozeroy
    line:
      shape: spline
      color: green
    filters:
      - exponential_moving_average:
          alpha: 0.05
  - entity: sensor.total_power_nur_verbrauch
    name: Netzbezug
    yaxis: y1
    fill: tozeroy
    line:
      shape: spline
      color: orange
    filters:
      - exponential_moving_average:
          alpha: 0.05
  - entity: sensor.solar_einspeisung_normiert_positiver_wert
    name: Netzeinspeisung
    yaxis: y1
    line:
      shape: spline
      color: red
    filters:
      - exponential_moving_average:
          alpha: 0.05
  - entity: sensor.acpowerzubatterie_energy_power
    name: Batterieladung
    yaxis: y1
    fill: tozeroy
    line:
      shape: spline
      color: "#AAE1FC"
    filters:
      - exponential_moving_average:
          alpha: 0.05
  - entity: sensor.solcast_pv_forecast_aktuelle_leistung
    name: Vorhersage
    yaxis: y2
    line:
      color: grey
      dash: dot
      width: 2
hours_to_show: current_day
refresh_interval: 120
min_y_axis: 0
fit_y_data: true
logarithmic_scale: true
max_y_axis: 3000
layout:
  height: 290
  paper_bgcolor: rgba(0,0,0,0)
  plot_bgcolor: rgba(0,0,0,0)
  margin:
    t: 0
    l: 45
    r: 40
grid_options:
  columns: full

Die Linien können auch geglättet werden. Dazu einfach einen Filter setzten. Der Filter ist im Beispiel schon gesetzt. Wollt ihr ihn nicht haben, einfach herauslöschen. Kleinere Zahlen bedeuten größere Glättung

:pushpin: Und zum Schluss noch ein Beispiel für die Summierung über die Monate hinweg.

type: custom:plotly-graph
defaults:
  yaxes:
    fixedrange: true
  entity:
    period: month
    type: bar
    statistic: sum
    filters:
      - filter: i>0
    texttemplate: "%{y:.1f}"
    textposition: auto
    width: 1200000000
entities:
  - entity: sensor.erzeugzung_taglich_nach_abzug_einspeisung
    name: Erzeugung
    marker:
      color: green
      opacity: 1
    textfont:
      color: black
  - entity: sensor.acpowervonbatterie_energy_today
    name: Akku Entladung
    marker:
      color: "#6C9BC4"
      opacity: 1
  - entity: sensor.stromverbrauch_taglich
    name: Bezug
    marker:
      color: darkorange
      opacity: 1
    textfont:
      color: black
  - entity: sensor.solar_netzeinspeisung_kwh_taglich
    name: Netzeinspeisung
    marker:
      color: red
      opacity: 0.5
    textfont:
      color: grey
layout:
  paper_bgcolor: rgba(0,0,0,0)
  plot_bgcolor: rgba(0,0,0,0)
  barmode: stack
  bargap: 0.05
  barcornerradius: 3
  uniformtext:
    minsize: 10
    mode: show
  modebar:
    orientation: v
    remove: zoom
  margin:
    t: 5
    l: 45
    r: 20
  height: 400
  legend:
    "y": -0.22
    x: 0.1
  xaxis:
    rangeselector:
      "y": 1.05
      buttons:
        - count: 1
          step: month
        - count: 7
          step: month
        - count: 10
          step: month
        - count: 30
          step: month
    gridcolor: rgba(238,235,235,0.3)
    autorange: true
    showgrid: true
  yaxis:
    visible: true
    autorange: true
hours_to_show: 120d
refresh_interval: 360
card_mod:
  style: |
    ha-card {
        border-width: 0px;
       }
view_layout:
  position: main
grid_options:
  columns: full


Was habt ihr für ploty-cards realisiert bzw. im Einsatz? Teilt gerne eure Ergebnisse hier, so können wir eine kleine Übersicht sammeln und für alle bereitstellen. :slight_smile:

13 „Gefällt mir“