|
|
- <!DOCTYPE HTML>
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <title>Highcharts Example</title>
-
- <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
- <style type="text/css">
- ${demo.css}
- </style>
- <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.symbolNames = [];
- this.precipitations = [];
- this.windDirections = [];
- this.windDirectionNames = [];
- this.windSpeeds = [];
- this.windSpeedNames = [];
- this.temperatures = [];
- this.pressures = [];
-
- // Initialize
- this.xml = xml;
- this.container = container;
-
- // Run
- this.parseYrData();
- }
- /**
- * Return weather symbol sprites as laid out at http://om.yr.no/forklaring/symbol/
- */
- Meteogram.prototype.getSymbolSprites = function (symbolSize) {
- return {
- '01d': {
- x: 0,
- y: 0
- },
- '01n': {
- x: symbolSize,
- y: 0
- },
- '16': {
- x: 2 * symbolSize,
- y: 0
- },
- '02d': {
- x: 0,
- y: symbolSize
- },
- '02n': {
- x: symbolSize,
- y: symbolSize
- },
- '03d': {
- x: 0,
- y: 2 * symbolSize
- },
- '03n': {
- x: symbolSize,
- y: 2 * symbolSize
- },
- '17': {
- x: 2 * symbolSize,
- y: 2 * symbolSize
- },
- '04': {
- x: 0,
- y: 3 * symbolSize
- },
- '05d': {
- x: 0,
- y: 4 * symbolSize
- },
- '05n': {
- x: symbolSize,
- y: 4 * symbolSize
- },
- '18': {
- x: 2 * symbolSize,
- y: 4 * symbolSize
- },
- '06d': {
- x: 0,
- y: 5 * symbolSize
- },
- '06n': {
- x: symbolSize,
- y: 5 * symbolSize
- },
- '07d': {
- x: 0,
- y: 6 * symbolSize
- },
- '07n': {
- x: symbolSize,
- y: 6 * symbolSize
- },
- '08d': {
- x: 0,
- y: 7 * symbolSize
- },
- '08n': {
- x: symbolSize,
- y: 7 * symbolSize
- },
- '19': {
- x: 2 * symbolSize,
- y: 7 * symbolSize
- },
- '09': {
- x: 0,
- y: 8 * symbolSize
- },
- '10': {
- x: 0,
- y: 9 * symbolSize
- },
- '11': {
- x: 0,
- y: 10 * symbolSize
- },
- '12': {
- x: 0,
- y: 11 * symbolSize
- },
- '13': {
- x: 0,
- y: 12 * symbolSize
- },
- '14': {
- x: 0,
- y: 13 * symbolSize
- },
- '15': {
- x: 0,
- y: 14 * symbolSize
- },
- '20d': {
- x: 0,
- y: 15 * symbolSize
- },
- '20n': {
- x: symbolSize,
- y: 15 * symbolSize
- },
- '20m': {
- x: 2 * symbolSize,
- y: 15 * symbolSize
- },
- '21d': {
- x: 0,
- y: 16 * symbolSize
- },
- '21n': {
- x: symbolSize,
- y: 16 * symbolSize
- },
- '21m': {
- x: 2 * symbolSize,
- y: 16 * symbolSize
- },
- '22': {
- x: 0,
- y: 17 * symbolSize
- },
- '23': {
- x: 0,
- y: 18 * symbolSize
- }
- };
- };
-
-
- /**
- * 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));
- }
- };
-
- /**
- * Callback function that is called from Highcharts on hovering each point and returns
- * HTML for the tooltip.
- */
- Meteogram.prototype.tooltipFormatter = function (tooltip) {
-
- // Create the header with reference to the time interval
- var index = tooltip.points[0].point.index,
- ret = '<small>' + Highcharts.dateFormat('%A, %b %e, %H:%M', tooltip.x) + '-' +
- Highcharts.dateFormat('%H:%M', tooltip.points[0].point.to) + '</small><br>';
-
- // Symbol text
- ret += '<b>' + this.symbolNames[index] + '</b>';
-
- ret += '<table>';
-
- // Add all series
- Highcharts.each(tooltip.points, function (point) {
- var series = point.series;
- ret += '<tr><td><span style="color:' + series.color + '">\u25CF</span> ' + series.name +
- ': </td><td style="white-space:nowrap">' + Highcharts.pick(point.point.value, point.y) +
- series.options.tooltip.valueSuffix + '</td></tr>';
- });
-
- // Add wind
- ret += '<tr><td style="vertical-align: top">\u25CF Wind</td><td style="white-space:nowrap">' + this.windDirectionNames[index] +
- '<br>' + this.windSpeedNames[index] + ' (' +
- Highcharts.numberFormat(this.windSpeeds[index], 1) + ' m/s)</td></tr>';
-
- // Close
- ret += '</table>';
-
-
- return ret;
- };
-
- /**
- * Draw the weather symbols on top of the temperature series. The symbols are sprites of a single
- * file, defined in the getSymbolSprites function above.
- */
- Meteogram.prototype.drawWeatherSymbols = function (chart) {
- var meteogram = this,
- symbolSprites = this.getSymbolSprites(30);
-
- $.each(chart.series[0].data, function (i, point) {
- var sprite,
- group;
-
- if (meteogram.resolution > 36e5 || i % 2 === 0) {
-
- sprite = symbolSprites[meteogram.symbols[i]];
- if (sprite) {
-
- // Create a group element that is positioned and clipped at 30 pixels width and height
- group = chart.renderer.g()
- .attr({
- translateX: point.plotX + chart.plotLeft - 15,
- translateY: point.plotY + chart.plotTop - 30,
- zIndex: 5
- })
- .clip(chart.renderer.clipRect(0, 0, 30, 30))
- .add();
-
- // Position the image inside it at the sprite position
- chart.renderer.image(
- 'http://www.highcharts.com/samples/graphics/meteogram-symbols-30px.png',
- -sprite.x,
- -sprite.y,
- 90,
- 570
- )
- .add(group);
- }
- }
- });
- };
-
- /**
- * Create wind speed symbols for the Beaufort wind scale. The symbols are rotated
- * around the zero centerpoint.
- */
- Meteogram.prototype.windArrow = function (name) {
- var level,
- path;
-
- // The stem and the arrow head
- path = [
- 'M', 0, 7, // base of arrow
- 'L', -1.5, 7,
- 0, 10,
- 1.5, 7,
- 0, 7,
- 0, -10 // top
- ];
-
- level = $.inArray(name, ['Calm', 'Light air', 'Light breeze', 'Gentle breeze', 'Moderate breeze',
- 'Fresh breeze', 'Strong breeze', 'Near gale', 'Gale', 'Strong gale', 'Storm',
- 'Violent storm', 'Hurricane']);
-
- if (level === 0) {
- path = [];
- }
-
- if (level === 2) {
- path.push('M', 0, -8, 'L', 4, -8); // short line
- } else if (level >= 3) {
- path.push(0, -10, 7, -10); // long line
- }
-
- if (level === 4) {
- path.push('M', 0, -7, 'L', 4, -7);
- } else if (level >= 5) {
- path.push('M', 0, -7, 'L', 7, -7);
- }
-
- if (level === 5) {
- path.push('M', 0, -4, 'L', 4, -4);
- } else if (level >= 6) {
- path.push('M', 0, -4, 'L', 7, -4);
- }
-
- if (level === 7) {
- path.push('M', 0, -1, 'L', 4, -1);
- } else if (level >= 8) {
- path.push('M', 0, -1, 'L', 7, -1);
- }
-
- return path;
- };
-
- /**
- * Draw the wind arrows. Each arrow path is generated by the windArrow function above.
- */
- Meteogram.prototype.drawWindArrows = function (chart) {
- var meteogram = this;
-
- $.each(chart.series[0].data, function (i, point) {
- var sprite, arrow, x, y;
-
- if (meteogram.resolution > 36e5 || i % 2 === 0) {
-
- // Draw the wind arrows
- x = point.plotX + chart.plotLeft + 7;
- y = 255;
- if (meteogram.windSpeedNames[i] === 'Calm') {
- arrow = chart.renderer.circle(x, y, 10).attr({
- fill: 'none'
- });
- } else {
- arrow = chart.renderer.path(
- meteogram.windArrow(meteogram.windSpeedNames[i])
- ).attr({
- rotation: parseInt(meteogram.windDirections[i], 10),
- translateX: x, // rotation center
- translateY: y // rotation center
- });
- }
- arrow.attr({
- stroke: (Highcharts.theme && Highcharts.theme.contrastTextColor) || 'black',
- 'stroke-width': 1.5,
- 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();
- }
- };
-
- /**
- * Get the title based on the XML data
- */
- Meteogram.prototype.getTitle = function () {
- return 'Meteogram for ' + this.xml.location.name + ', ' + this.xml.location.country;
- };
-
- /**
- * 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,
- width: 800,
- height: 310
- },
-
- title: {
- text: this.getTitle(),
- align: 'left'
- },
-
- credits: {
- text: 'Forecast from <a href="http://yr.no">yr.no</a>',
- href: this.xml.credit.link['@attributes'].url,
- position: {
- x: -40
- }
- },
-
- tooltip: {
- shared: true,
- useHTML: true,
- formatter: function () {
- return meteogram.tooltipFormatter(this);
- }
- },
-
- 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}'
- }
- }, { // 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
- }],
- // Custom positioner to provide even temperature ticks from top down
- tickPositioner: function () {
- var max = Math.ceil(this.max) + 1,
- pos = max - 12, // start
- ret;
-
- if (pos < this.min) {
- ret = [];
- while (pos <= max) {
- ret.push(pos += 1);
- }
- } // else return undefined and go auto
-
- return ret;
-
- },
- maxPadding: 0.3,
- tickInterval: 1,
- gridLineColor: (Highcharts.theme && Highcharts.theme.background2) || '#F0F0F0'
-
- }, { // precipitation axis
- title: {
- text: null
- },
- labels: {
- enabled: false
- },
- gridLineWidth: 0,
- tickLength: 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: {
- valueSuffix: '°C'
- },
- zIndex: 1,
- color: '#FF3333',
- negativeColor: '#48AFE8'
- }, {
- name: 'Precipitation',
- data: this.precipitations,
- type: 'column',
- color: '#68CFE8',
- yAxis: 1,
- groupPadding: 0,
- pointPadding: 0,
- borderWidth: 0,
- shadow: false,
- dataLabels: {
- enabled: true,
- formatter: function () {
- if (this.y > 0) {
- return this.y;
- }
- },
- style: {
- fontSize: '8px'
- }
- },
- 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
- }]
- }
- };
-
- /**
- * Post-process the chart from the callback function, the second argument to Highcharts.Chart.
- */
- Meteogram.prototype.onChartLoad = function (chart) {
-
- this.drawWeatherSymbols(chart);
- this.drawWindArrows(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);
- });
- };
-
- /**
- * 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;
-
- if (!xml || !xml.forecast) {
- $('#loading').html('<i class="fa fa-frown-o"></i> Failed loading data, please try again later');
- return;
- }
-
- // 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.
- $.each(xml.forecast.tabular.time, function (i, time) {
- // Get the times - only Safari can't parse ISO8601 so we need to do some replacements
- var from = time['@attributes'].from + ' UTC',
- to = time['@attributes'].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.symbol['@attributes']['var'].match(/[0-9]{2}[dnm]?/)[0]);
- meteogram.symbolNames.push(time.symbol['@attributes'].name);
-
- meteogram.temperatures.push({
- x: from,
- y: parseInt(time.temperature['@attributes'].value),
- // custom options used in the tooltip formatter
- to: to,
- index: i
- });
-
- meteogram.precipitations.push({
- x: from,
- y: parseFloat(time.precipitation['@attributes'].value)
- });
- meteogram.windDirections.push(parseFloat(time.windDirection['@attributes'].deg));
- meteogram.windDirectionNames.push(time.windDirection['@attributes'].name);
- meteogram.windSpeeds.push(parseFloat(time.windSpeed['@attributes'].mps));
- meteogram.windSpeedNames.push(time.windSpeed['@attributes'].name);
-
- meteogram.pressures.push({
- x: from,
- y: parseFloat(time.pressure['@attributes'].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
-
-
-
- $(function () { // On DOM ready...
-
- // Set the hash to the yr.no URL we want to parse
- if (!location.hash) {
- var 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 = 'http://www.yr.no/place/' + place + '/forecast_hour_by_hour.xml';
-
- }
-
- // Then get the XML file through Highcharts' jsonp provider, see
- // https://github.com/highslide-software/highcharts.com/blob/master/samples/data/jsonp.php
- // for source code.
- $.getJSON(
- 'http://www.highcharts.com/samples/data/jsonp.php?url=' + location.hash.substr(1) + '&callback=?',
- function (xml) {
- var meteogram = new Meteogram(xml, 'container');
- }
- );
-
- });
- </script>
- </head>
- <body>
-
- <script src="../../js/highcharts.js"></script>
- <script src="../../js/modules/exporting.js"></script>
- <link href="http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
-
- <div id="container" style="width: 800px; 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>
- -->
- </body>
- </html>
|