ApexCharts card - A highly customizable graph card

I really returns the data fast, in 2 seconds.
I have hosted it on my 2 proxmox servers, 2x active and 1 backup
It’s really fast and works great with mariaDB.

That specific device is my off peak usage, it will record ever time it changes.
Same for the other sensors.

I only keep like (all temperature/humid sensors and electricity consume + gas)
It does not make the database any big at all.

For example i also use google sheets integration and imported it into grafana.
On sheets i have 2 year data of all my temp sensors and electricity and gas usage, nothing else, it writes every time it changes value, so it has tons of data on that sheet.

But whole sheet is only 7mb for 1 year.

Hi all

I’m working on a graph for my solar system to show the current solar power coming through my panels and compares against the estimated maximum power I can get from the sun assuming the charge controller can accept it all.

I have the graph looking quite nice, however I would love it if the solar (PV) power would change color according to the charge controller state (eg. red for bulk charging, orange for absorption, then green for float)

Below is my current code:

type: custom:apexcharts-card
header:
  show: true
  title: Solar Power
  show_states: true
  colorize_states: true
series:
  - entity: sensor.solar_power
    data_generator: ''
    type: column
    stroke_width: 1
    group_by:
      func: avg
      duration: 5min
    yaxis_id: power
  - entity: sensor.solcast_pv_forecast_power_now
    type: column
    data_generator: ''
    stroke_width: 1
    offset: '-30m'
    yaxis_id: power
  - entity: sensor.battery_soc
    type: line
    stroke_width: 1
    yaxis_id: percentage
graph_span: 14h
span:
  start: day
  offset: +6h
now:
  show: true
  color: red
  label: Now
yaxis:
  - id: power
    decimals: 1
    apex_config:
      tickAmount: 5
  - id: percentage
    min: 0
    max: 100
    opposite: true
    decimals: 1
    apex_config:
      tickAmount: 5

image

Does anyone have a suggestion on how i could get this to work?
I was thinking something along the lines of an if statement alongside “color:”.

if "sensor.pv_charger_state" = Bulk, color = red
if "sensor.pv_charger_state" = absorbtion, color = orange
if "sensor.pv_charger_state" = float, color = green

My attempts along those lines haven’t worked yet though.

Any help would be appreciated!

I’ve googled and search these forums and read through peoples code but I cannot for the life of me to get my apexchart-card in Homeassistant to fill the width of the screen. They appear to be fixed but I’ve seen posts of graphs showing them much wider?

type: custom:apexcharts-card
header:
  title: Flow Temp History
  show: true
  show_states: true
  colorize_states: true
apex_config:
  chart:
    type: area
    height: 300
    fill:
      type: gradient
series:
  - entity: sensor.current_flow_temperature_in_circuit_0
    name: ^^ Flow Temperature Now
    stroke_width: 1
    show:
      datalabels: true
      extremas: true

It’s a HA thing - you’ll need to use something like layout-card to customise.

For example, I’ve got this on a couple of dashboard views.

grid-template-columns: 25% 25% 25% 25%
grid-template-rows: auto
grid-gap: 1px 1px
grid-template-areas: |
  "header header header header"
  "tl tlm trm tr"
  "bl blm brm br"
  "footer footer footer footer"
  "status status status status"

You can then add something like this to your card to have it fill the width:

view_layout:
  grid-area: footer

There may be another way, this works for me.

Edit: I found this site very useful for getting the CSS https://grid.layoutit.com/.

Here’s an example!
image

2 Likes

For anyone who also has the problem with the overlap when displaying a forecast: I have found a solution that cleanly separates both graphs. I’ve modified the data generator as follows:

  data_generator: |
      return entity.attributes.forecast.map((entry) => {
        let cur_hour = new Date(Date.now()).getHours();
        return [new Date(entry.datetime).setHours(cur_hour), entry.temperature];
      });

before:

afterwards:

For the forecast, the average value is displayed in the middle of the day. In the afternoon, there will be overlaps with the actual measured values of the day. My solution is to modify the timestamp so that the forecast value is calculated at the current hour of the day. This is not a problem as long as the forecast consists of only one value per day. By grouping, the forecast value is still displayed in the middle of the day.

It may not be the best option, but it works.

Complete example code
type: custom:apexcharts-card
graph_span: 8d
span:
  start: day
  offset: '-3d'
header:
  show: true
  title: Temperature Forecast
  show_states: true
now:
  show: true
  label: jetzt
apex_config:
  legend:
    show: false
series:
  - entity: weather.wetterstation
    name: Temperature
    unit: °C
    attribute: temperature
    fill_raw: last
    extend_to: now
    group_by:
      func: avg
      duration: 1h
  - entity: weather.forecast_home
    type: line
    unit: °C
    show:
      in_header: false
    data_generator: |
      return entity.attributes.forecast.map((entry) => {
        let cur_hour = new Date(Date.now()).getHours();
        return [new Date(entry.datetime).setHours(cur_hour), entry.temperature];
      });

3 Likes

Firstly, love this card.

I’m trying to make a network traffic graph. Here is how it looks in a low activity state:
image

The issue I’m having relates to the scale of the yaxis(s).

  1. As my maximum download speed is serval times my upload, this necessitates the use of 2 yaxis to provide sufficent resolution for both plots.
  2. Fixed min/max: Either have to set the values high and lose resolution for low values, or set them low and clip the high values.
  3. Soft bounds (min: ~20, max:~20): Work fine until a value exceeds the bound, resulting in the ‘0’ point shifting off centre in the vertical. With two yaxis this looks wrong and defeats the purpose.
  4. Adding to min/max (min: '|-20|', max: '|+20|'): same issues as soft bounds.

Has anyone found a way to use a variable (that’s ultimately determined by a sensor state over time) for min/max? As an example, it would be nice to have:
Max: ~20
Min: Max * -1

I’ve tried messing about with config-template-card but I’m either too dense or it’s not possible to grab a value (say max) from the apexcharts-card and modify it (* -1) to create a variable which can be used for min. I’ve managed to set a variable as a min value (MIN_DOWNLOAD: Number(states['sensor.downloadspeed'].state) * -1).

Other than making a helper which is the max value of that sensor over a given period (this just crossed my mind), is there any way to achive the result I’m seeking?

Edit: Went with the template route. Anyone looking to do this should check out this thread: Daily maximum during a time period

Nice!
You mention you have one forecast entry per day, would this work with two? E.g. high and low temperature…

True, that would make a lot of sense, but unfortunately my programming skills are not quite enough for it. This is very close:

    data_generator: |
      return entity.attributes.forecast.flatMap((entry) => {
        let now = new Date();
        
        if (new Date(entry.datetime).setHours(now.getHours(), now.getMinutes()) < now)
          return [];
           
        let maxValues = [new Date(entry.datetime).setHours(23,59,59,99), entry.temperature];
        let minValues = [new Date(entry.datetime).setHours(12,0,0,0), entry.templow];
             
        return [minValues, maxValues];
      });

Unfortunately, there is now a gap of up to 23 hours between the now line and the forecast. I have no idea how this can be solved. But hey: at least no overlap :slight_smile:

1 Like

image

Is there a way to bring the unit below the value?

Hello,

I have the following chart which I would like to invert the color (white->black and black->white). While I can turn the background to black (see ha-card section), I can’t find a way to change the color of all text. Can anybody help here?
PS: it should be done independently of the light/dark mode setting.
Many thanks

The main code:

type: custom:apexcharts-card
config_templates: temperature
all_series_config:
  stroke_width: 3
  curve: smooth
header:
  show: true
  show_states: false
  colorize_states: true
series:
  - entity: sensor.buienradar_temperature
    yaxis_id: temperature
    color: rgb(45 255 226)
    name: Extérieur
  - entity: sensor.thermostat_salon_1_current_temperature
    yaxis_id: temperature
    color: rgb(42 150 231)
    name: Intérieur
  - entity: sensor.air_quality_humidity
    yaxis_id: humidite
    color: rgb(135 44 255)
    name: Humidité
style: |
  ha-card {
    --ha-card-border-width: 0;
    --ha-card-border-radius: 0;
    /*--ha-card-background: black;*/
  }

The apex card template:

apexcharts_card_templates:
  temperature:
    graph_span: 24h
    header:
      show: true
      show_states: false
      colorize_states: true
    all_series_config:
      stroke_width: 3
      type: line
      curve: smooth
      show:
        extremas: true
    yaxis:
      - id: temperature
        decimals: 1
        apex_config:
          tickAmount: 4
      - id: humidite
        opposite: true
        decimals: 1
        apex_config:
          tickAmount: 4 

The graph:

not sure if you ever got this, but I somehow stumbled on what I think you were looking for:

apex

type: custom:apexcharts-card
apex_config:
  chart:
    stacked: true
graph_span: 7d
span:
  end: day
show:
  last_updated: true
header:
  show: true
  show_states: true
  colorize_states: true
  title: Monthly energy consumption in kWH
series:
  - entity: sensor.water_heater_14_1mon
    name: Water Heater
    type: column
    unit: ' kWH'
    color: slateblue
    group_by:
      func: max
      duration: 1d
  - entity: sensor.dishwasher_5_1mon
    name: Dishwasher
    type: column
    unit: ' kWH'
    color: darkviolet
    group_by:
      func: max
      duration: 1d
  - entity: sensor.stove_4_1mon
    name: Stove
    type: column
    unit: ' kWH'
    color: orangered
    group_by:
      func: max
      duration: 1d
  - entity: sensor.den_2_1mon
    name: Den
    type: column
    unit: ' kWH'
    color: green
    group_by:
      func: max
      duration: 1d
  - entity: sensor.gary_office_13_1mon
    name: Gary's Office
    type: column
    unit: ' kWH'
    color: yellow
    group_by:
      func: max
      duration: 1d
  - entity: sensor.washing_machine_1mon
    name: Washer
    type: column
    unit: ' kWH'
    color: dodgerblue
    group_by:
      func: max
      duration: 1d
  - entity: sensor.dryer_8_1mon
    name: Dryer
    type: column
    unit: ' kWH'
    color: red
    group_by:
      func: max
      duration: 1d

This is indeed how I would like to have the background and the text. But I only get this when if I use the dark mode (which I don’t wont to have permanently activate).

Thanks for the update. I’ve pushed this back to priority number 1 billion for the moment. If I ever get around to it and find a working solution, I’ll post it here.

1 Like

Would love to know this yaml if you are sharing.

Thanks.

Ok, in the end I found a solution that is satisfactory for me, which uses highs and lows of the forecast. From these, the average is calculated. No gaps, no overlaps. I’m happy with it.

    data_generator: |
      return entity.attributes.forecast.map((entry) => {
        let cur_hour = new Date(Date.now()).getHours();
        let result  = (entry.temperature + entry.templow) / 2;
        return [new Date(entry.datetime).setHours(cur_hour), result];
      });

Bildschirmfoto 2023-12-07 um 21.37.02

full code
type: custom:apexcharts-card
graph_span: 9d
span:
  start: day
  offset: '-3d'
header:
  show: false
  title: Temperatur
now:
  show: true
  label: jetzt
apex_config:
  chart:
    height: 200px
    dropShadow:
      enabled: true
      opacity: 0.3
  grid:
    show: true
  legend:
    show: false
  tooltip:
    enabled: true
  xaxis:
    type: datetime
    labels:
      datetimeFormatter:
        month: ddd
        day: ddd
    axisTicks:
      show: false
    axisBorder:
      show: false
yaxis:
  - show: true
    decimals: 0
    apex_config:
      tickAmount: 2
update_interval: 1h
all_series_config:
  stroke_width: 4
  unit: °C
series:
  - entity: weather.wetterstation
    name: Temperature
    attribute: temperature
    fill_raw: last
    extend_to: now
    color: orange
    opacity: 0.9
    curve: smooth
    group_by:
      func: avg
      duration: 1h
  - entity: weather.forecast_home
    color: orange
    opacity: 0.5
    data_generator: |
      return entity.attributes.forecast.map((entry) => {
        let cur_hour = new Date(Date.now()).getHours();
        let result  = (entry.temperature + entry.templow) / 2;
        return [new Date(entry.datetime).setHours(cur_hour), result];
      });
3 Likes

This issue is marked as completed on Nov 13, 2022
I did give it a try. I can configure a ccomplete apexchart as scatter but not just one of the dataseries while the others in the same card are lines.
Did you ever get it working @AleXSR700 ?

I would love to have some help with this quite unique case, hoping someone has done this.

I have not found any information about how to manipulate normal history entity state and that particular datetime of state inside ApexChart. I would like to move simple semsor containing float value and show all last week values in ApexChart shifted 1 week forward.

Can data_generator manipulate entity.state object this way?

Do you want to change the dates of last week’s values? If not then why not use offset? Provide and example as this may help instead of theoretical stuff

1 Like

Thank you, offset was just the thing I was looking for! Now I hope I can manipulate data a bit more with transform and additional calculations.

Only thing left is to scale the offset data to the right way as it seems to stick with original data. :slight_smile:

I’m a bit baffled with my multi-Y axis config as the main chart does not honor the yaxis_id, but the brush honors it. I just can not find what’s wrong it, deleted series by series but no avail, -7d offset stays not multiplied on main chart. :open_mouth:

Long shot, if someone else can spot the reason in the chart code?

type: custom:config-template-card
variables:
  MOVINGAVERAGE: states['sensor.nordpool_price_today_average_whole_day_longterm_mean']
entities:
  - ${MOVINGAVERAGE.entity_id}
card:
  type: custom:apexcharts-card
  graph_span: 13d
  span:
    start: day
    offset: '-7d'
  apex_config:
    xaxis:
      labels:
        datetimeFormatter:
          day: dd ddd
    chart:
      height: 300%
  yaxis:
    - id: tanaan
      decimals: 0
      apex_config:
        tickAmount: 4
    - id: power
      opposite: true
      decimals: 0
      min: 0
    - id: temperature
      opposite: true
      decimals: 0
  show:
    last_updated: true
  experimental:
    brush: true
    color_threshold: true
  brush:
    selection_span: 13d
    apex_config:
      xaxis:
        labels:
          datetimeFormatter:
            day: dd ddd
  header:
    title: Sähkön hinta, tuulivoima ja lämpötila
    show: true
    show_states: true
    colorize_states: true
  now:
    show: true
    label: Nyt
  series:
    - entity: sensor.fingrid_wind_power_generation_forecast_hourly
      yaxis_id: power
      name: Windpower gen forecast
      type: line
      curve: stepline
      stroke_width: 2
      color: grey
      data_generator: |
        return entity.attributes.events.event.map((d, index) => {
          return [new Date(d["start_time"]).getTime(), entity.attributes.events.event[index]["value"]*1];
        });
      show:
        header_color_threshold: true
        legend_value: false
        in_header: false
        name_in_header: false
        extremas: true
    - entity: weather.openweathermap
      yaxis_id: temperature
      name: Lämpötilaennuste
      curve: stepline
      stroke_width: 2
      color: purple
      data_generator: |
        return entity.attributes.forecast.map((entry) => {
              return [new Date(entry.datetime).getTime(), entry.temperature];
            });
      show:
        header_color_threshold: true
        legend_value: false
        in_header: false
        name_in_header: false
        extremas: true
    - entity: sensor.weather_combined_temperature
      yaxis_id: temperature
      name: Lämpötila
      curve: stepline
      stroke_width: 2
      extend_to: now
      color: purple
      show:
        header_color_threshold: true
        legend_value: false
        in_header: false
        name_in_header: false
        extremas: true
    - entity: sensor.nordpool_fi
      name: Tänään
      yaxis_id: tanaan
      show:
        in_brush: true
        in_chart: true
        extremas: true
        in_header: raw
        header_color_threshold: true
        legend_value: false
      type: column
      data_generator: |
        return entity.attributes.raw_today.map((start, index) => {
          return [new Date(start["start"]).getTime(), entity.attributes.raw_today[index]["value"]];
        });
      color_threshold:
        - value: ${MOVINGAVERAGE.state * 1}
          color: lightblue
        - value: ${MOVINGAVERAGE.state * 33}
          color: darkgreen
        - value: ${MOVINGAVERAGE.state * 75}
          color: goldenrod
        - value: ${MOVINGAVERAGE.state * 125}
          color: darkred
    - entity: sensor.nordpool_fi
      name: Huomenna
      yaxis_id: tanaan
      show:
        in_brush: true
        in_chart: true
        extremas: false
        in_header: false
        header_color_threshold: true
        legend_value: false
      type: column
      data_generator: |
        return entity.attributes.raw_tomorrow.map((start, index) => {
          return [new Date(start["start"]).getTime(), entity.attributes.raw_tomorrow[index]["value"]];
        });
      color_threshold:
        - value: ${MOVINGAVERAGE.state * 1}
          color: lightblue
        - value: ${MOVINGAVERAGE.state * 33}
          color: darkgreen
        - value: ${MOVINGAVERAGE.state * 75}
          color: goldenrod
        - value: ${MOVINGAVERAGE.state * 125}
          color: darkred
    - entity: sensor.nordpool_price_now_euro
      name: Price_euro
      yaxis_id: tanaan
      show:
        in_brush: true
        in_chart: true
        extremas: false
        in_header: false
        header_color_threshold: true
        legend_value: false
      type: column
      transform: return x * 100;
      color_threshold:
        - value: ${MOVINGAVERAGE.state * 1}
          color: lightblue
        - value: ${MOVINGAVERAGE.state * 33}
          color: darkgreen
        - value: ${MOVINGAVERAGE.state * 75}
          color: goldenrod
        - value: ${MOVINGAVERAGE.state * 125}
          color: darkred
    - entity: sensor.nordpool_price_now_euro
      name: Price_euro
      yaxis_id: tanaan
      offset: '-7d'
      show:
        in_brush: true
        in_chart: true
        extremas: false
        in_header: false
        header_color_threshold: true
        legend_value: false
      type: column
      transform: return x * 100 * 3
      color_threshold:
        - value: ${MOVINGAVERAGE.state * 1}
          color: lightblue
        - value: ${MOVINGAVERAGE.state * 33}
          color: darkgreen
        - value: ${MOVINGAVERAGE.state * 75}
          color: goldenrod
        - value: ${MOVINGAVERAGE.state * 125}
          color: darkred


After eliminating all other bells and whistles from chart, I definetely do not get why multi-Y is behaving like this:

type: custom:apexcharts-card
graph_span: 14d
span:
  start: day
  offset: '-7d'
yaxis:
  - id: first
experimental:
  brush: true
brush:
  selection_span: 14d
series:
  - entity: sensor.nordpool_price_now_euro
    name: Price_euro
    yaxis_id: first
    offset: '-7d'
    show:
      in_brush: true
      in_chart: true
    type: column
    transform: return x * 3
  - entity: sensor.nordpool_price_now_euro
    name: Price_euro
    yaxis_id: first
    show:
      in_brush: true
      in_chart: true
    type: column