Hiding null values in Apex Charts for Octopus rates

Hi, I’m trying to hide the line for the second entity while values are not available yet but I’m not being able to figure out how to do so. I’d also like for the x-axis to go as far as the first series and only extend to the second series once values are available. Is this possible? Thank you!

type: custom:apexcharts-card
header:
  show: true
  show_states: true
  colorize_states: true
  title: Upcoming Rates
graph_span: 1d
span:
  start: minute
apex_config:
  legend:
    show: false
yaxis:
  - min: auto
    max: auto
    decimals: 2
    apex_config:
      tickAmount: 12
series:
  - entity: >-
      event.octopus_energy_electricity_xxxx_xxxx_current_day_rates
    type: line
    name: Current Rate
    color: purple
    opacity: 1
    stroke_width: 3
    unit: p
    show:
      in_header: before_now
      legend_value: false
    data_generator: |
      return entity.attributes.rates.map((entry) => {
      return [new Date(entry.start), entry.value_inc_vat * 100];
      });
  - entity: event.octopus_energy_electricity_xxxx_xxxx_next_day_rates
    type: line
    name: Next Day Rates
    color: pink
    opacity: 1
    stroke_width: 3
    unit: p
    extend_to: false
    show:
      in_header: false
      legend_value: false
    data_generator: |
      return entity.attributes.rates.map((entry) => {
      return [new Date(entry.start), entry.value_inc_vat * 100];
      });

I think the simple answer is, you have your extend_to: false on the wrong series. And no, Apex Charts graphs do not plot null values.

At a guess you are plotting todays agile prices, in purple, and tomorrows agile prices (when they become available every afternoon) in pink, on the same graph. I only see, in your picture, purple and no pink.

Current (today) rates will apply from yesterday at 23:00 UTC to today at 23:00 UTC, and tomorrows rate will be visible from around 16:00 UTC today going up to 23:00 UTC tomorrow. With an Apex graph set as graph_span: 1d you will see a graph plot window, from the start of the current hour covering the next 24 hours. Hence what you actually see is very much a changing picture. For myself I prefer to have a graph spanning two full days, anchored at the start of today and showing today and tomorrow. If the prices for tomorrow are not yet available, then that part of the graph is blank. When tomorrow is available, I just show this as part of the same graph line (I use my own code in Node-RED to capture agile prices and to generate the graph data required).

Since the only time you need tomorrows data today is during 23:00 UTC to 00:00 UTC, you only need to show tomorrow today in this one hour, so perhaps two graphs, one for today and one for tomorrow, would be better. Today is always there, but tomorrow is only there from around 16:00, and only useable from 23:00.

Anyway, as for the line…

The bit of the purple line you are pointing out is probably due to the default behaviour of Apex graphs to extend any line plot to the end of the graph. This typically results in an horizontal line past the last data point, and can be turned off. I see that you already have extend_to: false in your pink (tomorrow) line, but not for your purple (today) line. Perhaps adding entend_to: false for the purple line is the simplest solution? [NOTE extend_to_end was depreciated, and extend_to only applies from v2]

I don’t have your data set, and I assume that today stops at 23:00 UTC today. However, if the data does indeed extend forwards with 0 values, then these data points can be excused from the plot, if required.

Apex charts plots data as an array of [time, value] pairs, which you are generating using JS in the data_generator. If the value is ‘null’ then it is not plotted - null being JS for nothing which is different to ‘0’.

You can either test for the value being 0, and substitute null (which will work expect on the rare occasion that the actual value is really zero) or you can test for time being > 23:00 UTC today, which is the end of the last current price period for today.

Something like (entry.value_inc_vat != 0) ? entry.value_inc_vat * 100 : null should work.

You can also try just truncating the generated array, however I find it better to maintain the full array with all timestamp entries, leaving anything I don’t want plotted as value null.

Another way of doing it is a bit of a trick. Just add another series, below this one, with values all 0 and set the plot colour to grey or black with a thicker line. The graph plots the series in the given order, overlaying each, and a black line at y=0 (ie the x-axis) just hides the purple one plotted behind it.

Dynamic x-axis

Adjusting the x-axis / graph span dynamically is more difficult. The card does not accept variables for these values. It can be done by enclosing the card inside a template configuration card, and computing the variables there. This then applies a textual replacement on the graph card before calling the Apex chart, but frankly I question if it is worth it. For me, if the right hand side of the graph is empty, then the new prices are clearly not yet there.

I just use

graph_span: 48h
span:
  start: day
  offset: "-1h"

which gives me all of today and tomorrow, starting today but back an hour (so 23:00 yesterday). Then I can see today, and if available, tomorrow all on the same graph.

1 Like

I’m using this to generate a view where it hides old data, and expands when new data arrives.

Right now, it’s 12:54 so it shows from 12:00 and to midnight.
In a few minutes it will hide all values from 12:00 to 13:00, and expand another day with tomorrows values.

type: custom:config-template-card
variables:
  span: states['sensor.getgraphspan'].state
  tomorrowData: states['sensor.nordpool_kwh_se4_sek_3_10_025'].attributes.raw_tomorrow
  offset: states['sensor.offset'].state
entities:
  - sensor.nordpool_kwh_se4_sek_3_10_025
card:
  type: custom:apexcharts-card
  graph_span: ${span}
  experimental:
    color_threshold: true
  span:
    start: day
    offset: ${offset}
  now:
    show: true
    color: black
    label: Nu
  series:
    - entity: sensor.nordpool_kwh_se4_sek_3_10_025
      type: column
      name: Prices
      color_threshold:
        - value: 0
          color: black
          opacity: 1
        - value: 0.5
          color: pink
        - value: 1
          color: red
      data_generator: |
        const todayData = entity.attributes.raw_today;
        const tomorrowData = entity.attributes.raw_tomorrow || [];

        const combinedData = [...todayData, ...tomorrowData];

        return combinedData.map((item) => {
          const date = new Date(item.start.slice(0, -6));

          return {
            x: date.getTime(),
            y: item.value,
          };
        });

The templates used in the code above is:

sensor.getgraphspan:

{% if state_attr('sensor.nordpool_kwh_se4_sek_3_10_025','raw_tomorrow') | count > 0 %}
{{ 48-now().hour }}h
{% else %}
{{ 24-now().hour }}h
{% endif %}

sensor.offset:

+{{ now().hour }}h

And it uses iantrich/config-template-card: :memo: Templatable Lovelace Configurations

EDIT:
With tomorrow values I get this:

1 Like

Hi, thank you for such a comprehensive reply.

You got it right. This graph is more agile price with purple being today and pink tomorrow. I find it that it helps me to visually break up the day. In my case, showing 2d is a bit much so 1d is about right for me.

I have moved the extend_to: false to the first entity, as per your recommendation, and this works. I’ve also added a offset: +0.5h to the second series so both graphs line up.

It’s a shame the x-axis can’t be a bit more dynamic but this will have to do for now.

Also, I can see in your screenshot two cheap/peak areas. How do you define these? I can see a few situation where something like this could be helpful!

Thanks again for taking the time to help.

Thank you for sharing your setup as well. This will definitely be useful sometime in the near future!

Apex graphs have many extra features. The apex_config option opens the opportunity for annotations such as these.

A horizontal line can be added as an entry on the yaxis. I add the current standard rate prices, but there is little opportunity to do this dynamically so it just uses a given literal. The label has background set to clear.

The vertical lines can be added as entries on the xaxis, and if two values are given then this becomes an area between them. The first value is x, and the second is x2.

Since the x-axis is always a timestamp plot using a data object, the values required must be computed so as to fall at the correct ‘time’ point. In this case, as I am plotting today - I need values for the JS date object for 16:00 and 19:00 today. This is done using the EVAL: feature, which sends the line to a JavaScript evaluation, returning the result.

EVAL: new Date().setHours(16,0,0) creates a new date object, defaulting to now (ie today), but changes the hour, minute, and seconds accordingly.

In the part of the graph that is ‘tomorrow’ the same times 16:00 and 19:00 have to be moved forwards by an extra day, or 86400000 milliseconds.

EVAL:new Date(Date.now()+86400000).setHours(16,0,0) manages to do this by first using Date.now() as a static method on the Date object to get the current Unix millisecond time, adding one day, then using this to create a new date object, and the setHour() method to set the hour, minute, and second accordingly.

The complexity here arises since the EVAL operation must accept a single line JavaScript expression, and in this case it is not possible to chain methods on the date object. At least it works.

And finally, if you like having the ‘now’ line shown on the graph, the use of the in-built now option stops all annotations from working, therefore it cannot be used here. It is easy enough to generate our own ‘now’. Again EVAL: is used, this time to obtain the current Unix millisecond time and to floor this down to the most recent half-hour period, hence it always shows against the current price period.

Enjoy.

apex_config:
  annotations:
    yaxis:
      - "y": 15
        borderColor: green
        label:
          text: EX
          borderWidth: 0
          style:
            background: "#0000"
      - "y": 23.16
        borderColor: red
        label:
          text: IN
          borderWidth: 0
          style:
            background: "#0000"
    xaxis:
      - x: EVAL:new Date().setHours(16,0,0)
        x2: EVAL:new Date().setHours(19,0,0)
        fillColor: "#FEB019"
        label:
          text: Peak
          borderWidth: 0
          style:
            background: "#0000"
      - x: EVAL:new Date().setHours(2,0,0)
        x2: EVAL:new Date().setHours(5,0,0)
        fillColor: "#B3F7CA"
        label:
          text: Cheap
          borderWidth: 0
          style:
            background: "#0000"
      - x: EVAL:new Date(Date.now()+86400000).setHours(16,0,0)
        x2: EVAL:new Date(Date.now()+86400000).setHours(19,0,0)
        fillColor: "#FEB019"
        label:
          text: Peak
          borderWidth: 0
          style:
            background: "#0000"
      - x: EVAL:new Date(Date.now()+86400000).setHours(2,0,0)
        x2: EVAL:new Date(Date.now()+86400000).setHours(5,0,0)
        fillColor: "#B3F7CA"
        label:
          text: Cheap
          borderWidth: 0
          style:
            background: "#0000"
      - x: EVAL:Math.floor(Date.now()/1800000)*1800000
        label:
          text: Now
          position: bottom
          borderWidth: 1
1 Like