Makin-Things / platinum-weather-card

This is a fully customisable weather card for Home Assistant with a graphical configuration.
MIT License
147 stars 33 forks source link

Add graph #40

Open Mariusthvdb opened 2 years ago

Mariusthvdb commented 2 years ago

Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Not an issue but a FR: please add graph like we had in the former weather-card-chart

Describe the solution you'd like A clear and concise description of what you want to happen.

Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered.

Ive made my own graph card for now, which essentially is the same card, but cut out all the other bits, to only leave the graph section:

Schermafbeelding 2022-07-24 om 12 30 23

Additional context Add any other context or screenshots about the feature request here.

Main idea was to glue these 2 together :

Schermafbeelding 2022-07-24 om 12 33 23

but since you're in the making of a 'definitive' card, it would be cool if you would add it optionally.

code:

console.info(
  `%c  WEATHER-CHART  \n%c  Version 0.2`,
  'color: orange; font-weight: bold; background: black',
  'color: white; font-weight: bold; background: dimgray',
);

const locale = {
  da: {
    tempHi: "Temperatur",
    tempLo: "Temperatur nat",
    precip: "Nedbør",
    uPress: "hPa",
    uSpeed: "m/s",
    uPrecip: "mm",
    uVisib: "km",
    cardinalDirections: [
      'N', 'N-NØ', 'NØ', 'Ø-NØ', 'Ø', 'Ø-SØ', 'SØ', 'S-SØ',
      'S', 'S-SV', 'SV', 'V-SV', 'V', 'V-NV', 'NV', 'N-NV', 'N'
    ]
  },
  en: {
    tempHi: "Temperature",
    tempLo: "Temperature night",
    precip: "Precipitations",
    uPress: "hPa",
    uSpeed: "km/h",
    uPrecip: "mm",
    uVisib: "mi",
    cardinalDirections: [
      'N', 'N-NE', 'NE', 'E-NE', 'E', 'E-SE', 'SE', 'S-SE',
      'S', 'S-SW', 'SW', 'W-SW', 'W', 'W-NW', 'NW', 'N-NW', 'N'
    ]
  },
  fr: {
    tempHi: "Température",
    tempLo: "Température nuit",
    precip: "Précipitations",
    uPress: "hPa",
    uSpeed: "m/s",
    uPrecip: "mm",
    uVisib: "km",
    cardinalDirections: [
      'N', 'N-NE', 'NE', 'E-NE', 'E', 'E-SE', 'SE', 'S-SE',
      'S', 'S-SO', 'SO', 'O-SO', 'O', 'O-NO', 'NO', 'N-NO', 'N'
    ]
  },
  nl: {
    tempHi: "Max temperatuur",
    tempLo: "Min temperatuur",
    precip: "Neerslag",
    uPress: "hPa",
    uSpeed: "km/u",
    uPrecip: "mm",
    uVisib: "km",
    cardinalDirections: [
      'N', 'N-NO', 'NO', 'O-NO', 'O', 'O-ZO', 'ZO', 'Z-ZO',
      'Z', 'Z-ZW', 'ZW', 'W-ZW', 'W', 'W-NW', 'NW', 'N-NW', 'N'
    ]
  },
  ru: {
    tempHi: "Температура",
    tempLo: "Температура ночью",
    precip: "Осадки",
    uPress: "гПа",
    uSpeed: "м/с",
    uPrecip: "мм",
    uVisib: "км",
    cardinalDirections: [
      'С', 'С-СВ', 'СВ', 'В-СВ', 'В', 'В-ЮВ', 'ЮВ', 'Ю-ЮВ',
      'Ю', 'Ю-ЮЗ', 'ЮЗ', 'З-ЮЗ', 'З', 'З-СЗ', 'СЗ', 'С-СЗ', 'С'
    ]
  }
};

class WeatherChart extends Polymer.Element {

  static get template() {
    return Polymer.html`
      <style>
        ha-icon {
          color: var(--paper-item-icon-color);
        }
        .card {
          padding: 16px;
        }
      </style>
      <ha-card header="[[title]]">
        <div class="card">
          <ha-chart-base data="[[ChartData]]" options="[[ChartOptions]]" ></ha-chart-base>
        </div>
      </ha-card>
    `;
  }

  static get properties() {
    return {
      config: Object,
      mode: String,
      weatherObj: {
        type: Object,
        observer: 'dataChanged',
      },
    };
  }

  setConfig(config) {
    this.config = config;
    this.title = config.title;
    this.weatherObj = config.weather;
    this.mode = config.mode;
    if (!config.weather) {
      throw new Error('Please define "weather" entity in the card config');
    }
  }

  set hass(hass) {
    this._hass = hass;
    this.lang = this.config.locale in hass.states ? hass.states[this.config.locale] : this._hass.language;
    this.weatherObj = this.config.weather in hass.states ? hass.states[this.config.weather] : null;
    this.forecast = this.weatherObj.attributes.forecast.slice(0,9);
  }

  dataChanged() {
    this.drawChart();
  }

  ll(str) {
    if (locale[this.lang] === undefined)
      return locale.en[str];
    return locale[this.lang][str];
  }

  drawChart() {
    var data = this.weatherObj.attributes.forecast.slice(0,9);
    var tempUnit = this._hass.config.unit_system.temperature;
    var lengthUnit = this._hass.config.unit_system.length;
    var precipUnit = lengthUnit === 'km' ? this.ll('uPrecip') : 'in';
    var mode = this.mode;
    var i;
    if (!this.weatherObj.attributes.forecast) {
      return [];
    }
    var dateTime = [];
    var tempHigh = [];
    var tempLow = [];
    var precip = [];
    for (i = 0; i < data.length; i++) {
      var d = data[i];
      dateTime.push(new Date(d.datetime));
      tempHigh.push(d.temperature);
      tempLow.push(d.templow);
      precip.push(d.precipitation);
    }
    var style = getComputedStyle(document.body);
    var textColor = style.getPropertyValue('--primary-text-color');
    var dividerColor = style.getPropertyValue('--divider-color');
    const chartData = {
        labels: dateTime,
        datasets: [
          {
            label: this.ll('tempHi'),
            type: 'line',
            data: tempHigh,
            yAxisID: 'yTempAxis',
            borderColor: 'red',
            backgroundColor: 'orange',
            borderWidth: 2.0,
            lineTension: 0.4,
            pointRadius: 1.0,
            pointHitRadius: 5.0,
            fill: false,

            tooltip: {
             callbacks: {
               label: function(context) {
                 var label = context.dataset.label || '';
                 return label += ': ' + context.parsed.y + tempUnit;
               }
             }
           }
           },

          {
            label: this.ll('tempLo'),
            type: 'line',
            data: tempLow,
            yAxisID: 'yTempAxis',
            borderColor: 'green',
            backgroundColor: 'purple',
            borderWidth: 2.0,
            lineTension: 0.4,
            pointRadius: 0.0,
            pointHitRadius: 5.0,
            fill: false,

            tooltip: {
             callbacks: {
               label: function(context) {
                 var label = context.dataset.label || '';
                 return label += ': ' + context.parsed.y + tempUnit;
               }
             }
           }
          },
          {
            label: this.ll('precip'),
            type: 'bar',
            data: precip,
            yAxisID: 'yPrecipAxis',
            borderColor: 'skyblue',
            backgroundColor: 'steelblue',
            maxBarThickness: 15,
            barThickness: 8,
            tooltip: {
             callbacks: {
               label: function(context) {
                 var label = context.dataset.label || '';
                 return label += ': ' + context.parsed.y + precipUnit;
               }
             }
           }
          },
        ]
      }

    const chartOptions = {
        animation: {
          duration: 300,
          easing: 'linear',
          onComplete: function (animation) {
            var chartInstance = animation.chart,
              ctx = chartInstance.ctx;
            ctx.fillStyle = textColor;
            var fontSize = 10;
            var fontStyle = 'normal';
            var fontFamily = 'Roboto';
            ctx.font = fontStyle + ' ' + fontSize + 'px ' + fontFamily;
            ctx.textAlign = 'center';
            ctx.textBaseline = 'bottom';
            var meta = chartInstance.getDatasetMeta(2);
            meta.data.forEach(function (bar, index) {
              var data = (Math.round((chartInstance.data.datasets[2].data[index]) * 10) / 10).toFixed(1);
              if (data > 0)
                 ctx.fillText(data, bar.x, bar.y - 5);
            });
          },
        },
        legend: {
          display: false,
        },
        scales: {
          xAxes: {
            type: 'time',
            adapters: {
              date: {
                locale: this._hass.locale,
              },
            },
//             display: true,
            ticks: {
              display: false,
            },
            grid: {
              display: false,
            },
          },
          xDateAxis: {
            type: 'time',
            position: 'top',
            adapters: {
              date: {
                locale: this._hass.locale,
              },
            },
            grid: {
              display: true,
              drawBorder: false,
              color: dividerColor,
            },
            ticks: {
              display: true,
              source: 'labels',
              autoSkip: true,
              color: textColor,
              maxRotation: 0,
              callback: function(value, index, values) {
                var date = new Date(0);
                date.setUTCMilliseconds(values[index].value);
                if (mode == 'hourly') {
                  return date.toLocaleTimeString(locale, { hour: 'numeric' });
                }
                return date.toLocaleDateString(locale, { weekday: 'short' });;
              },
            },
          },
          yTempAxis: {
            position: 'left',
            adapters: {
              date: {
                locale: this._hass.locale,
              },
            },
            grid: {
              display: true,
              drawBorder: false,
              color: dividerColor,
              borderDash: [1,3],
            },
            ticks: {
              display: true,
              color: textColor,
              callback: function(value, index, values) {
                 return value + 'º';
               },
            },
            afterFit: function(scaleInstance) {
              scaleInstance.width = 37;
            },
          },
          yPrecipAxis: {
            display: false,
            position: 'right',
            suggestedMax: 20,
            adapters: {
              date: {
                locale: this._hass.locale,
              },
            },
            grid: {
              display: false,
              drawBorder: false,
              color: dividerColor,
            },
            ticks: {
              display: false,
              min: 0,
              color: textColor,
            },
            afterFit: function(scaleInstance) {
              scaleInstance.width = 15;
            },
          },
        },
        plugins: {
           tooltip: {
             callbacks: {
               title: function(context) {
                 return new Date(context[0].label).toLocaleDateString(locale, {
                   month: 'long',
                   day: 'numeric',
                   weekday: 'long',
//                    hour: 'numeric',
//                    minute: 'numeric',
                 });
              }
            }
           }
        },
      }
    this.ChartData = chartData;
    this.ChartOptions = chartOptions;
/*    this.ChartType = chartType;*/
  }

  _fire(type, detail, options) {
    const node = this.shadowRoot;
    options = options || {};
    detail = (detail === null || detail === undefined) ? {} : detail;
    const e = new Event(type, {
      bubbles: options.bubbles === undefined ? true : options.bubbles,
      cancelable: Boolean(options.cancelable),
      composed: options.composed === undefined ? true : options.composed
    });
    e.detail = detail;
    node.dispatchEvent(e);
    return e;
  }
}

customElements.define('weather-chart', WeatherChart);
Makin-Things commented 2 years ago

Things like this are planned, but it won't happen for a while. I need to get the card to a stable release before I start adding stuff.