WIP - Weather Dashboard

9 Likes

Nice work!
Can you please share a yaml?

2 Likes

When I get it completed I will share yaml for each individual card.

2 Likes

You’re not done yet? It already looks good. I wonder how you’ll improve it :grin:
Waiting for the update. Thanks! :slight_smile:

I should have it done this weekend. Going through and cleaning up and simplify code.

12 Likes

Nice cards ! Good job :slight_smile:

No code, no like!
haha joke!

Congratz! Very nice job.
Waiting for the code :eye: :eyes:

1 Like

im intrested in the diffrent card names.
I really like UV Index und Air Quality card!

1 Like

The UV and Air Quality card are custom button cards. Below is the air quality:


type: custom:button-card
icon: ic:baseline-fitbit
entity: sensor.u_s_air_quality_index
name: Air Quality
show_name: true
show_icon: true
show_label: false
show_state: false
custom_fields:
  level: |
    [[[
      var aqi = parseFloat(entity.state) || 0;
      var lev = Math.round((aqi / 301) * 100);
      lev = Math.min(Math.max(lev, 0), 100);

      return `<div><div style="background: var(--white); height: 1.7rem; width: 1.7rem; border-radius: 100px; position: relative; left: ${lev}%; box-shadow: 2px 1px 10px var(--contrast10);"></div></div>`;
    ]]]
  label: |
    [[[
      let aqi = parseFloat(entity.state) || 0;
      if (aqi <= 50) return '<span style="font-size: 1.2rem">GOOD</span>';
      else if (aqi <= 100) return '<span style="font-size: 1.2rem">MODERATE</span>';
      else if (aqi <= 150) return '<span style="font-size: 1.2rem">UNHEALTHY<br><small>for sensitive groups</small></span>';
      else if (aqi <= 200) return '<span style="font-size: 1.2rem">UNHEALTHY</span>';
      else if (aqi <= 300) return '<span style="font-size: 1.2rem">VERY UNHEALTHY</span>';
      else return '<span style="font-size: 1.2rem">HAZARDOUS</span>';
    ]]]
  index: |
    [[[
      return `${entity.state} <span style="font-size: 1rem; color: var(--contrast7)">AQI</span>`;
    ]]]
  uv: |
    [[[
      return `<div></div>`
    ]]]
styles:
  grid:
    - grid-template-areas: |
        "n . index"  
        "n . label" 
        ". uv uv" 
        ". level level"
    - grid-template-rows: min-content 1fr min-content 0px
    - grid-template-columns: min-content 1fr 1fr
  card:
    - height: 115px
    - padding: 10px
    - border-radius: 20px
  name:
    - font-size: 1.5rem
    - font-weight: 500
    - justify-self: start
    - align-self: start
    - padding-bottom: 10px
    - color: var(--contrast5)
    - padding-left: 5px
  img_cell:
    - position: absolute
    - top: 15%
    - right: 40%
    - overflow: visible
  icon:
    - position: absolute
    - width: 5rem
    - color: var(--blue)
  custom_fields:
    uv:
      - background: >
          linear-gradient(90deg, rgba(137,204,141,1) 0%, rgba(255,217,119,1)
          20%, rgba(255,175,108,1) 40%, rgba(247,126,117,1) 60%,
          rgba(167,136,210,1) 80%, rgba(116,0,152,1) 100%);
      - border-radius: 10px
      - height: 1rem
      - width: 100%
      - margin-bottom: 10px
      - z-index: 1
    level:
      - background: transparent
      - justify-self: start
      - border-radius: 10px
      - position: relative
      - right: 5px
      - bottom: 17px
      - width: 100%
      - z-index: 2
      - overflow: visible
    label:
      - align-self: start
      - justify-self: end
      - margin-bottom: 10px
      - color: var(--contrast14)
      - font-weight: 500
    index:
      - font-size: 2.2rem
      - font-weight: 600
      - justify-self: end
      - color: var(--contrast14)
      - padding-bottom: 0px
      - margin-left: 5px

4 Likes

UV Index


type: custom:button-card
icon: fa-solid:sun
entity: sensor.gw2000b_uv_index
name: UV Index
show_name: true
show_icon: true
show_label: false
show_state: false
custom_fields:
  level: |
    [[[
      var uv = parseFloat(entity.state) || 0;
      var lev = Math.round((uv / 12) * 100);
      lev = Math.min(Math.max(lev, 0), 100);
      
      return `<div><div style="background: var(--white); height: 1.7rem; width: 1.7rem; border-radius: 100px; position: relative; left: ${lev}%; box-shadow: 2px 1px 10px var(--contrast10);"></div></div>`;
    ]]]
  uv: |
    [[[
      return `<div></div>`
    ]]]
  label: |
    [[[
      let uv = parseFloat(entity.state) || 0;
      if (uv <= 2) return '<span style="font-size: 1.2rem">LOW</span>';
      else if (uv <= 5) return '<span style="font-size: 1.2rem">MODERATE</span>';
      else if (uv <= 7) return '<span style="font-size: 1.2rem">HIGH</span>';
      else if (uv <= 10) return '<span style="font-size: 1.2rem">VERY HIGH</span>';
      else return '<span style="font-size: 1.2rem">EXTREME</span>';
    ]]]
  index: |
    [[[
      return entity.state;
    ]]]
styles:
  grid:
    - grid-template-areas: |
        "n . index" 
        "n . label"  
        ". uv uv"  
        ". level level"
    - grid-template-rows: min-content 1fr min-content 0px
    - grid-template-columns: min-content 1fr 1fr
  card:
    - height: 115px
    - padding: 10px
    - border-radius: 20px
  name:
    - font-size: 1.5rem
    - font-weight: 500
    - justify-self: start
    - align-self: start
    - padding-bottom: 10px
    - color: var(--contrast5)
    - padding-left: 5px
  img_cell:
    - position: absolute
    - top: 15%
    - right: 40%
    - overflow: visible
  icon:
    - position: absolute
    - width: 5rem
    - color: var(--yellow)
  custom_fields:
    uv:
      - background: >
          linear-gradient(90deg, rgba(137,204,141,1) 0%, rgba(255,217,119,1)
          27%, rgba(255,175,108,1) 52%, rgba(247,126,117,1) 77%,
          rgba(167,136,210,1) 100%);
      - border-radius: 10px
      - height: 1rem
      - width: 100%
      - margin-bottom: 10px
      - z-index: 1
    level:
      - background: transparent
      - justify-self: start
      - border-radius: 10px
      - position: relative
      - right: 5px
      - bottom: 17px
      - width: 100%
      - z-index: 2
      - overflow: visible
    label:
      - align-self: start
      - justify-self: end
      - margin-bottom: 10px
      - color: var(--contrast14)
      - font-weight: 500
    index:
      - font-size: 2.2rem
      - font-weight: 600
      - justify-self: end
      - color: var(--contrast14)
      - padding-bottom: 0px
      - margin-left: 5px

3 Likes

Thanks :slight_smile:

Can you share the picture too please ? :slight_smile:

Icons are pulled from this HACS integration
https://github.com/thomasloven/hass-custom_icons

2 Likes

Thanks :slight_smile:
The wind card is finished ?

Nice icons thanks for this integration :slight_smile:

meteo

Yes. It uses custom button card and mini-graph. The images for the compass rose are svg’s I created (I will post those tomorrow, don’t have access to them currently):


type: custom:button-card
tap_action:
  action: more-info
entity: sensor.gw2000b_wind_speed
name: Wind
show_name: true
show_state: false
show_icon: false
custom_fields:
  graph:
    card:
      type: custom:mini-graph-card
      entities:
        - entity: "[[[ return entity.entity_id ]]]"
          show_fill: true
          show_line: false
      show:
        name: false
        icon: false
        state: false
      line_color: var(--purple)
  point: |
    [[[
      return `<ha-icon icon='custom:compass-arrow' style="transform: rotate(${states['sensor.gw2000b_wind_direction'].state}deg);"/>`;
    ]]]
  ticks: |
    [[[
      return `<ha-icon icon='custom:compass-ticks-b'/>`;
    ]]]
  name1: Speed
  name2: Gust
  name3: Direction
  speed: |
    [[[
      return states[entity.entity_id].state
    ]]]
  gust: |
    [[[
      return states['sensor.gw2000b_wind_gust'].state
    ]]]
  dir: |
    [[[
      return states['sensor.gw2000b_wind_direction'].state + '° ' + states['sensor.wind_direction'].state
    ]]]
styles:
  grid:
    - grid-template-areas: |
        "n n point"  
        "name1 speed point"  
        "name2 gust point" 
        "name3 dir point"
    - grid-template-rows: 1fr 1fr 1fr 1fr
    - grid-template-columns: 30% 25% 1fr
  card:
    - padding: 10px 10px 20px 10px
    - border-radius: 20px
    - height: 177px
  name:
    - text-transform: capitalize
    - font-size: 1.5rem
    - font-weight: 500
    - justify-self: start
    - align-self: start
    - color: var(--contrast6)
    - opacity: 0.5
    - padding-left: 5px
  icon:
    - color: var(--contrast5)
    - width: 3rem
    - height: 3rem
  img_cell:
    - justify-self: start
    - width: 3rem
    - padding-left: 5px
  custom_fields:
    speed:
      - font-size: 1.3rem
      - font-weight: 800
      - justify-self: end
      - align-self: end
      - color: var(--contrast14)
      - padding-bottom: 0
      - margin-left: 5px
      - z-index: 2
    gust:
      - font-size: 1.3rem
      - font-weight: 800
      - justify-self: end
      - align-self: end
      - color: var(--contrast14)
      - padding-bottom: 0
      - margin-left: 5px
      - z-index: 2
    dir:
      - font-size: 1.3rem
      - font-weight: 800
      - justify-self: end
      - align-self: end
      - color: var(--contrast14)
      - padding-bottom: 0px
      - margin-left: 5px
      - z-index: 2
    name1:
      - font-size: 1.2rem
      - font-weight: 500
      - justify-self: start
      - align-self: end
      - color: var(--contrast14)
      - padding-bottom: 0
      - margin-left: 5px
      - z-index: 2
    name2:
      - font-size: 1.2rem
      - font-weight: 500
      - justify-self: start
      - align-self: end
      - color: var(--contrast14)
      - padding-bottom: 0
      - margin-left: 5px
      - z-index: 2
    name3:
      - font-size: 1.2rem
      - font-weight: 500
      - justify-self: start
      - align-self: end
      - color: var(--contrast14)
      - padding-bottom: 0
      - margin-left: 5px
      - z-index: 2
    point:
      - justify-self: end
      - align-self: center
      - width: 8rem
      - margin-top: 15px
      - margin-right: 1.3rem
      - padding-right: 0
      - color: var(--red)
      - z-index: 3
    ticks:
      - justify-self: right
      - align-self: center
      - width: 10rem
      - position: absolute
      - right: 1rem
      - top: 20px
      - color: var(--contrast7)
      - z-index: 2
    graph:
      - display: block
      - position: absolute
      - left: 0
      - bottom: 0
      - width: 100%
      - z-index: 1

2 Likes

Thanks a lot !

What’s your integration for monitoring wind please ?

I pull data from an Ecowitt wittboy weather station.
https://shop.ecowitt.com/collections/lan-wlan-weather-station/products/wittboy

1 Like

Nws alerts card please :grin:

Just recently updated this one. It will now list and scroll through the warnings.


type: custom:button-card
tap_action:
  action: more-info
entity: sensor.nws_alerts
name: NWS Alerts
show_name: true
show_state: true
icon: ph:warning-duotone
custom_fields:
  list: |
    [[[
      const alerts = entity.attributes.Alerts;
      if (alerts && alerts.length > 0) {
        const numAlerts = alerts.length;
        const alertHeight = 50; // in pixels
        const timePerAlert = 5; // seconds per alert
        const animationDuration = numAlerts * timePerAlert

        let style = `<style>
          .auto-scroll {
            overflow: hidden;
            height: ${alertHeight}px;
          }
          .scroll-inner {
            animation: scrollAlerts ${animationDuration}s steps(${numAlerts}) infinite;
          }
          @keyframes scrollAlerts {
            from { transform: translateY(0); }
            to { transform: translateY(-${(numAlerts) * alertHeight}px); }
          }
          .alert-item {
            height: ${alertHeight}px;
            display: flex;
            align-items: center;
          }
        </style>`;
        
        // Build the alerts list
        let html = `<div class="auto-scroll"><div class="scroll-inner">`;
        alerts.forEach((a) => {
          html += `<div class="alert-item">${a.Event}</div>`;
        });
        html += `</div></div>`;
        
        return style + html;
      } else {
        return "No active alerts";
      }
    ]]]
styles:
  grid:
    - grid-template-areas: |
        "n n s" 
        "i list list"
    - grid-template-rows: min-content 1fr
    - grid-template-columns: min-content 1fr min-content
  card:
    - height: 115px
    - padding: 10px
    - background: var(--orange)
    - border-radius: 20px
  name:
    - font-size: 1.5rem
    - font-weight: 500
    - justify-self: start
    - align-self: start
    - padding-bottom: 10px
    - color: var(--contrast14)
    - padding-left: 5px
  icon:
    - color: var(--contrast14)
    - align-self: center
    - width: 2.5rem
    - height: 3rem
  img_cell:
    - justify-self: start
    - width: 2.6rem
  state:
    - font-size: 2rem
    - font-weight: 600
    - justify-self: start
    - align-self: start
    - color: var(--contrast14)
  custom_fields:
    list:
      - font-size: 1.5rem
      - font-weight: 500
      - color: var(--white)
      - text-align: left
      - padding-bottom: 5px
      - padding-left: 10px
      - align-self: end
state:
  - value: 0
    operator: "="
  - value: 1
    operator: ">="
    styles:
      card:
        - background: var(--red)
      name:
        - color: var(--white)
      state:
        - color: var(--white)
      icon:
        - color: var(--yellow)
  - value: unavailable
    styles:
      name:
        - text-decoration: line-through
      icon:
        - color: var(--contrast12)
      img_cell:
        - background: var(--contrast4)
      card:
        - opacity: 0.6
        - pointer-events: none
    label: Unknown
    icon: ph:question-duotone

Here are the links to the 2 svg files used in the wind card:
Compass Arrow
Compass Directions

1 Like