ApexCharts first chart

I’ve been trying for two days to get my first chart to work. It’s trivial so I don’t understand why it’s not working. The chart displays no data. The data array is exactly like the sample chart - datetime and a value. 47 entries in the array, one for each 30 minute increment from 0:00 to 23:30. It’s tomorrow’s solar forecast from Solcast.

type: custom:apexcharts-card
header:
  show: true
  title: PV Forecast for Tomorrow
  show_states: true
  colorize_states: true
series:
  - entity: sensor.solcast_pv_forecast_forecast_tomorrow
    type: column
    data_generator: |
      const forecastData = entity.attributes.detailedForecast;
      const data = [];
      forecastData.forEach(forecast => {
        const periodStart = forecast.period_start;
        const pvEstimate = forecast.pv_estimate;
        const timestamp = new Date(periodStart).getTime();
        data.push([timestamp, pvEstimate]);
      });
      return data;

Have a look here
ApexCharts card - A highly customizable graph card - Share your Projects! / Dashboards & Frontend - Home Assistant Community

Yup, Apex Charts are brilliant, but if you don’t have the settings exactly right, nothing shows up.

The graph defaults to showing entity history, so to show anything else we use a data generator. I am assuming that your data generator is working correctly.

After that, the issue is that a graph display is a window into time, and the graphs default to show ‘today’ and work from ‘now’. Whatever you plot will get plotted, but if you only plot data for tomorrow and you graph is only showing today, you get a blank graph.

The settings you probably require are
graph_span and span

These control how much time (span width) the graph shows, and at what point the start is anchored down to (start and offset). Here are my settings for my own (processed data array) forecast of three days - I need to get span to show three days, and to anchor into tomorrow, otherwise tomorrow does not show.

Hope that helps!

This is gold. Thank you. I’ll give it a try immediately.

I suspected something like that since the starting time was always the next hour, so I assumed it was starting 24 hours ago. i didn’t know how to change the start and end times.

Where does the kwh number come from? it doesn’t automatically sum the values from the data? I’d like to show just the daylight hours. I can shift the start time to 7 AM with ‘graph_span: 17h’ but can I set the end time as well? The ‘end’ parameter only accepts day, hour, month, etc

I suspect my data_generator is stupidly overly complicated. What’s a more elegant way?

(I was a software engineer in a previous life, but with legacy programming languages. I’m brand new to javascript.)

  entity: sensor.solcast_pv_forecast_forecast_tomorrow

  data_generator: |
      const forecastData = entity.attributes.detailedForecast;
      const now = new Date();
      const data = [];
      forecastData.forEach(forecast => {
        const periodStart = forecast.period_start;
        const pvEstimate = forecast.pv_estimate;
        const timestamp = new Date(periodStart).getTime();
        data.push([timestamp, pvEstimate]);
      });
      return data;

The graph is plotted based on the entity you give it. Since the default for Apex Charts is to plot an entity historical data, that is the way it is set up.

When we want to plot something else, we need to create the data ourselves, however the graph still works on the basis that we are plotting the entity.

You gave the graph

and by default it will plot this series using the entity current state value and units in the header display.

There are lots of settings, and to turn off the header, or the series value, you will need to dig into the documentation.

There is a main thread on Apex Charts that has been going for ages, and lots of questions relating to the settings. It is complicated, but a bit of experimentation will show what turns on/off what.

The data generator is just some JavaScript that is executed (on your browser) at the point of displaying the graph. Worth noting that JS is the most used language in the World…

The data generator needs to return an array of [timestamp, value] (sub-array) pairs, and this is ‘usually’ done by mapping over an array.

    data_generator: |
      return entity.attributes.time.map((fchr, index) => {
        return [new Date(fchr).getTime(), entity.attributes.yesterday[index]];
      });

Note that, for the series, ‘entity’ in this code returns the entity you defined as the full entity object, hence entity.attributes.detailedForecast gets to the forecast data in your entity attribute.

In my code above, I have an attribute ‘time’ as an array of times, and a different attribute ‘yesterday’ as an array of matching values, so I have to map over the time-array, and reference the matching value in the ‘yesterday’ array.

I would have to see exactly what is in this array, but mapping over an array will offer, for each item in the array, the individual array item and the index, so for each item inside the { } we just need to return an array of timestamp, value pair.

Assuming your array contains a series of objects with “period_start” for the start time, and “pv_estimate” as the value, I think the following should work.

data_generator: |
  return entity.attributes.detailedForecast.map((item, index) => {
    return [new Date(item.period_start).getTime(), item.pv_estimate] })

although I may have messed up somewhere… and yes, the index is not used here so I don’t need to include that in the mapping function.

The data generator is documented with examples here

Your 3 lines replaces my 11 lines. Well done. Now I just need to understand how it works so I can write code like this.

It certainly takes time to understand a new code language. I was a systems programmer for a while, and have used machine code, assembler, FORTRAN, BASIC (interpreted and compiled) COBOL, c, Pascal and several proprietary languages (and dabbled with Lisp, Forth, BCPL). Now learning JavaScript, Python and a few others and yes they are different!

map(myFunction) is just an array method that iterates over the array and returns a new array where each item has the given function myFunction() applied to it. Normally we just pass the name of the function, and declare the function elsewhere.

function myFunction(arrayItem) { *statement* }

Note that when map calls the named function for each item it passes the individual item value (and optionally the index and the full array) to the function. A very powerful tool for working with arrays.

JS supports anonymous functions, and the shortest form is the arrow function. This removes the need for not only a name, but also the function keyword. All that is left is the () to represent a function, and the => code to define it.

() => { "hello world"; }

For a single line statement you don’t even need the {}

Since map() needs a function, we can place the arrow function directly as the function to be used, as long as we set at least the item parameter.

["one", "three", "sixteen"].map((eachWord) => eachWord.length)

which returns an array of the lengths of the words.

So, you just need to map over the original array of objects, and define a function that takes each array item object and returns an array of [time, value].

I hope this helps you (and anyone else who, like me, struggled to work out what was going on here…)

1 Like