Custom UI: Weather state card

I would love to have this too, the current options for displaying weather (listing sensors) aren’t that great. Any update?

I would also love this feature!!

this would be awesome to implement!

would like to add this.
is there a update or something on it?

I would love this one too! Is there some progress jet?

Thanks for picking that up! Have very limited time at the moment…

1 Like

@eddi89, the legend!

I actually misread the thread and thought you had updated the weather state card to be this animated forecast by default; not the ability to do so

Can you link to the PR please…

What is the source of data? Can you share the config please?

Could something like this be implemented as an iframe that references the weather card generated elsewhere? For instance creating the necessary card with HTML and javascript residing in the /www folder… the using the iframe custom component card ( I saw one made here somewhere…) and the viola animated weather?

Is this possible to implement it as a secondary UI card? As per this manual? https://github.com/andrey-git/home-assistant-custom-ui/blob/master/docs/features.md

well custom cards won’t be merged until the frontend restructure.

here is a modified version to run as state-card

\\HASSIO\config\www\weather_icons\animated\*: https://www.amcharts.com/free-animated-svg-weather-icons/

\\HASSIO\config\www\custom_ui\custom-weather-card.html:

<dom-module id='custom-weather-card'>
  <template>
    <style>
      .clear {
        clear:both;
      }
      .card {
        margin:1em auto;
        padding-left: 1em;
        padding-right:1em;
        position: relative;
      }
      iron-icon {
        height: 20px;
        color: #c8c8c8;
      }
      .temp {
        font-weight: 300;
        font-size: 5em;
        color:#5b5b5b;
        position: absolute;
        right: .3em;
      }
      .variations {
        font-weight:300;
        color:#8c8c8c;
        list-style:none;
        margin-left:-2em;
        margin-top: 3.5em;
      }
      .variations.right {
        float: right;
        margin-left: 0;
        margin-right: 1em;
      }
      .unit {
        font-size:.8em;
      }
      .forecast {
        width:100%;
        margin:0 auto;
        height:9em;
      }
      .day {
        display:block;
        width: 25%;
        float:left;
        text-align:center;
        color: #5b5b5b;
        border-right:.1em solid #d9d9d9;
        line-height: 2;
        box-sizing: border-box;
      }
      .dayname {
        text-transform: uppercase;
      }

      .forecast .day:first-child {
        margin-left: 0;
      }

      .forecast .day:nth-last-child(2) {
        border-right:none;
        margin-right: 0;
      }
      .highTemp {
        font-weight:bold;
      }
      .lowTemp {
        color: #8c8c8c;
      }

      .icon.bigger {
          width: 10em;
          height: 10em;
          margin-top: -4em;
        position: absolute;
      }

      .icon {
          width: 34px;
          height: 34px;
          display: inline-block;
          vertical-align: middle;
          background-size: contain;
          background-position: center center;
          background-repeat: no-repeat;
          text-indent: -9999px;
      }
    </style>
    <div class="card">
      <span class="city"></span>
      <br>
      <i class="icon bigger" style="background: none, url(/local/weather_icons/animated/[[nowCond]].svg) no-repeat; background-size: contain;"></i>
      <span class="temp">[[roundedTemp]]&#176;</span>
      <span>
        <ul class="variations right">
          <template is="dom-if" if="[[weatherObj.attributes.humidity]]">
              <li><iron-icon icon="mdi:water-percent"></iron-icon>[[weatherObj.attributes.humidity]]<span class="unit">%</span></li>
        </template>
          <template is="dom-if" if="[[weatherObj.attributes.pressure]]">
              <li><iron-icon icon="mdi:gauge"></iron-icon>[[weatherObj.attributes.pressure]]<span class="unit">hPa</span></li>
        </template>
          <template is="dom-if" if="[[weatherObj.attributes.visibility]]">
              <li><iron-icon icon="mdi:weather-fog"></iron-icon>[[weatherObj.attributes.visibility]]<span class="unit">m</span></li>
        </template>
        </ul>
        <ul class="variations">
          <li>[[weatherObj.state]]</li>
          <template is="dom-if" if="[[weatherObj.attributes.wind_speed]]">
            <li><iron-icon icon="mdi:weather-windy"></iron-icon>[[windBearing]] [[weatherObj.attributes.wind_speed]]<span class="unit">m/s</span></li>
        </template>
        </ul>
      </span>

      <div class="forecast clear">
        <template is="dom-repeat" items="[[forecast]]">
        <div class="day"><span class="dayname">[[item.day]]</span>
          <template is="dom-if" if="[[item.condIcon]]">
          <br> <i class="icon" style="background: none, url(/local/weather_icons/animated/[[item.condIcon]].svg) no-repeat; background-size: contain;"></i> 
          </template>
          <template is="dom-if" if="[[item.tempHigh]]">
          <br> <span class="highTemp">[[item.tempHigh]]&#176;</span> 
          </template>
          <template is="dom-if" if="[[item.tempLow]]">
          <br> <span class="lowTemp">[[item.tempLow]]&#176;</span>
          </template>
        </div>
      </template>
      </div>
    </div>
  </template>
</dom-module>

<script>
(function () {
  'use strict';

  var _WEATHER_TO_ICON = {
    cloudy: 'cloudy',
    fog: 'cloudy',
    hail: 'rainy-7',
    lightning: 'lightning',
    'lightning-rainy': 'lightning',
    'Zwaar bewolkt': 'cloudy',
    partlycloudy: 'cloudy-day-2',
    pouring: 'rainy-7',
    rainy: 'rainy-5',
    snowy: 'snowy-5',
    'snowy-rainy': 'snowy-6',
    sunny: 'day',
    windy: 'cloudy',
    'windy-variant': '[]',
    exceptional: '!!',
  };

  var _DEGREE_TEXT = [
    'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE',
    'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N'
  ];

  Polymer({
    is: 'custom-weather-card',

    properties: {
      hass: {
        type: Object,
      },
      stateObj: {
        type: Object,
      },
      weatherObj: {
        type: Object,
        observer: 'checkRequirements',
        computed: 'comptuteWeatherObj(hass, stateObj)',
      },
    },

    comptuteWeatherObj: function (hass, stateObj) {
      return stateObj && stateObj.attributes && stateObj.attributes.config && stateObj.attributes.config.weather ? hass.states[stateObj.attributes.config.weather] : null;
    },

    getForecastArray: function () {
      if (!this.weatherObj.attributes.forecast) {
        return [];
      }
	  
      var data = this.weatherObj.attributes.forecast;
	  var forecast = [];
	  var prevDay = '';
	  
      for (var i = 0; i < data.length; i++) {
        var day = new Date(data[i].datetime).toString().split(' ')[0];
        if (day != prevDay) {
          if (data[i].max_temp) {
            var tempHigh = Math.round(data[i].max_temp * 10) / 10;
          } else {
            var tempHigh = Math.round(data[i].temperature * 10) / 10;
          }
          var tempLow = Math.round(data[i].min_temp * 10) / 10;
          var condIcon = _WEATHER_TO_ICON[data[i].condition];
          forecast.push({day:day, tempHigh:tempHigh, tempLow:tempLow, condIcon:condIcon});
          prevDay = day;
        } else {
          if (data[i].max_temp) {
            var tempHigh = Math.round(data[i].max_temp * 10) / 10;
          } else {
            var tempHigh = Math.round(data[i].temperature * 10) / 10;
          }
          var tempLow = Math.round(data[i].tempLow * 10) / 10;
          if (tempLow > forecast[forecast.length-1].tempHigh) {
            forecast[forecast.length-1].tempHigh = tempLow;
          }
          if (tempHigh > forecast[forecast.length-1].tempHigh) {
            forecast[forecast.length-1].tempHigh = tempHigh;
          }
          if (!forecast[forecast.length-1].tempLow) {
            forecast[forecast.length-1].tempLow = tempHigh;
          }
          if (tempHigh < forecast[forecast.length-1].tempLow) {
            forecast[forecast.length-1].tempLow = tempHigh;
          }
          if (tempLow < forecast[forecast.length-1].tempLow) {
            forecast[forecast.length-1].tempLow = tempLow;
          }
        }
      }

      return forecast;
    },

    checkRequirements: function () {
      if (!this.weatherObj) {
        return;
      }
  
      this.nowCond = _WEATHER_TO_ICON[this.weatherObj.state];
      if (this.weatherObj.attributes.temperature) {
      this.roundedTemp = Math.round( this.weatherObj.attributes.temperature * 10) / 10;
      }
      if (this.weatherObj.attributes.windBearing) {
      this.windBearing = this.windBearingToText(this.weatherObj.attributes.windBearing);
      }
	    this.forecast = this.getForecastArray().slice(0, 4);
    },

    windBearingToText: function (degree) {
      return _DEGREE_TEXT[((parseInt(degree) + 5.63) / 11.25) | 0];
    },
  });
}());
</script>

configuration.yaml

homeassistant:
  customize:
    input_boolean.weather:
      custom_ui_state_card: custom-weather-card
      config:
        weather: weather.yweather

frontend:
  extra_html_url:
    - /local/custom_ui/custom-weather-card.html
  extra_html_url_es5:
    - /local/custom_ui/custom-weather-card.html

weather:
  - platform: yweather

input_boolean:
  weather:

group:
  weather:
    name: Weather
    entities:
      - input_boolean.weather

config:
discovery:
7 Likes

Thank you so much for sharing that state card Eddi89 !!

Following your instructions, I had to correct a few things in the code of the state-card to make it work nicely, so I’ll add here if someone else need:
The lightning icon does not exist in the icon pack linked in the post so replace lightning to thunder for the icon name (not for the variable):

lightning: 'thunder',
 'lightning-rainy': 'thunder',

-the ‘data[i].min_temp’ must be replaced by ‘data[i].templow’
-the ‘data[i].tempLow’ must be replaced by ‘data[i].templow’

And then it all work well.
Thanks again!

1 Like

Thank you so much for this awesome state card, this should be the ha default weather card.

I made a change to add the possibility to change the language, just need to change the name in the mapping function inside the script.
I did also a small layout to improve the information display.
The result is this:
Nuova%20immagine%20bitmap

I have no java or html skill so i don’t know if this is the best way to do it, but work so i’m happy with it.
Thank again for sharing this.

I post the code in case someone need it or want to make the same change in a better way:

<dom-module id='custom-weather-card'>
  <template>
    <style>
      .clear {
        clear:both;
      }
      .card {
        margin:1em auto;
        padding-left: 1em;
        padding-right:1em;
        position: relative;
      }
      .iron-icon {
        height: 18px;
        color: #c8c8c8;
      }
      .temp {
        font-weight: 300;
        font-size: 5em;
        color:#5b5b5b;
        position: absolute;
        right: 0em;
      }
      .variations {
        font-weight:300;
        color:#8c8c8c;
        list-style:none;
        margin-left:-2em;
        margin-top: 2.5em;
      }
      .variations.right {
        float: right;
        margin-left: 0;
        margin-right: 1em;
      }
      .unit {
        font-size:.8em;
      }
      .forecast {
        width:100%;
        margin:0 auto;
        height:9em;
      }
      .day {
        display:block;
        width: 25%;
        float:left;
        text-align:center;
        color: #5b5b5b;
        border-right:.1em solid #d9d9d9;
        line-height: 2;
        box-sizing: border-box;
      }
      .dayname {
        text-transform: uppercase;
      }

      .forecast .day:first-child {
        margin-left: 0;
      }
      .forecast .day:nth-last-child(2) {
        border-right:none;
        margin-right: 0;
      }
      .highTemp {
        font-weight:bold;
      }
      .lowTemp {
        color: #8c8c8c;
      }
      .icon.bigger {
        width: 10em;
        height: 10em;
        margin-top: -4em;
        position: absolute;
        left: 0em;
      }
      .icon {
          width: 50px;
          height: 50px;
          display: inline-block;
          vertical-align: middle;
          background-size: contain;
          background-position: center center;
          background-repeat: no-repeat;
          text-indent: -9999px;
      }
       .weather {
        font-weight: 300;
        font-size: 1.5em;
        color:#5b5b5b;
        text-align:center;
        position: absolute;
        top: 0em;
        left: 6em;
      }

    </style>
    <div class="card">
      <span class="icon bigger" style="background: none, url(/local/weather_icons/animated/[[nowCond]].svg) no-repeat; background-size: contain;"></span>
      <span class="temp">[[roundedTemp]]&#176;</span>
      <span class="weather">[[nowCondIT]]</span>
      <br>
      <span>
        <ul class="variations right">
          <template is="dom-if" if="[[weatherObj.attributes.humidity]]">
              <li><iron-icon icon="mdi:water-percent"></iron-icon> Umidità [[weatherObj.attributes.humidity]]<span class="unit">%</span></li>
          </template>
          <template is="dom-if" if="[[weatherObj.attributes.pressure]]">
              <li><iron-icon icon="mdi:gauge"></iron-icon> Pressione [[weatherObj.attributes.pressure]]<span class="unit">hPa</span></li>
          </template>

        </ul>
        <ul class="variations">
          <template is="dom-if" if="[[weatherObj.attributes.wind_speed]]">
            <li><iron-icon icon="mdi:weather-windy"></iron-icon> Vento [[item.windBearing]] [[weatherObj.attributes.wind_speed]]<span class="unit">m/s</span></li>
          </template>
          <template is="dom-if" if="[[weatherObj.attributes.visibility]]">
              <li><iron-icon icon="mdi:weather-fog"></iron-icon> Visibilità [[weatherObj.attributes.visibility]]<span class="unit">m</span></li>
          </template>
        </ul>
      </span>

      <div class="forecast clear">
        <template is="dom-repeat" items="[[forecast]]">
        <div class="day"><span class="dayname">[[item.dayIT]]</span>
          <template is="dom-if" if="[[item.condIcon]]">
          <br> <i class="icon" style="background: none, url(/local/weather_icons/animated/[[item.condIcon]].svg) no-repeat; background-size: contain;"></i> 
          </template>
          <template is="dom-if" if="[[item.tempHigh]]">
          <br> <span class="highTemp">[[item.tempHigh]]&#176;</span> 
          </template>
          <template is="dom-if" if="[[item.tempLow]]">
          <br> <span class="lowTemp">[[item.tempLow]]&#176;</span>
          </template>
        </div>
      </template>
      </div>
    </div>
  </template>
</dom-module>

<script>
(function () {
  'use strict';

  var _WEATHER_TO_ICON = {
    cloudy: 'cloudy',
    fog: 'cloudy',
    hail: 'rainy-7',
    lightning: 'thunder',
    'lightning-rainy': 'thunder',
    partlycloudy: 'cloudy-day-3',
    pouring: 'rainy-6',
    rainy: 'rainy-5',
    snowy: 'snowy-6',
    'snowy-rainy': 'rainy-7',
    sunny: 'day',
    windy: 'cloudy',
    'windy-variant': 'cloudy-day-3',
    exceptional: '!!',
  };
  
  var _WEATHER_TO_NAME = {
    cloudy: 'Nuvoloso',
    fog: 'Nebbia',
    hail: 'Grandine',
    lightning: 'Temporale',
    'lightning-rainy': 'Temporale',
    partlycloudy: 'Parzialmente Nuvoloso',
    pouring: 'Pioggia Forte',
    rainy: 'Pioggia',
    snowy: 'Neve',
    'snowy-rainy': 'Nevischio',
    sunny: 'Soleggiato',
    windy: 'Ventoso',
    'windy-variant': 'Variabile Ventoso',
    exceptional: '!',
  };


  var _DEGREE_TEXT = [
    'N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SO', 'OSO', 'O', 'ONO', 'NO', 'NNO', 'N',
  ];
  
  var _DAY_TO_DAY = {
    Mon: 'lunedì',
    Tue: 'martedì',
    Wed : 'mercoledì',
    Thu: 'giovedì',
    Fri: 'venerdì',
    Sat: 'sabato',
    Sun: 'domemica',
  };

  Polymer({
    is: 'custom-weather-card',

    properties: {
      hass: {
        type: Object,
      },
      stateObj: {
        type: Object,
      },
      weatherObj: {
        type: Object,
        observer: 'checkRequirements',
        computed: 'comptuteWeatherObj(hass, stateObj)',
      },
    },

    comptuteWeatherObj: function (hass, stateObj) {
      return stateObj && stateObj.attributes && stateObj.attributes.config && stateObj.attributes.config.weather ? hass.states[stateObj.attributes.config.weather] : null;
    },

    getForecastArray: function () {
      if (!this.weatherObj.attributes.forecast) {
        return [];
      }
      
      var data = this.weatherObj.attributes.forecast;
      var forecast = [];
      var prevDay = '';
      
      for (var i = 0; i < data.length; i++) {
        var day = new Date(data[i].datetime).toString().split(' ')[0];
        if (day != prevDay) {
          if (data[i].max_temp) {
            var tempHigh = Math.round(data[i].max_temp * 10) / 10;
          } else {
            var tempHigh = Math.round(data[i].temperature * 10) / 10;
          }
          var tempLow = Math.round(data[i].templow * 10) / 10;
          var condIcon = _WEATHER_TO_ICON[data[i].condition];
          var dayIT = _DAY_TO_DAY[day];
          forecast.push({dayIT:dayIT, tempHigh:tempHigh, tempLow:tempLow, condIcon:condIcon});
          prevDay = day;
        } else {
          if (data[i].max_temp) {
            var tempHigh = Math.round(data[i].max_temp * 10) / 10;
          } else {
            var tempHigh = Math.round(data[i].temperature * 10) / 10;
          }
          var tempLow = Math.round(data[i].tempLow * 10) / 10;
          if (tempLow > forecast[forecast.length-1].tempHigh) {
            forecast[forecast.length-1].tempHigh = tempLow;
          }
          if (tempHigh > forecast[forecast.length-1].tempHigh) {
            forecast[forecast.length-1].tempHigh = tempHigh;
          }
          if (!forecast[forecast.length-1].tempLow) {
            forecast[forecast.length-1].tempLow = tempHigh;
          }
          if (tempHigh < forecast[forecast.length-1].tempLow) {
            forecast[forecast.length-1].tempLow = tempHigh;
          }
          if (tempLow < forecast[forecast.length-1].tempLow) {
            forecast[forecast.length-1].tempLow = tempLow;
          }
        }
      }

      return forecast;
    },

    checkRequirements: function () {
      if (!this.weatherObj) {
        return;
      }
  
      this.nowCond = _WEATHER_TO_ICON[this.weatherObj.state];
      this.nowCondIT = _WEATHER_TO_NAME[this.weatherObj.state];
      if (this.weatherObj.attributes.temperature) {
      this.roundedTemp = Math.round( this.weatherObj.attributes.temperature * 10) / 10;
      }
      if (this.weatherObj.attributes.windBearing) {
      this.windBearing = this.windBearingToText(this.weatherObj.attributes.windBearing);
      }
        this.forecast = this.getForecastArray().slice(0, 4);
    },

    windBearingToText: function (degree) {
      return _DEGREE_TEXT[((parseInt(degree) + 5.63) / 11.25) | 0];
    },
  });
}());
</script>
15 Likes

Wow, this is great!
Thanks @eddi89, @PItax and @Bram_Kragten :smile:

1 Like

I followed all of the config but just getting a blank card. Any ideas what I missed? I’m using Safari on both a Mac and iOS. My other custom_ui files work ok. But I usually have a different es5 version. Could that be it?

53%20PM

Try to remove all unnecessary lines in the weather component

weather:
  - platform: yweather

refresh your browser cache

I found out it’s Yahoo Weather which isn’t properly getting my location without setting the WOEID.