| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610 | <!DOCTYPE HTML><html>	<head>		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">		<meta name="viewport" content="width=device-width, initial-scale=1">		<title>Highcharts Example</title>		<style type="text/css">		</style>	</head>	<body><script src="https://code.jquery.com/jquery-3.1.1.min.js"></script><script src="../../code/highcharts.js"></script><script src="../../code/modules/windbarb.js"></script><script src="../../code/modules/exporting.js"></script><script src="https://highcharts.github.io/pattern-fill/pattern-fill-v2.js"></script><link href="https://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet"><div id="container" style="max-width: 800px; min-width: 380px; height: 310px; margin: 0 auto">    <div style="margin-top: 100px; text-align: center" id="loading">        <i class="fa fa-spinner fa-spin"></i> Loading data from external source    </div></div><!--<div style="width: 800px; margin: 0 auto">    <a href="#http://www.yr.no/place/United_Kingdom/England/London/forecast_hour_by_hour.xml">London</a>,    <a href="#http://www.yr.no/place/France/Rhône-Alpes/Val_d\'Isère~2971074/forecast_hour_by_hour.xml">Val d'Isère</a>,    <a href="#http://www.yr.no/place/United_States/California/San_Francisco/forecast_hour_by_hour.xml">San Francisco</a>,    <a href="#http://www.yr.no/place/Norway/Vik/Vikafjell/forecast_hour_by_hour.xml">Vikjafjellet</a></div>-->		<script type="text/javascript">/** * This is a complex demo of how to set up a Highcharts chart, coupled to a * dynamic source and extended by drawing image sprites, wind arrow paths * and a second grid on top of the chart. The purpose of the demo is to inpire * developers to go beyond the basic chart types and show how the library can * be extended programmatically. This is what the demo does: * * - Loads weather forecast from www.yr.no in form of an XML service. The XML *   is translated on the Higcharts website into JSONP for the sake of the demo *   being shown on both our website and JSFiddle. * - When the data arrives async, a Meteogram instance is created. We have *   created the Meteogram prototype to provide an organized structure of the different *   methods and subroutines associated with the demo. * - The parseYrData method parses the data from www.yr.no into several parallel arrays. These *   arrays are used directly as the data option for temperature, precipitation *   and air pressure. As the temperature data gives only full degrees, we apply *   some smoothing on the graph, but keep the original data in the tooltip. * - After this, the options structure is build, and the chart generated with the *   parsed data. * - In the callback (on chart load), we weather icons on top of the temperature series. *   The icons are sprites from a single PNG image, placed inside a clipped 30x30 *   SVG <g> element. VML interprets this as HTML images inside a clipped div. * - Lastly, the wind arrows are built and added below the plot area, and a grid is *   drawn around them. The wind arrows are basically drawn north-south, then rotated *   as per the wind direction. */function Meteogram(xml, container) {    // Parallel arrays for the chart data, these are populated as the XML/JSON file    // is loaded    this.symbols = [];    this.precipitations = [];    this.precipitationsError = []; // Only for some data sets    this.winds = [];    this.temperatures = [];    this.pressures = [];    // Initialize    this.xml = xml;    this.container = container;    // Run    this.parseYrData();}/** * Function to smooth the temperature line. The original data provides only whole degrees, * which makes the line graph look jagged. So we apply a running mean on it, but preserve * the unaltered value in the tooltip. */Meteogram.prototype.smoothLine = function (data) {    var i = data.length,        sum,        value;    while (i--) {        data[i].value = value = data[i].y; // preserve value for tooltip        // Set the smoothed value to the average of the closest points, but don't allow        // it to differ more than 0.5 degrees from the given value        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));    }};/** * Draw the weather symbols on top of the temperature series. The symbols are * fetched from yr.no's MIT licensed weather symbol collection. * https://github.com/YR/weather-symbols */Meteogram.prototype.drawWeatherSymbols = function (chart) {    var meteogram = this;    $.each(chart.series[0].data, function (i, point) {        if (meteogram.resolution > 36e5 || i % 2 === 0) {            chart.renderer                .image(                    'https://cdn.rawgit.com/YR/weather-symbols/6.0.2/dist/svg/' +                        meteogram.symbols[i] + '.svg',                    point.plotX + chart.plotLeft - 8,                    point.plotY + chart.plotTop - 30,                    30,                    30                )                .attr({                    zIndex: 5                })                .add();        }    });};/** * Draw blocks around wind arrows, below the plot area */Meteogram.prototype.drawBlocksForWindArrows = function (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) {        // Get the X position        isLast = pos === max + 36e5;        x = Math.round(xAxis.toPixels(pos)) + (isLast ? 0.5 : -0.5);        // Draw the vertical dividers and ticks        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();    }      // Center items in block    chart.get('windbarbs').markerGroup.attr({        translateX: chart.get('windbarbs').markerGroup.translateX + 8    });};/** * Get the title based on the XML data */Meteogram.prototype.getTitle = function () {    return 'Meteogram for ' + this.xml.querySelector('location name').textContent +        ', ' + this.xml.querySelector('location country').textContent;};/** * Build and return the Highcharts options structure */Meteogram.prototype.getChartOptions = function () {    var meteogram = this;    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: 'Forecast from <a href="http://yr.no">yr.no</a>',            href: this.xml.querySelector('credit link').getAttribute('url'),            position: {                x: -40            }        },        tooltip: {            shared: true,            useHTML: true,            headerFormat:                '<small>{point.x:%A, %b %e, %H:%M} - {point.point.to:%H:%M}</small><br>' +                '<b>{point.point.symbolName}</b><br>'        },        xAxis: [{ // Bottom X axis            type: 'datetime',            tickInterval: 2 * 36e5, // two hours            minorTickInterval: 36e5, // one hour            tickLength: 0,            gridLineWidth: 1,            gridLineColor: (Highcharts.theme && Highcharts.theme.background2) || '#F0F0F0',            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:<span style="font-size: 12px; font-weight: bold">%a</span> %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: (Highcharts.theme && Highcharts.theme.background2) || '#F0F0F0'        }, { // 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: '<span style="color:{point.color}">\u25CF</span> ' +                    '{series.name}: <b>{point.value}°C</b><br/>'            },            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: '<span style="color:{point.color}">\u25CF</span> ' +                    '{series.name}: <b>{point.minvalue} mm - {point.maxvalue} mm</b><br/>'            },            grouping: false,            dataLabels: {                enabled: meteogram.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: !meteogram.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'            }        }]    };};/** * Post-process the chart from the callback function, the second argument to Highcharts.Chart. */Meteogram.prototype.onChartLoad = function (chart) {    this.drawWeatherSymbols(chart);    this.drawBlocksForWindArrows(chart);};/** * Create the chart. This function is called async when the data file is loaded and parsed. */Meteogram.prototype.createChart = function () {    var meteogram = this;    this.chart = new Highcharts.Chart(this.getChartOptions(), function (chart) {        meteogram.onChartLoad(chart);    });};Meteogram.prototype.error = function () {    $('#loading').html('<i class="fa fa-frown-o"></i> Failed loading data, please try again later');};/** * Handle the data. This part of the code is not Highcharts specific, but deals with yr.no's * specific data format */Meteogram.prototype.parseYrData = function () {    var meteogram = this,        xml = this.xml,        pointStart,        forecast = xml && xml.querySelector('forecast');    if (!forecast) {        return this.error();    }    // The returned xml variable is a JavaScript representation of the provided    // XML, generated on the server by running PHP simple_load_xml and    // converting it to JavaScript by json_encode.    Highcharts.each(        forecast.querySelectorAll('tabular time'),        function (time, i) {            // Get the times - only Safari can't parse ISO8601 so we need to do            // some replacements            var from = time.getAttribute('from') + ' UTC',                to = time.getAttribute('to') + ' UTC';            from = from.replace(/-/g, '/').replace('T', ' ');            from = Date.parse(from);            to = to.replace(/-/g, '/').replace('T', ' ');            to = Date.parse(to);            if (to > pointStart + 4 * 24 * 36e5) {                return;            }            // If it is more than an hour between points, show all symbols            if (i === 0) {                meteogram.resolution = to - from;            }            // Populate the parallel arrays            meteogram.symbols.push(                time.querySelector('symbol').getAttribute('var')                    .match(/[0-9]{2}[dnm]?/)[0]            );            meteogram.temperatures.push({                x: from,                y: parseInt(                    time.querySelector('temperature').getAttribute('value'),                    10                ),                // custom options used in the tooltip formatter                to: to,                symbolName: time.querySelector('symbol').getAttribute('name')            });            var precipitation = time.querySelector('precipitation');            meteogram.precipitations.push({                x: from,                y: parseFloat(                    Highcharts.pick(                        precipitation.getAttribute('minvalue'),                        precipitation.getAttribute('value')                    )                )            });            if (precipitation.getAttribute('maxvalue')) {                meteogram.hasPrecipitationError = true;                meteogram.precipitationsError.push({                    x: 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) {                meteogram.winds.push({                    x: from,                    value: parseFloat(time.querySelector('windSpeed')                        .getAttribute('mps')),                    direction: parseFloat(time.querySelector('windDirection')                        .getAttribute('deg'))                });            }            meteogram.pressures.push({                x: from,                y: parseFloat(time.querySelector('pressure').getAttribute('value'))            });            if (i === 0) {                pointStart = (from + to) / 2;            }        }    );    // Smooth the line    this.smoothLine(this.temperatures);    // Create the chart when the data is loaded    this.createChart();};// End of the Meteogram protype // On DOM ready...// Set the hash to the yr.no URL we want to parsevar place,    url;if (!location.hash) {    place = 'United_Kingdom/England/London';    //place = 'France/Rhône-Alpes/Val_d\'Isère~2971074';    //place = 'Norway/Sogn_og_Fjordane/Vik/Målset';    //place = 'United_States/California/San_Francisco';    //place = 'United_States/Minnesota/Minneapolis';    location.hash = 'https://www.yr.no/place/' + place + '/forecast_hour_by_hour.xml';}// Then get the XML file through Highcharts' CORS proxy. Our proxy is limited to// this specific location. Useing the third party, rate limited cors.io service// for experimenting with other locations.url = location.hash.substr(1);$.ajax({    dataType: 'xml',    url: url === 'https://www.yr.no/place/United_Kingdom/England/London/forecast_hour_by_hour.xml' ?        'https://www.highcharts.com/samples/data/cors.php?url=' + url :        'https://cors.io/?' + url,    success: function (xml) {        window.meteogram = new Meteogram(xml, 'container');    },    error: Meteogram.prototype.error});		</script>	</body></html>
 |