Tag: ApexCharts

  • Generating a Beautiful Annual Solar PV Chart in Home Assistant

    This beautiful apexcharts chart gives me a clean annual view of solar PV performance month on month.

    chart showing annual solar pv generation month to month

    I show two things at once:

    1. Total kWh per month as orange columns.
    2. Average daily PV for that month as a dashed line.

    That pairing makes it easy to compare seasonal variation and spot how consistent generation is within each month.


    1) The time window

    I want a rolling 12-month view, aligned to the current year start, updating every 30 minutes.

    graph_span: 12month
    update_interval: 30m
    span:
      start: year
      offset: "-0d"

    2) Monthly totals as columns

    I read SolarEdge lifetime energy, convert Wh to kWh, then sum by month. I also enable statistics so ApexCharts aligns changes to month boundaries for accurate month on month totals.

    - entity: sensor.solaredge_lifetime_energy
      name: PV Last 12 Months
      color: var(--energy-solar-color)
      type: column
      float_precision: 0
      yaxis_id: first
      transform: return x / 1000;
      show:
        datalabels: true
      group_by:
        func: sum
        duration: 1month
        fill: zero
      statistics:
        type: change
        period: month
        align: end

    Why:

    • transform converts Wh to kWh.
    • group_by: sum gives a single monthly bar.
    • statistics: period: month ensures the change is aligned to the end of each month.

    3) Average daily PV as a dashed line

    I reuse the same entity and conversion, but average across the month, with statistics aligned by day. This produces a smooth “average day in this month” figure.

    - entity: sensor.solaredge_lifetime_energy
      name: PV Last 12 Months
      type: line
      stroke_dash: 3
      float_precision: 0
      color: "#3399FF"
      yaxis_id: second
      transform: return x / 1000;
      show:
        datalabels: true
      group_by:
        func: avg
        duration: 1month
        fill: zero
      statistics:
        type: change
        period: day
        align: end
      extend_to: false

    Why:

    • group_by: avg over a month produces the per-month daily average.
    • statistics: period: day uses daily changes to derive a meaningful daily rate.
    • stroke_dash: 3 makes the line read as a secondary metric.

    4) Dual y-axes for tidy scales

    Totals and averages sit on different ranges, so I bind columns to the left axis and the line to the right axis. Both are hidden to keep the design clean.

    yaxis:
      - id: first
        decimals: 2
        show: false
        min: 0
      - id: second
        opposite: true
        decimals: 0
        min: 0
        max: 80
        show: false

    5) Bar styling and stroke

    I keep columns compact with a subtle corner radius, and give lines a clear white stroke outline for contrast on dark themes.

    apex_config:
      plotOptions:
        bar:
          columnWidth: 28
          borderRadius: 1
      stroke:
        width: 2
        colors:
          - "#FFFFFF"

    6) Data labels with a custom formatter

    The line’s labels always show. Column labels only show for useful values, which keeps winter months uncluttered.

    apex_config:
      dataLabels:
        offsetY: -10
        style:
          fontSize: 10
        formatter: |
          EVAL: function(value, {seriesIndex, dataPointIndex, w }) {
            const roundedValue = Number(value).toFixed(0);
            if (seriesIndex === 1) {
              return roundedValue;
            }
            return roundedValue < 100 ? '' : roundedValue;
          }

    Why:

    • When seriesIndex === 1 (the dashed line), always show the rounded value.
    • For the column series, hide values below 100 kWh to avoid visual noise.

    7) Minimal legend and tooltip

    This is a glanceable tile, so I remove the legend and tooltips.

    apex_config:
      legend:
        show: false
      tooltip:
        enabled: false

    8) Month labels

    Three-letter month labels save space and remain readable.

    apex_config:
      xaxis:
        labels:
          hideOverlappingLabels: false
          rotate: 90
          show: true
          style:
            fontSize: 9
          format: MMM

    Full YAML

    type: custom:apexcharts-card
    graph_span: 12month
    update_interval: 30m
    span:
      start: year
      offset: "-0d"
    header:
      show: true
      title: Total Solar PV 2025 (kWh)
    series:
      - entity: sensor.solaredge_lifetime_energy
        name: PV Last 12 Months
        color: var(--energy-solar-color)
        type: column
        float_precision: 0
        yaxis_id: first
        transform: return x / 1000;
        show:
          datalabels: true
        group_by:
          func: sum
          duration: 1month
          fill: zero
        statistics:
          type: change
          period: month
          align: end
      - entity: sensor.solaredge_lifetime_energy
        name: PV Last 12 Months
        type: line
        stroke_dash: 3
        float_precision: 0
        color: "#3399FF"
        yaxis_id: second
        transform: return x / 1000;
        show:
          datalabels: true
        group_by:
          func: avg
          duration: 1month
          fill: zero
        statistics:
          type: change
          period: day
          align: end
        extend_to: false
    yaxis:
      - id: first
        decimals: 2
        show: false
        min: 0
      - id: second
        opposite: true
        decimals: 0
        min: 0
        max: 80
        show: false
    apex_config:
      chart:
        height: 300px
      legend:
        show: false
      plotOptions:
        bar:
          columnWidth: 28
          borderRadius: 1
      stroke:
        width: 2
        colors:
          - "#FFFFFF"
      tooltip:
        enabled: false
      dataLabels:
        offsetY: -10
        style:
          fontSize: 10
        formatter: |
          EVAL: function(value, {seriesIndex, dataPointIndex, w }) {
            const roundedValue = Number(value).toFixed(0);
            if (seriesIndex === 1) {
              return roundedValue;
            }
            return roundedValue < 100 ? '' : roundedValue;      
          }      
      xaxis:
        labels:
          hideOverlappingLabels: false
          rotate: 90
          show: true
          style:
            fontSize: 9
          format: MMM
  • Building a Min-Max-Avg Temperature Chart in Home Assistant

    This guide walks you through how I created a clean, monthly temperature overview card in Home Assistant, showing maximum, minimum, and average temperatures across the year – with a live annotation for the current reading.

    Chart showing monthly temperatures for my location

    The chart provides a fantastic visual snapshot of temperature trends throughout the year and uses the excellent apexcharts-card combined with config-template-card for dynamic annotations.


    Why I Built This

    I wanted a quick way to compare month-to-month temperatures for 2025 – not just the highs and lows, but also how the average temperatures trend over time. And with the real-time temperature annotated on the chart, it’s easy to see how the current conditions stack up against the year so far.


    What You’ll Need

    Before you start, make sure you have:

    • Home Assistant running with HACS
    • Installed the following custom cards via HACS:
    • A temperature sensor that reports regularly (e.g., sensor.tempest_temperature in my case)

    How Monthly Max, Min, and Average Temperatures Work

    The magic behind this chart lies in the use of Home Assistant’s built-in long-term statistics capability, which powers the statistics: feature of apexcharts-card.

    Each series uses the following:

    • type: max and type: min with period: month to extract monthly maximum and minimum temperatures respectively
    • type: mean along with group_by.func: avg to generate a monthly average line (in white)

    This means you’re not visualising raw sensor data points, but summarised monthly metrics – ideal for year-on-year comparisons or spotting unusual weather patterns.

    You can control how values are aggregated using align: start or align: end, which defines where in the month each value is anchored on the timeline.


    How the Live Annotation Works

    This chart includes a live annotation showing the current temperature as a small dot with a label.

    This is done using the annotations: section of ApexCharts, made dynamic by wrapping the entire card in a config-template-card.

    Here’s what happens:

    • We define datenow using JavaScript to get the timestamp for the first day of the current month
    • We read the current value from the sensor (sensor.tempest_temperature) using states[...]
    • We format that value to one decimal place and append the unit (°C)
    • These variables (${datenow}, ${temp}, ${tempString}) are injected into the chart’s annotations dynamically

    Without config-template-card, you couldn’t do this interpolation – it’s what allows us to reference JavaScript and Home Assistant state directly within the card definition.


    Customising the X-Axis Labels

    A small detail, but one that makes a big visual difference: the X-axis month labels are rotated and styled for clarity.

    xaxis:
      labels:
        rotate: 90
        style:
          fontSize: 9
        format: MMM

    This does three things:

    1. Rotates the labels so they don’t overlap
    2. Uses short month format (e.g. Jan, Feb, Mar) for a cleaner look
    3. Applies smaller font size to avoid clutter

    This is particularly useful when plotting an entire year, where 12 data points can otherwise get cramped.


    YAML Configuration

    Below is the full YAML you can drop into your dashboard. I used sensor.tempest_temperature as the data source, but you can replace it with any temperature sensor you have.

    type: custom:config-template-card
    variables:
      datenow: new Date(new Date().getFullYear(), new Date().getMonth(), 1).getTime()
      temp: parseFloat(states["sensor.tempest_temperature"].state)
      tempString: >-
        parseFloat(states["sensor.tempest_temperature"].state).toFixed(1) +
        states["sensor.tempest_temperature"].attributes.unit_of_measurement
    entities:
      - sensor.tempest_temperature
    card:
      type: custom:apexcharts-card
      update_interval: 30m
      graph_span: 11month
      span:
        start: year
      header:
        show: true
        title: Max|Min Temps 2025 (°C)
      now:
        show: false
      series:
        - entity: sensor.tempest_temperature
          name: Max Temp
          type: area
          color: "#e63946"
          opacity: 0.3
          float_precision: 1
          show:
            datalabels: true
          statistics:
            type: max
            period: month
            align: start
          extend_to: false
    
        - entity: sensor.tempest_temperature
          name: Min Temp
          type: area
          color: "#457b9d"
          opacity: 0.3
          float_precision: 1
          show:
            datalabels: true
          statistics:
            type: min
            period: month
            align: start
          extend_to: false
    
        - entity: sensor.tempest_temperature
          name: Avg Temp
          type: line
          color: "#ffffff"
          stroke_dash: 3
          float_precision: 1
          opacity: 0.8
          show:
            datalabels: false
          group_by:
            func: avg
            duration: 1month
            fill: zero
          statistics:
            type: mean
            period: month
            align: end
          extend_to: false
    
      apex_config:
        chart:
          height: 200px
        legend:
          show: false
        tooltip:
          enabled: false
        stroke:
          width: 2
        dataLabels:
          offsetY: -5
          style:
            fontSize: 9px
          formatter: |
            EVAL:function(val, opts) {
              return val.toFixed(1) + "°C";
            }
        xaxis:
          labels:
            hideOverlappingLabels: false
            rotate: 90
            show: true
            style:
              fontSize: 9
            format: MMM
        yaxis:
          show: false
        annotations:
          points:
            - x: ${datenow}
              y: ${temp}
              marker:
                size: 2
                shape: circle
                fillColor: "#FFFFFF"
                strokeColor: "#FFFFFF"
              label:
                offsetX: 26
                offsetY: 11
                text: ${tempString}
                style:
                  fontSize: 9
                  color: "#000000"

    Final Thoughts

    This kind of chart makes your Home Assistant dashboard much more informative. With a little templating and the power of ApexCharts, you can surface meaningful trends without needing to dive into graphs or statistics manually.

    The addition of dynamic annotations, paired with Home Assistant’s native statistics engine, creates a beautiful and functional visual that updates automatically.

  • Home Assistant Dashboard for Solar PV tracking

    One of the most satisfying parts of running a home solar setup is being able to visualise how your system performs compared to the forecast, how its performing financially, and how might it perform in the next few days.

    home assistant dashboard previewing solar pv

    In this walkthrough I’ll explain at a high level how I’ve combined Solcast, Predbat & Octopus Energy integrations, with Bar Card, Apex Charts and more to get a really great looking dashboard. I’ll do a deeper dive on how its all possible soon.


    Live PV Overview Using Bar Card

    Screenshot 2025-09-01 at 17.36.56

    At the top of this section, I use a compact bar-card layout to display:

    • PV Power: current output from my solar inverter
    • PV Max: the peak output so far today
    • Income: estimated real-time value in £/hour based on grid interaction

    These are not standard stat cards. They are styled bar-card elements, which give me better layout control and a consistent visual design.

    The live PV data (including PV Power and Max) comes from my GivEnergy system, using the excellent GivTCP integration. GivTCP is a local MQTT-based integration that makes real-time data from both the GivEnergy battery and inverter available in Home Assistant. It is fast, reliable, and fully local, which is ideal for anyone running GivEnergy hardware.

    The income figure is calculated using a template sensor that combines live import/export power with live tariff rates from the Octopus Energy integration. This gives a real-time estimate of financial benefit, whether from exported power or avoided import during peak rates. It updates every few seconds and is one of the most useful numbers on the dashboard. The bar card also dynamically changes colour, red for import, green for export.


    Solar PV Forecast vs Actual Chart

    Screenshot 2025-09-01 at 17.36.56

    This section is powered by ApexCharts Card and visualises:

    • Solcast Forecast (grey)
    • 10% Confidence Range (dark grey)
    • Actual PV Today (orange)
    • PV from Yesterday (white dotted line)

    It provides a live comparison throughout the day. I can instantly see whether production is tracking above or below expectations, and by how much.

    The forecast data is retrieved via Predbat. Predbat serves as the bridge between Solcast and Home Assistant, pulling in high-resolution forecasts and exposing them as sensors. I then feed these directly into the chart.


    5-Day Forecast Using Bar Card

    Screenshot 2025-09-01 at 17.36.56

    Just below the chart, I show a 5-day solar forecast using bar-card again. This includes:

    • The Forecast for today and the next 4 days
    • Colour-coded bars based on expected total generation

    Each bar represents total energy (kWh) for the day, with dynamic colouring applied using templates. Lower days show in red, wheras higher days in orange. I’ve configured these in such a way that it uses entities for max PV values (35 kWh in my case) as well as my "threshold" for minimum (around your average use). This enables the dynamic colour coding of the bars.

    If you want to replicate this setup, I’ve created a full walkthrough: PV Card Preview GitHub Repo

    The guide includes:

    • How to access and format forecast data from Predbat
    • Template examples for value display and colour thresholds
    • Complete YAML for the 5-day bar-card layout

    Forecast vs Reality Delta

    Screenshot 2025-09-01 at 17.36.56

    At the bottom of this section, I include a summary of:

    • Forecasted PV power for right now
    • Forecasted PV energy for right now
    • Percentage difference between the forecast and reality

    For example, as of this image I am 38% up on the forecasted PV energy generation today. These numbers dynamically change as the hours go during the day, giving me a strong signal for how much better or worse reality is compared to the prediction.

  • Monitoring Predbat Plan Generation Times in Home Assistant

    Predbat from springfall2008 is a fantastic custom integration for Home Assistant that intelligently manages solar battery charging and discharging based on predicted solar generation and energy tariffs. It’s especially helpful for optimising when to charge from the grid or discharge to avoid peak tariffs, and it’s saved me a decent amount on my energy bill.

    chart showing predbat on times

    But like any planning tool, Predbat needs time to generate its plan. Most of the time it does this quickly – but occasionally it can take a little longer. I wanted a way to visualise and track how long Predbat was taking to generate its plan each time it ran.

    Why track Predbat’s ON time?

    The provided switch.predbat_active entity goes into the on state when Predbat is generating a new plan. Once complete, it switches back to off. By measuring how long it remains ON, I can catch cases where Predbat is taking longer than expected.

    If it ever takes more than 5 minutes, I want to know – either through a notification or visually via my dashboard.

    Step 1: Creating the Sensor

    I used a trigger-based template sensor that calculates the duration in minutes that the switch.predbat_active was ON.

    Here’s the YAML for the sensor:

    - trigger:
        - platform: state
          entity_id: switch.predbat_active
          from: 'on'
          to: 'off'
      sensor:
        - name: Predbat Last ON Duration
          unique_id: predbat_last_on_duration
          state: >
            {{ (as_timestamp(now()) - as_timestamp(trigger.from_state.last_changed)) / 60 }}
          unit_of_measurement: "minutes"
          icon: mdi:timer

    This sensor will only update when the switch turns OFF. It records how many minutes it was ON – which is effectively how long Predbat took to generate its plan.

    Step 2: Visualising ON Durations in ApexCharts

    To make this information more useful, I plotted it using the excellent ApexCharts card for Home Assistant.

    Here’s the chart YAML I used:

    type: custom:apexcharts-card
    graph_span: 24h
    header:
      show: true
      title: Predbat ON Durations
      show_states: true
      colorize_states: true
    series:
      - entity: sensor.predbat_last_on_duration
        type: column
    apex_config:
      yaxis:
        min: 0
        max: 6
      annotations:
        yaxis:
          - "y": 5
            borderColor: "#FF0000"
            strokeDashArray: 1

    This renders a neat column chart of how long each plan generation took. The red annotation line at 5 minutes gives a clear visual cue if something is taking too long.

    Notifications and Automations

    Now that we have this entity, its a simple job to create a notification if the ON time is longer than my desired 5 minutes. Just use the new entity to trigger a notification to home assistant or your mobile device.

  • Building a Hourly Rain Forecast Chart in Home Assistant

    I’ve been playing around with ways to better visualise upcoming rainfall. The main weather integrations in Home Assistant often focus on current conditions or broad daily forecasts, but I wanted something more granular.

    Chart showing hourly rainfall

    Specifically, I wanted to see hourly rainfall predictions for the next 24 hours in a compact, clean, and useful format. Ideal for deciding if I need to bring the washing in or delay watering the garden. Here’s how I set it up.

    Step 1: Create the Input Text Helper (YAML)

    First, we need a place to store the hourly rainfall values. I created a simple input_text helper in configuration.yaml. This holds a JSON string that we can read later in the chart.

    Here’s the code to add:

    input_text:
      hourly_forecast_json:
        name: Hourly Forecast JSON
        initial: ""
        max: 255

    Note: The default limit is 255 characters, which fits roughly 24 hourly values. If you want to expand this for a longer range, you can increase the max value — for example, set max: 1024 for future-proofing.

    Once added, restart Home Assistant to activate the helper.

    Step 2: Pull the Hourly Forecast Data

    Next, I created an automation that fetches the next 24 hours of hourly weather data from my forecast provider. It extracts just the precipitation values and stores them as a JSON array in the helper.

    This runs every 5 minutes to keep the data fresh:

    alias: Update Hourly Forecast
    description: ""
    trigger:
      - platform: time_pattern
        minutes: "/5"
    action:
      - service: weather.get_forecasts
        data:
          type: hourly
        target:
          entity_id: weather.my_forecast_provider
        response_variable: forecast_data
    
      - service: input_text.set_value
        data:
          entity_id: input_text.hourly_forecast_json
          value: >
            {{
              forecast_data['weather.my_forecast_provider'].forecast[:24]
              | map(attribute='precipitation')
              | list
              | to_json
            }}
    mode: single

    This gives you a neat string like [0.0, 0.2, 0.4, 0.0, ...] representing each hour’s predicted rainfall in millimetres.

    Step 3: Display the Chart with ApexCharts

    I wanted the final chart to be small, clean, and focused. Using apexcharts-card, I built a column chart that only shows hours where rain is expected — zeroes are filtered out to reduce clutter.

    Here’s the YAML for the card:

    - type: custom:apexcharts-card
      header:
        title: 24h Precipitation Forecast
        show: true
      graph_span: 24h
      span:
        start: hour
      yaxis:
        - min: 0
          decimals: 2
      series:
        - entity: sun.sun  # dummy entity to enable chart
          name: Rain (mm)
          type: column
          color: rgb(76, 166, 238)
          data_generator: |
            try {
              const raw = hass.states['input_text.hourly_forecast_json']?.state;
              if (!raw || !raw.startsWith("[")) return [];
              const data = JSON.parse(raw);
              if (!Array.isArray(data)) return [];
              const now = new Date();
              now.setMinutes(0, 0, 0);  // align to hour start
              return data.reduce((acc, val, i) => {
                if (val !== 0) {
                  const time = new Date(now.getTime() + i * 3600 * 1000).getTime();
                  acc.push([time, val]);
                }
                return acc;
              }, []);
            } catch (e) {
              console.error("ApexCharts data_generator error:", e);
              return [];
            }
      apex_config:
        chart:
          height: 200px
        tooltip:
          enabled: false
        plotOptions:
          bar:
            columnWidth: 14
            borderRadius: 1
        stroke:
          width: 1
          colors:
            - "#FFFFFF"
        dataLabels:
          offsetY: -10
          style:
            fontSize: 9
        xaxis:
          labels:
            hideOverlappingLabels: false
            rotate: 90
            show: true
            style:
              fontSize: 9
            format: HH
        yaxis:
          show: false
        legend:
          show: false

    This chart gives a very quick heads-up on upcoming rain intensity. Because it only shows hours with rain (zero values are filtered out), it’s minimal and takes up little space. It works well on both mobile and wall-mounted dashboards.

    More importantly, it pulls real hourly forecast data rather than estimates or summaries. You get the precise precipitation expected in millimetres for each hour. Handy if you’re deciding whether to pause a garden irrigation schedule or go out for a walk.