lubeda / EspHoMaTriXv2

A simple DIY status display with a 8x32 RGB LED matrix, implemented with esphome.io and Home Assistant.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[FEATURE REQUEST] Hourly forecast bar - Interesting idea

andrewjswan opened this issue · comments

Feature Request

Describe the solution / feature you'd like

Interesting idea, to make a screen with weather, but exactly made for the weather, but here we need to figure out how to fit it into the current concept of our screens.

I mean, we need to transfer to the screen:

  • weather icon - icon name
  • current temperature - text
  • font (default, special) - default_font
  • hourly forecast bar - forecast (string with json, 22 length max)
  • Indicates whether to display the temperature in the color from the strip or in the default color - default_color

like:

weather_screen(icon_name, text, default_font, default_color, forecast)

IMHO better

icon_prognosis_screen(icon_name, text, default_font, default_color, forecast)
icon_prognosis_screen_rgb(icon_name, text, default_font, default_color, forecast, r, g, b)

Additional context

image

{%- set values = namespace(temp=[]) %}
{% for temp in  (states.weather.current_weather.attributes.forecast | map(attribute='temperature') | list)[:5] %}
  {%- set json = ([255,0,0] if temp > 30 else [0,255,0] if temp > 20 else [255,255,0] if temp > 5 else [0,0,255]) -%}
  {%- set values.temp = values.temp + [ json ] %}
{% endfor %}
{{ values.temp }}
    {% set current_temp = states('sensor.temperature_outside') %}
    {% set forecast = state_attr('weather.current_weather','forecast') %}
    {% set forecast_temp_field = "temperature" %} {# "feels_like" #}
    {% set hours_to_show = 3 %}
    {% set color_dict = {-12: "#D977DF",-6: "#9545BC",-1: "#4B379C",0: "#FEC4FF",4: "#31B8DB",10: "#31DB8B",15: "#6ED228",21: "#FFFF28",27: "#F87E27",32: "#CF3927",38: "#A12527"} %}
    
    {%- macro interpolate(dictionary, x) -%}
      {%- set sorted_keys = dictionary|dictsort -%}
      {%- set above = sorted_keys|selectattr('0', 'gt', x)|map(attribute='0')|list|first -%}
      {%- set below = sorted_keys|selectattr('0', 'lt', x)|map(attribute='0')|list|last -%}

      {#- Key matches x exactly -#}
      {%- if above is defined and dictionary[above] == x -%}
        {%- set value = dictionary[above] -%}
        {{ value }}
      {%- elif below is defined and dictionary[below] == x -%}
        {%- set value = dictionary[below] -%}
        {{ value }}
      {#- Interpolation between two values -#}
      {%- elif below is defined and above is defined -%}
        {%- set lower_value = dictionary[below] -%}
        {%- set upper_value = dictionary[above] -%}
        {%- set lower_rgb = lower_value[1:] -%}
        {%- set upper_rgb = upper_value[1:] -%}

        {%- set lower_r = lower_rgb[0:2]|int(base=16) -%}
        {%- set lower_g = lower_rgb[2:4]|int(base=16) -%}
        {%- set lower_b = lower_rgb[4:6]|int(base=16) -%}

        {%- set upper_r = upper_rgb[0:2]|int(base=16) -%}
        {%- set upper_g = upper_rgb[2:4]|int(base=16) -%}
        {%- set upper_b = upper_rgb[4:6]|int(base=16) -%}

        {%- set interpolation_factor = (x - below) / (above - below) -%}
        {%- set interpolated_r = ((1 - interpolation_factor) * lower_r + interpolation_factor * upper_r)|int -%}
        {%- set interpolated_g = ((1 - interpolation_factor) * lower_g + interpolation_factor * upper_g)|int -%}
        {%- set interpolated_b = ((1 - interpolation_factor) * lower_b + interpolation_factor * upper_b)|int -%}

        {%- set interpolated_value = interpolated_r ~ ',' ~ interpolated_g ~ ',' ~ interpolated_b -%}
        {{ interpolated_value }}
      {#- Only below key available -#}
      {%- elif below is defined -%}
        {%- set value = dictionary[below] -%}
        {{ value }}
      {#- Only above key available -#}
      {%- elif above is defined -%}
        {%- set value = dictionary[above] -%}
        {{ value }}
      {#- No matching keys available -#}
      {%- else -%}
        No matching key found.
      {%- endif -%}
    {%- endmacro -%}
    
    {%- macro draw_forecast_lines(hours) -%}
      {%- if forecast -%}
        {%- for hour in range(hours) -%}
          {%- if hour < forecast | count -%}
            [{{ interpolate(color_dict, forecast[hour][forecast_temp_field]) }}]
            {%- if hour + 1 != hours -%},{%- endif -%}
          {%- endif -%}
        {%- endfor -%}
      {%- endif -%}
    {%- endmacro -%}

    {{ draw_forecast_lines(hours_to_show) }} 
[
  [
    100,
    187,
    228
  ],
  [
    100,
    187,
    228
  ],
  [
    100,
    187,
    228
  ]
]
    {% set current_temp = states('sensor.temperature_outside') %}
    {% set forecast = state_attr('weather.current_weather','forecast') %}
    {% set forecast_temp_field = "temperature" %} {# "feels_like" #}
    {% set hours_to_show = 3 %}
    {% set color_dict = {-12: "#D977DF",-6: "#9545BC",-1: "#4B379C",0: "#FEC4FF",4: "#31B8DB",10: "#31DB8B",15: "#6ED228",21: "#FFFF28",27: "#F87E27",32: "#CF3927",38: "#A12527"} %}
    
    {%- macro interpolate(dictionary, x) -%}
      {%- set sorted_keys = dictionary|dictsort -%}
      {%- set above = sorted_keys|selectattr('0', 'gt', x)|map(attribute='0')|list|first -%}
      {%- set below = sorted_keys|selectattr('0', 'lt', x)|map(attribute='0')|list|last -%}

      {#- Key matches x exactly -#}
      {%- if above is defined and dictionary[above] == x -%}
        {%- set value = dictionary[above] -%}
        {{ value }}
      {%- elif below is defined and dictionary[below] == x -%}
        {%- set value = dictionary[below] -%}
        {{ value }}
      {#- Interpolation between two values -#}
      {%- elif below is defined and above is defined -%}
        {%- set lower_value = dictionary[below] -%}
        {%- set upper_value = dictionary[above] -%}
        {%- set lower_rgb = lower_value[1:] -%}
        {%- set upper_rgb = upper_value[1:] -%}

        {%- set lower_r = lower_rgb[0:2]|int(base=16) -%}
        {%- set lower_g = lower_rgb[2:4]|int(base=16) -%}
        {%- set lower_b = lower_rgb[4:6]|int(base=16) -%}

        {%- set upper_r = upper_rgb[0:2]|int(base=16) -%}
        {%- set upper_g = upper_rgb[2:4]|int(base=16) -%}
        {%- set upper_b = upper_rgb[4:6]|int(base=16) -%}

        {%- set interpolation_factor = (x - below) / (above - below) -%}
        {%- set interpolated_r = ((1 - interpolation_factor) * lower_r + interpolation_factor * upper_r)|int -%}
        {%- set interpolated_g = ((1 - interpolation_factor) * lower_g + interpolation_factor * upper_g)|int -%}
        {%- set interpolated_b = ((1 - interpolation_factor) * lower_b + interpolation_factor * upper_b)|int -%}

        {%- set interpolated_value = interpolated_r ~ ',' ~ interpolated_g ~ ',' ~ interpolated_b -%}
        {{ interpolated_value }}
      {#- Only below key available -#}
      {%- elif below is defined -%}
        {%- set value = dictionary[below] -%}
        {{ value }}
      {#- Only above key available -#}
      {%- elif above is defined -%}
        {%- set value = dictionary[above] -%}
        {{ value }}
      {#- No matching keys available -#}
      {%- else -%}
        No matching key found.
      {%- endif -%}
    {%- endmacro -%}
    
    {%- macro get_forecast_array(hours) -%}
      {% set array = namespace(data = []) %}
      {%- if forecast -%}
        {%- for hour in range(min(hours, 24)) -%}
          {%- if hour < forecast | count -%}
            {% set array.data = array.data + [interpolate(color_dict, forecast[hour][forecast_temp_field])] %}
          {%- endif -%}
        {%- endfor -%}
      {%- endif -%}
      {{ '{ "data": ' ~ array.data | to_json ~ '}' }}
    {%- endmacro -%}
 
    {%- macro make_forecast_line(hours) -%}
      {% set forecast = get_forecast_array(hours) | from_json %}
      {%- if forecast.data | count > 0 -%}
        {% set step = namespace(steps = 0) %}
        {%- for hour in range(min(hours, 24)) -%}
          {%- set step.steps = step.steps + 1 -%}
          {%- for h in range( ((hour * 24 / hours) | round(0)), ((step.steps * 24 / hours) | round(0)) ) -%}
            [{{ forecast.data[hour] }}]
          {%- endfor -%}
        {%- endfor -%}
      {%- endif -%}
    {%- endmacro -%}

    {{ make_forecast_line(hours_to_show) | replace('][',',') }} 
commented

The visual impression is great but the coding on the client side is "rocket science". I will take a look on how blueprints could help users to use the complex features of ehmtx.

@andrewjswan your creativity pushes this project. Thanx

The visual impression is great but the coding on the client side is "rocket science". I will take a look on how blueprints could help users to use the complex features of ehmtx.

I'm not good at Blueprint, but I think adapting the template above into Blueprint is not a problem.
And this screen will essentially be able to display any information, not necessarily the weather, for example a certain forecast, with an icon, text and a forecast line.

service: esphome.ulanzi_icon_prognosis_screen
data:
  icon_name: "weather_{{ states('weather.current_weather') | replace('-','_') }}|weather"
  text: "{{ states('sensor.temperature_outside') }}¬"
  prognosis: >-
    {% set forecast = state_attr('weather.current_weather','forecast') %}
    {% set forecast_temp_field = "temperature" %} {# "feels_like" #}
    {% set hours_to_show = 12 %}
    {% set color_dict = {-12: "#D977DF",-6: "#9545BC",-1: "#4B379C",0: "#FEC4FF",4: "#31B8DB",10: "#31DB8B",15: "#6ED228",21: "#FFFF28",27: "#F87E27",32: "#CF3927",38: "#A12527"} %}
    
    {%- macro interpolate(dictionary, x) -%}
      {%- set sorted_keys = dictionary|dictsort -%}
      {%- set above = sorted_keys|selectattr('0', 'gt', x)|map(attribute='0')|list|first -%}
      {%- set below = sorted_keys|selectattr('0', 'lt', x)|map(attribute='0')|list|last -%}
      
      {#- Key matches x exactly -#}
      {%- if above is defined and above == x -%}
        {%- set value = dictionary[above] -%}
        {%- set rgb = value[1:] -%}
        {{ rgb[0:2]|int(base=16) ~ ',' ~ rgb[2:4]|int(base=16) ~ ',' ~ rgb[4:6]|int(base=16)}}
      {%- elif below is defined and below == x -%}
        {%- set value = dictionary[below] -%}
        {%- set rgb = value[1:] -%}
        {{ rgb[0:2]|int(base=16) ~ ',' ~ rgb[2:4]|int(base=16) ~ ',' ~ rgb[4:6]|int(base=16)}}
      {#- Interpolation between two values -#}
      {%- elif below is defined and above is defined -%}
        {%- set lower_value = dictionary[below] -%}
        {%- set upper_value = dictionary[above] -%}
        {%- set lower_rgb = lower_value[1:] -%}
        {%- set upper_rgb = upper_value[1:] -%}
    
        {%- set lower_r = lower_rgb[0:2]|int(base=16) -%}
        {%- set lower_g = lower_rgb[2:4]|int(base=16) -%}
        {%- set lower_b = lower_rgb[4:6]|int(base=16) -%}
    
        {%- set upper_r = upper_rgb[0:2]|int(base=16) -%}
        {%- set upper_g = upper_rgb[2:4]|int(base=16) -%}
        {%- set upper_b = upper_rgb[4:6]|int(base=16) -%}
    
        {%- set interpolation_factor = (x - below) / (above - below) -%}
        {%- set interpolated_r = ((1 - interpolation_factor) * lower_r + interpolation_factor * upper_r)|int -%}
        {%- set interpolated_g = ((1 - interpolation_factor) * lower_g + interpolation_factor * upper_g)|int -%}
        {%- set interpolated_b = ((1 - interpolation_factor) * lower_b + interpolation_factor * upper_b)|int -%}
    
        {%- set interpolated_value = interpolated_r ~ ',' ~ interpolated_g ~ ',' ~ interpolated_b -%}
        {{ interpolated_value }}
      {#- Only below key available -#}
      {%- elif below is defined -%}
        {%- set value = (sorted_keys|last)[1] -%}
        {%- set rgb = value[1:] -%}
        {{ rgb[0:2]|int(base=16) ~ ',' ~ rgb[2:4]|int(base=16) ~ ',' ~ rgb[4:6]|int(base=16)}}
      {#- Only above key available -#}
      {%- elif above is defined -%}
        {%- set value = (sorted_keys|first)[1] -%}
        {%- set rgb = value[1:] -%}
        {{ rgb[0:2]|int(base=16) ~ ',' ~ rgb[2:4]|int(base=16) ~ ',' ~ rgb[4:6]|int(base=16)}}
      {#- No matching keys available -#}
      {%- else -%}
        No matching key found.
      {%- endif -%}
    {%- endmacro -%} 
    
    {%- macro get_forecast_array(hours) -%}
      {% set array = namespace(data = []) %}
      {%- if forecast -%}
        {%- for hour in range(min(hours, 24)) -%}
          {%- if hour < forecast | count -%}
            {% set array.data = array.data + [interpolate(color_dict, forecast[hour][forecast_temp_field])] %}
          {%- endif -%}
        {%- endfor -%}
      {%- endif -%}
      {{ '{ "data": ' ~ array.data | to_json ~ '}' }}
    {%- endmacro -%}
 
    {%- macro make_forecast_line(hours) -%}
      {% set forecast = get_forecast_array(hours) | from_json %}
      {%- if forecast.data | count > 0 -%}
        {% set step = namespace(steps = 0) %}
        {% set result = forecast.data | count %}
        {%- for hour in range(min(result, 24)) -%}
          {%- set step.steps = step.steps + 1 -%}
          {%- for h in range( ((hour * 24 / result) | round(0)), ((step.steps * 24 / result) | round(0)) ) -%}
            [{{ forecast.data[hour] }}]
          {%- endfor -%}
        {%- endfor -%}
      {%- endif -%}
    {%- endmacro -%}

    {{ make_forecast_line(hours_to_show) | replace('][',',') }}
  lifetime: 1
  screen_time: 10
  default_font: true

Farenheit:

{% set color_dict = {0: "#FEC4FF", 10: "#D977DF", 20: "#9545BC", 30: "#4B379C", 40: "#31B8DB", 50: "#31DB8B", 60: "#6ED228", 70 : "#FFFF28", 80: "#F87E27", 90: "#CF3927", 100: "#A12527"} %}

@lubeda
That's a funny screen we get, it's basically ready to go, but I'm going to have obvious difficulties with documentation.
I can briefly describe it, but there is no point in writing a sheet of code in Blueprint in the documentation....
Do a PR with minimal documentation?

I added a little to the jinja template to move away from fixed temperature and color values (changes only in color_dict).
In this option, a forecast is taken for the n-period, the minimum value for the period is assigned cold blue, the maximum value warm yellow, and the average value neutral white.

With floating min/max values, you can very clearly see when it will be warmer and when it will be colder during the period, compared to the average value for the period.

This is what my forecast looks like, which consists of values:
+2, =0, +1, +1, -1, +1, +5° (by day)
The matrix shows that at the end of the week (7th day) warming can be expected:

image

        {% set forecast = state_attr('weather.home','forecast') %} 
        {% set forecast_temp_field = "temperature" %} {# "feels_like" #} 
        {% set hours_to_show = 7 %} 
        {% set color_dict = {
           forecast | map(attribute=forecast_temp_field) | min: '#0077ff', 
           forecast | map(attribute=forecast_temp_field) | average: '#ffffff', 
           forecast | map(attribute=forecast_temp_field) | max: '#ffaa00' } %}

        {%- macro interpolate(dictionary, x) -%}
          {%- set sorted_keys = dictionary|dictsort -%}
          {%- set above = sorted_keys|selectattr('0', 'gt', x)|map(attribute='0')|list|first -%}
          {%- set below = sorted_keys|selectattr('0', 'lt', x)|map(attribute='0')|list|last -%}
          
          {#- Key matches x exactly -#}
          {%- if above is defined and above == x -%}
            {%- set value = dictionary[above] -%}
            {%- set rgb = value[1:] -%}
            {{ rgb[0:2]|int(base=16) ~ ',' ~ rgb[2:4]|int(base=16) ~ ',' ~ rgb[4:6]|int(base=16)}}
          {%- elif below is defined and below == x -%}
            {%- set value = dictionary[below] -%}
            {%- set rgb = value[1:] -%}
            {{ rgb[0:2]|int(base=16) ~ ',' ~ rgb[2:4]|int(base=16) ~ ',' ~ rgb[4:6]|int(base=16)}}
          {#- Interpolation between two values -#}
          {%- elif below is defined and above is defined -%}
            {%- set lower_value = dictionary[below] -%}
            {%- set upper_value = dictionary[above] -%}
            {%- set lower_rgb = lower_value[1:] -%}
            {%- set upper_rgb = upper_value[1:] -%}
        
            {%- set lower_r = lower_rgb[0:2]|int(base=16) -%}
            {%- set lower_g = lower_rgb[2:4]|int(base=16) -%}
            {%- set lower_b = lower_rgb[4:6]|int(base=16) -%}
        
            {%- set upper_r = upper_rgb[0:2]|int(base=16) -%}
            {%- set upper_g = upper_rgb[2:4]|int(base=16) -%}
            {%- set upper_b = upper_rgb[4:6]|int(base=16) -%}
        
            {%- set interpolation_factor = (x - below) / (above - below) -%}
            {%- set interpolated_r = ((1 - interpolation_factor) * lower_r + interpolation_factor * upper_r)|int -%}
            {%- set interpolated_g = ((1 - interpolation_factor) * lower_g + interpolation_factor * upper_g)|int -%}
            {%- set interpolated_b = ((1 - interpolation_factor) * lower_b + interpolation_factor * upper_b)|int -%}
        
            {%- set interpolated_value = interpolated_r ~ ',' ~ interpolated_g ~ ',' ~ interpolated_b -%}
            {{ interpolated_value }}
          {#- Only below key available -#}
          {%- elif below is defined -%}
            {%- set value = (sorted_keys|last)[1] -%}
            {%- set rgb = value[1:] -%}
            {{ rgb[0:2]|int(base=16) ~ ',' ~ rgb[2:4]|int(base=16) ~ ',' ~ rgb[4:6]|int(base=16)}}
          {#- Only above key available -#}
          {%- elif above is defined -%}
            {%- set value = (sorted_keys|first)[1] -%}
            {%- set rgb = value[1:] -%}
            {{ rgb[0:2]|int(base=16) ~ ',' ~ rgb[2:4]|int(base=16) ~ ',' ~ rgb[4:6]|int(base=16)}}
          {#- No matching keys available -#}
          {%- else -%}
            No matching key found.
          {%- endif -%}
        {%- endmacro -%}
        
        {%- macro get_forecast_array(hours) -%}
          {% set array = namespace(data = []) %}
          {%- if forecast -%}
            {%- for hour in range(min(hours, 24)) -%}
              {%- if hour < forecast | count -%}
                {% set array.data = array.data + [interpolate(color_dict, forecast[hour][forecast_temp_field])] %}
              {%- endif -%}
            {%- endfor -%}
          {%- endif -%}
          {{ '{ "data": ' ~ array.data | to_json ~ '}' }}
        {%- endmacro -%}
     
        {%- macro make_forecast_line(hours) -%}
          {% set forecast = get_forecast_array(hours) | from_json %}
          {%- if forecast.data | count > 0 -%}
            {% set step = namespace(steps = 0) %}
            {% set result = forecast.data | count %}
            {%- for hour in range(min(result, 24)) -%}
              {%- set step.steps = step.steps + 1 -%}
              {%- for h in range( ((hour * 24 / result) | round(0)), ((step.steps * 24 / result) | round(0)) ) -%}
                [{{ forecast.data[hour] }}]
              {%- endfor -%}
            {%- endfor -%}
          {%- endif -%}
        {%- endmacro -%}
    
        {{ make_forecast_line(hours_to_show) | replace('][',',') }}

Instead of my addition in previous message,
Fixed values (from 0 to 100% for example) perfectly fits for some cases, for example, chance of rain for 5 hours:
image

    {% set forecast = state_attr('weather.home','forecast') %} 
    {% set forecast_temp_field = "precipitation_probability" %} {# "feels_like" #} 
    {% set hours_to_show = 5 %} 
    {% set max_prob = forecast | map(attribute=forecast_temp_field) | max %}
    {% set color_dict = {0: '#999999', 100: '#0000ff'} %}

    {%- macro interpolate(dictionary, x) -%}
      {%- set sorted_keys = dictionary|dictsort -%}
      {%- set above = sorted_keys|selectattr('0', 'gt', x)|map(attribute='0')|list|first -%}
      {%- set below = sorted_keys|selectattr('0', 'lt', x)|map(attribute='0')|list|last -%}
      
      {#- Key matches x exactly -#}
      {%- if above is defined and above == x -%}
        {%- set value = dictionary[above] -%}
        {%- set rgb = value[1:] -%}
        {{ rgb[0:2]|int(base=16) ~ ',' ~ rgb[2:4]|int(base=16) ~ ',' ~ rgb[4:6]|int(base=16)}}
      {%- elif below is defined and below == x -%}
        {%- set value = dictionary[below] -%}
        {%- set rgb = value[1:] -%}
        {{ rgb[0:2]|int(base=16) ~ ',' ~ rgb[2:4]|int(base=16) ~ ',' ~ rgb[4:6]|int(base=16)}}
      {#- Interpolation between two values -#}
      {%- elif below is defined and above is defined -%}
        {%- set lower_value = dictionary[below] -%}
        {%- set upper_value = dictionary[above] -%}
        {%- set lower_rgb = lower_value[1:] -%}
        {%- set upper_rgb = upper_value[1:] -%}
    
        {%- set lower_r = lower_rgb[0:2]|int(base=16) -%}
        {%- set lower_g = lower_rgb[2:4]|int(base=16) -%}
        {%- set lower_b = lower_rgb[4:6]|int(base=16) -%}
    
        {%- set upper_r = upper_rgb[0:2]|int(base=16) -%}
        {%- set upper_g = upper_rgb[2:4]|int(base=16) -%}
        {%- set upper_b = upper_rgb[4:6]|int(base=16) -%}
    
        {%- set interpolation_factor = (x - below) / (above - below) -%}
        {%- set interpolated_r = ((1 - interpolation_factor) * lower_r + interpolation_factor * upper_r)|int -%}
        {%- set interpolated_g = ((1 - interpolation_factor) * lower_g + interpolation_factor * upper_g)|int -%}
        {%- set interpolated_b = ((1 - interpolation_factor) * lower_b + interpolation_factor * upper_b)|int -%}
    
        {%- set interpolated_value = interpolated_r ~ ',' ~ interpolated_g ~ ',' ~ interpolated_b -%}
        {{ interpolated_value }}
      {#- Only below key available -#}
      {%- elif below is defined -%}
        {%- set value = (sorted_keys|last)[1] -%}
        {%- set rgb = value[1:] -%}
        {{ rgb[0:2]|int(base=16) ~ ',' ~ rgb[2:4]|int(base=16) ~ ',' ~ rgb[4:6]|int(base=16)}}
      {#- Only above key available -#}
      {%- elif above is defined -%}
        {%- set value = (sorted_keys|first)[1] -%}
        {%- set rgb = value[1:] -%}
        {{ rgb[0:2]|int(base=16) ~ ',' ~ rgb[2:4]|int(base=16) ~ ',' ~ rgb[4:6]|int(base=16)}}
      {#- No matching keys available -#}
      {%- else -%}
        No matching key found.
      {%- endif -%}
    {%- endmacro -%}
    
    {%- macro get_forecast_array(hours) -%}
      {% set array = namespace(data = []) %}
      {%- if forecast -%}
        {%- for hour in range(min(hours, 24)) -%}
          {%- if hour < forecast | count -%}
            {% set array.data = array.data + [interpolate(color_dict, forecast[hour][forecast_temp_field])] %}
          {%- endif -%}
        {%- endfor -%}
      {%- endif -%}
      {{ '{ "data": ' ~ array.data | to_json ~ '}' }}
    {%- endmacro -%}
 
    {%- macro make_forecast_line(hours) -%}
      {% set forecast = get_forecast_array(hours) | from_json %}
      {%- if forecast.data | count > 0 -%}
        {% set step = namespace(steps = 0) %}
        {% set result = forecast.data | count %}
        {%- for hour in range(min(result, 24)) -%}
          {%- set step.steps = step.steps + 1 -%}
          {%- for h in range( ((hour * 24 / result) | round(0)), ((step.steps * 24 / result) | round(0)) ) -%}
            [{{ forecast.data[hour] }}]
          {%- endfor -%}
        {%- endfor -%}
      {%- endif -%}
    {%- endmacro -%}

    {{ make_forecast_line(hours_to_show) | replace('][',',') }}

Added to 2023.9.1