import { Component, Input, ElementRef, OnChanges } from '@angular/core'; import { RdLib } from "../../../base/rdLib"; import { BaseHighChart } from './baseHighChart'; import { Portlet } from "../../portlet/portlet"; import { ScriptLoaderService } from "../../../library/script-loader.service"; import { themes } from './themes'; declare const jQuery; declare const Highcharts; /* url = 'https://www.yr.no/place/' + place + '/forecast_hour_by_hour.xml'; */ @Component({ selector: 'rd-highchart-meteogram', template: `
` }) export class Meteogram extends BaseHighChart implements OnChanges { constructor(element: ElementRef, portlet: Portlet, private script: ScriptLoaderService) { super(element, portlet, script); this.script.load([ "./assets/js/highcharts/highcharts.js", "./assets/js/highcharts/windbarb.js", "./assets/js/highcharts/pattern-fill.js", "./assets/js/highcharts/exporting.js", "./assets/js/highcharts/export-data.js" ]) .then(() => Highcharts.setOptions(themes.default)) } @Input("rd-place") place: string; // country/province/county symbols = []; precipitations = []; precipitationsError = []; winds = []; temperatures = []; pressures = []; resolution; xml; hasPrecipitationError; themeSwitch; province; block = false; themes = themes; ngOnChanges(changes) { if (changes.place.currentValue) { this.province = this.place.substring(this.place.indexOf("/") + 1, this.place.indexOf("/", this.place.indexOf("/") + 1)); this.getData(); } } getData() { this.block = true; RdLib.serviceCall.xmlHttpGet("/place/" + this.place + "/forecast_hour_by_hour.xml").then((resp) => { let parser = new DOMParser(); this.xml = parser.parseFromString(resp, "text/xml"); this.resetLists(); this.parseYrData(); this.block = false; }) .catch((err) => { this.block = false; RdLib.screenOperations.toastr.warning("Forecast not found", this.place); }); } parseYrData() { var xml = this.xml, pointStart, forecast = xml && xml.querySelector('forecast'); if (!forecast) { console.log("error") } Highcharts.each( forecast.querySelectorAll('tabular time'), function (time, i) { var from = time.getAttribute('from') + ' UTC', to = time.getAttribute('to') + ' UTC'; from = from.replace(/-/g, '/').replace('T', ' '); from = Date.parse(from).toString(); to = to.replace(/-/g, '/').replace('T', ' '); to = Date.parse(to).toString(); if (to > pointStart + 4 * 24 * 36e5) { return; } if (i === 0) { this.resolution = parseInt(to) - parseInt(from); } this.symbols.push( time.querySelector('symbol').getAttribute('var') .match(/[0-9]{2}[dnm]?/)[0] ); this.temperatures.push({ x: parseInt(from), y: parseInt( time.querySelector('temperature').getAttribute('value'), 10 ), to: parseInt(to), symbolName: time.querySelector('symbol').getAttribute('name') }); var precipitation = time.querySelector('precipitation'); this.precipitations.push({ x: parseInt(from), y: parseFloat( Highcharts.pick( precipitation.getAttribute('minvalue'), precipitation.getAttribute('value') ) ) }); if (precipitation.getAttribute('maxvalue')) { this.hasPrecipitationError = true; this.precipitationsError.push({ x: parseInt(from), y: parseFloat(precipitation.getAttribute('maxvalue')), minvalue: parseFloat(precipitation.getAttribute('minvalue')), maxvalue: parseFloat(precipitation.getAttribute('maxvalue')), value: parseFloat(precipitation.getAttribute('value')) }); } if (i % 2 === 0) { this.winds.push({ x: parseInt(from), value: parseFloat(time.querySelector('windSpeed') .getAttribute('mps')), direction: parseFloat(time.querySelector('windDirection') .getAttribute('deg')) }); } this.pressures.push({ x: parseInt(from), y: parseFloat(time.querySelector('pressure').getAttribute('value')) }); if (i === 0) { pointStart = (parseInt(from) + parseInt(to)) / 2; } }.bind(this) ); this.smoothLine(this.temperatures); this.render(); } smoothLine(data) { var i = data.length, sum, value; while (i--) { data[i].value = value = data[i].y; sum = (data[i - 1] || data[i]).y + value + (data[i + 1] || data[i]).y; data[i].y = Math.max(value - 0.5, Math.min(sum / 3, value + 0.5)); } } render() { this.chart = new Highcharts.Chart(this.getChartOptions(), (chart) => { this.onChartLoad(chart); }); } getChartOptions() { return { chart: { renderTo: this.container, marginBottom: 70, marginRight: 40, marginTop: 50, plotBorderWidth: 1, height: 310, alignTicks: false, scrollablePlotArea: { minWidth: 720 } }, defs: { patterns: [{ id: 'precipitation-error', path: { d: [ 'M', 3.3, 0, 'L', -6.7, 10, 'M', 6.7, 0, 'L', -3.3, 10, 'M', 10, 0, 'L', 0, 10, 'M', 13.3, 0, 'L', 3.3, 10, 'M', 16.7, 0, 'L', 6.7, 10 ].join(' '), stroke: '#68CFE8', strokeWidth: 1 } }] }, title: { text: this.getTitle(), align: 'left', style: { whiteSpace: 'nowrap', textOverflow: 'ellipsis' } }, credits: { text: '', href: "#", position: { x: -40 } }, tooltip: { shared: true, useHTML: true, headerFormat: '{point.x:%A, %b %e, %H:%M} - {point.point.to:%H:%M}
' + '{point.point.symbolName}
' }, xAxis: [{ // Bottom X axis type: 'datetime', tickInterval: 2 * 36e5, // two hours minorTickInterval: 36e5, // one hour tickLength: 0, gridLineWidth: 1, gridLineColor: 'rgba(128, 128, 128, 0.1)', startOnTick: false, endOnTick: false, minPadding: 0, maxPadding: 0, offset: 30, showLastLabel: true, labels: { format: '{value:%H}' }, crosshair: true }, { // Top X axis linkedTo: 0, type: 'datetime', tickInterval: 24 * 3600 * 1000, labels: { format: '{value:%a %b %e}', align: 'left', x: 3, y: -5 }, opposite: true, tickLength: 20, gridLineWidth: 1 }], yAxis: [{ // temperature axis title: { text: null }, labels: { format: '{value}°', style: { fontSize: '10px' }, x: -3 }, plotLines: [{ // zero plane value: 0, color: '#BBBBBB', width: 1, zIndex: 2 }], maxPadding: 0.3, minRange: 8, tickInterval: 1, gridLineColor: 'rgba(128, 128, 128, 0.1)' }, { // precipitation axis title: { text: null }, labels: { enabled: false }, gridLineWidth: 0, tickLength: 0, minRange: 10, min: 0 }, { // Air pressure allowDecimals: false, title: { // Title on top of axis text: 'hPa', offset: 0, align: 'high', rotation: 0, style: { fontSize: '10px', color: Highcharts.getOptions().colors[2] }, textAlign: 'left', x: 3 }, labels: { style: { fontSize: '8px', color: Highcharts.getOptions().colors[2] }, y: 2, x: 3 }, gridLineWidth: 0, opposite: true, showLastLabel: false }], legend: { enabled: false }, plotOptions: { series: { pointPlacement: 'between' } }, series: [{ name: 'Temperature', data: this.temperatures, type: 'spline', marker: { enabled: false, states: { hover: { enabled: true } } }, tooltip: { pointFormat: '\u25CF ' + '{series.name}: {point.value}°C
' }, zIndex: 1, color: '#FF3333', negativeColor: '#48AFE8' }, { name: 'Precipitation', data: this.precipitationsError, type: 'column', color: 'url(#precipitation-error)', yAxis: 1, groupPadding: 0, pointPadding: 0, tooltip: { valueSuffix: ' mm', pointFormat: '\u25CF ' + '{series.name}: {point.minvalue} mm - {point.maxvalue} mm
' }, grouping: false, dataLabels: { enabled: this.hasPrecipitationError, formatter: function () { if (this.point.maxvalue > 0) { return this.point.maxvalue; } }, style: { fontSize: '8px', color: 'gray' } } }, { name: 'Precipitation', data: this.precipitations, type: 'column', color: '#68CFE8', yAxis: 1, groupPadding: 0, pointPadding: 0, grouping: false, dataLabels: { enabled: !this.hasPrecipitationError, formatter: function () { if (this.y > 0) { return this.y; } }, style: { fontSize: '8px', color: 'gray' } }, tooltip: { valueSuffix: ' mm' } }, { name: 'Air pressure', color: Highcharts.getOptions().colors[2], data: this.pressures, marker: { enabled: false }, shadow: false, tooltip: { valueSuffix: ' hPa' }, dashStyle: 'shortdot', yAxis: 2 }, { name: 'Wind', type: 'windbarb', id: 'windbarbs', color: Highcharts.getOptions().colors[1], lineWidth: 1.5, data: this.winds, vectorLength: 18, yOffset: -15, tooltip: { valueSuffix: ' m/s' } } ] }; } onChartLoad(chart) { this.drawWeatherSymbols(chart); this.drawBlocksForWindArrows(chart); } drawWeatherSymbols(chart) { jQuery.each(chart.series[0].data, function (i, point) { if (this.resolution > 36e5 || i % 2 === 0) { chart.renderer .image( './assets/js/highcharts/symbols/' + this.symbols[i] + '.svg', point.plotX + chart.plotLeft - 8, point.plotY + chart.plotTop - 30, 30, 30 ).attr({ zIndex: 5 }).add(); } }.bind(this)); } drawBlocksForWindArrows(chart) { var xAxis = chart.xAxis[0], x, pos, max, isLong, isLast, i; for (pos = xAxis.min, max = xAxis.max, i = 0; pos <= max + 36e5; pos += 36e5, i += 1) { isLast = pos === max + 36e5; x = Math.round(xAxis.toPixels(pos)) + (isLast ? 0.5 : -0.5); if (this.resolution > 36e5) { isLong = pos % this.resolution === 0; } else { isLong = i % 2 === 0; } chart.renderer.path(['M', x, chart.plotTop + chart.plotHeight + (isLong ? 0 : 28), 'L', x, chart.plotTop + chart.plotHeight + 32, 'Z']) .attr({ 'stroke': chart.options.chart.plotBorderColor, 'stroke-width': 1 }) .add(); } chart.get('windbarbs').markerGroup.attr({ translateX: chart.get('windbarbs').markerGroup.translateX + 8 }); } getTitle() { return 'Meteogram for ' + this.xml.querySelector('location name').textContent + "/" + this.province + ', ' + this.xml.querySelector('location country').textContent; }; resetLists() { this.symbols = []; this.precipitations = []; this.precipitationsError = []; this.winds = []; this.temperatures = []; this.pressures = []; } }