Custom animated weather card for Lovelace

you can find a very good readme on github

maybe this helps. it works for me

Thanks but i already have done this, whenever i trey to create to card 0.84) i am getting this error

2019-01-09 17:32:55 ERROR (MainThread) [frontend.js.latest.201812112] http://10.3.1.2:8123/frontend_latest/73dd7f5c04d8a295f213.chunk.js:14:1162 Uncaught NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

youre post show type js but githob shows type module

I migrated this card to Lit for better performance, it can be found here:

3 Likes

I also like to convert from the Dark sky platform to the Buienradar platform but I don’t have a clue how to map or convert from buienradar icons to the animated icons. Are you willing to share your code ?

why of course:

this is the Lovelace card:

cards:
  - type: custom:useful-markdown-card
    content: >
      **Weersverwachting:** <font color=var(--secondary-text-color)>[[ sensor.vandaag.state ]]</font> 
                [[ sensor.dark_sky_daily_summary.state ]]
  - type: custom:card-modder
    style:
      background-image: url("/local/lovelace/images/weather-background-[[ sun.sun.state ]].png")
      background-size: 100% 400px
      --primary-text-color: var(--primary-text-color)
      --secondary-text-color: var(--secondary-text-color)
      --paper-item-icon-color: var(--primary-text-color) #small variation icons
    card:
      type: custom:weather-card
      title: Modded Woensdrecht
      entity_weather: weather.woensdrecht
      entity_sun: sun.sun
      entity_apparent_temperature: sensor.jagti_windchill
      entity_wind_force: sensor.br_wind_force

weather.woensdrecht is my Buienradar weather component, and the apparent temperature sensor is my own template sensor:

  jagti_windchill:
    value_template: >
      {% set temp = states('sensor.br_temperature')|float %}
      {% set wind = states('sensor.br_wind_speed')|float %}
      {{(13.12 +0.6215*temp  + 0.3965*(temp - 28.676)*(wind**0.16)) | round(2) }}
    unit_of_measurement: '°C'
    device_class: temperature
    friendly_name: Jag/Ti Wchill

I did change several settings in the weather-card and .js file:

weather-card.css:

.clear {
    clear: both;
  }

  .card {
    margin: auto;
    padding-top: 2em;
    padding-bottom: 1em;
    padding-left: 1em;
    padding-right:1em;
    position: relative;
  }

  .header {
    font-weight: 400;
    font-size: 2em;
    color: var(--primary-text-color);
    text-align: left;
    position: absolute;
    top: 1em;
    left: 1em;
    text-transform: capitalize;
    word-wrap: break-word;
    width: 50%;
  }

  .weather {
    font-weight: 400;
    font-size: 2em;
    color: grey;
    position: absolute;
    margin-right: 0.5em;
    top: 1em;
    right: 0em;
    text-transform: capitalize;
#    word-wrap: break-word;
#    width: 40%;
  }

  .icon.bigger {
    width: 12em;
    height: 12em;
    left: 0em;
  }

  .ha-icon {
    height: 18px;
    margin-right: 5px;
    color: var(--paper-item-icon-color);
  }

  .temp {
    font-weight: 300;
    font-size: 4em;
    color: var(--primary-text-color);
    position: absolute;
    top: 1.5em;
    right: 1em;
  }

  .tempa {
    position: absolute;
    color: var(--primary-text-color);
    right: 1em;
    font-size: 1em;
    font-weight: 300;
    margin-right: 2em;
    margin-top: -5.5em;
  }

  .unitc {
    position: absolute;
    color: var(--secondary-text-color);
    font-weight: 300;
    font-size: 1em;
    right: .6em;
    vertical-align: super;
    margin-left: 0;
    margin-right: 1em;
    margin-top: -5.5em;
  }

  .tempc {
    position: absolute;
    color: var(--secondary-text-color);
    font-weight: 300;
    font-size: 1.5em;
    vertical-align: super;
    right: 1em;
    margin-top: -6em;
    margin-right: 7px;
  }

  .variations {
    display: inline-block;
    font-weight: 300;
    color: var(--primary-text-color);
    list-style: none;
    margin-left: -2em;
    margin-top: -2em;
  }

  .variations.right {
    position: absolute;
    right: 1em;
    margin-left: 0;
    margin-right: 1em;
  }

  .unit {
    font-size: .8em;
  }

  .forecast {
    width: 100%;
    margin: 0 auto;
    height: 9em;
  }

  .day {
    display: block;
    width: 20%;
    float: left;
    text-align: center;
    color: var(--primary-text-color);
    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(1) {
    border-right: none;
    margin-right: 0;
  }

  .highTemp {
    font-weight: bold;
  }

  .lowTemp {
    color: var(--secondary-text-color);
  }

  .icon {
    width: 50px;
    height: 50px;
    margin-right: 5px;
    display: inline-block;
    vertical-align: middle;
    background-size: contain;
    background-position: center center;
    background-repeat: no-repeat;
    text-indent: -9999px;
  }

weather-card.js:

class WeatherCard extends HTMLElement {
  set hass(hass) {
    if (!this.content) {
      const card = document.createElement('ha-card');
      const link = document.createElement('link');
      link.type = 'text/css';
      link.rel = 'stylesheet';
      link.href = '/local/lovelace/resources/weather-card.css';
      card.appendChild(link);
      this.content = document.createElement('div');
      this.content.className = 'card';
      card.appendChild(this.content);
      this.appendChild(card);
    }

    const getUnit = function (measure) {
      const lengthUnit = hass.config.unit_system.length;
      switch (measure) {
        case 'air_pressure':
          return lengthUnit === 'km' ? 'hPa' : 'inHg';
        case 'length':
          return lengthUnit;
        case 'visibility':
          return lengthUnit === 'km' ? 'm' : 'in';
        case 'precipitation':
          return lengthUnit === 'km' ? 'mm' : 'in';
        default:
          return hass.config.unit_system[measure] || '';
      }
    };

    const transformDayNight = {
      "below_horizon": "night",
      "above_horizon": "day",
    }
    const sunLocation = transformDayNight[hass.states[this.config.entity_sun].state];
    const weatherIcons = {
      'clear-night': `${sunLocation}`,
      'cloudy': 'cloudy',
      'fog': 'cloudy',
      'hail': 'rainy-7',
      'lightning': 'thunder',
      'lightning-rainy': 'thunder',
      'partlycloudy': `cloudy-${sunLocation}-3`,
      'pouring': 'rainy-6',
      'rainy': 'rainy-5',
      'snowy': 'snowy-6',
      'snowy-rainy': 'rainy-7',
      'sunny': `${sunLocation}`,
      'windy': 'cloudy',
      'windy-variant': `cloudy-${sunLocation}-3`,
      'exceptional': '!!',
    }

    const windDirections = ['N','NNO','NO','ONO',
                            'O','OZO','ZO','ZZO',
                            'Z','ZZW','ZW','WZW',
                            'W','WNW','NW','NNW',
                            'N'];

    const entity = hass.states[this.config.entity_weather];
    const station = entity.attributes.friendly_name;
    const currentCondition = entity.state;
    const humidity = entity.attributes.humidity;
    const pressure = entity.attributes.pressure;
    const temperature = entity.attributes.temperature;
    const visibility = entity.attributes.visibility;
    const windBearing = windDirections[(parseInt((entity.attributes.wind_bearing + 11.25) / 22.5))];
    const windSpeed = entity.attributes.wind_speed;
    const windBft = hass.states[this.config.entity_wind_force].state;
    const forecast = entity.attributes.forecast.slice(0, 5);
    const apparent_temperature = hass.states[this.config.entity_apparent_temperature].state;

    var sunSetOrRiseA = new Date(hass.states[this.config.entity_sun].attributes.next_setting);
    var sunSetOrRiseIconA = "mdi:weather-sunset-down";
    var sunSetOrRiseB = new Date(hass.states[this.config.entity_sun].attributes.next_rising);
    var sunSetOrRiseIconB = "mdi:weather-sunset-up";
    // A == sunset   B == sunrise
    if ( hass.states[this.config.entity_sun].state == "above_horizon" ) {
        // sun has risen, but hasn't set
        // sunset is today (no date displayed). sunrise is tomorrow (display date)
        // next is sunset == A
        
        sunSetOrRiseA = sunSetOrRiseA.toLocaleTimeString();
        var ssrI = sunSetOrRiseA.lastIndexOf(":");
        sunSetOrRiseA = sunSetOrRiseA.substr(0,ssrI) + sunSetOrRiseA.substr(ssrI+4);
        sunSetOrRiseB = sunSetOrRiseB.toDateString().substr(0,3) + " " + sunSetOrRiseB.toLocaleTimeString();
        ssrI = sunSetOrRiseB.lastIndexOf(":");
        sunSetOrRiseB = sunSetOrRiseB.substr(0,ssrI) + sunSetOrRiseB.substr(ssrI+4);
    } else {
        // next is sunrise == B
        var ss = sunSetOrRiseA;
        if ( new Date().getDate() != sunSetOrRiseB.getDate() ) {
            // sun hasn't risen, and it's not same day
            // so display dates for both
            sunSetOrRiseA = sunSetOrRiseB.toDateString().substr(0,3) + " " + sunSetOrRiseB.toLocaleTimeString();
            sunSetOrRiseB = ss.toDateString().substr(0,3) + " " + ss.toLocaleTimeString();
        } else {
            // sun hasn't risen, but it's the same day
            // since rise and set are today, no dates displayed
            sunSetOrRiseA = sunSetOrRiseB.toLocaleTimeString();
            sunSetOrRiseB = ss.toLocaleTimeString();
        }
        var ssrI = sunSetOrRiseA.lastIndexOf(":");
        sunSetOrRiseA = sunSetOrRiseA.substr(0,ssrI) + sunSetOrRiseA.substr(ssrI+4);
        ssrI = sunSetOrRiseB.lastIndexOf(":");
        sunSetOrRiseB = sunSetOrRiseB.substr(0,ssrI) + sunSetOrRiseB.substr(ssrI+4);
        sunSetOrRiseIconA = "mdi:weather-sunset-up";
        sunSetOrRiseIconB = "mdi:weather-sunset-down";
    }

    this.content.innerHTML = `
      <span class="header" >${currentCondition}</span><span class="weather">${station}</span>
      <span class="icon bigger" style="background: none, url(/local/weather/animated/${weatherIcons[currentCondition]}.svg) no-repeat; background-size: contain;"> </span>

      <ul style="list-style-type:none">
        <li><span class="temp">${temperature}</span><span class="tempc"> ${getUnit('temperature')}</span></li>
        <li><span class="tempa">Feels like ${apparent_temperature}</span><span class="unitc"> ${getUnit('temperature')}</span></li>
      </ul>
      <span>
        <ul class="variations right">
            <li><span class="ha-icon"><ha-icon icon="mdi:water-percent"></ha-icon></span>${humidity}<span class="unit"> %</span></li>
            <li><span class="ha-icon"><ha-icon icon="mdi:gauge"></ha-icon></span>${pressure}<span class="unit"> ${getUnit('air_pressure')}</span></li>
            <li><span class="ha-icon"><ha-icon icon=${sunSetOrRiseIconB}></ha-icon></span>${sunSetOrRiseB}</li>
        </ul>
        <ul class="variations">
            <li><span class="ha-icon"><ha-icon icon="mdi:weather-windy"></ha-icon></span>Bft: ${windBft} - ${windBearing} ${windSpeed}<span class="unit"> ${getUnit('length')}/h</span></li>
            <li><span class="ha-icon"><ha-icon icon="mdi:weather-fog"></ha-icon></span>${visibility}<span class="unit"> ${getUnit('visibility')}</span></li>
            <li><span class="ha-icon"><ha-icon icon=${sunSetOrRiseIconA}></ha-icon></span>${sunSetOrRiseA}</li>
        </ul>
      </span>
      <div class="forecast clear">
          ${forecast.map(daily => `
              <div class="day">
                  <span class="dayname">${(new Date(daily.datetime)).toLocaleDateString((navigator.language) ? navigator.language : navigator.userLanguage, {weekday: 'short'}).split(' ')[0]}</span>
                  <br><i class="icon" style="background: none, url(/local/weather/animated/forecast/${weatherIcons[daily.condition]}.svg);"></i>
                  <br><span class="highTemp">${daily.temperature}${getUnit('temperature')}</span>
                  <br><span class="lowTemp">${daily.templow}${getUnit('temperature')}</span>
              </div>`).join('')}
      </div>`;
  }

  setConfig(config) {
    if (!config.entity_weather || !config.entity_sun) {
      throw new Error('Please define entities');
    }
    this.config = config;
  }

  getCardSize() {
    return 3;
  }
}

customElements.define('weather-card', WeatherCard);

HI Bram,

what exactly is that Lit? and why is this better for performance? I always notice heavy fanning when on the weather page, so very interested if a lighter weather card could solve that.

Lit:

A simple base class for creating fast, lightweight web components

We use it for Lovelace instead of Polymer.

The old card would be rebuilt on every state change in hass, so even if a light would be updated, the weather card is rebuilt.

Now it will update if the weather state is changed, like the built-in cards do it.

1 Like

Thanks for the code. As a “dummy” i’ll have to dive in to it to find out how it works but finally I will get there .

sounds good :wink:
will update swiftly and hope my ole Air will be quieter…

thanks for taking the effort explaining

quick look for the night/day icons doesn’t seem this new card has it built-in? In my current card I use this, maybe you would be willing to incorporate that too @Bram_Kragten ?

Always a silly sight when a clear night show a radiating sun…

const transformDayNight = {
  "below_horizon": "night",
  "above_horizon": "day",
}
const sunLocation = transformDayNight[hass.states[this.config.entity_sun].state];
const weatherIcons = {
  'clear-night': `${sunLocation}`,
  'cloudy': 'cloudy',
  'fog': 'cloudy',
  'hail': 'rainy-7',
  'lightning': 'thunder',
  'lightning-rainy': 'thunder',
  'partlycloudy': `cloudy-${sunLocation}-3`,
  'pouring': 'rainy-6',
  'rainy': 'rainy-5',
  'snowy': 'snowy-6',
  'snowy-rainy': 'rainy-7',
  'sunny': `${sunLocation}`,
  'windy': 'cloudy',
  'windy-variant': `cloudy-${sunLocation}-3`,
  'exceptional': '!!',
}

thanks for considering!

I does have it, just works different:

1 Like

Using it, works fine!
Only the description in the readme is wrong.
It says:

  1. Save, the amcharts 1.9k icon under

www\icons\weather_icons\animated

But looking in the file I see

/local/custom_ui/weather_icons/animated/

Just my 2 cents :slight_smile:

Thanks will fix!

cool. sorry for my confusion. should have read it more carefully.
thanks!

now only need to ask about the sun up/down and apparent temp icons/sensors, are they there too?

If I check the card on your repo, they seem to be omitted?
33

Can fiddle in the wind force Bft myself I guess…

(never mind the background, which is a modded card)

24

That can be added… What card is this? Where is the source? It wasn’t in the source of the card in the start post… (the screenshot is a old one btw, should update it)

that would be magic! I posted the card about an hour ago, few posts up:

I get this error after updating to 0.85.0 release:

https://myduckdns.duckdns.org/local/custom_ui/weather-card.js?v=1.0.0:1:8 Uncaught SyntaxError: Unexpected token {

How to solve?

I got the Monday blues using Dark Sky weather!

I’ve seen others post this, but didn’t see solution.

Can someone please point in the right direction?
image

When using Dark Sky you should put the mode to daily if you want a daily forecast with highs and lows.

# Example configuration.yaml entry
weather:
  - platform: darksky
    api_key: YOUR_API_KEY
    mode: daily

What browser do you use? This card uses very new HTML/JS specs that are only supported in the most recent browsers.