ApexCharts card - A highly customizable graph card

Try clearing your browser cache.

Can anybody see why forceNiceScale: true is being ignored in my charts?

type: vertical-stack
cards:
  - type: entities
    entities:
      - entity: input_select.days_back_to_show
        name: Number Of Days To Show
    title: Power
  - type: custom:config-template-card
    entities:
      - input_select.days_back_to_show
    variables:
      span: states['input_select.days_back_to_show'].state+'d'
      days: |
        -states['input_select.days_back_to_show'].state+'d'+'1d'
    card:
      type: vertical-stack
      cards:
        - type: custom:auto-entities
          filter:
            include:
              - entity_id: sensor.*_power
                area: living_room
                options:
                  yaxis_id: first
                  stroke_width: 2
                  group_by:
                    func: raw
                  type: line
                  opacity: 1
                  curve: stepline
                  fill_raw: last
          card:
            type: custom:apexcharts-card
            graph_span: ${span}
            yaxis:
              - id: first
                show: true
                min: 0
                decimals: 0
              - id: second
                show: true
                min: 0
                decimals: 2
            header:
              show: true
              show_states: true
              colorize_states: true
            apex_config:
              legend:
                show: false
              grid:
                show: true
              yaxis:
                forceNiceScale: true


Thank you for the reply.
I did that, without a result… Also tried a different machine, no luck.

Thanks, it in the appeared you where right.

1 Like

Is there any way to like change the group_by when moving from like 7d to 31d. I would like to have the daily and weekly grouped by hours and monthly and above to days. Same goes if your using the statistics, how to change the period from hour to day?

Like if days to show is more than 7 return day

I think you have two options:

  1. use an input_select together with an automation
  2. use code with an IF statement (e.g. javascript using the config-template-card GitHub - iantrich/config-template-card: 📝 Templatable Lovelace Configurations)

The second would be far prettier and I am sure someone can help with this (I am pretty bad with the coding part inside cards). You might well find examples in the forum though if nobody smarter than me responds.

The first option would be based on this thread:

Then you would create a second input_select for your groups and with the new IF statement in automations, change the grouping input_select based on the span input_select. All input_selects are global, so changing it on one graph will change the others also.

So that is an old school solution. Hoping for a slicker solution by someone else. :wink:

1 Like

I know that apex charts supports binary_sensor via

    transform: "return x === 'on' ? 1 : 0;"

But does anybody know if this can be used also for normal sensors with more than two states.
So instead of this if...else statement, an if...else if... statement?

Thanks!!! :+1::+1::+1:

Awesome, Thank you! I just added my sensors and changed the span and it works great for the most part.

There is an issue from time to time that the color gradient doesn’t work as it should. It is more of a compressed limit of every 15 minutes it starts over. I think it is from one of my sensors going offline and it resets the entire span causing this.

This is one I think a 12 hour span which is what it is supposed to look like.

This one is when the sensor went offline with a span of 24 hours set.

I also need to look at setting two different series (I think?). My living room and basement sensor is now on this same gradient which can be confusing. I have set the extremas which shows the color in the min/max box. But the actual line is now the gradient colors.

Another thing I’d like to look into is a dynamic time. I think someone else posted it above with a dropdown of various time spans.

And if we could also incorporate a input of set temps with an alarm and even a percentage of cook time remaining to that set point that would be awesome! This of course is for my Fireboard temp probes.

Something like this from Meater with the purple internal, blue target temp. Then it calculates the remaining time. I’m not even sure if ApexCharts is capable of this??

Thanks again!

Hi! Bit of a late reply but did you ever get this figured out? The radial bar size.

Hello here! I’ve got a chart with two series. For one of the two (Buienradar in the images below), if I set the type to either line or area, the line is not visible. On hover it is confirmed that the the data point does exist. Also, for the column type it shows just fine.

The other serries does not show this behavior and displays the line correctly. Does anyone know what is going on?

type: column
image

type: line
image

Dear Arnault,

Any chances to see code for it? :slight_smile:

I have weird problem with weeks.
In the end I am trying to explore my long term statistics similar to the energy-dashboard.
But i can’t figure out how to deal with different time spans.
This problem only seems to affect statistics-data, not the regular recorder/history.

Here is what works as expected:

type: custom:apexcharts-card
graph_span: 1month
span:
  start: month
  offset: -30d
series:
  - entity: sensor.home_meter_grid_energy
    type: column
    statistics:
      type: sum
      period: day
      align: end
    group_by:
      func: diff
      duration: 1d
      fill: last
      start_with_last: true
    float_precision: 2

When I change the graph_span, the span start and offset to week, I don’t get any data:

The scale of the x-axis adjusts, but no data.
Am I doing something wrong? Is this a bug?

I have the same issues and I’m also trying to display the long term statistics just like the energy dashboard but in the beautiful Apex way :slight_smile: A few weeks ago it sounded like RomRider har an idea on how to change this so hopefully there will be a fix for this. Please share if you find a work-around :slight_smile:

1 Like

Beautiful layout! Would you mind sharing some code showing how you got the small graph cards and the orange button?

Sorry for the delay @filikun, @Airyphyla, @alexreddy78 and @Lunkobelix .

I’m at the begining to create my own card with custom:button-card and I’m sure there are much cleaner ways of doing things than me. But here is the yaml of my card :

card_pool:
  styles:
    card:
      - padding: "0px"
    grid:
      - grid-template-areas: "'cam overlay' 'measures measures'"
      - grid-template-columns: "1fr 0"
      - grid-template-rows: "180px min-content"
    custom_fields:
      overlay:
        - height: 180px
        - background: linear-gradient(180deg, rgba(var(--rgb-card-background-color),0) 45%, rgba(var(--rgb-card-background-color),1) 100%)
        - opacity: 1
        - padding: 0
  custom_fields:
    cam:
      card:
        type: picture-glance
        camera_image: camera.poolcamera
        entities: []
        card_mod:
          style:
            hui-image:
              $: |
                div img {
                  height: 180px;
                  object-fit: cover;
                  object-position: center -25px;
                }
              .: |
                hui-image {
                  height: calc(100% - 0px);
                }
            .: |
              ha-card {
                border-bottom-left-radius: 0px;
                border-bottom-right-radius: 0px;
                box-shadow: none;
              }
              ha-card .box {
                display: none;
              }
    overlay: <div></div>
    measures: 
      card:
        type: custom:button-card
        name: Analyses de l'eau
        label: "[[[ return states['sensor.pool_analysed_at'].state; ]]]"
        show_name: true
        show_label: true
        styles:
          card:
            - padding: 4%
            - border-radius: 0px
            - box-shadow: "none"
          grid:
            - grid-template-areas: "'n tmp . ph . redox . sel' 'l tmp . ph . redox . sel' 'graph graph graph graph graph graph graph graph' 'actions actions actions actions actions actions actions actions' 'infos infos infos infos infos infos infos infos'"
            - grid-template-columns: "1fr 45px 5px 45px 5px 45px 5px 45px"
            - grid-template-rows: "min-content min-content min-content"
          name:
            - font-weight: "bold"
            - font-size: "14px"
            - place-self: end left
          label:
            - font-size: "10px"
            - place-self: start
            - filter: "opacity(40%)"
        custom_fields:
          tmp:
            card:
              type: custom:button-card
              template: button_pool_measure
              name: Temp
              label: "[[[ return states['sensor.pool_temperature'].state + '°C'; ]]]"
              variables:
                  color: "blue"
                  select_value: "Temperature"
          ph:
            card:
              type: custom:button-card
              template: button_pool_measure
              name: pH
              label: "[[[ return states['sensor.pool_ph'].state; ]]]"
              variables:
                  color: "green"
                  select_value: "PH"
                  alert: >
                    [[[
                      let ph = states['sensor.pool_ph'].state;
                      if (ph < 7.1 || ph > 7.5) return true;
                      return false;
                    ]]]
          redox:
            card:
              type: custom:button-card
              template: button_pool_measure
              name: RedOx
              label: "[[[ return states['sensor.pool_orp'].state + 'mV'; ]]]"
              variables:
                  color: "yellow"
                  select_value: "ORP"
                  alert: >
                    [[[
                      let orp = states['sensor.pool_orp'].state;
                      if (orp < 600 || orp > 800) return true;
                      return false;
                    ]]]
          sel:
            card:
              type: custom:button-card
              template: button_pool_measure
              name: Sel
              label: "[[[ return states['sensor.pool_salinity'].state + 'g/l'; ]]]"
              variables:
                  color: "red"
                  select_value: "Sel"
                  alert: >
                    [[[
                      let salinity = states['sensor.pool_salinity'].state;
                      if (salinity < 3 || salinity > 3.4) return true;
                      return false;
                    ]]]
          graph:
            card:
              type: custom:button-card
              entity: input_select.ui_pool_select_measure
              show_icon: false
              state:
                - name: Température de l'eau
                  value: Temperature
                - name: pH de l'eau
                  value: PH
                - name: Rédox de l'eau
                  value: ORP
                - name: Salinité de l'eau
                  value: Sel
              styles:
                card:
                  - border-radius: 0px
                  - box-shadow: "none"
                  - padding-bottom: 0
                name:
                  - font-weight: "normal"
                  - font-size: "12px"
                  - place-self: center left
                grid:
                  - grid-template-areas: "'n i1 . i2 . i3' 'graph graph graph graph graph graph'"
                  - grid-template-columns: "1fr 61px 6px 61px 6px 61px"
                  - grid-template-rows: "min-content min-content"
              custom_fields:
                i1: 
                  card:
                    type: custom:button-card
                    template: button_pool_period
                    name: Jour
                    variables:
                      period: Jour
                i2: 
                  card:
                    type: custom:button-card
                    template: button_pool_period
                    name: Semaine
                    variables:
                      period: Semaine
                i3:
                  card:
                    type: custom:button-card
                    template: button_pool_period
                    name: Mois
                    variables:
                      period: Mois
                graph:
                  card:
                    type: grid
                    square: false
                    columns: 1
                    cards:
                      - type: conditional
                        conditions:
                          - entity: input_select.ui_pool_select_measure
                            state: 'Temperature'
                        card:
                          type: custom:button-card
                          template: graph_pool_measure
                          variables:
                            entity: sensor.pool_temperature
                            color: "blue"
                            decimal: 0
                      - type: conditional
                        conditions:
                          - entity: input_select.ui_pool_select_measure
                            state: 'PH'
                        card:
                          type: custom:button-card
                          template: graph_pool_measure
                          variables:
                            entity: sensor.pool_ph
                            color: "green"
                            decimal: 1
                            yaxis:
                              min: 6.9
                              max: 7.7
                            annotations:
                              min:
                                value: 7.1
                                text: "7.1"
                              max:
                                value: 7.5
                                text: "7.5"
                      - type: conditional
                        conditions:
                          - entity: input_select.ui_pool_select_measure
                            state: 'ORP'
                        card:
                          type: custom:button-card
                          template: graph_pool_measure
                          variables:
                            entity: sensor.pool_orp
                            color: "yellow"
                            decimal: 0
                            yaxis:
                              min: 500
                              max: 800
                            annotations:
                              min:
                                value: 650
                                text: "650mv"
                              max:
                                value: 750
                                text: "750mv"
                      - type: conditional
                        conditions:
                          - entity: input_select.ui_pool_select_measure
                            state: 'Sel'
                        card:
                          type: custom:button-card
                          template: graph_pool_measure
                          variables:
                            entity: sensor.pool_salinity
                            color: "red"
                            decimal: 1
                            curve: "stepline"
                            yaxis:
                              min: 2.9
                              max: 3.4
          actions:
            card:
              type: custom:button-card
              styles:
                card:
                  - border-radius: 0px
                  - box-shadow: "none"
                  - padding-top: 0
                grid:
                  - grid-template-areas: " 'a1 . a2 . a3'"
                  - grid-template-columns: "1fr 10px 1fr 10px 1fr"
                  - grid-template-rows: "min-content"
              custom_fields:
                a1:
                  card:
                    type: custom:button-card
                    template: button_pool_action 
                    entity: switch.piscine_filtration
                    name: Filtration
                    icon: mdi:engine
                    label: "[[[ return states['sensor.pool_pump_duration'].state; ]]]"
                    variables:
                      schedules: "[[[ return states['sensor.pool_pump_hours'].state; ]]]"
                a2:
                  card:
                    type: custom:button-card
                    template: button_pool_action 
                    entity: switch.piscine_surpresseur
                    name: Robot
                    icon: mdi:robot-mower
                    label: "[[[ return states['sensor.robot_duration'].state; ]]]"
                    variables:
                      schedules: "[[[ return states['input_text.robot_hours'].state; ]]]"
                a3:
                  card:
                    type: custom:button-card
                    template: button_pool_action
                    entity: switch.piscine_electrolyseur_sel
                    name: Electrolyseur
                    icon: mdi:electron-framework
                    label: "[[[ return states['sensor.electrolyseur_sel_duration'].state; ]]]"
                    variables:
                      schedules: "-"
          infos:
            card:
              type: custom:button-card
              styles:
                card:
                  - border-radius: 0px
                  - box-shadow: "none"
                  - padding-top: 0
                grid:
                  - grid-template-areas: " 'i1 . i2 . i3 . i4'"
                  - grid-template-columns: "1fr 10px 1fr 10px 1fr 10px 1fr"
                  - grid-template-rows: "min-content"
              custom_fields:
                i1:
                  card:
                    type: custom:button-card
                    template: button_pool_info
                    name: Lumières
                    icon: mdi:lightbulb
                i2:
                  card:
                    type: custom:button-card
                    template: button_pool_info
                    name: Mode<br/>filtration
                    label: "[[[ return states['input_select.pool_pump'].state; ]]]"
                    tap_action:
                      action: 'fire-dom-event'
                      browser_mod:
                        command: "popup"
                        large: true
                        hide_header: true
                        card:
                          type: "custom:button-card"
                          template: "popup_pool_pump"
                        style:
                          $: |
                            .mdc-dialog .mdc-dialog__container .mdc-dialog__surface {
                              box-shadow: none;
                              border-radius: 0px;
                              background-color: var(--main-background-color);
                            }
                i3:
                  card:
                    type: custom:button-card
                    template: button_pool_info
                    name: Lavage<br/>filtre
                    label: Il y a 10 jours
                    variables:
                      alert: false
                i4:
                  card:
                    type: custom:button-card
                    template: button_pool_info
                    name: Lavage<br/>skimmers
                    label: Il y a 25 jours
                    variables:
                      alert: true

graph_pool_measure:
  variables:
    entity : ""
    color: ""
    decimal: 0
    curve: "smooth"
    annotations:
      min:
        value: -100
        text: ""
      max:
        value: -100
        text: ""
    yaxis:
      min: auto
      max: auto
  styles:
    card:
      - padding: 0px
      - border-radius: 0px
      - box-shadow: "none"
    grid:
      - grid-template-areas: "'graph'"
      - grid-template-columns: "1fr"
      - grid-template-rows: "min-content"
  custom_fields:
    graph:
      card:
        type: custom:apexcharts-card
        header:
          show: false
        graph_span: >
          [[[
            switch (states['input_select.ui_pool_select_measure_period'].state) {
              case "Mois":
                return "4w";
                break
              case "Semaine":
                return "1w";
                break;
              case "Jour":
                return "1d";
                break;
            }
          ]]]
        series:
          - entity: "[[[ return variables.entity; ]]]"
            stroke_width: 3
            color: "[[[ return `var(--google-${variables.color})`; ]]]"
            fill_raw: last
            type: area
        yaxis:
          - decimals: "[[[ return variables.decimal; ]]]"
            min: "[[[ return variables.yaxis.min; ]]]"
            max: "[[[ return variables.yaxis.max; ]]]"
            apex_config:
              tickAmount: 5
              forceNiceScale: true
              labels:
                offsetX: -15
        apex_config:
          chart:
            height: 200px
          grid:
            padding:
              left: 0
              right: 0
          fill:
            type: 'gradient'
            gradient:
              shade: "light"
              type: "vertical"
              opacityFrom: 0.7
              opacityTo: 0.1
              stops: [0, 100]
          stroke:
            curve: "[[[ return variables.curve; ]]]"
          annotations:
            position: 'front'
            yaxis:
              - y: "[[[ return variables.annotations.min.value; ]]]"
                y2: "[[[ return variables.annotations.max.value; ]]]"
                opacity: 0.2
              - y: "[[[ return variables.annotations.max.value; ]]]"
                strokeDashArray: 0
                label:
                  text: "[[[ return variables.annotations.max.text; ]]]"
                  offsetX: -5
                  offsetY: -1
                  style:
                    fontSize: 8px
              - y: "[[[ return variables.annotations.min.value; ]]]"
                strokeDashArray: 0
                label:
                  text: "[[[ return variables.annotations.min.text; ]]]"
                  offsetX: -5
                  offsetY: -1
                  style:
                    fontSize: 8px
        card_mod:
          style: |
            ha-card {
              border-radius: 0px;
              box-shadow: none;
              padding: 0;
              margin: 0;
            }
            #state__name, .apexcharts-xaxis-label, .apexcharts-yaxis-label {
              font-size: 10px !important;
            }

button_pool_measure:
  variables:
    color: "blue"
    select_value: "Temperature"
    alert: false
  show_name: true
  show_label: true
  aspect_ratio: 1/1
  styles:
    card:
      - padding: "0px"
      - box-shadow: none
      - background-color: var(--primary-background-color)
      - background-image: >
          [[[
              let theme = hass.themes.themes[hass.themes.theme].modes[(hass.themes.darkMode ? "dark" : "light")];
              let color_bckg = theme[`google-${variables.color}`].replace('#', '%23');
              let opacity = theme['opacity-bg'];
              return "url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 1440 1440'><path fill='"+color_bckg+"' fill-opacity='"+opacity+"' d='m 0,566 48,26.7 C 96,619 192,673 288,699.3 384,726 480,726 576,710 c 96,-16 192,-48 288,-42.7 96,5.7 192,47.7 288,58.7 96,11 192,-11 240,-21.3 l 48,-10.7 v 754 H 1392 1152 864 576 288 48 0 Z'></path></svg>\")";
          ]]]
      - background-size: 100%
    grid:
      - grid-template-areas: "'n' 'l'"
      - grid-template-columns: "1fr"
      - grid-template-rows: "auto auto"
    name:
      - font-size: "10px"
      - place-self: center
    label:
      - font-size: "10px"
      - place-self: center
      - font-weight: "bold"
      - color: >
          [[[
            if (hass.themes.darkMode){
              return "#FFFFFF";
            } else {
              return `rgba(var(--color-${variables.color}), 1)`;
            }
          ]]]
    custom_fields:
      notification:
        - border-radius: "50%"
        - position: "absolute"
        - right: "2px"
        - top: "14px"
        - height: "12px"
        - width: "12px"
        - border: "1px solid var(--card-background-color)"
        - font-size: "6px"
        - line-height: "12px"
        - background-color: >
            [[[
              return "rgba(var(--color-red),1)";
            ]]]
  custom_fields:
    notification: >
      [[[
        if (variables.alert) {
          return `<ha-icon icon="mdi:exclamation" style="width: 9px; height: 9px; color: var(--primary-background-color);"></ha-icon>`
        }
      ]]]
  tap_action:
    action: call-service
    service: input_select.select_option
    service_data:
      entity_id: input_select.ui_pool_select_measure
      option: "[[[ return `${variables.select_value}`; ]]]"

button_pool_period:
  variables:
    period: "Semaine"
  show_name: true
  styles:
    card:
      - padding: "5px 0px"
      - box-shadow: none
      - background-color: >
          [[[
            if (states['input_select.ui_pool_select_measure_period'].state == variables.period) {
              switch (states['input_select.ui_pool_select_measure'].state) {
                case "Temperature":
                  return "var(--google-blue)";
                case "PH":
                  return "var(--google-green)";
                case "ORP":
                  return "var(--google-yellow)";
                case "Sel":
                  return "var(--google-red)";
                default:
                  return "var(--primary-background-color)";
              }
            } else {
              return "var(--primary-background-color)";
            }
          ]]]
    name:
      - font-size: "10px"
      - place-self: center
      - color: >
          [[[
            if (states['input_select.ui_pool_select_measure_period'].state == variables.period) {
              return "white";
            } else {
              return "var(--primary-text-color)";
            }
          ]]]
  tap_action:
    action: call-service
    service: input_select.select_option
    service_data:
      entity_id: input_select.ui_pool_select_measure_period
      option: "[[[ return `${variables.period}`; ]]]"

button_pool_action:
  variables:
    schedules: "-"
  show_name: true
  show_label: true
  show_icon: true
  styles:
    card:
      - padding: "5px 0px"
      - box-shadow: none
      - background-color: var(--primary-background-color)
    grid:
      - grid-template-areas: "'n' 'l' 'i' 'schedules'"
      - grid-template-columns: "1fr"
      - grid-template-rows: "min-content min-content min-content min-content"
    name:
      - font-size: "12px"
      - place-self: center
      - font-weight: "bold"
    label:
      - font-size: "10px"
      - place-self: center
      - filter: "opacity(40%)"
    custom_fields:
      schedules:
        - font-size: "10px"
        - place-self: center
        - filter: "opacity(40%)"
  custom_fields:
    schedules: "[[[ return `${variables.schedules}`; ]]]"

button_pool_info:
  variables:
    alert: false
  show_name: true
  show_label: true
  show_icon: true
  styles:
    card:
      - padding: "5px 10px"
      - box-shadow: none
      - background-color: var(--primary-background-color)
      - height: 55px
    grid:
      - grid-template-areas: "'n' 'l' 'i'"
      - grid-template-columns: "1fr"
      - grid-template-rows: "min-content min-content"
    name:
      - font-size: "10px"
      - place-self: center
      - font-weight: "bold"
    label:
      - font-size: "10px"
      - place-self: center
      - filter: "opacity(40%)"
      - padding-top: 5px
    icon:
      - padding-top: 3px
    custom_fields:
      notification:
        - border-radius: "50%"
        - position: "absolute"
        - right: "2px"
        - top: "2px"
        - height: "16px"
        - width: "16px"
        - border: "2px solid var(--card-background-color)"
        - font-size: "12px"
        - line-height: "14px"
        - background-color: >
            [[[
              return "rgba(var(--color-red),1)";
            ]]]
  custom_fields:
    notification: >
      [[[
        if (variables.alert) {
          return `<ha-icon icon="mdi:exclamation" style="width: 12px; height: 12px; color: var(--primary-background-color);"></ha-icon>`
        }
      ]]]

For the question :

It’s both at the same time :slight_smile:
For the choice of temperature, redox,… it’s an input_select with a conditional card and a unique chart template.
For the choice of the period it is also an input_select that I process in my chart template on the attribute graph_span

8 Likes

Wow! Thanks :+1:

this made me want to rebuild my own card (see above) that uses the config-template-card to do just that, adapt the graph_span based on an input_select.apex_span. Why use an extra card, if we can template directly (I wasnt aware)?

but, using

graph_span: >
  [[[ return states['input_select.apex_span'].state; ]]]

throws the error

making me think templates aren’t supported on that field…

and the docs dont mention templates being supported either:

obviously my input_selects holds only valid options

now why would that be? How did you make the templates a valid graph_span input…

for now, back to:

type: custom:config-template-card
entities:
  - input_select.apex_span
variables:
  span: states['input_select.apex_span'].state
card:

  type: custom:apexcharts-card
  graph_span: ${span}

if I enter the template you use, and adapt with my options, the card is displayed alright, but changing a selection does not update the graph at all. So a bit of a mystery here…:wink:
just in case: FR: allow template on graph_span · Issue #344 · RomRider/apexcharts-card · GitHub

Hi,

I am seeking some help as I have spent hours fiddling can not see the forest from the trees now.

My Card is loading blank… annoying. I have other apex cards, working with no issue. This particular entity (actually group of entities) just will not work.

You can see its consuming some level of data, as it nows the timeline and scales on the axis’s. Legend also shows max values for the period.

Card:

type: custom:apexcharts-card
span:
  start: minute
header:
  show: true
  title: EMHASS Forecasts
  show_states: true
  colorize_states: true
now:
  show: true
  label: now
series:
  - entity: sensor.p_pv_forecast
    curve: stepline
    show:
      in_header: before_now
    stroke_width: 1
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
        return [new Date(entry.date), entry.p_pv_forecast];
      });
  - entity: sensor.p_batt_forecast
    curve: stepline
    show:
      in_header: before_now
    stroke_width: 1
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
        return [new Date(entry.date), entry.p_batt_forecast];
      });
  - entity: sensor.p_load_forecast
    curve: stepline
    show:
      in_header: before_now
    stroke_width: 1
    data_generator: |
      return entity.attributes.forecasts.map((entry) => {
        return [new Date(entry.date), entry.p_load_forecast];
      });

Entity Attributes:

unit_of_measurement: W
friendly_name: Battery Power Forecast
forecasts:
  - date: '2022-06-03 20:30:00+10:00'
    p_batt_forecast: -7899
  - date: '2022-06-03 21:00:00+10:00'
    p_batt_forecast: -4320
  - date: '2022-06-03 21:30:00+10:00'
    p_batt_forecast: -7015
  - date: '2022-06-03 22:00:00+10:00'
    p_batt_forecast: 0
  - date: '2022-06-03 22:30:00+10:00'
    p_batt_forecast: 0
  - date: '2022-06-03 23:00:00+10:00'
    p_batt_forecast: 0
  - date: '2022-06-03 23:30:00+10:00'
    p_batt_forecast: 0
  - date: '2022-06-04 00:00:00+10:00'
    p_batt_forecast: 0
  - date: '2022-06-04 00:30:00+10:00'
    p_batt_forecast: 0
  - date: '2022-06-04 01:00:00+10:00'
    p_batt_forecast: 0
  - date: '2022-06-04 01:30:00+10:00'
    p_batt_forecast: 0
  - date: '2022-06-04 02:00:00+10:00'
    p_batt_forecast: 0
  - date: '2022-06-04 02:30:00+10:00'
    p_batt_forecast: 0
  - date: '2022-06-04 03:00:00+10:00'
    p_batt_forecast: 0
  - date: '2022-06-04 03:30:00+10:00'
    p_batt_forecast: 669
  - date: '2022-06-04 04:00:00+10:00'
    p_batt_forecast: -669
  - date: '2022-06-04 04:30:00+10:00'
    p_batt_forecast: 0
  - date: '2022-06-04 05:00:00+10:00'
    p_batt_forecast: 699
  - date: '2022-06-04 05:30:00+10:00'
    p_batt_forecast: 2643
  - date: '2022-06-04 06:00:00+10:00'
    p_batt_forecast: -4988
  - date: '2022-06-04 06:30:00+10:00'
    p_batt_forecast: 3653
  - date: '2022-06-04 07:00:00+10:00'
    p_batt_forecast: 2346
  - date: '2022-06-04 07:30:00+10:00'
    p_batt_forecast: 2268
  - date: '2022-06-04 08:00:00+10:00'
    p_batt_forecast: 1289
  - date: '2022-06-04 08:30:00+10:00'
    p_batt_forecast: 882
  - date: '2022-06-04 09:00:00+10:00'
    p_batt_forecast: -944
  - date: '2022-06-04 09:30:00+10:00'
    p_batt_forecast: -1528
  - date: '2022-06-04 10:00:00+10:00'
    p_batt_forecast: -2063
  - date: '2022-06-04 10:30:00+10:00'
    p_batt_forecast: -2357
  - date: '2022-06-04 11:00:00+10:00'
    p_batt_forecast: -2568
  - date: '2022-06-04 11:30:00+10:00'
    p_batt_forecast: -2674
  - date: '2022-06-04 12:00:00+10:00'
    p_batt_forecast: -2649
  - date: '2022-06-04 12:30:00+10:00'
    p_batt_forecast: -5000
  - date: '2022-06-04 13:00:00+10:00'
    p_batt_forecast: -5000
  - date: '2022-06-04 13:30:00+10:00'
    p_batt_forecast: -5000
  - date: '2022-06-04 14:00:00+10:00'
    p_batt_forecast: -5000
  - date: '2022-06-04 14:30:00+10:00'
    p_batt_forecast: -1661
  - date: '2022-06-04 15:00:00+10:00'
    p_batt_forecast: 2157
  - date: '2022-06-04 15:30:00+10:00'
    p_batt_forecast: 4782
  - date: '2022-06-04 16:00:00+10:00'
    p_batt_forecast: 2131
  - date: '2022-06-04 16:30:00+10:00'
    p_batt_forecast: 5000
  - date: '2022-06-04 17:00:00+10:00'
    p_batt_forecast: -1152
  - date: '2022-06-04 17:30:00+10:00'
    p_batt_forecast: 5000
  - date: '2022-06-04 18:00:00+10:00'
    p_batt_forecast: 5000
  - date: '2022-06-04 18:30:00+10:00'
    p_batt_forecast: 5000
  - date: '2022-06-04 19:00:00+10:00'
    p_batt_forecast: 5000
  - date: '2022-06-04 19:30:00+10:00'
    p_batt_forecast: 5000
  - date: '2022-06-04 20:00:00+10:00'
    p_batt_forecast: 5000

Any help would be appreciated as I am starting to lose my marbles.

Maybe you are missing the graph_span.
Try adding:

graph_span: 24h
span:
  start: minute