šŸ“ 100% Templatable Lovelace Configurations

of course you don’t, since Apexcharts has a refresh parameter you can set (which is very nice. I’ve requested the same for other cards, unanswered though unfortunately).\

Note this is not a ā€˜trick’ at all. It is using native Frontend options only which makes it so robust.

More importantly: it is fully supported, so if something would be amiss, the core team would accept an issue tracker on that.

Having said all of that, I still have 1 config-template-card myself too I couldn’t not reasonably replace by a more robust core setup.

So I hear you :wink:

(btw, if you dont permanently stare at those graphs all day, the redrawing of the charts isnt that much of a problem isnt it?)

My config-template uses the history charts for selected domain entities, and tbh, I dont notice it flicker while the sensors are updated

      - type: custom:hui-element #extra entities to keep header while scrolling
        card_type: entities
        entities:
          - type: custom:config-template-card
            entities:
              - input_number.history_span
              - input_select.domain
            variables:
              span: states['input_number.history_span'].state
              domain: states['input_select.domain'].state
            card:
              type: custom:auto-entities
              card:
                type: history-graph
                hours_to_show: ${span}
              filter:
                include:
                  - domain: ${domain}

note I only have the inputs as variables and entities, and still the history-graph updates the individual entities.

No redrawing at all

as a matter of fact, playing with apexcharts now and some anchor pasting makes it even more adaptable, because we can set an update_interval depending on the graph_span easily and still keep the main card config identical

Ill hide it because it isnt a config-template-card
type: vertical-stack
cards:
  - type: conditional
    conditions:
      - condition: state
        entity: input_select.apex_span
        state: '15m'
    card:
      graph_span: 15m
      update_interval: 1min
      <<: &apex_config
        type: custom:apexcharts-card
        series:
          - entity: sensor.zp_actuele_opbrengst
            color: var(--power-color)
            type: area
            name: Zon
            stroke_width: 3
            opacity: 0.5
          - entity: sensor.bruto_stroom_verbruik
            color: darkblue
            name: Verbruik
            group_by:
              func: avg
              duration: 2min
            stroke_width: 1
          - entity: sensor.netto_verbruik
            color: lightblue
            type: area
            name: Vermogen
            group_by:
              func: avg
              duration: 2min
            stroke_width: 3
            opacity: 0.5
  - type: conditional
    conditions:
      - condition: state
        entity: input_select.apex_span
        state: '30m'
    card:
      graph_span: 30m
      update_interval: 1min
      <<: *apex_config
  - type: conditional
    conditions:
      - condition: state
        entity: input_select.apex_span
        state: '1h'
    card:
      graph_span: 1h
      update_interval: 15min
      <<: *apex_config
  - type: conditional
    conditions:
      - condition: state
        entity: input_select.apex_span
        state: '2h'
    card:
      graph_span: 2h
      update_interval: 30min
      <<: *apex_config
  - type: conditional
    conditions:
      - condition: state
        entity: input_select.apex_span
        state: '6h'
    card:
      graph_span: 6h
      update_interval: 1h
      <<: *apex_config
  - type: conditional
    conditions:
      - condition: state
        entity: input_select.apex_span
        state: '12h'
    card:
      graph_span: 12h
      update_interval: 1h
      <<: *apex_config
  - type: conditional
    conditions:
      - condition: state
        entity: input_select.apex_span
        state: '16h'
    card:
      graph_span: 16h
      update_interval: 1h
      <<: *apex_config
  - type: conditional
    conditions:
      - condition: state
        entity: input_select.apex_span
        state: '24h'
    card:
      graph_span: 24h
      update_interval: 1d
      <<: *apex_config
  - type: conditional
    conditions:
      - condition: state
        entity: input_select.apex_span
        state: '2d'
    card:
      graph_span: 2d
      update_interval: 1d
      <<: *apex_config

Probably, but it is in a small dashboard (that cleanly fits on my phone) and I don’t notice any performance issues. The many variables I use to dynamically change the chart title, series names, axis range and draw a lot of annotations. That allows me to visualise a lot of info while keeping the chart and dashboard very clean (at least to my taste :))

Yes, but the monitored entities do not change state very often, when they do it triggers a new annotation and the chart redraws very quickly, so in practice I do not see that (or it is not bothering me). And, most importantly, the entities used in the chart are NOT monitored.

Using a conditional chart to draw the same chart multiple times with a different option (and always show just one), is a nice idea (or trick :wink:), won’t allow me to get rid of the config-template-card because I change so many things I would need a crazy amount of versions of my chart (and have some functions I would probably have to put outside my chart YAML too) but I am sure I will find a use case for it one day.

Same here, the entities in my chart are not declared in the config-template part but update fine. I could clean up a little but my chart YAML is currently 400+ lines, sounds like a really bad idea but it works fine and I have no flickering, and I watch this chart a lot, monitoring the behaviour of my heatpump and the many automations I wrote to optimise how it works.

Oh, and off-topic but thanks, I learned a lot from your posts on badges and themes!

1 Like

Whenever I need those dynamic details I use a card or set of cards especially suited for that in a stack

Custom button-card does all that and serves as ā€˜header’ a vertical stack for example , while the chart can be untouched for those details and do what it was designed for

Use each card for its strengths per design is the best attitude for a robust frontend for me.

Only resort to hacky solutions if no other option left….

I hear you, and this is definitely not for everyone, but I like to make complex charts showing relations between different entities. In this chart I use the title to show which elements decide the state of the heating (e.g. when I know tomorrow will be sunny I lower the heating preemptively). That is reflected in the names of the series (they show target temperatures in the chart, the name shows whether is in comfort, normal, lower or auto). There are also several events I show with little symbols (xaxis annotations) such as when it switched to tapwater heating. Finally, I use annotations also to get the datetime labels on the brush to fall exactly at midnight and midday.

The title and series names could be shown in other ways (but take up a little more screen real estate). The axis annotations however are difficult to show otherwise and besides explaining what happens on the same timeline, I think they also look cool :grinning:

Crazy long YAML for the chart above
type: custom:config-template-card
variables:
  lastoil: states['sensor.last_oil_recirculation'].attributes.last_updated_ts * 1000
  previousoil: >-
    states['sensor.previous_oil_recirculation'].attributes.last_updated_ts *
    1000
  previousoil2: >-
    states['sensor.previous2_oil_recirculation'].attributes.last_updated_ts *
    1000
  previousoil3: >-
    states['sensor.previous3_oil_recirculation'].attributes.last_updated_ts *
    1000
  previousoil4: >-
    states['sensor.previous4_oil_recirculation'].attributes.last_updated_ts *
    1000
  lastdefrost: states['sensor.last_defrost'].attributes.last_updated_ts * 1000
  previousdefrost: states['sensor.previous_defrost'].attributes.last_updated_ts * 1000
  previousdefrost2: states['sensor.previous2_defrost'].attributes.last_updated_ts * 1000
  lastwarmwater: states['sensor.last_warm_water'].attributes.last_updated_ts * 1000
  previouswarmwater: states['sensor.previous_warm_water'].attributes.last_updated_ts * 1000
  test_unknown: |
    test => {
      if (isNaN(test)) {
        return 0;
      }
      return test
    }
  circuit1full: states['select.wh_heizkreis_betriebsart'].state
  circuit3full: states['select.wh_heizkreis3_betriebsart3'].state
  short: |
    full => {
      if (full == 'hz_operationmode_normal') {
        return 'normaal';
      }
      else if (full == 'hz_operationmode_lowering') {
        return 'verlaagd';
      }
      else if (full == 'hz_operationmode_comfort') {
        return 'comfort';
      }
      else if (full == 'hz_operationmode_automatic') {
        return 'auto';
      }
      else if (full == 'hz_operationmode_standby') {
        return 'standby';
      }
      return full;
    }
  test_zero: |
    testforzero => {
      if (testforzero == 0) {
        return 1;
      }
      return testforzero;
    }
  mid_span: new Date().setTime(new Date().getTime() - 4 * 3600 * 1000)
  tomorrowIs: states['input_text.tomorrow_is'].state
  choiceHPmode: states['input_text.choice_hp_mode'].state
  today: new Date().setHours(0,0,0,0)
  todayformatted: >-
    new Date(vars['today']).toLocaleDateString("nl-BE", {weekday:'short',
    day:'numeric', month:'long'})
  todaynoon: vars['today'] + 12 * 60 * 60 * 1000
  yesterday: vars['today'] - 24 * 60 * 60 * 1000
  yesterdayformatted: >-
    new Date(vars['yesterday']).toLocaleDateString("nl-BE", {weekday:'short',
    day:'numeric', month:'long'})
  yesterdaynoon: vars['today'] - 12 * 60 * 60 * 1000
  beforeyesterday: vars['today'] - 48 * 60 * 60 * 1000
  beforeyesterdayformatted: >-
    new Date(vars['beforeyesterday']).toLocaleDateString("nl-BE",
    {weekday:'short', day:'numeric', month:'long'})
  beforeyesterdaynoon: vars['today'] - 36 * 60 * 60 * 1000
  beforeyesterdaynoonlabel: |
    beforeyesterdaynoon => {
      if (new Date() - 43 * 60 * 60 * 1000 > beforeyesterdaynoon) {
        return "";
        }
      return "12u";
    }
  sys_mode: states['select.wh_system_system_operation_mode'].state
  temp_scale_offset: |
    sys_mode => {
      if (sys_mode == 'sys_operationmode_cooling') {
        return 0;
        }
      else if (sys_mode == 'sys_operationmode_summer') {
        return 2;
        }
      return 14;
    }
  color_red: states['input_select.color_red'].state
entities:
  - select.wh_heizkreis_betriebsart
  - sensor.last_oil_recirculation
  - sensor.last_defrost
  - sensor.last_warm_water
card:
  type: custom:apexcharts-card
  experimental:
    brush: true
    color_threshold: true
  apex_config:
    title:
      text: ${tomorrowIs + choiceHPmode}
      align: middle
      margin: 0
      offsetY: 7
      offsetX: -15
      style:
        fontSize: 11px
        fontWeight: bold
    grid:
      padding:
        left: -3
        right: 6
      yaxis:
        lines:
          show: false
    tooltip:
      shared: true
    chart:
      width: 387
      height: 325
    legend:
      position: top
      horizontalAlign: left
      itemMargin:
        horizontal: 3
    xaxis:
      tooltip:
        enabled: false
      axisBorder:
        show: false
      axisTicks:
        show: false
      labels:
        offsetY: 0
        datetimeFormatter:
          day: ddd
          hour: H\u
    annotations:
      xaxis:
        - x: ${ test_unknown(lastoil) }
          borderWidth: 0
          label:
            text: ā™»
            orientation: horizontal
            borderWidth: 0
            offsetX: 3
            offsetY: -8
            position: top
            textAnchor: middle
            style:
              background: transparent
        - x: ${ test_unknown(previousoil) }
          borderWidth: 0
          label:
            text: ā™»
            orientation: horizontal
            borderWidth: 0
            offsetX: 3
            offsetY: -8
            position: top
            textAnchor: middle
            style:
              background: transparent
        - x: ${ test_unknown(previousoil2) }
          borderWidth: 0
          label:
            text: ā™»
            orientation: horizontal
            borderWidth: 0
            offsetX: 3
            offsetY: -8
            position: top
            textAnchor: middle
            style:
              background: transparent
        - x: ${ test_unknown(previousoil3) }
          borderWidth: 0
          label:
            text: ā™»
            orientation: horizontal
            borderWidth: 0
            offsetX: 3
            offsetY: -8
            position: top
            textAnchor: middle
            style:
              background: transparent
        - x: ${ test_unknown(previousoil4) }
          borderWidth: 0
          label:
            text: ā™»
            orientation: horizontal
            borderWidth: 0
            offsetX: 3
            offsetY: -8
            position: top
            textAnchor: middle
            style:
              background: transparent
        - x: ${ test_unknown(lastdefrost) }
          borderWidth: 0
          label:
            text: ā„ļø
            orientation: horizontal
            borderWidth: 0
            offsetX: 3
            offsetY: -8
            position: top
            textAnchor: middle
            style:
              background: transparent
        - x: ${ test_unknown(previousdefrost) }
          borderWidth: 0
          label:
            text: ā„ļø
            orientation: horizontal
            borderWidth: 0
            offsetX: 3
            offsetY: -8
            position: top
            textAnchor: middle
            style:
              background: transparent
        - x: ${ test_unknown(previousdefrost2) }
          borderWidth: 0
          label:
            text: ā„ļø
            orientation: horizontal
            borderWidth: 0
            offsetX: 3
            offsetY: -8
            position: top
            textAnchor: middle
            style:
              background: transparent
        - x: ${ test_unknown(lastwarmwater) }
          borderWidth: 0
          label:
            text: šŸ›
            orientation: horizontal
            borderWidth: 0
            offsetX: 5
            offsetY: -8
            position: top
            textAnchor: middle
            style:
              background: transparent
        - x: ${ test_unknown(previouswarmwater) }
          borderWidth: 0
          label:
            text: šŸ›
            orientation: horizontal
            borderWidth: 0
            offsetX: 5
            offsetY: -8
            position: top
            textAnchor: middle
            style:
              background: transparent
  yaxis:
    - id: first
      decimals: 0
      min: ${ 8 + temp_scale_offset(sys_mode)}
      max: ${ 24 + temp_scale_offset(sys_mode)}
      apex_config:
        showalways: true
        tickAmount: 8
        labels:
          offsetX: -13
          formatter: "EVAL: function (value) {return value + '°C'}"
    - id: second
      opposite: true
      decimals: 0
      min: 0
      max: 200
      apex_config:
        tickAmount: 8
        labels:
          offsetX: 4
          align: right
          formatter: "EVAL: function (value) {return value + '%'}"
    - id: third
      show: false
      min: 0
      max: 40
      apex_config:
        tickAmount: 4
    - id: fourth
      show: false
      opposite: true
      decimals: 0
      min: 0
      max: 200
  hours_12: false
  span:
    offset: "-0h"
  graph_span: 48h
  brush:
    selection_span: 8h
    apex_config:
      chart:
        width: 360px
        height: 80px
      grid:
        padding:
          bottom: -5
          left: 0
        yaxis:
          lines:
            show: false
      xaxis:
        axisBorder:
          show: false
        axisTicks:
          show: false
        labels:
          show: false
          datetimeFormatter:
            day: ddd d MMMM
            hour: ddd d MMMM
          showDuplicates: false
      yaxis:
        min: 0
        max: 20
        axisTicks:
          show: false
        labels:
          offsetX: -10
          formatter: "EVAL: function (value) {return value + 'kW'}"
          style:
            fontSize: 10px
      annotations:
        xaxis:
          - x: ${ beforeyesterday }
            borderWidth: 0
            opacity: 0.2
            label:
              text: ${ beforeyesterdayformatted }
              orientation: horizontal
              borderWidth: 0
              offsetX: 0
              offsetY: 15
              position: bottom
              textAnchor: middle
              style:
                background: transparent
          - x: ${ yesterday }
            borderWidth: 0
            opacity: 0.2
            label:
              text: ${ yesterdayformatted }
              orientation: horizontal
              borderWidth: 0
              offsetX: 0
              offsetY: 15
              position: bottom
              textAnchor: middle
              style:
                background: transparent
          - x: ${ today }
            borderWidth: 0
            opacity: 0.2
            label:
              text: ${ todayformatted }
              orientation: horizontal
              borderWidth: 0
              offsetX: 0
              offsetY: 15
              position: bottom
              textAnchor: middle
              style:
                background: transparent
          - x: ${ beforeyesterdaynoon }
            borderWidth: 0
            label:
              text: 12u
              orientation: horizontal
              borderWidth: 0
              offsetX: 0
              offsetY: 15
              position: bottom
              textAnchor: middle
              style:
                background: transparent
          - x: ${ yesterdaynoon }
            borderWidth: 0
            label:
              text: 12u
              orientation: horizontal
              borderWidth: 0
              offsetX: 0
              offsetY: 15
              position: bottom
              textAnchor: middle
              style:
                background: transparent
          - x: ${ todaynoon }
            borderWidth: 0
            label:
              text: 12u
              orientation: horizontal
              borderWidth: 0
              offsetX: 0
              offsetY: 15
              position: bottom
              textAnchor: middle
              style:
                background: transparent
  update_interval: 30s
  series:
    - entity: sensor.gewenstetempkring3
      name: ${short(circuit3full)}
      yaxis_id: first
      curve: stepline
      fill_raw: last
      stroke_width: 0.5
      color: ${ color_red.split(' ')[1] }
    - entity: sensor.gewenstetempkring1
      name: ${short(circuit1full)}
      curve: stepline
      fill_raw: last
      yaxis_id: first
      stroke_width: 0.5
      color: CornflowerBlue
    - entity: sensor.wh_warmepumpe_weichentemperatur
      name: evenwicht
      curve: stepline
      fill_raw: last
      yaxis_id: first
      stroke_width: 0.5
      color: navy
    - entity: sensor.vermogenbeperking
      name: beperking
      curve: stepline
      fill_raw: last
      yaxis_id: fourth
      float_precision: 0
      stroke_width: 0.5
      stroke_dash: 1
      color: ${ color_red.split(' ')[1] }
    - entity: sensor.wh_warmepumpe_leistungsanforderung
      name: vermogen
      curve: stepline
      fill_raw: last
      yaxis_id: second
      stroke_width: 0.5
      color: CornflowerBlue
    - entity: sensor.vermogenafgifteinkw
      name: geleverd
      curve: stepline
      fill_raw: last
      yaxis_id: third
      stroke_width: 0.5
      color: navy
      opacity: 1
      show:
        in_brush: true
    - entity: sensor.wh_warmepumpe_vorlauftemperatur_prazise_summenvorlauf_b7
      name: vertrek
      curve: stepline
      fill_raw: last
      yaxis_id: first
      stroke_width: 0.25
      show:
        in_legend: false
      color: CornflowerBlue
1 Like

For the sake of this thread it would be nice if you threw in the yaml for that under a ā€˜hide’ tag

My memory says this is the most complex card it recalls :wink:

Great learner for everyone probably

Sure, happy to share (I added it to my previous post). But a word of warning, it is 470 lines long, some of those could / should be removed, some I keep only to remember the option exists etc

Thanks, I had no idea Yaml anchors existed (HA is my first foray into Yaml).
I did a quick read on the syntax - define a block with &NAME and then reference it with *NAME (reminds me of pointers in C).

If the <<: is an override that merges the block into existing values, why do you need it on the <<: &map_config in your example?

What determines the end of a defined block? When the level of indentation changes?

I’ll play around with this on my dashboard now.

http://thomasloven.com/blog/2018/08/YAML-For-Nonprogrammers/

2 Likes

I was expecting the anchors to stay in my code, but it seems like HA expands them and saves it that way. If I create a view with the example @Mariusthvdb gave, it works fine, but then when I go back to edit it and view the code, it now reads as:

type: vertical-stack
cards:
  - features:
      - type: select-options
    type: tile
    entity: input_select.hours_of_tracking_to_show
    features_position: bottom
    vertical: false
  - type: conditional
    conditions:
      - condition: state
        entity: put_select.hours_of_tracking_to_show
        state: "24"
    card:
      hours_to_show: 24
      type: map
      entities:
        - entity: person.user1
        - entity: zone.home
        - entity: zone.work
        - entity: person.user2
      theme_mode: auto
      default_zoom: 13
  - type: conditional
    conditions:
      - condition: state
        entity: input_select.hours_of_tracking_to_show
        state: "12"
    card:
      hours_to_show: 12
      type: map
      entities:
        - entity: person.user1
        - entity: zone.home
        - entity: zone.work
        - entity: person.user2
      theme_mode: auto
      default_zoom: 13
  - type: conditional
    conditions:
      - condition: state
        entity: input_select.hours_of_tracking_to_show
        state: "8"
    card:
      hours_to_show: 8
      type: map
      entities:
        - entity: person.user1
        - entity: zone.home
        - entity: zone.work
        - entity: person.user2
      theme_mode: auto
      default_zoom: 13

Is this intended or a side effect of the visual editor?

You can’t use anchors with any visual editor. That includes the yaml editors in the ui. You have to use full yaml mode.

Well you can once :slight_smile:

Is is possible to wrap an apex-chart card in a config-template-card wrapped in an auto-entities card? I’ve been playing with this and can’t seem to get data down to the chart.

I would like to use to use the picture element card with this however its not working as I hoped. I am trying the conditional state to show a picture has anyone got any advice or is this a no go?

Conditional element may be used to show an element conditionally.
A ā€œstate_imageā€ option may be used to show an image conditionally.
If both variants are not fine for your case - there is a good example to show an image with using config-template-card, google ā€œPicture elements small tutorialā€ - 1st post - show an image dynamically. This example is for the main image, adapt it for an elementā€s image.

Btw, some user proposed to remove config-template-card from HACS referring to some issue (which is ā€œuser’s mistakeā€ issue) instead of asking himself a question either in Community or in repo.
I wonder what is in heads of these guys. They ā€œcancelledā€ a famous bar-card and tried to ā€œcancelā€ an also famous state-switch card.

2 Likes

the state_image option is a brilliant idea i hadn’t thought of that, so thank you.

However after a quick test with the code. The image updates when i am just using a picture elements card with this mod but on a picture elements car with this mod the changing state has no effect!!

" - type: image
style:
left: 50%
top: 50%
state_image:
ā€œonā€: /api/image/serve/d30b91bccb46de39072a32dd8369124e/512x512
ā€œoffā€: /api/image/serve/a336cc03d71d090de9f345e7effa7cd3/512x512
entity: light.main_light
image: /api/image/serve/97fd79b392993e3940a29247f17bd44c/512x512"

Technically, since you refused to use the config-template-card, a further discussion is an off-topic).
Anyway, suggest to format your code properly as per forum’s rules and clarify what is not working.

One of the last HA updates brought this

No margin/padding anymore if using config-template-card without title in section card.

Any solution without card-mod?


type: custom:config-template-card
variables:
  REST_MINUTEN: states['input_number.musikbox'].state
entities:
  - input_number.musikbox
card:
  type: entities
  title: null
  entities:
    - entity: input_number.musikbox
      card_mod:
        style:
          .: |
            ha-slider {
              margin-right: 10px;
            }
      name: |
        '${Math.floor(REST_MINUTEN) + ' Minuten'}

Chery on the cake. Same for right margin/padding, here still solved with card-mod. Would like to don’t do it either.