Lovelace: mini graph card

Using my method effectively frees up the Y-axis to be used by another reading, effectively giving a second Y-axis, pushing the binary_sensor to the back which only has two possible states.

It hurts my brain to make OFF/ON = 0/100 rather than the traditional 0/1 but at the end of the day we are applying a fudge to make the graph display what we want. Using 0/100 gives some range to the Y-axis to allow it to display something else quantitative.

Also dewpoint and temperature are very close together, this gives me the ability to see the relationship better.

There is a few use cases when comparing a percentage, with a numeric value and an ON/OFF state.

  • %CPU Use, CPU Temp, Fan ON/OFF state
  • %Humidity, Temperature, Humidifier ON/OFF state
  • %Humidity, CO2ppm, Door OPEN/CLOSED state

This method also makes all values on the grid available via mouse over. Plus less code.

Thanks for that link for display: grid, I will use the card mod to move my values where I want them.

LINK TO THE FINAL CARD WITH UPDATED CODE

image

4 State Mini Graph Card: Using the below card-mod I got the above style (Comments in yaml.)

type: custom:mini-graph-card
name: House Temperatures
group: true /*takes space away from top of card*/
card_mod:
  style: |
    .states {
      justify-content: normal !important;
      font-size: 11px !important; /*Adjusts state font size*/
    }
    .state {
      flex-grow: 1;
    }
    .states--secondary {
      flex-flow: row !important;
      flex-grow: 2; /*Adjusts spacing of 4 readings*/
      margin-left: 0px !important;
    }
    .states--secondary .state.state--small {
      font-size: unset;
      margin-bottom: unset;
      flex-grow: unset;
    }
    .states--secondary .state.state--small:nth-of-type(2) {
      margin-left: auto !important; /*Adjusts the 3rd state*/
    }
    .states--secondary .state.state--small:nth-of-type(3) {
      margin-left: auto !important; /*Adjusts the 4th state*/
    }
    .ellipsis {
      font-size: 12px ; /*Adjusts legend font size*/

    }

image

3 State Mini Graph Card: Using the below card-mod I got the above style (Comments in yaml.)

type: custom:mini-graph-card
name: Downstairs Temperatures
group: true /*takes space away from top of card*/
card_mod:
  style: |
    .states {
      justify-content: normal !important;
      font-size: 11px !important; /*Adjusts state font size*/
    }
    .state {
      flex-grow: 1;
    }
    .states--secondary {
      flex-flow: row !important;
      flex-grow: 1.2; /*Adjusts spacing of 3 readings*/
      margin-left: 0px !important;
    }
    .states--secondary .state.state--small {
      font-size: unset;
      margin-bottom: unset;
      flex-grow: unset;
    }
    .states--secondary .state.state--small:nth-of-type(2) {
      margin-left: auto !important; /*Adjusts the 3rd state*/
    }
    .states--secondary .state.state--small:nth-of-type(3) {
      margin-left: auto !important; 
    }
    .ellipsis {
      font-size: 12px ; /*Adjusts legend font size*/
    }

Below example of group: false

  • Enables paddings and box-shadow.

image

Overall result on dashboard, no more cluttered look within cards

These comments inside a string will cause errors in css.
Use “/* 
 */” for comments.

Thanks mate was trying to work that out

FYI with a few days testing when using 0/100 aggregate_func: last has no affect.
upper_bound_secondary: 100 and lower_bound_secondary: 0 is what is required.

Only for your case since you use secondary Y-axis for physical values & binary values.

Needed for 0/1 & may be not for 0/100.
Besides, even for ANY values I see no need to use other values than “last”.

1 Like

I appreciate the conversation and the many posts you have put up here to help others. Thanks.

Can you explain what the aggregate function is actually acting on?

It has many possible values & algorithm is complex, better to check a code. Just consider this simplest example:
Assume there are 60 readings (records in DB) within an hour; points_per_hour=6. Means - you do not need all 60 values for a graph, it will use only 6 values. Now how to convert each 10 real values into 1? Average, min, max, last? If you need only 6 values - then probably “average” is your choice. But if you have points_per_hour=60? 120? 240? These greater values are needed sometimes to show a “steplined” graph. In these cases an additional calculation to get “average” = not needed. Besides, for 0/1 graphs sometimes it shows 0 where it must be 1 - if you set aggregate_func wrongly.

LINK TO THE FINAL CARD WITH UPDATED CODE

With what you have noted above and me re-reading the docs have explained the behavior I am seeing in the below graphs. With some commentary.

Aggregate functions

Recorded values are grouped in time buckets which are determined by group_by, points_per_hour configuration. These buckets are converted later to single point/bar on the graph. Aggregate function defines the methods of that conversion.

Name Since Description
avg v0.8.0 Average
median v0.11.0 Median
min v0.8.0 Minimum - lowest value
max v0.8.0 Maximum - largest value
first v0.9.0
last v0.9.0
sum v0.9.2
delta v0.9.4 Calculates difference between max and min value
diff v0.11.0 Calculates difference between first and last value
Name Type Default Since Description
group boolean false v0.2.0 Disable paddings and box-shadow, useful when nesting the card. #Not to be confused for group_by
points_per_hour number 0.5 v0.2.0 Specify amount of data points the graph should display for each hour, (basically the detail/accuracy/smoothing of the graph).
aggregate_func string avg v0.8.0 Specify aggregate function used to calculate point/bar in the graph.
group_by string interval v0.8.0 Specify type of grouping of data, dynamic interval, date or hour.

With 0/100- Setting no aggregate_function: for the entity.

  • The door open shows the short duration event at half the range.
  • (Must be some sort of default average?)
    image

With 0/100- Setting aggregate_function: avg for the entity.

  • The door open shows the short duration event at half the range.
  • Showing the average of readings in the time bucket.
    image

With 0/100 - Setting aggregate_function: last for the entity.

  • The door open does not show the short duration event unless it is last in the time bucket.
    image

With 0/100 - Setting aggregate_function:min for the entity.

  • The door open only shows the minimum reading within each time bucket.
  • Short duration events won’t register unless there is a high point per hour count or short group_by time.
    image

With 0/100 - Setting aggregate_function: max for the entity.

  • The door open only shows the maximum reading within each time bucket.
  • The door open uses the full range of graph, even for short duration events.
    image

LINK TO THE FINAL CARD WITH UPDATED CODE

Hi all there,
I’m diabetic, and I would to share what I obtained after many hours of fiddling


image

This is a graph from my CGM dexcom G6 linked to HA with dexcom integration.
I have achieved, with the help of some previous messages + an advice from ildar170975 to change dynamically the icon with the trend information of the dexcom integration, and to colorize the sate information accordingly to the value read. So visually i know immediately if all is OK or if something goes wrong !

I must just find a way for the 2 extreme trends states (rising-quickly and falling-quickly) to show a double arrow (up ⇈ or down ⇊ ) that indicates visually that the BG is changing very quickly and that I must make something about that
 The double arrow is used on my pump and on the dexcom app, so it’s a known signal.

If someone has an idea to load an custom svg via card-mod, or show a second icon in that case


Here is the YAML of my card :

type: custom:mini-graph-card
entities:
  - sensor.xxxxxxx_glucose_value
hours_to_show: 12
name: Glycémie Christophe
icon: mdi:wifi
align_state: center
points_per_hour: 12
hour24: true
decimals: 0
lower_bound: 40
height: 200
font_size: 150
color_thresholds:
  - value: 0
    color: "#ff0000"
  - value: 60
    color: "#ff0000"
  - value: 100
    color: "#0077ff"
  - value: 110
    color: "#00ff00"
  - value: 160
    color: "#00ff00"
  - value: 190
    color: "#ff7700"
card_mod:
  style: |
    ha-card .state__value {
      font-weight: bold;
      font-size: 350%;
      margin-right: 10px;

      {% set VALUE = states('sensor.xxxxxx_glucose_value') %}
      {% if (VALUE | int >= 180) %}
        color: #ff7700;
      {% elif (VALUE | int >= 100) %}
        color: #00ff77;
      {% elif (VALUE | int >= 70) %}
        color: #00aaff;
      {% elif (VALUE | int <= 69) %}
        color: #ff5050;
      {% endif %}
      
    }
    ha-card .state__uom {
      font-weight: normal;
      font-size: 100%;
      position: relative;
      top: -25px;
    }
    ha-card .header.flex .icon {
      color: white;
      --mdc-icon-size: 55px;
      position: relative;
      top: 10px
    }
    ha-card .header.flex, ha-card .states.flex {
      padding-bottom: 0;
    }

    ha-card.flex {
      padding-top: 0;
      {% set TREND = states('sensor.xxxxxx_glucose_trend') %}
      {% if (TREND == 'rising_quickly') %}
        --card-mod-icon: mdi:arrow-up-bold-box;
      {% elif (TREND == 'rising') %}
        --card-mod-icon: mdi:arrow-up-bold;
      {% elif (TREND == 'rising_slightly') %}
        --card-mod-icon: mdi:arrow-top-right-thick;
      {% elif (TREND == 'steady') %}
        --card-mod-icon: mdi:arrow-right-bold;
      {% elif (TREND == 'falling_slightly') %}
        --card-mod-icon: mdi:arrow-bottom-right-thick;
      {% elif (TREND == 'falling') %}
        --card-mod-icon: mdi:arrow-down-bold;
      {% elif (TREND == 'falling_quickly') %}
        --card-mod-icon: mdi:arrow-down-bold-box;
      {% endif %}
    }

2 Likes

Divide it to

ha-card.flex {
  padding-top: 0;
}

ha-card {
  {% set TREND = states('sensor.xxxxxx_glucose_trend') -%}
  {%- set mapper = {
    'rising_quickly':  'mdi:arrow-up-bold-box',
    'rising':          'mdi:arrow-up-bold',
    'rising_slightly': 'mdi:arrow-top-right-thick',
    'steady':          'mdi:arrow-right-bold',
    'falling_slightly':'mdi:arrow-bottom-right-thick',
    'falling':         'mdi:arrow-down-bold',
    'falling_quickly': 'mdi:arrow-down-bold-box'
  } -%}
  {%- set ICON = mapper[TREND] if TREND in mapper else 'mdi:help' -%}
  --card-mod-icon: {{ICON}};
}

Also, all these “ha-card .header.flex”, “ha-card abc” may be replaced by “abc” (of course if “abc” does not exist in several levels of DOM).

Currently you are using “-box” icons for these extrema states.
We can colorize the icon for these states (make it blinking, for example).

2 Likes

If it helps, this can be more concisely written as

{{ iif( states( "binary_sensor.middle_floor_sensor_group" ),1, 0 ) }}
1 Like

I am using “binary_sensor → 0/1” template in many places - so just made a macro for it. Reusing a code drastically reduces a probability of error.
If this template is only used for template sensors - then a possible way could be using a blueprint for template sensor (it currently does not work for trigger-based sensors only).

Yeah, waiting eagerly for the template sensor blueprints to get fixed so I can use a template sensor - mines also in a macro - I hate writing stuff more than once - no need to maintain multiple lines of code :slight_smile:

HTML Arrow Symbol, Arrow Entity and ASCII Arrow Character Code Reference

I use code similar to Ildar’s for my Son’s Dexcom, but with the UTF8 arrows (all of which have an HTML entity equivalent, same as you inserted into your post :slight_smile: )

They cover all of the various Dexcom states

1 Like

It is possible to add any character to mini-graph-card.
But imho an icon is more visible.
Of course these unicode chars helps when we do not have appropriate icons


1 Like

Thanks mate, always looking for ways to reduce lines. I assume if rather than iif.

No, it acually is Immediate if (iif) - A common case is to conditionally return a value based on another value. For example, return a “Yes” or “No” when the light is on or off.

1 Like

Note that using “iif” can cause issues due to a sequence of calculations.

1 Like

True: I guess this would be more robust


{{ states("binary_sensor.middle_floor_sensor_group") | bool | int }}