ApexCharts card - A highly customizable graph card

Hi,

I wonder if anyone can help. I have an entity with a bunch of attributes that’s retrieved from my electricity providor. Its an array which contains my consumption every half hour. Example output is below from the attribute.

Is there any way I can graph this using ApexCharts? I have tried and can’t work out what I would need to do, can I use both the consumption as the y axis and the interval start/end as the x axis?

Thank you in advance!

Craig

Results

  • consumption: 0.097 interval_start: ‘2021-08-04T00:00:00+01:00’ interval_end: ‘2021-08-04T00:30:00+01:00’ - consumption: 0.08 interval_start: ‘2021-08-03T23:30:00+01:00’ interval_end: ‘2021-08-04T00:00:00+01:00’ - consumption: 0.139 interval_start: ‘2021-08-03T23:00:00+01:00’ interval_end: ‘2021-08-03T23:30:00+01:00’ - consumption: 0.176 interval_start: ‘2021-08-03T22:30:00+01:00’ interval_end: ‘2021-08-03T23:00:00+01:00’ - consumption: 0.167

I have a goal of displaying external data so that I don’t need to include my weather data into homeassistant (and can have a long term graph) and today we succeeded in doing so with a custom web server. But I do get interesting results displayed.

image
image

Using (almost) the same data via mqtt sensor works like it should
image

This is the complete code of the data generator card:

type: custom:apexcharts-card
graph_span: 1d
hours_12: false
header:
  show: true
  title: Innentemperatur
  show_states: true
  colorize_states: true
  standard_format: false
now:
  show: true
all_series_config:
  type: area
  stroke_width: 2
  opacity: 0.5
  unit: °C
  show:
    legend_value: false
series:
  - entity: sensor.time
    name: Wohnzimmer
    data_generator: |
      function makeRequest(method, url) {
          return new Promise(function (resolve, reject) {
              let xhr = new XMLHttpRequest();
              xhr.open(method, url);
              xhr.onload = function () {
                  if (this.status >= 200 && this.status < 300) {
                      resolve(xhr.response);
                  } else {
                      reject({
                          status: this.status,
                          statusText: xhr.statusText
                      });
                  }
              };
              xhr.onerror = function () {
                  reject({
                      status: this.status,
                      statusText: xhr.statusText
                  });
              };
              xhr.send();
          });
      }
      const request = async () => {
        var http = new XMLHttpRequest();
        var response = await makeRequest('GET', 'https://kuroi1992.dyndns.pro/test?type=temperature&location=inside&name=wohnzimmer&time=24h');
        console.debug(response);
        var json = JSON.parse(response);
        console.debug(json);
        return json.map(x => {
              return [x['time'], x['value']];
          });
      } 
      return request();
  - entity: sensor.time
    name: Schlafzimmer
    data_generator: |
      function makeRequest(method, url) {
          return new Promise(function (resolve, reject) {
              let xhr = new XMLHttpRequest();
              xhr.open(method, url);
              xhr.onload = function () {
                  if (this.status >= 200 && this.status < 300) {
                      resolve(xhr.response);
                  } else {
                      reject({
                          status: this.status,
                          statusText: xhr.statusText
                      });
                  }
              };
              xhr.onerror = function () {
                  reject({
                      status: this.status,
                      statusText: xhr.statusText
                  });
              };
              xhr.send();
          });
      }
      const request = async () => {
        var http = new XMLHttpRequest();
        var response = await makeRequest('GET', 'https://kuroi1992.dyndns.pro/test?type=temperature&location=inside&name=schlafzimmer&time=24h');
        console.debug(response);
        var json = JSON.parse(response);
        console.debug(json);
        return json.map(x => {
              return [x['time'], x['value']];
          });
      } 
      return request();
  - entity: sensor.time
    name: Keller
    data_generator: |
      function makeRequest(method, url) {
          return new Promise(function (resolve, reject) {
              let xhr = new XMLHttpRequest();
              xhr.open(method, url);
              xhr.onload = function () {
                  if (this.status >= 200 && this.status < 300) {
                      resolve(xhr.response);
                  } else {
                      reject({
                          status: this.status,
                          statusText: xhr.statusText
                      });
                  }
              };
              xhr.onerror = function () {
                  reject({
                      status: this.status,
                      statusText: xhr.statusText
                  });
              };
              xhr.send();
          });
      }
      const request = async () => {
        var http = new XMLHttpRequest();
        var response = await makeRequest('GET', 'https://kuroi1992.dyndns.pro/test?type=temperature&location=inside&name=keller&time=24h');
        console.debug(response);
        var json = JSON.parse(response);
        console.debug(json);
        return json.map(x => {
              return [x['time'], x['value']];
          });
      } 
      return request();
apex_config:
  markers:
    hover:
      size: 5

Before anyone suggests it, yes, the same problem happens even if I use the sensors corresponding to the temperature sensor, I just replaced them with sensor.time because in the long run I want to run completely via data generator and I needed to test if it works with any random sensor.

I have another graph using the same code and web backend with similar temperatures and on thet one it displays correctly so far.

image

The webserver used for testing is not online most of the time, so if anyone tries to test the example ot, it most likely won’t work.

Anyone got any idea how to force the curves to display correctly? I did try to manually set a minimum for the yaxis, but that only moved the yaxis, but still didn’t render the curves down to the axis.

1 Like

I just want to say that I absolutely love this card. I struggled a lot trying to get the mini-graph-card to do what I wanted and ended up only partially succeeding, while it only took about 30mins to get this working 100%. Between this and the custom button card, my dashboard is about 90% RomRider-powered :slight_smile: .

Is there any way to extend a radialBar margins out? I am getting a small radial which isn’t taking up a lot of space, would love if I could make it bigger? I am using a layout-card and giving it almost 70% of the width but the graph itself hardly uses any of it. If I add headers they extend to the full width but the graph does not.

I’m not sure of the ‘proper’ way to do this but I found an inelegant workaround for the climate entity using this config:

type: custom:apexcharts-card
graph_span: 10h
config_templates: standard_chart
header:
  title: ApexCharts-Card
series:
  - entity: climate.bedroom_2
    attribute: current_temperature
    group_by:
      func: max
      duration: 5min
  - entity: climate.bedroom_2
    attribute: temperature
    curve: stepline
  - entity: climate.bedroom_2
    attribute: hvac_action
    transform: 'return x === ''heating'' ? entity.attributes.current_temperature : null;'
    curve: stepline
    type: area
    opacity: 0.5

You would have to replace ‘heating’ with whatever hvac mode you are tracking.

3 Likes

Thank you for this wonderful card!

I have five two challenges with this card showing our TV consumption over the past week:

  • The x-axis doesn’t show a label for the first column. This is independent of the data range, so if I only pick 6 days instead of 7, the first column is still without label. Am I doing something wrong or should I report this as a bug?

  • I would like to change the color of the data labels to the normal text color used in the rest of the chart, e.g. for the annotation of the x-axis

  • I would like the data labels to not show hours in decimals, but show hours and minutes like the tooltip data does

  • I would like to change the tooltip data formatting to only show hours and minutes, not seconds.

  • The tooltip somehow seems to be offset. In the example, when I hover over the 3.9h column, it highlights that column but puts a vertical line on the 0.4h column and shows the 21m data from Aug 8 in the tooltip. Something is wrong here.

Any help is appreciated! Here is my configuration (updated):

type: custom:apexcharts-card
graph_span: 1w
span:
  end: day
yaxis:
  - id: tv
    show: false
series:
  - entity: sensor.watched_tv_today
    type: column
    group_by:
      func: max
      duration: 1d
    show:
      as_duration: hour
      datalabels: true
    yaxis_id: tv
apex_config:
  grid:
    show: false
  dataLabels:
    formatter: |
      EVAL:function(value, opts) {
        h = Math.floor(value);
        m = Math.floor((value - h) * 60);
        result = "";
        if (h > 0)
          result = h + "h ";
        if (m > 0)
          result += m + "m";
        return result;
      }
    offsetY: -10
    background:
      enabled: false
    style:
      colors: ['var(--primary-text-color)']
  tooltip:
    enabled: false

New chart with updated config. I’ve disabled the tooltip but this isn’t fixed.

3 Likes

I just learnt that this is a bug of the Apex library itself: First date is missing from graph · Issue #198 · RomRider/apexcharts-card · GitHub

I also just learnt that this is another bug in the underlying library. Too bad: Hovering offset in minimal layout · Issue #147 · RomRider/apexcharts-card · GitHub

Is it possible to dynamically set the tickAmount for the yaxis depending on the data?

Same problem. You solve it?
Thanks

Yup, I used a group_by option to make the datapoints exist at same instances of time:

group_by:
    func: avg
    duration: 1min
    fill: last

form me not working. only one value on tooltip.

Incredible! Works very well - thank you.

1 Like

Another question on your code/implementation. It appears the Apex Charts omits some data compared to the standard history graph. See attached picture. The only thing I see tell visually is that the filled in areas below (when the HVAC action is ‘cooling’) appear thinner on the Apex Charts and maybe that’s why the shorter time-spans are getting cut out? Rounding error or something?

I don’t seem to be able to replicate that unfortunately. Try experimenting with the group_by: settings or remove that section altogether and see how you go.

Is there a way to only show the hours or minutes below the card? Also in the circle itself it shows % instead of hours, any way to change this? Thx

Hey guys, is there a way to overlay a single big number (Say current value of one of the series?). Would want to tinker with transparency etc.

I had a look through the thread but couldn’t spot anything? Ta.

image

1 Like

A little question that should probably be simple but I can’t seem to figure it out – if I have a pie/donut chart with a single entity source using percentage (in this case a battery sensor), how do I get the chart to render so the pie/donut shape reflects that percentage value?

For example, below I have a battery sensor entity with 75%, and the 75% value is displayed correctly in some text places on the chart, but the pie itself always shows a complete 100% chart.

Thanks.

image

I haven’t gone in to how you would actually do this but a pie chart by design will show various values as a percentage of the total. If you only give it one value that value will always be 100% of the total no matter what the absolute reading is. I am assuming that you would have to provide two values eg, amount_full=75, amount_empty=25 or amount_empty=100 - amount_full.

Probably best to ask this on GitHub.