This repository is a custom fork of apexcharts-card by @RomRider for Home Assistant, focused on visualizing Zonneplan electricity prices and dynamic energy tariffs for Home Assistant users in the Netherlands. It adds interactive features such as drag-to-pan, viewport persistence, per-bar price color thresholds, and compact hour-based tooltips—making it easier to identify cheap and expensive hours at a glance.
This project is an independent fork and is not affiliated with Zonneplan or the upstream ApexCharts Card project.
All original apexcharts-card functionality is 100% preserved. You can use this fork exactly as you would the upstream version—new features are optional and only enabled when explicitly configured.
Mobile-friendly advantage: Unlike Plotly-based Zonneplan charts, ApexCharts' touch interactions and tooltips work reliably on mobile devices—no hover-state limitations.
- Use cases
- Installation
- Required integration
- Setup
- Examples
- Fork-only features
- Feature Documentation
- Status / Known limitations
- Compatibility
- Upstream documentation
- License
- Visualize Zonneplan electricity prices per hour in Home Assistant
- Identify the cheapest and most expensive tariff windows
- Explore 24–48h energy price forecasts interactively
- Optimize appliance usage based on dynamic electricity pricing
Download: Get apexcharts-card.js from the Releases page or build from source with npm run build (output in dist/apexcharts-card.js).
- HACS → Frontend → ⋮ → Custom repositories
- Add:
https://github.com/<you>/<repo> - Category: Lovelace
- Install → Restart Home Assistant
- Add resource:
/hacsfiles/<repo-name>/apexcharts-card.js(type: module)
- Copy
apexcharts-card.jsto/config/www/(so it becomes/local/apexcharts-card.js) - Add Lovelace resource:
/local/apexcharts-card.js(type: module) - Restart Home Assistant (or reload resources)
- Zonneplan One – provides
sensor.zonneplan_current_electricity_tariffwith forecast data.
- Install Zonneplan One and confirm the tariff sensor exists.
- Paste one of the examples below into a Manual card and adjust entity IDs/colors.
type: custom:apexcharts-card
graph_span: 48h
span:
start: day
cache: false
stacked: false
now:
show: true
label: Nu
header:
show: true
title: Elektriciteitsprijzen
show_states: true
colorize_states: true
apex_config:
chart:
height: 250
series:
- entity: sensor.zonneplan_current_electricity_tariff
type: column
name: Prognose
unit: ct/kWh
float_precision: 1
show:
in_chart: true
in_header: false
color_thresholds:
- lt: 20
color: "#00a3a9"
- lt: 27
color: "#00a964"
- color: "#ed5e18"
tooltip_template: "{day} {h1}-{h2}: <b>{value:.1f}</b> {unit}"
data_generator: |
const forecast = entity.attributes.forecast || [];
return forecast.map((r) => [new Date(r.datetime).getTime(), (r.electricity_price / 10000000) * 100]);
- entity: sensor.zonneplan_current_electricity_tariff
name: Huidige prijs
unit: ct/kWh
float_precision: 1
show:
in_chart: false
in_header: true
transform: return x * 100;
- entity: sensor.zonneplan_current_electricity_tariff
name: Goedkoopste uur
unit: ct/kWh
float_precision: 1
show:
in_chart: false
in_header: true
header_color: "#00a3a9"
data_generator: |
const forecast = entity.attributes.forecast || [];
if (forecast.length === 0) return [[Date.now(), 0]];
let minItem = forecast[0];
forecast.forEach((r) => {
if ((r.electricity_price / 10000000) * 100 < (minItem.electricity_price / 10000000) * 100) {
minItem = r;
}
});
return [[Date.now(), (minItem.electricity_price / 10000000) * 100]];
attribute: last_updated
- entity: sensor.zonneplan_current_electricity_tariff
name: Duurste uur
unit: ct/kWh
float_precision: 1
show:
in_chart: false
in_header: true
header_color: "#ed5e18"
data_generator: |
const forecast = entity.attributes.forecast || [];
if (forecast.length === 0) return [[Date.now(), 0]];
let maxItem = forecast[0];
forecast.forEach((r) => {
if ((r.electricity_price / 10000000) * 100 > (maxItem.electricity_price / 10000000) * 100) {
maxItem = r;
}
});
return [[Date.now(), (maxItem.electricity_price / 10000000) * 100]];
yaxis:
- id: main
min: auto
min_padding: 1type: custom:apexcharts-card
graph_span: 24h
series:
- entity: sensor.zonneplan_current_electricity_tariff
type: column
data_generator: |
const forecast = entity.attributes.forecast || [];
return forecast.map((r) => [new Date(r.datetime).getTime(), (r.electricity_price / 10000000) * 100]);
interaction:
drag_pan: true
persist_view: true
persist_view_storage: localStorage
reset_on_doubleclick: true
overscroll:
mode: soft
factor: 1.5type: custom:apexcharts-card
graph_span: 24h
series:
- entity: sensor.zonneplan_current_electricity_tariff
type: column
unit: ct/kWh
color_thresholds:
- lt: 15
color: "#00a964"
- lt: 25
color: "#f59e0b"
- color: "#ef4444"
tooltip_template: "{day} {h1}-{h2}: <b>{value:.1f}</b> {unit}"
data_generator: |
const forecast = entity.attributes.forecast || [];
return forecast.map((r) => [new Date(r.datetime).getTime(), (r.electricity_price / 10000000) * 100]);
yaxis:
- min: auto
min_padding: 0.5The following configuration options are added by this fork and are not available in upstream apexcharts-card:
interaction.drag_paninteraction.persist_viewinteraction.persist_view_storageinteraction.reset_on_doubleclickinteraction.view_idinteraction.overscroll.*series.color_thresholdsseries.tooltip_templateyaxis.min_paddingseries.show.header_color
Drag horizontally to scroll through forecasts. Optionally remember scroll position across page reloads.
interaction:
drag_pan: true
persist_view: true
persist_view_storage: localStorage # or "memory" (default)
reset_on_doubleclick: true # double-click resets to default view
view_id: "my-chart" # optional: stable storage key
overscroll:
mode: soft # or "none" (strict) / "infinite" (unlimited)
factor: 1.5 # soft mode: allows ±1.5× window beyond dataUse case: Zonneplan forecasts often span 48h; drag to explore cheapest/expensive hours.
Color individual bars based on their value, without needing multiple series.
series:
- entity: sensor.zonneplan_current_electricity_tariff
type: column
color_thresholds:
- lt: 20
color: "#00a964" # ≤20 ct/kWh: green (cheap)
- lt: 30
color: "#f59e0b" # 20–30: amber
- color: "#ef4444" # >30: red (expensive)Comparators: lt (less than) | lte | gt | gte | none (fallback color).
Use case: Quickly spot cheap/expensive tariff windows in a single column.
Compact, formatted tooltips with placeholders.
series:
- entity: sensor.zonneplan_current_electricity_tariff
tooltip_template: "{day} {h1}-{h2}: <b>{value:.1f}</b> {unit}"Available Placeholders
| Placeholder | Description | Example Output |
|---|---|---|
{value} |
Raw numeric value | 23.456 |
{value:.1f} |
Value with 1 decimal | 23.5 |
{value:.2f} |
Value with 2 decimals | 23.46 |
{unit} |
Series unit | ct/kWh, W, kWh |
{day} |
Weekday (short) | Ma, Di, Wo |
{h1} |
Hour start | 14:00 |
{h2} |
Hour end | 15:00 |
{time} |
Full time (HH:MM:SS) | 14:23:45 |
{x} |
Raw x-axis timestamp | 1704294225000 |
Example Tooltip Templates
Energy tariff (hour range):
tooltip_template: "{day} {h1}-{h2}: <b>{value:.1f}</b> {unit}"
# Output: "Ma 14:00-15:00: 23.5 ct/kWh"Solar power (precise time):
tooltip_template: "Today {time}: <b>{value:.1f}</b> {unit}"
# Output: "Today 14:23:45: 3.2 kW"Simple value display:
tooltip_template: "<b>{value:.2f}</b> {unit}"
# Output: "23.46 ct/kWh"Use case: Show tariff hour-by-hour clearly or display solar production with precise timestamps.
Add headroom below the computed minimum value.
yaxis:
- min: auto
min_padding: 1 # add 1 unit of padding below minNote: Ignored if min is explicitly set (not auto).
Use case: Prevent bars from touching the bottom edge when minimum values are small.
Override the header text color for individual series.
series:
- entity: sensor.zonneplan_current_electricity_tariff
show:
in_chart: false
in_header: true
header_color: "#00a964" # force green, even if colorize_states is onUse case: Highlight key series (e.g., cheapest hour in green, most expensive in red).
This is a personal fork that targets a specific Zonneplan use case. While it is working for day-to-day usage, some parts may still be rough around the edges and you may encounter minor issues.
Status: Fixed in current version via chart destruction and rebuild when stacked config changes.
Previously, bars would occasionally render stacked vertically on data updates due to ApexCharts internal render state corruption. The fix detects when the stacked configuration changes and fully destroys/recreates the chart instance with the correct configuration, ensuring a clean render state.
Verified: Tested extensively (40+ reloads with cache clears) with no recurrence.
On mobile devices, when rapidly tapping between bars, the tooltip may show a stale value from the previously selected bar instead of updating to the current selection.
Workarounds:
- Tap deliberately with brief pauses between selections to allow the tooltip to update
- Tap elsewhere on the dashboard to dismiss the tooltip, then tap the new bar (forces full refresh)
Technical details: This appears to be an ApexCharts internal behavior where the tooltip callback isn't invoked on rapid successive touch events—it gets debounced or cached. Attempted fixes:
followCursor: false– reduces cursor tracking overheadhideDelay/showDelayadjustments – no improvement- Custom tooltip refresh hooks – limited by ApexCharts API
The issue is under investigation. If you find a solution or workaround, please open an issue or PR.
This fork is based on upstream apexcharts-card and will likely not track upstream changes immediately. If you update Home Assistant or ApexCharts Card-related dependencies and something breaks, please open an issue on this repository (not upstream) and include your YAML and browser console logs.
Fork base: upstream apexcharts-card @ 6d3f1e9
For all standard options and comprehensive usage, see the original project: https://github.com/RomRider/apexcharts-card
This project is licensed under the MIT License.
Based on the original ApexCharts Card by Jérôme Wiedemann.
