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 parse
- var 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>
|