Blame | Letzte Änderung | Log anzeigen | RSS feed
<!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 loadedthis.symbols = [];this.symbolNames = [];this.precipitations = [];this.windDirections = [];this.windDirectionNames = [];this.windSpeeds = [];this.windSpeedNames = [];this.temperatures = [];this.pressures = [];// Initializethis.xml = xml;this.container = container;// Runthis.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 valuesum = (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 intervalvar 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 textret += '<b>' + this.symbolNames[index] + '</b>';ret += '<table>';// Add all seriesHighcharts.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 windret += '<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>';// Closeret += '</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 heightgroup = 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 positionchart.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 headpath = ['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 arrowsx = 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 centertranslateY: 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 positionisLast = pos === max + 36e5;x = Math.round(xAxis.toPixels(pos)) + (isLast ? 0.5 : -0.5);// Draw the vertical dividers and ticksif (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 axistype: 'datetime',tickInterval: 2 * 36e5, // two hoursminorTickInterval: 36e5, // one hourtickLength: 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 axislinkedTo: 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 axistitle: {text: null},labels: {format: '{value}°',style: {fontSize: '10px'},x: -3},plotLines: [{ // zero planevalue: 0,color: '#BBBBBB',width: 1,zIndex: 2}],// Custom positioner to provide even temperature ticks from top downtickPositioner: function () {var max = Math.ceil(this.max) + 1,pos = max - 12, // startret;if (pos < this.min) {ret = [];while (pos <= max) {ret.push(pos += 1);}} // else return undefined and go autoreturn ret;},maxPadding: 0.3,tickInterval: 1,gridLineColor: (Highcharts.theme && Highcharts.theme.background2) || '#F0F0F0'}, { // precipitation axistitle: {text: null},labels: {enabled: false},gridLineWidth: 0,tickLength: 0}, { // Air pressureallowDecimals: false,title: { // Title on top of axistext: '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 replacementsvar 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 symbolsif (i === 0) {meteogram.resolution = to - from;}// Populate the parallel arraysmeteogram.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 formatterto: 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 linethis.smoothLine(this.temperatures);// Create the chart when the data is loadedthis.createChart();};// End of the Meteogram protype$(function () { // On DOM ready...// Set the hash to the yr.no URL we want to parseif (!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>