summaryrefslogtreecommitdiff
path: root/html/includes/js/modules
diff options
context:
space:
mode:
Diffstat (limited to 'html/includes/js/modules')
-rw-r--r--html/includes/js/modules/canvas-tools.js133
-rw-r--r--html/includes/js/modules/canvas-tools.src.js3113
-rw-r--r--html/includes/js/modules/data.js24
-rw-r--r--html/includes/js/modules/data.src.js1025
-rw-r--r--html/includes/js/modules/drilldown.js15
-rw-r--r--html/includes/js/modules/drilldown.src.js606
-rw-r--r--html/includes/js/modules/exporting.js23
-rw-r--r--html/includes/js/modules/exporting.src.js719
-rw-r--r--html/includes/js/modules/funnel.js13
-rw-r--r--html/includes/js/modules/funnel.src.js310
-rw-r--r--html/includes/js/modules/heatmap.js23
-rw-r--r--html/includes/js/modules/heatmap.src.js687
-rw-r--r--html/includes/js/modules/multicolor_series.js567
-rw-r--r--html/includes/js/modules/no-data-to-display.js12
-rw-r--r--html/includes/js/modules/no-data-to-display.src.js130
-rw-r--r--html/includes/js/modules/solid-gauge.js13
-rw-r--r--html/includes/js/modules/solid-gauge.src.js234
17 files changed, 7647 insertions, 0 deletions
diff --git a/html/includes/js/modules/canvas-tools.js b/html/includes/js/modules/canvas-tools.js
new file mode 100644
index 0000000..3b71dc2
--- /dev/null
+++ b/html/includes/js/modules/canvas-tools.js
@@ -0,0 +1,133 @@
+/*
+ A class to parse color values
+ @author Stoyan Stefanov <sstoo@gmail.com>
+ @link http://www.phpied.com/rgb-color-parser-in-javascript/
+ Use it if you like it
+
+ canvg.js - Javascript SVG parser and renderer on Canvas
+ MIT Licensed
+ Gabe Lerner (gabelerner@gmail.com)
+ http://code.google.com/p/canvg/
+
+ Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/
+
+ Highcharts JS v4.0.4 (2014-09-02)
+ CanVGRenderer Extension module
+
+ (c) 2011-2012 Torstein Honsi, Erik Olsson
+
+ License: www.highcharts.com/license
+*/
+function RGBColor(m){this.ok=!1;m.charAt(0)=="#"&&(m=m.substr(1,6));var m=m.replace(/ /g,""),m=m.toLowerCase(),a={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",
+darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dodgerblue:"1e90ff",feldspar:"d19275",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",
+gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgrey:"d3d3d3",lightgreen:"90ee90",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",
+lightslateblue:"8470ff",lightslategray:"778899",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"00ff00",limegreen:"32cd32",linen:"faf0e6",magenta:"ff00ff",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370d8",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",
+oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"d87093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",red:"ff0000",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",
+slategray:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",violetred:"d02090",wheat:"f5deb3",white:"ffffff",whitesmoke:"f5f5f5",yellow:"ffff00",yellowgreen:"9acd32"},c;for(c in a)m==c&&(m=a[c]);var d=[{re:/^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,example:["rgb(123, 234, 45)","rgb(255,234,245)"],process:function(b){return[parseInt(b[1]),parseInt(b[2]),parseInt(b[3])]}},{re:/^(\w{2})(\w{2})(\w{2})$/,
+example:["#00ff00","336699"],process:function(b){return[parseInt(b[1],16),parseInt(b[2],16),parseInt(b[3],16)]}},{re:/^(\w{1})(\w{1})(\w{1})$/,example:["#fb0","f0f"],process:function(b){return[parseInt(b[1]+b[1],16),parseInt(b[2]+b[2],16),parseInt(b[3]+b[3],16)]}}];for(c=0;c<d.length;c++){var b=d[c].process,k=d[c].re.exec(m);if(k)channels=b(k),this.r=channels[0],this.g=channels[1],this.b=channels[2],this.ok=!0}this.r=this.r<0||isNaN(this.r)?0:this.r>255?255:this.r;this.g=this.g<0||isNaN(this.g)?0:
+this.g>255?255:this.g;this.b=this.b<0||isNaN(this.b)?0:this.b>255?255:this.b;this.toRGB=function(){return"rgb("+this.r+", "+this.g+", "+this.b+")"};this.toHex=function(){var b=this.r.toString(16),a=this.g.toString(16),d=this.b.toString(16);b.length==1&&(b="0"+b);a.length==1&&(a="0"+a);d.length==1&&(d="0"+d);return"#"+b+a+d};this.getHelpXML=function(){for(var b=[],k=0;k<d.length;k++)for(var c=d[k].example,j=0;j<c.length;j++)b[b.length]=c[j];for(var h in a)b[b.length]=h;c=document.createElement("ul");
+c.setAttribute("id","rgbcolor-examples");for(k=0;k<b.length;k++)try{var l=document.createElement("li"),o=new RGBColor(b[k]),n=document.createElement("div");n.style.cssText="margin: 3px; border: 1px solid black; background:"+o.toHex()+"; color:"+o.toHex();n.appendChild(document.createTextNode("test"));var q=document.createTextNode(" "+b[k]+" -> "+o.toRGB()+" -> "+o.toHex());l.appendChild(n);l.appendChild(q);c.appendChild(l)}catch(p){}return c}}
+if(!window.console)window.console={},window.console.log=function(){},window.console.dir=function(){};if(!Array.prototype.indexOf)Array.prototype.indexOf=function(m){for(var a=0;a<this.length;a++)if(this[a]==m)return a;return-1};
+(function(){function m(){var a={FRAMERATE:30,MAX_VIRTUAL_PIXELS:3E4};a.init=function(c){a.Definitions={};a.Styles={};a.Animations=[];a.Images=[];a.ctx=c;a.ViewPort=new function(){this.viewPorts=[];this.Clear=function(){this.viewPorts=[]};this.SetCurrent=function(a,b){this.viewPorts.push({width:a,height:b})};this.RemoveCurrent=function(){this.viewPorts.pop()};this.Current=function(){return this.viewPorts[this.viewPorts.length-1]};this.width=function(){return this.Current().width};this.height=function(){return this.Current().height};
+this.ComputeSize=function(a){return a!=null&&typeof a=="number"?a:a=="x"?this.width():a=="y"?this.height():Math.sqrt(Math.pow(this.width(),2)+Math.pow(this.height(),2))/Math.sqrt(2)}}};a.init();a.ImagesLoaded=function(){for(var c=0;c<a.Images.length;c++)if(!a.Images[c].loaded)return!1;return!0};a.trim=function(a){return a.replace(/^\s+|\s+$/g,"")};a.compressSpaces=function(a){return a.replace(/[\s\r\t\n]+/gm," ")};a.ajax=function(a){var d;return(d=window.XMLHttpRequest?new XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP"))?
+(d.open("GET",a,!1),d.send(null),d.responseText):null};a.parseXml=function(a){if(window.DOMParser)return(new DOMParser).parseFromString(a,"text/xml");else{var a=a.replace(/<!DOCTYPE svg[^>]*>/,""),d=new ActiveXObject("Microsoft.XMLDOM");d.async="false";d.loadXML(a);return d}};a.Property=function(c,d){this.name=c;this.value=d;this.hasValue=function(){return this.value!=null&&this.value!==""};this.numValue=function(){if(!this.hasValue())return 0;var b=parseFloat(this.value);(this.value+"").match(/%$/)&&
+(b/=100);return b};this.valueOrDefault=function(b){return this.hasValue()?this.value:b};this.numValueOrDefault=function(b){return this.hasValue()?this.numValue():b};var b=this;this.Color={addOpacity:function(d){var c=b.value;if(d!=null&&d!=""){var f=new RGBColor(b.value);f.ok&&(c="rgba("+f.r+", "+f.g+", "+f.b+", "+d+")")}return new a.Property(b.name,c)}};this.Definition={getDefinition:function(){var d=b.value.replace(/^(url\()?#([^\)]+)\)?$/,"$2");return a.Definitions[d]},isUrl:function(){return b.value.indexOf("url(")==
+0},getFillStyle:function(b){var d=this.getDefinition();return d!=null&&d.createGradient?d.createGradient(a.ctx,b):d!=null&&d.createPattern?d.createPattern(a.ctx,b):null}};this.Length={DPI:function(){return 96},EM:function(b){var d=12,c=new a.Property("fontSize",a.Font.Parse(a.ctx.font).fontSize);c.hasValue()&&(d=c.Length.toPixels(b));return d},toPixels:function(d){if(!b.hasValue())return 0;var c=b.value+"";return c.match(/em$/)?b.numValue()*this.EM(d):c.match(/ex$/)?b.numValue()*this.EM(d)/2:c.match(/px$/)?
+b.numValue():c.match(/pt$/)?b.numValue()*1.25:c.match(/pc$/)?b.numValue()*15:c.match(/cm$/)?b.numValue()*this.DPI(d)/2.54:c.match(/mm$/)?b.numValue()*this.DPI(d)/25.4:c.match(/in$/)?b.numValue()*this.DPI(d):c.match(/%$/)?b.numValue()*a.ViewPort.ComputeSize(d):b.numValue()}};this.Time={toMilliseconds:function(){if(!b.hasValue())return 0;var a=b.value+"";if(a.match(/s$/))return b.numValue()*1E3;a.match(/ms$/);return b.numValue()}};this.Angle={toRadians:function(){if(!b.hasValue())return 0;var a=b.value+
+"";return a.match(/deg$/)?b.numValue()*(Math.PI/180):a.match(/grad$/)?b.numValue()*(Math.PI/200):a.match(/rad$/)?b.numValue():b.numValue()*(Math.PI/180)}}};a.Font=new function(){this.Styles=["normal","italic","oblique","inherit"];this.Variants=["normal","small-caps","inherit"];this.Weights="normal,bold,bolder,lighter,100,200,300,400,500,600,700,800,900,inherit".split(",");this.CreateFont=function(d,b,c,e,f,g){g=g!=null?this.Parse(g):this.CreateFont("","","","","",a.ctx.font);return{fontFamily:f||
+g.fontFamily,fontSize:e||g.fontSize,fontStyle:d||g.fontStyle,fontWeight:c||g.fontWeight,fontVariant:b||g.fontVariant,toString:function(){return[this.fontStyle,this.fontVariant,this.fontWeight,this.fontSize,this.fontFamily].join(" ")}}};var c=this;this.Parse=function(d){for(var b={},d=a.trim(a.compressSpaces(d||"")).split(" "),k=!1,e=!1,f=!1,g=!1,j="",h=0;h<d.length;h++)if(!e&&c.Styles.indexOf(d[h])!=-1){if(d[h]!="inherit")b.fontStyle=d[h];e=!0}else if(!g&&c.Variants.indexOf(d[h])!=-1){if(d[h]!="inherit")b.fontVariant=
+d[h];e=g=!0}else if(!f&&c.Weights.indexOf(d[h])!=-1){if(d[h]!="inherit")b.fontWeight=d[h];e=g=f=!0}else if(k)d[h]!="inherit"&&(j+=d[h]);else{if(d[h]!="inherit")b.fontSize=d[h].split("/")[0];e=g=f=k=!0}if(j!="")b.fontFamily=j;return b}};a.ToNumberArray=function(c){for(var c=a.trim(a.compressSpaces((c||"").replace(/,/g," "))).split(" "),d=0;d<c.length;d++)c[d]=parseFloat(c[d]);return c};a.Point=function(a,d){this.x=a;this.y=d;this.angleTo=function(b){return Math.atan2(b.y-this.y,b.x-this.x)};this.applyTransform=
+function(b){var a=this.x*b[1]+this.y*b[3]+b[5];this.x=this.x*b[0]+this.y*b[2]+b[4];this.y=a}};a.CreatePoint=function(c){c=a.ToNumberArray(c);return new a.Point(c[0],c[1])};a.CreatePath=function(c){for(var c=a.ToNumberArray(c),d=[],b=0;b<c.length;b+=2)d.push(new a.Point(c[b],c[b+1]));return d};a.BoundingBox=function(a,d,b,k){this.y2=this.x2=this.y1=this.x1=Number.NaN;this.x=function(){return this.x1};this.y=function(){return this.y1};this.width=function(){return this.x2-this.x1};this.height=function(){return this.y2-
+this.y1};this.addPoint=function(b,a){if(b!=null){if(isNaN(this.x1)||isNaN(this.x2))this.x2=this.x1=b;if(b<this.x1)this.x1=b;if(b>this.x2)this.x2=b}if(a!=null){if(isNaN(this.y1)||isNaN(this.y2))this.y2=this.y1=a;if(a<this.y1)this.y1=a;if(a>this.y2)this.y2=a}};this.addX=function(b){this.addPoint(b,null)};this.addY=function(b){this.addPoint(null,b)};this.addBoundingBox=function(b){this.addPoint(b.x1,b.y1);this.addPoint(b.x2,b.y2)};this.addQuadraticCurve=function(b,a,d,c,k,l){d=b+2/3*(d-b);c=a+2/3*(c-
+a);this.addBezierCurve(b,a,d,d+1/3*(k-b),c,c+1/3*(l-a),k,l)};this.addBezierCurve=function(b,a,d,c,k,l,o,n){var q=[b,a],p=[d,c],t=[k,l],m=[o,n];this.addPoint(q[0],q[1]);this.addPoint(m[0],m[1]);for(i=0;i<=1;i++)b=function(b){return Math.pow(1-b,3)*q[i]+3*Math.pow(1-b,2)*b*p[i]+3*(1-b)*Math.pow(b,2)*t[i]+Math.pow(b,3)*m[i]},a=6*q[i]-12*p[i]+6*t[i],d=-3*q[i]+9*p[i]-9*t[i]+3*m[i],c=3*p[i]-3*q[i],d==0?a!=0&&(a=-c/a,0<a&&a<1&&(i==0&&this.addX(b(a)),i==1&&this.addY(b(a)))):(c=Math.pow(a,2)-4*c*d,c<0||(k=
+(-a+Math.sqrt(c))/(2*d),0<k&&k<1&&(i==0&&this.addX(b(k)),i==1&&this.addY(b(k))),a=(-a-Math.sqrt(c))/(2*d),0<a&&a<1&&(i==0&&this.addX(b(a)),i==1&&this.addY(b(a)))))};this.isPointInBox=function(b,a){return this.x1<=b&&b<=this.x2&&this.y1<=a&&a<=this.y2};this.addPoint(a,d);this.addPoint(b,k)};a.Transform=function(c){var d=this;this.Type={};this.Type.translate=function(b){this.p=a.CreatePoint(b);this.apply=function(b){b.translate(this.p.x||0,this.p.y||0)};this.applyToPoint=function(b){b.applyTransform([1,
+0,0,1,this.p.x||0,this.p.y||0])}};this.Type.rotate=function(b){b=a.ToNumberArray(b);this.angle=new a.Property("angle",b[0]);this.cx=b[1]||0;this.cy=b[2]||0;this.apply=function(b){b.translate(this.cx,this.cy);b.rotate(this.angle.Angle.toRadians());b.translate(-this.cx,-this.cy)};this.applyToPoint=function(b){var a=this.angle.Angle.toRadians();b.applyTransform([1,0,0,1,this.p.x||0,this.p.y||0]);b.applyTransform([Math.cos(a),Math.sin(a),-Math.sin(a),Math.cos(a),0,0]);b.applyTransform([1,0,0,1,-this.p.x||
+0,-this.p.y||0])}};this.Type.scale=function(b){this.p=a.CreatePoint(b);this.apply=function(b){b.scale(this.p.x||1,this.p.y||this.p.x||1)};this.applyToPoint=function(b){b.applyTransform([this.p.x||0,0,0,this.p.y||0,0,0])}};this.Type.matrix=function(b){this.m=a.ToNumberArray(b);this.apply=function(b){b.transform(this.m[0],this.m[1],this.m[2],this.m[3],this.m[4],this.m[5])};this.applyToPoint=function(b){b.applyTransform(this.m)}};this.Type.SkewBase=function(b){this.base=d.Type.matrix;this.base(b);this.angle=
+new a.Property("angle",b)};this.Type.SkewBase.prototype=new this.Type.matrix;this.Type.skewX=function(b){this.base=d.Type.SkewBase;this.base(b);this.m=[1,0,Math.tan(this.angle.Angle.toRadians()),1,0,0]};this.Type.skewX.prototype=new this.Type.SkewBase;this.Type.skewY=function(b){this.base=d.Type.SkewBase;this.base(b);this.m=[1,Math.tan(this.angle.Angle.toRadians()),0,1,0,0]};this.Type.skewY.prototype=new this.Type.SkewBase;this.transforms=[];this.apply=function(b){for(var a=0;a<this.transforms.length;a++)this.transforms[a].apply(b)};
+this.applyToPoint=function(b){for(var a=0;a<this.transforms.length;a++)this.transforms[a].applyToPoint(b)};for(var c=a.trim(a.compressSpaces(c)).split(/\s(?=[a-z])/),b=0;b<c.length;b++){var k=c[b].split("(")[0],e=c[b].split("(")[1].replace(")","");this.transforms.push(new this.Type[k](e))}};a.AspectRatio=function(c,d,b,k,e,f,g,j,h,l){var d=a.compressSpaces(d),d=d.replace(/^defer\s/,""),o=d.split(" ")[0]||"xMidYMid",d=d.split(" ")[1]||"meet",n=b/k,q=e/f,p=Math.min(n,q),m=Math.max(n,q);d=="meet"&&(k*=
+p,f*=p);d=="slice"&&(k*=m,f*=m);h=new a.Property("refX",h);l=new a.Property("refY",l);h.hasValue()&&l.hasValue()?c.translate(-p*h.Length.toPixels("x"),-p*l.Length.toPixels("y")):(o.match(/^xMid/)&&(d=="meet"&&p==q||d=="slice"&&m==q)&&c.translate(b/2-k/2,0),o.match(/YMid$/)&&(d=="meet"&&p==n||d=="slice"&&m==n)&&c.translate(0,e/2-f/2),o.match(/^xMax/)&&(d=="meet"&&p==q||d=="slice"&&m==q)&&c.translate(b-k,0),o.match(/YMax$/)&&(d=="meet"&&p==n||d=="slice"&&m==n)&&c.translate(0,e-f));o=="none"?c.scale(n,
+q):d=="meet"?c.scale(p,p):d=="slice"&&c.scale(m,m);c.translate(g==null?0:-g,j==null?0:-j)};a.Element={};a.Element.ElementBase=function(c){this.attributes={};this.styles={};this.children=[];this.attribute=function(b,d){var c=this.attributes[b];if(c!=null)return c;c=new a.Property(b,"");d==!0&&(this.attributes[b]=c);return c};this.style=function(b,d){var c=this.styles[b];if(c!=null)return c;c=this.attribute(b);if(c!=null&&c.hasValue())return c;c=this.parent;if(c!=null&&(c=c.style(b),c!=null&&c.hasValue()))return c;
+c=new a.Property(b,"");d==!0&&(this.styles[b]=c);return c};this.render=function(b){if(this.style("display").value!="none"&&this.attribute("visibility").value!="hidden"){b.save();this.setContext(b);if(this.attribute("mask").hasValue()){var a=this.attribute("mask").Definition.getDefinition();a!=null&&a.apply(b,this)}else this.style("filter").hasValue()?(a=this.style("filter").Definition.getDefinition(),a!=null&&a.apply(b,this)):this.renderChildren(b);this.clearContext(b);b.restore()}};this.setContext=
+function(){};this.clearContext=function(){};this.renderChildren=function(b){for(var a=0;a<this.children.length;a++)this.children[a].render(b)};this.addChild=function(b,d){var c=b;d&&(c=a.CreateElement(b));c.parent=this;this.children.push(c)};if(c!=null&&c.nodeType==1){for(var d=0;d<c.childNodes.length;d++){var b=c.childNodes[d];b.nodeType==1&&this.addChild(b,!0)}for(d=0;d<c.attributes.length;d++)b=c.attributes[d],this.attributes[b.nodeName]=new a.Property(b.nodeName,b.nodeValue);b=a.Styles[c.nodeName];
+if(b!=null)for(var k in b)this.styles[k]=b[k];if(this.attribute("class").hasValue())for(var d=a.compressSpaces(this.attribute("class").value).split(" "),e=0;e<d.length;e++){b=a.Styles["."+d[e]];if(b!=null)for(k in b)this.styles[k]=b[k];b=a.Styles[c.nodeName+"."+d[e]];if(b!=null)for(k in b)this.styles[k]=b[k]}if(this.attribute("style").hasValue()){b=this.attribute("style").value.split(";");for(d=0;d<b.length;d++)a.trim(b[d])!=""&&(c=b[d].split(":"),k=a.trim(c[0]),c=a.trim(c[1]),this.styles[k]=new a.Property(k,
+c))}this.attribute("id").hasValue()&&a.Definitions[this.attribute("id").value]==null&&(a.Definitions[this.attribute("id").value]=this)}};a.Element.RenderedElementBase=function(c){this.base=a.Element.ElementBase;this.base(c);this.setContext=function(d){if(this.style("fill").Definition.isUrl()){var b=this.style("fill").Definition.getFillStyle(this);if(b!=null)d.fillStyle=b}else if(this.style("fill").hasValue())b=this.style("fill"),this.style("fill-opacity").hasValue()&&(b=b.Color.addOpacity(this.style("fill-opacity").value)),
+d.fillStyle=b.value=="none"?"rgba(0,0,0,0)":b.value;if(this.style("stroke").Definition.isUrl()){if(b=this.style("stroke").Definition.getFillStyle(this),b!=null)d.strokeStyle=b}else if(this.style("stroke").hasValue())b=this.style("stroke"),this.style("stroke-opacity").hasValue()&&(b=b.Color.addOpacity(this.style("stroke-opacity").value)),d.strokeStyle=b.value=="none"?"rgba(0,0,0,0)":b.value;if(this.style("stroke-width").hasValue())d.lineWidth=this.style("stroke-width").Length.toPixels();if(this.style("stroke-linecap").hasValue())d.lineCap=
+this.style("stroke-linecap").value;if(this.style("stroke-linejoin").hasValue())d.lineJoin=this.style("stroke-linejoin").value;if(this.style("stroke-miterlimit").hasValue())d.miterLimit=this.style("stroke-miterlimit").value;if(typeof d.font!="undefined")d.font=a.Font.CreateFont(this.style("font-style").value,this.style("font-variant").value,this.style("font-weight").value,this.style("font-size").hasValue()?this.style("font-size").Length.toPixels()+"px":"",this.style("font-family").value).toString();
+this.attribute("transform").hasValue()&&(new a.Transform(this.attribute("transform").value)).apply(d);this.attribute("clip-path").hasValue()&&(b=this.attribute("clip-path").Definition.getDefinition(),b!=null&&b.apply(d));if(this.style("opacity").hasValue())d.globalAlpha=this.style("opacity").numValue()}};a.Element.RenderedElementBase.prototype=new a.Element.ElementBase;a.Element.PathElementBase=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.path=function(d){d!=null&&d.beginPath();
+return new a.BoundingBox};this.renderChildren=function(d){this.path(d);a.Mouse.checkPath(this,d);d.fillStyle!=""&&d.fill();d.strokeStyle!=""&&d.stroke();var b=this.getMarkers();if(b!=null){if(this.style("marker-start").Definition.isUrl()){var c=this.style("marker-start").Definition.getDefinition();c.render(d,b[0][0],b[0][1])}if(this.style("marker-mid").Definition.isUrl())for(var c=this.style("marker-mid").Definition.getDefinition(),e=1;e<b.length-1;e++)c.render(d,b[e][0],b[e][1]);this.style("marker-end").Definition.isUrl()&&
+(c=this.style("marker-end").Definition.getDefinition(),c.render(d,b[b.length-1][0],b[b.length-1][1]))}};this.getBoundingBox=function(){return this.path()};this.getMarkers=function(){return null}};a.Element.PathElementBase.prototype=new a.Element.RenderedElementBase;a.Element.svg=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.baseClearContext=this.clearContext;this.clearContext=function(d){this.baseClearContext(d);a.ViewPort.RemoveCurrent()};this.baseSetContext=this.setContext;
+this.setContext=function(d){d.strokeStyle="rgba(0,0,0,0)";d.lineCap="butt";d.lineJoin="miter";d.miterLimit=4;this.baseSetContext(d);this.attribute("x").hasValue()&&this.attribute("y").hasValue()&&d.translate(this.attribute("x").Length.toPixels("x"),this.attribute("y").Length.toPixels("y"));var b=a.ViewPort.width(),c=a.ViewPort.height();if(typeof this.root=="undefined"&&this.attribute("width").hasValue()&&this.attribute("height").hasValue()){var b=this.attribute("width").Length.toPixels("x"),c=this.attribute("height").Length.toPixels("y"),
+e=0,f=0;this.attribute("refX").hasValue()&&this.attribute("refY").hasValue()&&(e=-this.attribute("refX").Length.toPixels("x"),f=-this.attribute("refY").Length.toPixels("y"));d.beginPath();d.moveTo(e,f);d.lineTo(b,f);d.lineTo(b,c);d.lineTo(e,c);d.closePath();d.clip()}a.ViewPort.SetCurrent(b,c);if(this.attribute("viewBox").hasValue()){var e=a.ToNumberArray(this.attribute("viewBox").value),f=e[0],g=e[1],b=e[2],c=e[3];a.AspectRatio(d,this.attribute("preserveAspectRatio").value,a.ViewPort.width(),b,a.ViewPort.height(),
+c,f,g,this.attribute("refX").value,this.attribute("refY").value);a.ViewPort.RemoveCurrent();a.ViewPort.SetCurrent(e[2],e[3])}}};a.Element.svg.prototype=new a.Element.RenderedElementBase;a.Element.rect=function(c){this.base=a.Element.PathElementBase;this.base(c);this.path=function(d){var b=this.attribute("x").Length.toPixels("x"),c=this.attribute("y").Length.toPixels("y"),e=this.attribute("width").Length.toPixels("x"),f=this.attribute("height").Length.toPixels("y"),g=this.attribute("rx").Length.toPixels("x"),
+j=this.attribute("ry").Length.toPixels("y");this.attribute("rx").hasValue()&&!this.attribute("ry").hasValue()&&(j=g);this.attribute("ry").hasValue()&&!this.attribute("rx").hasValue()&&(g=j);d!=null&&(d.beginPath(),d.moveTo(b+g,c),d.lineTo(b+e-g,c),d.quadraticCurveTo(b+e,c,b+e,c+j),d.lineTo(b+e,c+f-j),d.quadraticCurveTo(b+e,c+f,b+e-g,c+f),d.lineTo(b+g,c+f),d.quadraticCurveTo(b,c+f,b,c+f-j),d.lineTo(b,c+j),d.quadraticCurveTo(b,c,b+g,c),d.closePath());return new a.BoundingBox(b,c,b+e,c+f)}};a.Element.rect.prototype=
+new a.Element.PathElementBase;a.Element.circle=function(c){this.base=a.Element.PathElementBase;this.base(c);this.path=function(d){var b=this.attribute("cx").Length.toPixels("x"),c=this.attribute("cy").Length.toPixels("y"),e=this.attribute("r").Length.toPixels();d!=null&&(d.beginPath(),d.arc(b,c,e,0,Math.PI*2,!0),d.closePath());return new a.BoundingBox(b-e,c-e,b+e,c+e)}};a.Element.circle.prototype=new a.Element.PathElementBase;a.Element.ellipse=function(c){this.base=a.Element.PathElementBase;this.base(c);
+this.path=function(d){var b=4*((Math.sqrt(2)-1)/3),c=this.attribute("rx").Length.toPixels("x"),e=this.attribute("ry").Length.toPixels("y"),f=this.attribute("cx").Length.toPixels("x"),g=this.attribute("cy").Length.toPixels("y");d!=null&&(d.beginPath(),d.moveTo(f,g-e),d.bezierCurveTo(f+b*c,g-e,f+c,g-b*e,f+c,g),d.bezierCurveTo(f+c,g+b*e,f+b*c,g+e,f,g+e),d.bezierCurveTo(f-b*c,g+e,f-c,g+b*e,f-c,g),d.bezierCurveTo(f-c,g-b*e,f-b*c,g-e,f,g-e),d.closePath());return new a.BoundingBox(f-c,g-e,f+c,g+e)}};a.Element.ellipse.prototype=
+new a.Element.PathElementBase;a.Element.line=function(c){this.base=a.Element.PathElementBase;this.base(c);this.getPoints=function(){return[new a.Point(this.attribute("x1").Length.toPixels("x"),this.attribute("y1").Length.toPixels("y")),new a.Point(this.attribute("x2").Length.toPixels("x"),this.attribute("y2").Length.toPixels("y"))]};this.path=function(d){var b=this.getPoints();d!=null&&(d.beginPath(),d.moveTo(b[0].x,b[0].y),d.lineTo(b[1].x,b[1].y));return new a.BoundingBox(b[0].x,b[0].y,b[1].x,b[1].y)};
+this.getMarkers=function(){var a=this.getPoints(),b=a[0].angleTo(a[1]);return[[a[0],b],[a[1],b]]}};a.Element.line.prototype=new a.Element.PathElementBase;a.Element.polyline=function(c){this.base=a.Element.PathElementBase;this.base(c);this.points=a.CreatePath(this.attribute("points").value);this.path=function(d){var b=new a.BoundingBox(this.points[0].x,this.points[0].y);d!=null&&(d.beginPath(),d.moveTo(this.points[0].x,this.points[0].y));for(var c=1;c<this.points.length;c++)b.addPoint(this.points[c].x,
+this.points[c].y),d!=null&&d.lineTo(this.points[c].x,this.points[c].y);return b};this.getMarkers=function(){for(var a=[],b=0;b<this.points.length-1;b++)a.push([this.points[b],this.points[b].angleTo(this.points[b+1])]);a.push([this.points[this.points.length-1],a[a.length-1][1]]);return a}};a.Element.polyline.prototype=new a.Element.PathElementBase;a.Element.polygon=function(c){this.base=a.Element.polyline;this.base(c);this.basePath=this.path;this.path=function(a){var b=this.basePath(a);a!=null&&(a.lineTo(this.points[0].x,
+this.points[0].y),a.closePath());return b}};a.Element.polygon.prototype=new a.Element.polyline;a.Element.path=function(c){this.base=a.Element.PathElementBase;this.base(c);c=this.attribute("d").value;c=c.replace(/,/gm," ");c=c.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,"$1 $2");c=c.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,"$1 $2");c=c.replace(/([MmZzLlHhVvCcSsQqTtAa])([^\s])/gm,"$1 $2");c=c.replace(/([^\s])([MmZzLlHhVvCcSsQqTtAa])/gm,"$1 $2");c=c.replace(/([0-9])([+\-])/gm,
+"$1 $2");c=c.replace(/(\.[0-9]*)(\.)/gm,"$1 $2");c=c.replace(/([Aa](\s+[0-9]+){3})\s+([01])\s*([01])/gm,"$1 $3 $4 ");c=a.compressSpaces(c);c=a.trim(c);this.PathParser=new function(d){this.tokens=d.split(" ");this.reset=function(){this.i=-1;this.previousCommand=this.command="";this.start=new a.Point(0,0);this.control=new a.Point(0,0);this.current=new a.Point(0,0);this.points=[];this.angles=[]};this.isEnd=function(){return this.i>=this.tokens.length-1};this.isCommandOrEnd=function(){return this.isEnd()?
+!0:this.tokens[this.i+1].match(/^[A-Za-z]$/)!=null};this.isRelativeCommand=function(){return this.command==this.command.toLowerCase()};this.getToken=function(){this.i+=1;return this.tokens[this.i]};this.getScalar=function(){return parseFloat(this.getToken())};this.nextCommand=function(){this.previousCommand=this.command;this.command=this.getToken()};this.getPoint=function(){return this.makeAbsolute(new a.Point(this.getScalar(),this.getScalar()))};this.getAsControlPoint=function(){var b=this.getPoint();
+return this.control=b};this.getAsCurrentPoint=function(){var b=this.getPoint();return this.current=b};this.getReflectedControlPoint=function(){return this.previousCommand.toLowerCase()!="c"&&this.previousCommand.toLowerCase()!="s"?this.current:new a.Point(2*this.current.x-this.control.x,2*this.current.y-this.control.y)};this.makeAbsolute=function(b){if(this.isRelativeCommand())b.x=this.current.x+b.x,b.y=this.current.y+b.y;return b};this.addMarker=function(b,a,d){d!=null&&this.angles.length>0&&this.angles[this.angles.length-
+1]==null&&(this.angles[this.angles.length-1]=this.points[this.points.length-1].angleTo(d));this.addMarkerAngle(b,a==null?null:a.angleTo(b))};this.addMarkerAngle=function(b,a){this.points.push(b);this.angles.push(a)};this.getMarkerPoints=function(){return this.points};this.getMarkerAngles=function(){for(var b=0;b<this.angles.length;b++)if(this.angles[b]==null)for(var a=b+1;a<this.angles.length;a++)if(this.angles[a]!=null){this.angles[b]=this.angles[a];break}return this.angles}}(c);this.path=function(d){var b=
+this.PathParser;b.reset();var c=new a.BoundingBox;for(d!=null&&d.beginPath();!b.isEnd();)switch(b.nextCommand(),b.command.toUpperCase()){case "M":var e=b.getAsCurrentPoint();b.addMarker(e);c.addPoint(e.x,e.y);d!=null&&d.moveTo(e.x,e.y);for(b.start=b.current;!b.isCommandOrEnd();)e=b.getAsCurrentPoint(),b.addMarker(e,b.start),c.addPoint(e.x,e.y),d!=null&&d.lineTo(e.x,e.y);break;case "L":for(;!b.isCommandOrEnd();){var f=b.current,e=b.getAsCurrentPoint();b.addMarker(e,f);c.addPoint(e.x,e.y);d!=null&&
+d.lineTo(e.x,e.y)}break;case "H":for(;!b.isCommandOrEnd();)e=new a.Point((b.isRelativeCommand()?b.current.x:0)+b.getScalar(),b.current.y),b.addMarker(e,b.current),b.current=e,c.addPoint(b.current.x,b.current.y),d!=null&&d.lineTo(b.current.x,b.current.y);break;case "V":for(;!b.isCommandOrEnd();)e=new a.Point(b.current.x,(b.isRelativeCommand()?b.current.y:0)+b.getScalar()),b.addMarker(e,b.current),b.current=e,c.addPoint(b.current.x,b.current.y),d!=null&&d.lineTo(b.current.x,b.current.y);break;case "C":for(;!b.isCommandOrEnd();){var g=
+b.current,f=b.getPoint(),j=b.getAsControlPoint(),e=b.getAsCurrentPoint();b.addMarker(e,j,f);c.addBezierCurve(g.x,g.y,f.x,f.y,j.x,j.y,e.x,e.y);d!=null&&d.bezierCurveTo(f.x,f.y,j.x,j.y,e.x,e.y)}break;case "S":for(;!b.isCommandOrEnd();)g=b.current,f=b.getReflectedControlPoint(),j=b.getAsControlPoint(),e=b.getAsCurrentPoint(),b.addMarker(e,j,f),c.addBezierCurve(g.x,g.y,f.x,f.y,j.x,j.y,e.x,e.y),d!=null&&d.bezierCurveTo(f.x,f.y,j.x,j.y,e.x,e.y);break;case "Q":for(;!b.isCommandOrEnd();)g=b.current,j=b.getAsControlPoint(),
+e=b.getAsCurrentPoint(),b.addMarker(e,j,j),c.addQuadraticCurve(g.x,g.y,j.x,j.y,e.x,e.y),d!=null&&d.quadraticCurveTo(j.x,j.y,e.x,e.y);break;case "T":for(;!b.isCommandOrEnd();)g=b.current,j=b.getReflectedControlPoint(),b.control=j,e=b.getAsCurrentPoint(),b.addMarker(e,j,j),c.addQuadraticCurve(g.x,g.y,j.x,j.y,e.x,e.y),d!=null&&d.quadraticCurveTo(j.x,j.y,e.x,e.y);break;case "A":for(;!b.isCommandOrEnd();){var g=b.current,h=b.getScalar(),l=b.getScalar(),f=b.getScalar()*(Math.PI/180),o=b.getScalar(),j=b.getScalar(),
+e=b.getAsCurrentPoint(),n=new a.Point(Math.cos(f)*(g.x-e.x)/2+Math.sin(f)*(g.y-e.y)/2,-Math.sin(f)*(g.x-e.x)/2+Math.cos(f)*(g.y-e.y)/2),q=Math.pow(n.x,2)/Math.pow(h,2)+Math.pow(n.y,2)/Math.pow(l,2);q>1&&(h*=Math.sqrt(q),l*=Math.sqrt(q));o=(o==j?-1:1)*Math.sqrt((Math.pow(h,2)*Math.pow(l,2)-Math.pow(h,2)*Math.pow(n.y,2)-Math.pow(l,2)*Math.pow(n.x,2))/(Math.pow(h,2)*Math.pow(n.y,2)+Math.pow(l,2)*Math.pow(n.x,2)));isNaN(o)&&(o=0);var p=new a.Point(o*h*n.y/l,o*-l*n.x/h),g=new a.Point((g.x+e.x)/2+Math.cos(f)*
+p.x-Math.sin(f)*p.y,(g.y+e.y)/2+Math.sin(f)*p.x+Math.cos(f)*p.y),m=function(b,a){return(b[0]*a[0]+b[1]*a[1])/(Math.sqrt(Math.pow(b[0],2)+Math.pow(b[1],2))*Math.sqrt(Math.pow(a[0],2)+Math.pow(a[1],2)))},s=function(b,a){return(b[0]*a[1]<b[1]*a[0]?-1:1)*Math.acos(m(b,a))},o=s([1,0],[(n.x-p.x)/h,(n.y-p.y)/l]),q=[(n.x-p.x)/h,(n.y-p.y)/l],p=[(-n.x-p.x)/h,(-n.y-p.y)/l],n=s(q,p);if(m(q,p)<=-1)n=Math.PI;m(q,p)>=1&&(n=0);j==0&&n>0&&(n-=2*Math.PI);j==1&&n<0&&(n+=2*Math.PI);q=new a.Point(g.x-h*Math.cos((o+n)/
+2),g.y-l*Math.sin((o+n)/2));b.addMarkerAngle(q,(o+n)/2+(j==0?1:-1)*Math.PI/2);b.addMarkerAngle(e,n+(j==0?1:-1)*Math.PI/2);c.addPoint(e.x,e.y);d!=null&&(m=h>l?h:l,e=h>l?1:h/l,h=h>l?l/h:1,d.translate(g.x,g.y),d.rotate(f),d.scale(e,h),d.arc(0,0,m,o,o+n,1-j),d.scale(1/e,1/h),d.rotate(-f),d.translate(-g.x,-g.y))}break;case "Z":d!=null&&d.closePath(),b.current=b.start}return c};this.getMarkers=function(){for(var a=this.PathParser.getMarkerPoints(),b=this.PathParser.getMarkerAngles(),c=[],e=0;e<a.length;e++)c.push([a[e],
+b[e]]);return c}};a.Element.path.prototype=new a.Element.PathElementBase;a.Element.pattern=function(c){this.base=a.Element.ElementBase;this.base(c);this.createPattern=function(d){var b=new a.Element.svg;b.attributes.viewBox=new a.Property("viewBox",this.attribute("viewBox").value);b.attributes.x=new a.Property("x",this.attribute("x").value);b.attributes.y=new a.Property("y",this.attribute("y").value);b.attributes.width=new a.Property("width",this.attribute("width").value);b.attributes.height=new a.Property("height",
+this.attribute("height").value);b.children=this.children;var c=document.createElement("canvas");c.width=this.attribute("width").Length.toPixels("x");c.height=this.attribute("height").Length.toPixels("y");b.render(c.getContext("2d"));return d.createPattern(c,"repeat")}};a.Element.pattern.prototype=new a.Element.ElementBase;a.Element.marker=function(c){this.base=a.Element.ElementBase;this.base(c);this.baseRender=this.render;this.render=function(d,b,c){d.translate(b.x,b.y);this.attribute("orient").valueOrDefault("auto")==
+"auto"&&d.rotate(c);this.attribute("markerUnits").valueOrDefault("strokeWidth")=="strokeWidth"&&d.scale(d.lineWidth,d.lineWidth);d.save();var e=new a.Element.svg;e.attributes.viewBox=new a.Property("viewBox",this.attribute("viewBox").value);e.attributes.refX=new a.Property("refX",this.attribute("refX").value);e.attributes.refY=new a.Property("refY",this.attribute("refY").value);e.attributes.width=new a.Property("width",this.attribute("markerWidth").value);e.attributes.height=new a.Property("height",
+this.attribute("markerHeight").value);e.attributes.fill=new a.Property("fill",this.attribute("fill").valueOrDefault("black"));e.attributes.stroke=new a.Property("stroke",this.attribute("stroke").valueOrDefault("none"));e.children=this.children;e.render(d);d.restore();this.attribute("markerUnits").valueOrDefault("strokeWidth")=="strokeWidth"&&d.scale(1/d.lineWidth,1/d.lineWidth);this.attribute("orient").valueOrDefault("auto")=="auto"&&d.rotate(-c);d.translate(-b.x,-b.y)}};a.Element.marker.prototype=
+new a.Element.ElementBase;a.Element.defs=function(c){this.base=a.Element.ElementBase;this.base(c);this.render=function(){}};a.Element.defs.prototype=new a.Element.ElementBase;a.Element.GradientBase=function(c){this.base=a.Element.ElementBase;this.base(c);this.gradientUnits=this.attribute("gradientUnits").valueOrDefault("objectBoundingBox");this.stops=[];for(c=0;c<this.children.length;c++)this.stops.push(this.children[c]);this.getGradient=function(){};this.createGradient=function(d,b){var c=this;this.attribute("xlink:href").hasValue()&&
+(c=this.attribute("xlink:href").Definition.getDefinition());for(var e=this.getGradient(d,b),f=0;f<c.stops.length;f++)e.addColorStop(c.stops[f].offset,c.stops[f].color);if(this.attribute("gradientTransform").hasValue()){c=a.ViewPort.viewPorts[0];f=new a.Element.rect;f.attributes.x=new a.Property("x",-a.MAX_VIRTUAL_PIXELS/3);f.attributes.y=new a.Property("y",-a.MAX_VIRTUAL_PIXELS/3);f.attributes.width=new a.Property("width",a.MAX_VIRTUAL_PIXELS);f.attributes.height=new a.Property("height",a.MAX_VIRTUAL_PIXELS);
+var g=new a.Element.g;g.attributes.transform=new a.Property("transform",this.attribute("gradientTransform").value);g.children=[f];f=new a.Element.svg;f.attributes.x=new a.Property("x",0);f.attributes.y=new a.Property("y",0);f.attributes.width=new a.Property("width",c.width);f.attributes.height=new a.Property("height",c.height);f.children=[g];g=document.createElement("canvas");g.width=c.width;g.height=c.height;c=g.getContext("2d");c.fillStyle=e;f.render(c);return c.createPattern(g,"no-repeat")}return e}};
+a.Element.GradientBase.prototype=new a.Element.ElementBase;a.Element.linearGradient=function(c){this.base=a.Element.GradientBase;this.base(c);this.getGradient=function(a,b){var c=b.getBoundingBox(),e=this.gradientUnits=="objectBoundingBox"?c.x()+c.width()*this.attribute("x1").numValue():this.attribute("x1").Length.toPixels("x"),f=this.gradientUnits=="objectBoundingBox"?c.y()+c.height()*this.attribute("y1").numValue():this.attribute("y1").Length.toPixels("y"),g=this.gradientUnits=="objectBoundingBox"?
+c.x()+c.width()*this.attribute("x2").numValue():this.attribute("x2").Length.toPixels("x"),c=this.gradientUnits=="objectBoundingBox"?c.y()+c.height()*this.attribute("y2").numValue():this.attribute("y2").Length.toPixels("y");return a.createLinearGradient(e,f,g,c)}};a.Element.linearGradient.prototype=new a.Element.GradientBase;a.Element.radialGradient=function(c){this.base=a.Element.GradientBase;this.base(c);this.getGradient=function(a,b){var c=b.getBoundingBox(),e=this.gradientUnits=="objectBoundingBox"?
+c.x()+c.width()*this.attribute("cx").numValue():this.attribute("cx").Length.toPixels("x"),f=this.gradientUnits=="objectBoundingBox"?c.y()+c.height()*this.attribute("cy").numValue():this.attribute("cy").Length.toPixels("y"),g=e,j=f;this.attribute("fx").hasValue()&&(g=this.gradientUnits=="objectBoundingBox"?c.x()+c.width()*this.attribute("fx").numValue():this.attribute("fx").Length.toPixels("x"));this.attribute("fy").hasValue()&&(j=this.gradientUnits=="objectBoundingBox"?c.y()+c.height()*this.attribute("fy").numValue():
+this.attribute("fy").Length.toPixels("y"));c=this.gradientUnits=="objectBoundingBox"?(c.width()+c.height())/2*this.attribute("r").numValue():this.attribute("r").Length.toPixels();return a.createRadialGradient(g,j,0,e,f,c)}};a.Element.radialGradient.prototype=new a.Element.GradientBase;a.Element.stop=function(c){this.base=a.Element.ElementBase;this.base(c);this.offset=this.attribute("offset").numValue();c=this.style("stop-color");this.style("stop-opacity").hasValue()&&(c=c.Color.addOpacity(this.style("stop-opacity").value));
+this.color=c.value};a.Element.stop.prototype=new a.Element.ElementBase;a.Element.AnimateBase=function(c){this.base=a.Element.ElementBase;this.base(c);a.Animations.push(this);this.duration=0;this.begin=this.attribute("begin").Time.toMilliseconds();this.maxDuration=this.begin+this.attribute("dur").Time.toMilliseconds();this.getProperty=function(){var a=this.attribute("attributeType").value,b=this.attribute("attributeName").value;return a=="CSS"?this.parent.style(b,!0):this.parent.attribute(b,!0)};this.initialValue=
+null;this.removed=!1;this.calcValue=function(){return""};this.update=function(a){if(this.initialValue==null)this.initialValue=this.getProperty().value;if(this.duration>this.maxDuration)if(this.attribute("repeatCount").value=="indefinite")this.duration=0;else return this.attribute("fill").valueOrDefault("remove")=="remove"&&!this.removed?(this.removed=!0,this.getProperty().value=this.initialValue,!0):!1;this.duration+=a;a=!1;if(this.begin<this.duration)a=this.calcValue(),this.attribute("type").hasValue()&&
+(a=this.attribute("type").value+"("+a+")"),this.getProperty().value=a,a=!0;return a};this.progress=function(){return(this.duration-this.begin)/(this.maxDuration-this.begin)}};a.Element.AnimateBase.prototype=new a.Element.ElementBase;a.Element.animate=function(c){this.base=a.Element.AnimateBase;this.base(c);this.calcValue=function(){var a=this.attribute("from").numValue(),b=this.attribute("to").numValue();return a+(b-a)*this.progress()}};a.Element.animate.prototype=new a.Element.AnimateBase;a.Element.animateColor=
+function(c){this.base=a.Element.AnimateBase;this.base(c);this.calcValue=function(){var a=new RGBColor(this.attribute("from").value),b=new RGBColor(this.attribute("to").value);if(a.ok&&b.ok){var c=a.r+(b.r-a.r)*this.progress(),e=a.g+(b.g-a.g)*this.progress(),a=a.b+(b.b-a.b)*this.progress();return"rgb("+parseInt(c,10)+","+parseInt(e,10)+","+parseInt(a,10)+")"}return this.attribute("from").value}};a.Element.animateColor.prototype=new a.Element.AnimateBase;a.Element.animateTransform=function(c){this.base=
+a.Element.animate;this.base(c)};a.Element.animateTransform.prototype=new a.Element.animate;a.Element.font=function(c){this.base=a.Element.ElementBase;this.base(c);this.horizAdvX=this.attribute("horiz-adv-x").numValue();this.isArabic=this.isRTL=!1;this.missingGlyph=this.fontFace=null;this.glyphs=[];for(c=0;c<this.children.length;c++){var d=this.children[c];if(d.type=="font-face")this.fontFace=d,d.style("font-family").hasValue()&&(a.Definitions[d.style("font-family").value]=this);else if(d.type=="missing-glyph")this.missingGlyph=
+d;else if(d.type=="glyph")d.arabicForm!=""?(this.isArabic=this.isRTL=!0,typeof this.glyphs[d.unicode]=="undefined"&&(this.glyphs[d.unicode]=[]),this.glyphs[d.unicode][d.arabicForm]=d):this.glyphs[d.unicode]=d}};a.Element.font.prototype=new a.Element.ElementBase;a.Element.fontface=function(c){this.base=a.Element.ElementBase;this.base(c);this.ascent=this.attribute("ascent").value;this.descent=this.attribute("descent").value;this.unitsPerEm=this.attribute("units-per-em").numValue()};a.Element.fontface.prototype=
+new a.Element.ElementBase;a.Element.missingglyph=function(c){this.base=a.Element.path;this.base(c);this.horizAdvX=0};a.Element.missingglyph.prototype=new a.Element.path;a.Element.glyph=function(c){this.base=a.Element.path;this.base(c);this.horizAdvX=this.attribute("horiz-adv-x").numValue();this.unicode=this.attribute("unicode").value;this.arabicForm=this.attribute("arabic-form").value};a.Element.glyph.prototype=new a.Element.path;a.Element.text=function(c){this.base=a.Element.RenderedElementBase;
+this.base(c);if(c!=null){this.children=[];for(var d=0;d<c.childNodes.length;d++){var b=c.childNodes[d];b.nodeType==1?this.addChild(b,!0):b.nodeType==3&&this.addChild(new a.Element.tspan(b),!1)}}this.baseSetContext=this.setContext;this.setContext=function(b){this.baseSetContext(b);if(this.style("dominant-baseline").hasValue())b.textBaseline=this.style("dominant-baseline").value;if(this.style("alignment-baseline").hasValue())b.textBaseline=this.style("alignment-baseline").value};this.renderChildren=
+function(b){for(var a=this.style("text-anchor").valueOrDefault("start"),c=this.attribute("x").Length.toPixels("x"),d=this.attribute("y").Length.toPixels("y"),j=0;j<this.children.length;j++){var h=this.children[j];h.attribute("x").hasValue()?h.x=h.attribute("x").Length.toPixels("x"):(h.attribute("dx").hasValue()&&(c+=h.attribute("dx").Length.toPixels("x")),h.x=c);c=h.measureText(b);if(a!="start"&&(j==0||h.attribute("x").hasValue())){for(var l=c,o=j+1;o<this.children.length;o++){var n=this.children[o];
+if(n.attribute("x").hasValue())break;l+=n.measureText(b)}h.x-=a=="end"?l:l/2}c=h.x+c;h.attribute("y").hasValue()?h.y=h.attribute("y").Length.toPixels("y"):(h.attribute("dy").hasValue()&&(d+=h.attribute("dy").Length.toPixels("y")),h.y=d);d=h.y;h.render(b)}}};a.Element.text.prototype=new a.Element.RenderedElementBase;a.Element.TextElementBase=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.getGlyph=function(a,b,c){var e=b[c],f=null;if(a.isArabic){var g="isolated";if((c==0||b[c-
+1]==" ")&&c<b.length-2&&b[c+1]!=" ")g="terminal";c>0&&b[c-1]!=" "&&c<b.length-2&&b[c+1]!=" "&&(g="medial");if(c>0&&b[c-1]!=" "&&(c==b.length-1||b[c+1]==" "))g="initial";typeof a.glyphs[e]!="undefined"&&(f=a.glyphs[e][g],f==null&&a.glyphs[e].type=="glyph"&&(f=a.glyphs[e]))}else f=a.glyphs[e];if(f==null)f=a.missingGlyph;return f};this.renderChildren=function(c){var b=this.parent.style("font-family").Definition.getDefinition();if(b!=null){var k=this.parent.style("font-size").numValueOrDefault(a.Font.Parse(a.ctx.font).fontSize),
+e=this.parent.style("font-style").valueOrDefault(a.Font.Parse(a.ctx.font).fontStyle),f=this.getText();b.isRTL&&(f=f.split("").reverse().join(""));for(var g=a.ToNumberArray(this.parent.attribute("dx").value),j=0;j<f.length;j++){var h=this.getGlyph(b,f,j),l=k/b.fontFace.unitsPerEm;c.translate(this.x,this.y);c.scale(l,-l);var o=c.lineWidth;c.lineWidth=c.lineWidth*b.fontFace.unitsPerEm/k;e=="italic"&&c.transform(1,0,0.4,1,0,0);h.render(c);e=="italic"&&c.transform(1,0,-0.4,1,0,0);c.lineWidth=o;c.scale(1/
+l,-1/l);c.translate(-this.x,-this.y);this.x+=k*(h.horizAdvX||b.horizAdvX)/b.fontFace.unitsPerEm;typeof g[j]!="undefined"&&!isNaN(g[j])&&(this.x+=g[j])}}else c.strokeStyle!=""&&c.strokeText(a.compressSpaces(this.getText()),this.x,this.y),c.fillStyle!=""&&c.fillText(a.compressSpaces(this.getText()),this.x,this.y)};this.getText=function(){};this.measureText=function(c){var b=this.parent.style("font-family").Definition.getDefinition();if(b!=null){var c=this.parent.style("font-size").numValueOrDefault(a.Font.Parse(a.ctx.font).fontSize),
+k=0,e=this.getText();b.isRTL&&(e=e.split("").reverse().join(""));for(var f=a.ToNumberArray(this.parent.attribute("dx").value),g=0;g<e.length;g++){var j=this.getGlyph(b,e,g);k+=(j.horizAdvX||b.horizAdvX)*c/b.fontFace.unitsPerEm;typeof f[g]!="undefined"&&!isNaN(f[g])&&(k+=f[g])}return k}b=a.compressSpaces(this.getText());if(!c.measureText)return b.length*10;c.save();this.setContext(c);b=c.measureText(b).width;c.restore();return b}};a.Element.TextElementBase.prototype=new a.Element.RenderedElementBase;
+a.Element.tspan=function(c){this.base=a.Element.TextElementBase;this.base(c);this.text=c.nodeType==3?c.nodeValue:c.childNodes.length>0?c.childNodes[0].nodeValue:c.text;this.getText=function(){return this.text}};a.Element.tspan.prototype=new a.Element.TextElementBase;a.Element.tref=function(c){this.base=a.Element.TextElementBase;this.base(c);this.getText=function(){var a=this.attribute("xlink:href").Definition.getDefinition();if(a!=null)return a.children[0].getText()}};a.Element.tref.prototype=new a.Element.TextElementBase;
+a.Element.a=function(c){this.base=a.Element.TextElementBase;this.base(c);this.hasText=!0;for(var d=0;d<c.childNodes.length;d++)if(c.childNodes[d].nodeType!=3)this.hasText=!1;this.text=this.hasText?c.childNodes[0].nodeValue:"";this.getText=function(){return this.text};this.baseRenderChildren=this.renderChildren;this.renderChildren=function(b){if(this.hasText){this.baseRenderChildren(b);var c=new a.Property("fontSize",a.Font.Parse(a.ctx.font).fontSize);a.Mouse.checkBoundingBox(this,new a.BoundingBox(this.x,
+this.y-c.Length.toPixels("y"),this.x+this.measureText(b),this.y))}else c=new a.Element.g,c.children=this.children,c.parent=this,c.render(b)};this.onclick=function(){window.open(this.attribute("xlink:href").value)};this.onmousemove=function(){a.ctx.canvas.style.cursor="pointer"}};a.Element.a.prototype=new a.Element.TextElementBase;a.Element.image=function(c){this.base=a.Element.RenderedElementBase;this.base(c);a.Images.push(this);this.img=document.createElement("img");this.loaded=!1;var d=this;this.img.onload=
+function(){d.loaded=!0};this.img.src=this.attribute("xlink:href").value;this.renderChildren=function(b){var c=this.attribute("x").Length.toPixels("x"),d=this.attribute("y").Length.toPixels("y"),f=this.attribute("width").Length.toPixels("x"),g=this.attribute("height").Length.toPixels("y");f==0||g==0||(b.save(),b.translate(c,d),a.AspectRatio(b,this.attribute("preserveAspectRatio").value,f,this.img.width,g,this.img.height,0,0),b.drawImage(this.img,0,0),b.restore())}};a.Element.image.prototype=new a.Element.RenderedElementBase;
+a.Element.g=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.getBoundingBox=function(){for(var c=new a.BoundingBox,b=0;b<this.children.length;b++)c.addBoundingBox(this.children[b].getBoundingBox());return c}};a.Element.g.prototype=new a.Element.RenderedElementBase;a.Element.symbol=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.baseSetContext=this.setContext;this.setContext=function(c){this.baseSetContext(c);if(this.attribute("viewBox").hasValue()){var b=
+a.ToNumberArray(this.attribute("viewBox").value),k=b[0],e=b[1];width=b[2];height=b[3];a.AspectRatio(c,this.attribute("preserveAspectRatio").value,this.attribute("width").Length.toPixels("x"),width,this.attribute("height").Length.toPixels("y"),height,k,e);a.ViewPort.SetCurrent(b[2],b[3])}}};a.Element.symbol.prototype=new a.Element.RenderedElementBase;a.Element.style=function(c){this.base=a.Element.ElementBase;this.base(c);for(var c=c.childNodes[0].nodeValue+(c.childNodes.length>1?c.childNodes[1].nodeValue:
+""),c=c.replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(^[\s]*\/\/.*)/gm,""),c=a.compressSpaces(c),c=c.split("}"),d=0;d<c.length;d++)if(a.trim(c[d])!="")for(var b=c[d].split("{"),k=b[0].split(","),b=b[1].split(";"),e=0;e<k.length;e++){var f=a.trim(k[e]);if(f!=""){for(var g={},j=0;j<b.length;j++){var h=b[j].indexOf(":"),l=b[j].substr(0,h),h=b[j].substr(h+1,b[j].length-h);l!=null&&h!=null&&(g[a.trim(l)]=new a.Property(a.trim(l),a.trim(h)))}a.Styles[f]=g;if(f=="@font-face"){f=g["font-family"].value.replace(/"/g,
+"");g=g.src.value.split(",");for(j=0;j<g.length;j++)if(g[j].indexOf('format("svg")')>0){l=g[j].indexOf("url");h=g[j].indexOf(")",l);l=g[j].substr(l+5,h-l-6);l=a.parseXml(a.ajax(l)).getElementsByTagName("font");for(h=0;h<l.length;h++){var o=a.CreateElement(l[h]);a.Definitions[f]=o}}}}}};a.Element.style.prototype=new a.Element.ElementBase;a.Element.use=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.baseSetContext=this.setContext;this.setContext=function(a){this.baseSetContext(a);
+this.attribute("x").hasValue()&&a.translate(this.attribute("x").Length.toPixels("x"),0);this.attribute("y").hasValue()&&a.translate(0,this.attribute("y").Length.toPixels("y"))};this.getDefinition=function(){var a=this.attribute("xlink:href").Definition.getDefinition();if(this.attribute("width").hasValue())a.attribute("width",!0).value=this.attribute("width").value;if(this.attribute("height").hasValue())a.attribute("height",!0).value=this.attribute("height").value;return a};this.path=function(a){var b=
+this.getDefinition();b!=null&&b.path(a)};this.renderChildren=function(a){var b=this.getDefinition();b!=null&&b.render(a)}};a.Element.use.prototype=new a.Element.RenderedElementBase;a.Element.mask=function(c){this.base=a.Element.ElementBase;this.base(c);this.apply=function(a,b){var c=this.attribute("x").Length.toPixels("x"),e=this.attribute("y").Length.toPixels("y"),f=this.attribute("width").Length.toPixels("x"),g=this.attribute("height").Length.toPixels("y"),j=b.attribute("mask").value;b.attribute("mask").value=
+"";var h=document.createElement("canvas");h.width=c+f;h.height=e+g;var l=h.getContext("2d");this.renderChildren(l);var o=document.createElement("canvas");o.width=c+f;o.height=e+g;var n=o.getContext("2d");b.render(n);n.globalCompositeOperation="destination-in";n.fillStyle=l.createPattern(h,"no-repeat");n.fillRect(0,0,c+f,e+g);a.fillStyle=n.createPattern(o,"no-repeat");a.fillRect(0,0,c+f,e+g);b.attribute("mask").value=j};this.render=function(){}};a.Element.mask.prototype=new a.Element.ElementBase;a.Element.clipPath=
+function(c){this.base=a.Element.ElementBase;this.base(c);this.apply=function(a){for(var b=0;b<this.children.length;b++)this.children[b].path&&(this.children[b].path(a),a.clip())};this.render=function(){}};a.Element.clipPath.prototype=new a.Element.ElementBase;a.Element.filter=function(c){this.base=a.Element.ElementBase;this.base(c);this.apply=function(a,b){var c=b.getBoundingBox(),e=this.attribute("x").Length.toPixels("x"),f=this.attribute("y").Length.toPixels("y");if(e==0||f==0)e=c.x1,f=c.y1;var g=
+this.attribute("width").Length.toPixels("x"),j=this.attribute("height").Length.toPixels("y");if(g==0||j==0)g=c.width(),j=c.height();c=b.style("filter").value;b.style("filter").value="";var h=0.2*g,l=0.2*j,o=document.createElement("canvas");o.width=g+2*h;o.height=j+2*l;var n=o.getContext("2d");n.translate(-e+h,-f+l);b.render(n);for(var q=0;q<this.children.length;q++)this.children[q].apply(n,0,0,g+2*h,j+2*l);a.drawImage(o,0,0,g+2*h,j+2*l,e-h,f-l,g+2*h,j+2*l);b.style("filter",!0).value=c};this.render=
+function(){}};a.Element.filter.prototype=new a.Element.ElementBase;a.Element.feGaussianBlur=function(c){function d(a,c,d,f,g){for(var j=0;j<g;j++)for(var h=0;h<f;h++)for(var l=a[j*f*4+h*4+3]/255,o=0;o<4;o++){for(var n=d[0]*(l==0?255:a[j*f*4+h*4+o])*(l==0||o==3?1:l),q=1;q<d.length;q++){var p=Math.max(h-q,0),m=a[j*f*4+p*4+3]/255,p=Math.min(h+q,f-1),p=a[j*f*4+p*4+3]/255,s=d[q],r;m==0?r=255:(r=Math.max(h-q,0),r=a[j*f*4+r*4+o]);m=r*(m==0||o==3?1:m);p==0?r=255:(r=Math.min(h+q,f-1),r=a[j*f*4+r*4+o]);n+=
+s*(m+r*(p==0||o==3?1:p))}c[h*g*4+j*4+o]=n}}this.base=a.Element.ElementBase;this.base(c);this.apply=function(a,c,e,f,g){var e=this.attribute("stdDeviation").numValue(),c=a.getImageData(0,0,f,g),e=Math.max(e,0.01),j=Math.ceil(e*4)+1;mask=[];for(var h=0;h<j;h++)mask[h]=Math.exp(-0.5*(h/e)*(h/e));e=mask;j=0;for(h=1;h<e.length;h++)j+=Math.abs(e[h]);j=2*j+Math.abs(e[0]);for(h=0;h<e.length;h++)e[h]/=j;tmp=[];d(c.data,tmp,e,f,g);d(tmp,c.data,e,g,f);a.clearRect(0,0,f,g);a.putImageData(c,0,0)}};a.Element.filter.prototype=
+new a.Element.feGaussianBlur;a.Element.title=function(){};a.Element.title.prototype=new a.Element.ElementBase;a.Element.desc=function(){};a.Element.desc.prototype=new a.Element.ElementBase;a.Element.MISSING=function(a){console.log("ERROR: Element '"+a.nodeName+"' not yet implemented.")};a.Element.MISSING.prototype=new a.Element.ElementBase;a.CreateElement=function(c){var d=c.nodeName.replace(/^[^:]+:/,""),d=d.replace(/\-/g,""),b=null,b=typeof a.Element[d]!="undefined"?new a.Element[d](c):new a.Element.MISSING(c);
+b.type=c.nodeName;return b};a.load=function(c,d){a.loadXml(c,a.ajax(d))};a.loadXml=function(c,d){a.loadXmlDoc(c,a.parseXml(d))};a.loadXmlDoc=function(c,d){a.init(c);var b=function(a){for(var b=c.canvas;b;)a.x-=b.offsetLeft,a.y-=b.offsetTop,b=b.offsetParent;window.scrollX&&(a.x+=window.scrollX);window.scrollY&&(a.y+=window.scrollY);return a};if(a.opts.ignoreMouse!=!0)c.canvas.onclick=function(c){c=b(new a.Point(c!=null?c.clientX:event.clientX,c!=null?c.clientY:event.clientY));a.Mouse.onclick(c.x,c.y)},
+c.canvas.onmousemove=function(c){c=b(new a.Point(c!=null?c.clientX:event.clientX,c!=null?c.clientY:event.clientY));a.Mouse.onmousemove(c.x,c.y)};var k=a.CreateElement(d.documentElement),e=k.root=!0,f=function(){a.ViewPort.Clear();c.canvas.parentNode&&a.ViewPort.SetCurrent(c.canvas.parentNode.clientWidth,c.canvas.parentNode.clientHeight);if(a.opts.ignoreDimensions!=!0){if(k.style("width").hasValue())c.canvas.width=k.style("width").Length.toPixels("x"),c.canvas.style.width=c.canvas.width+"px";if(k.style("height").hasValue())c.canvas.height=
+k.style("height").Length.toPixels("y"),c.canvas.style.height=c.canvas.height+"px"}var b=c.canvas.clientWidth||c.canvas.width,d=c.canvas.clientHeight||c.canvas.height;a.ViewPort.SetCurrent(b,d);if(a.opts!=null&&a.opts.offsetX!=null)k.attribute("x",!0).value=a.opts.offsetX;if(a.opts!=null&&a.opts.offsetY!=null)k.attribute("y",!0).value=a.opts.offsetY;if(a.opts!=null&&a.opts.scaleWidth!=null&&a.opts.scaleHeight!=null){var f=1,g=1;k.attribute("width").hasValue()&&(f=k.attribute("width").Length.toPixels("x")/
+a.opts.scaleWidth);k.attribute("height").hasValue()&&(g=k.attribute("height").Length.toPixels("y")/a.opts.scaleHeight);k.attribute("width",!0).value=a.opts.scaleWidth;k.attribute("height",!0).value=a.opts.scaleHeight;k.attribute("viewBox",!0).value="0 0 "+b*f+" "+d*g;k.attribute("preserveAspectRatio",!0).value="none"}a.opts.ignoreClear!=!0&&c.clearRect(0,0,b,d);k.render(c);e&&(e=!1,a.opts!=null&&typeof a.opts.renderCallback=="function"&&a.opts.renderCallback())},g=!0;a.ImagesLoaded()&&(g=!1,f());
+a.intervalID=setInterval(function(){var b=!1;g&&a.ImagesLoaded()&&(g=!1,b=!0);a.opts.ignoreMouse!=!0&&(b|=a.Mouse.hasEvents());if(a.opts.ignoreAnimation!=!0)for(var c=0;c<a.Animations.length;c++)b|=a.Animations[c].update(1E3/a.FRAMERATE);a.opts!=null&&typeof a.opts.forceRedraw=="function"&&a.opts.forceRedraw()==!0&&(b=!0);b&&(f(),a.Mouse.runEvents())},1E3/a.FRAMERATE)};a.stop=function(){a.intervalID&&clearInterval(a.intervalID)};a.Mouse=new function(){this.events=[];this.hasEvents=function(){return this.events.length!=
+0};this.onclick=function(a,d){this.events.push({type:"onclick",x:a,y:d,run:function(a){if(a.onclick)a.onclick()}})};this.onmousemove=function(a,d){this.events.push({type:"onmousemove",x:a,y:d,run:function(a){if(a.onmousemove)a.onmousemove()}})};this.eventElements=[];this.checkPath=function(a,d){for(var b=0;b<this.events.length;b++){var k=this.events[b];d.isPointInPath&&d.isPointInPath(k.x,k.y)&&(this.eventElements[b]=a)}};this.checkBoundingBox=function(a,d){for(var b=0;b<this.events.length;b++){var k=
+this.events[b];d.isPointInBox(k.x,k.y)&&(this.eventElements[b]=a)}};this.runEvents=function(){a.ctx.canvas.style.cursor="";for(var c=0;c<this.events.length;c++)for(var d=this.events[c],b=this.eventElements[c];b;)d.run(b),b=b.parent;this.events=[];this.eventElements=[]}};return a}this.canvg=function(a,c,d){if(a==null&&c==null&&d==null)for(var c=document.getElementsByTagName("svg"),b=0;b<c.length;b++){a=c[b];d=document.createElement("canvas");d.width=a.clientWidth;d.height=a.clientHeight;a.parentNode.insertBefore(d,
+a);a.parentNode.removeChild(a);var k=document.createElement("div");k.appendChild(a);canvg(d,k.innerHTML)}else d=d||{},typeof a=="string"&&(a=document.getElementById(a)),a.svg==null?(b=m(),a.svg=b):(b=a.svg,b.stop()),b.opts=d,a=a.getContext("2d"),typeof c.documentElement!="undefined"?b.loadXmlDoc(a,c):c.substr(0,1)=="<"?b.loadXml(a,c):b.load(a,c)}})();
+if(CanvasRenderingContext2D)CanvasRenderingContext2D.prototype.drawSvg=function(m,a,c,d,b){canvg(this.canvas,m,{ignoreMouse:!0,ignoreAnimation:!0,ignoreDimensions:!0,ignoreClear:!0,offsetX:a,offsetY:c,scaleWidth:d,scaleHeight:b})};
+(function(m){var a=m.css,c=m.CanVGRenderer,d=m.SVGRenderer,b=m.extend,k=m.merge,e=m.addEvent,f=m.createElement,g=m.discardElement;b(c.prototype,d.prototype);b(c.prototype,{create:function(a,b,c,d){this.setContainer(b,c,d);this.configure(a)},setContainer:function(a,b,c){var d=a.style,e=a.parentNode,g=d.left,d=d.top,k=a.offsetWidth,m=a.offsetHeight,s={visibility:"hidden",position:"absolute"};this.init.apply(this,[a,b,c]);this.canvas=f("canvas",{width:k,height:m},{position:"relative",left:g,top:d},a);
+this.ttLine=f("div",null,s,e);this.ttDiv=f("div",null,s,e);this.ttTimer=void 0;this.hiddenSvg=a=f("div",{width:k,height:m},{visibility:"hidden",left:g,top:d},e);a.appendChild(this.box)},configure:function(b){var c=this,d=b.options.tooltip,f=d.borderWidth,g=c.ttDiv,m=d.style,p=c.ttLine,t=parseInt(m.padding,10),m=k(m,{padding:t+"px","background-color":d.backgroundColor,"border-style":"solid","border-width":f+"px","border-radius":d.borderRadius+"px"});d.shadow&&(m=k(m,{"box-shadow":"1px 1px 3px gray",
+"-webkit-box-shadow":"1px 1px 3px gray"}));a(g,m);a(p,{"border-left":"1px solid darkgray"});e(b,"tooltipRefresh",function(d){var e=b.container,f=e.offsetLeft,e=e.offsetTop,k;g.innerHTML=d.text;k=b.tooltip.getPosition(g.offsetWidth,g.offsetHeight,{plotX:d.x,plotY:d.y});a(g,{visibility:"visible",left:k.x+"px",top:k.y+"px","border-color":d.borderColor});a(p,{visibility:"visible",left:f+d.x+"px",top:e+b.plotTop+"px",height:b.plotHeight+"px"});c.ttTimer!==void 0&&clearTimeout(c.ttTimer);c.ttTimer=setTimeout(function(){a(g,
+{visibility:"hidden"});a(p,{visibility:"hidden"})},3E3)})},destroy:function(){g(this.canvas);this.ttTimer!==void 0&&clearTimeout(this.ttTimer);g(this.ttLine);g(this.ttDiv);g(this.hiddenSvg);return d.prototype.destroy.apply(this)},color:function(a,b,c){a&&a.linearGradient&&(a=a.stops[a.stops.length-1][1]);return d.prototype.color.call(this,a,b,c)},draw:function(){window.canvg(this.canvas,this.hiddenSvg.innerHTML)}})})(Highcharts);
diff --git a/html/includes/js/modules/canvas-tools.src.js b/html/includes/js/modules/canvas-tools.src.js
new file mode 100644
index 0000000..3725c02
--- /dev/null
+++ b/html/includes/js/modules/canvas-tools.src.js
@@ -0,0 +1,3113 @@
+/**
+ * @license A class to parse color values
+ * @author Stoyan Stefanov <sstoo@gmail.com>
+ * @link http://www.phpied.com/rgb-color-parser-in-javascript/
+ * Use it if you like it
+ *
+ */
+function RGBColor(color_string)
+{
+ this.ok = false;
+
+ // strip any leading #
+ if (color_string.charAt(0) == '#') { // remove # if any
+ color_string = color_string.substr(1,6);
+ }
+
+ color_string = color_string.replace(/ /g,'');
+ color_string = color_string.toLowerCase();
+
+ // before getting into regexps, try simple matches
+ // and overwrite the input
+ var simple_colors = {
+ aliceblue: 'f0f8ff',
+ antiquewhite: 'faebd7',
+ aqua: '00ffff',
+ aquamarine: '7fffd4',
+ azure: 'f0ffff',
+ beige: 'f5f5dc',
+ bisque: 'ffe4c4',
+ black: '000000',
+ blanchedalmond: 'ffebcd',
+ blue: '0000ff',
+ blueviolet: '8a2be2',
+ brown: 'a52a2a',
+ burlywood: 'deb887',
+ cadetblue: '5f9ea0',
+ chartreuse: '7fff00',
+ chocolate: 'd2691e',
+ coral: 'ff7f50',
+ cornflowerblue: '6495ed',
+ cornsilk: 'fff8dc',
+ crimson: 'dc143c',
+ cyan: '00ffff',
+ darkblue: '00008b',
+ darkcyan: '008b8b',
+ darkgoldenrod: 'b8860b',
+ darkgray: 'a9a9a9',
+ darkgreen: '006400',
+ darkkhaki: 'bdb76b',
+ darkmagenta: '8b008b',
+ darkolivegreen: '556b2f',
+ darkorange: 'ff8c00',
+ darkorchid: '9932cc',
+ darkred: '8b0000',
+ darksalmon: 'e9967a',
+ darkseagreen: '8fbc8f',
+ darkslateblue: '483d8b',
+ darkslategray: '2f4f4f',
+ darkturquoise: '00ced1',
+ darkviolet: '9400d3',
+ deeppink: 'ff1493',
+ deepskyblue: '00bfff',
+ dimgray: '696969',
+ dodgerblue: '1e90ff',
+ feldspar: 'd19275',
+ firebrick: 'b22222',
+ floralwhite: 'fffaf0',
+ forestgreen: '228b22',
+ fuchsia: 'ff00ff',
+ gainsboro: 'dcdcdc',
+ ghostwhite: 'f8f8ff',
+ gold: 'ffd700',
+ goldenrod: 'daa520',
+ gray: '808080',
+ green: '008000',
+ greenyellow: 'adff2f',
+ honeydew: 'f0fff0',
+ hotpink: 'ff69b4',
+ indianred : 'cd5c5c',
+ indigo : '4b0082',
+ ivory: 'fffff0',
+ khaki: 'f0e68c',
+ lavender: 'e6e6fa',
+ lavenderblush: 'fff0f5',
+ lawngreen: '7cfc00',
+ lemonchiffon: 'fffacd',
+ lightblue: 'add8e6',
+ lightcoral: 'f08080',
+ lightcyan: 'e0ffff',
+ lightgoldenrodyellow: 'fafad2',
+ lightgrey: 'd3d3d3',
+ lightgreen: '90ee90',
+ lightpink: 'ffb6c1',
+ lightsalmon: 'ffa07a',
+ lightseagreen: '20b2aa',
+ lightskyblue: '87cefa',
+ lightslateblue: '8470ff',
+ lightslategray: '778899',
+ lightsteelblue: 'b0c4de',
+ lightyellow: 'ffffe0',
+ lime: '00ff00',
+ limegreen: '32cd32',
+ linen: 'faf0e6',
+ magenta: 'ff00ff',
+ maroon: '800000',
+ mediumaquamarine: '66cdaa',
+ mediumblue: '0000cd',
+ mediumorchid: 'ba55d3',
+ mediumpurple: '9370d8',
+ mediumseagreen: '3cb371',
+ mediumslateblue: '7b68ee',
+ mediumspringgreen: '00fa9a',
+ mediumturquoise: '48d1cc',
+ mediumvioletred: 'c71585',
+ midnightblue: '191970',
+ mintcream: 'f5fffa',
+ mistyrose: 'ffe4e1',
+ moccasin: 'ffe4b5',
+ navajowhite: 'ffdead',
+ navy: '000080',
+ oldlace: 'fdf5e6',
+ olive: '808000',
+ olivedrab: '6b8e23',
+ orange: 'ffa500',
+ orangered: 'ff4500',
+ orchid: 'da70d6',
+ palegoldenrod: 'eee8aa',
+ palegreen: '98fb98',
+ paleturquoise: 'afeeee',
+ palevioletred: 'd87093',
+ papayawhip: 'ffefd5',
+ peachpuff: 'ffdab9',
+ peru: 'cd853f',
+ pink: 'ffc0cb',
+ plum: 'dda0dd',
+ powderblue: 'b0e0e6',
+ purple: '800080',
+ red: 'ff0000',
+ rosybrown: 'bc8f8f',
+ royalblue: '4169e1',
+ saddlebrown: '8b4513',
+ salmon: 'fa8072',
+ sandybrown: 'f4a460',
+ seagreen: '2e8b57',
+ seashell: 'fff5ee',
+ sienna: 'a0522d',
+ silver: 'c0c0c0',
+ skyblue: '87ceeb',
+ slateblue: '6a5acd',
+ slategray: '708090',
+ snow: 'fffafa',
+ springgreen: '00ff7f',
+ steelblue: '4682b4',
+ tan: 'd2b48c',
+ teal: '008080',
+ thistle: 'd8bfd8',
+ tomato: 'ff6347',
+ turquoise: '40e0d0',
+ violet: 'ee82ee',
+ violetred: 'd02090',
+ wheat: 'f5deb3',
+ white: 'ffffff',
+ whitesmoke: 'f5f5f5',
+ yellow: 'ffff00',
+ yellowgreen: '9acd32'
+ };
+ for (var key in simple_colors) {
+ if (color_string == key) {
+ color_string = simple_colors[key];
+ }
+ }
+ // emd of simple type-in colors
+
+ // array of color definition objects
+ var color_defs = [
+ {
+ re: /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/,
+ example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
+ process: function (bits){
+ return [
+ parseInt(bits[1]),
+ parseInt(bits[2]),
+ parseInt(bits[3])
+ ];
+ }
+ },
+ {
+ re: /^(\w{2})(\w{2})(\w{2})$/,
+ example: ['#00ff00', '336699'],
+ process: function (bits){
+ return [
+ parseInt(bits[1], 16),
+ parseInt(bits[2], 16),
+ parseInt(bits[3], 16)
+ ];
+ }
+ },
+ {
+ re: /^(\w{1})(\w{1})(\w{1})$/,
+ example: ['#fb0', 'f0f'],
+ process: function (bits){
+ return [
+ parseInt(bits[1] + bits[1], 16),
+ parseInt(bits[2] + bits[2], 16),
+ parseInt(bits[3] + bits[3], 16)
+ ];
+ }
+ }
+ ];
+
+ // search through the definitions to find a match
+ for (var i = 0; i < color_defs.length; i++) {
+ var re = color_defs[i].re;
+ var processor = color_defs[i].process;
+ var bits = re.exec(color_string);
+ if (bits) {
+ channels = processor(bits);
+ this.r = channels[0];
+ this.g = channels[1];
+ this.b = channels[2];
+ this.ok = true;
+ }
+
+ }
+
+ // validate/cleanup values
+ this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);
+ this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);
+ this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);
+
+ // some getters
+ this.toRGB = function () {
+ return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';
+ }
+ this.toHex = function () {
+ var r = this.r.toString(16);
+ var g = this.g.toString(16);
+ var b = this.b.toString(16);
+ if (r.length == 1) r = '0' + r;
+ if (g.length == 1) g = '0' + g;
+ if (b.length == 1) b = '0' + b;
+ return '#' + r + g + b;
+ }
+
+ // help
+ this.getHelpXML = function () {
+
+ var examples = new Array();
+ // add regexps
+ for (var i = 0; i < color_defs.length; i++) {
+ var example = color_defs[i].example;
+ for (var j = 0; j < example.length; j++) {
+ examples[examples.length] = example[j];
+ }
+ }
+ // add type-in colors
+ for (var sc in simple_colors) {
+ examples[examples.length] = sc;
+ }
+
+ var xml = document.createElement('ul');
+ xml.setAttribute('id', 'rgbcolor-examples');
+ for (var i = 0; i < examples.length; i++) {
+ try {
+ var list_item = document.createElement('li');
+ var list_color = new RGBColor(examples[i]);
+ var example_div = document.createElement('div');
+ example_div.style.cssText =
+ 'margin: 3px; '
+ + 'border: 1px solid black; '
+ + 'background:' + list_color.toHex() + '; '
+ + 'color:' + list_color.toHex()
+ ;
+ example_div.appendChild(document.createTextNode('test'));
+ var list_item_value = document.createTextNode(
+ ' ' + examples[i] + ' -> ' + list_color.toRGB() + ' -> ' + list_color.toHex()
+ );
+ list_item.appendChild(example_div);
+ list_item.appendChild(list_item_value);
+ xml.appendChild(list_item);
+
+ } catch(e){}
+ }
+ return xml;
+
+ }
+
+}
+
+/**
+ * @license canvg.js - Javascript SVG parser and renderer on Canvas
+ * MIT Licensed
+ * Gabe Lerner (gabelerner@gmail.com)
+ * http://code.google.com/p/canvg/
+ *
+ * Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/
+ *
+ */
+if(!window.console) {
+ window.console = {};
+ window.console.log = function(str) {};
+ window.console.dir = function(str) {};
+}
+
+if(!Array.prototype.indexOf){
+ Array.prototype.indexOf = function(obj){
+ for(var i=0; i<this.length; i++){
+ if(this[i]==obj){
+ return i;
+ }
+ }
+ return -1;
+ }
+}
+
+(function(){
+ // canvg(target, s)
+ // empty parameters: replace all 'svg' elements on page with 'canvas' elements
+ // target: canvas element or the id of a canvas element
+ // s: svg string, url to svg file, or xml document
+ // opts: optional hash of options
+ // ignoreMouse: true => ignore mouse events
+ // ignoreAnimation: true => ignore animations
+ // ignoreDimensions: true => does not try to resize canvas
+ // ignoreClear: true => does not clear canvas
+ // offsetX: int => draws at a x offset
+ // offsetY: int => draws at a y offset
+ // scaleWidth: int => scales horizontally to width
+ // scaleHeight: int => scales vertically to height
+ // renderCallback: function => will call the function after the first render is completed
+ // forceRedraw: function => will call the function on every frame, if it returns true, will redraw
+ this.canvg = function (target, s, opts) {
+ // no parameters
+ if (target == null && s == null && opts == null) {
+ var svgTags = document.getElementsByTagName('svg');
+ for (var i=0; i<svgTags.length; i++) {
+ var svgTag = svgTags[i];
+ var c = document.createElement('canvas');
+ c.width = svgTag.clientWidth;
+ c.height = svgTag.clientHeight;
+ svgTag.parentNode.insertBefore(c, svgTag);
+ svgTag.parentNode.removeChild(svgTag);
+ var div = document.createElement('div');
+ div.appendChild(svgTag);
+ canvg(c, div.innerHTML);
+ }
+ return;
+ }
+ opts = opts || {};
+
+ if (typeof target == 'string') {
+ target = document.getElementById(target);
+ }
+
+ // reuse class per canvas
+ var svg;
+ if (target.svg == null) {
+ svg = build();
+ target.svg = svg;
+ }
+ else {
+ svg = target.svg;
+ svg.stop();
+ }
+ svg.opts = opts;
+
+ var ctx = target.getContext('2d');
+ if (typeof(s.documentElement) != 'undefined') {
+ // load from xml doc
+ svg.loadXmlDoc(ctx, s);
+ }
+ else if (s.substr(0,1) == '<') {
+ // load from xml string
+ svg.loadXml(ctx, s);
+ }
+ else {
+ // load from url
+ svg.load(ctx, s);
+ }
+ }
+
+ function build() {
+ var svg = { };
+
+ svg.FRAMERATE = 30;
+ svg.MAX_VIRTUAL_PIXELS = 30000;
+
+ // globals
+ svg.init = function(ctx) {
+ svg.Definitions = {};
+ svg.Styles = {};
+ svg.Animations = [];
+ svg.Images = [];
+ svg.ctx = ctx;
+ svg.ViewPort = new (function () {
+ this.viewPorts = [];
+ this.Clear = function() { this.viewPorts = []; }
+ this.SetCurrent = function(width, height) { this.viewPorts.push({ width: width, height: height }); }
+ this.RemoveCurrent = function() { this.viewPorts.pop(); }
+ this.Current = function() { return this.viewPorts[this.viewPorts.length - 1]; }
+ this.width = function() { return this.Current().width; }
+ this.height = function() { return this.Current().height; }
+ this.ComputeSize = function(d) {
+ if (d != null && typeof(d) == 'number') return d;
+ if (d == 'x') return this.width();
+ if (d == 'y') return this.height();
+ return Math.sqrt(Math.pow(this.width(), 2) + Math.pow(this.height(), 2)) / Math.sqrt(2);
+ }
+ });
+ }
+ svg.init();
+
+ // images loaded
+ svg.ImagesLoaded = function() {
+ for (var i=0; i<svg.Images.length; i++) {
+ if (!svg.Images[i].loaded) return false;
+ }
+ return true;
+ }
+
+ // trim
+ svg.trim = function(s) { return s.replace(/^\s+|\s+$/g, ''); }
+
+ // compress spaces
+ svg.compressSpaces = function(s) { return s.replace(/[\s\r\t\n]+/gm,' '); }
+
+ // ajax
+ svg.ajax = function(url) {
+ var AJAX;
+ if(window.XMLHttpRequest){AJAX=new XMLHttpRequest();}
+ else{AJAX=new ActiveXObject('Microsoft.XMLHTTP');}
+ if(AJAX){
+ AJAX.open('GET',url,false);
+ AJAX.send(null);
+ return AJAX.responseText;
+ }
+ return null;
+ }
+
+ // parse xml
+ svg.parseXml = function(xml) {
+ if (window.DOMParser)
+ {
+ var parser = new DOMParser();
+ return parser.parseFromString(xml, 'text/xml');
+ }
+ else
+ {
+ xml = xml.replace(/<!DOCTYPE svg[^>]*>/, '');
+ var xmlDoc = new ActiveXObject('Microsoft.XMLDOM');
+ xmlDoc.async = 'false';
+ xmlDoc.loadXML(xml);
+ return xmlDoc;
+ }
+ }
+
+ svg.Property = function(name, value) {
+ this.name = name;
+ this.value = value;
+
+ this.hasValue = function() {
+ return (this.value != null && this.value !== '');
+ }
+
+ // return the numerical value of the property
+ this.numValue = function() {
+ if (!this.hasValue()) return 0;
+
+ var n = parseFloat(this.value);
+ if ((this.value + '').match(/%$/)) {
+ n = n / 100.0;
+ }
+ return n;
+ }
+
+ this.valueOrDefault = function(def) {
+ if (this.hasValue()) return this.value;
+ return def;
+ }
+
+ this.numValueOrDefault = function(def) {
+ if (this.hasValue()) return this.numValue();
+ return def;
+ }
+
+ /* EXTENSIONS */
+ var that = this;
+
+ // color extensions
+ this.Color = {
+ // augment the current color value with the opacity
+ addOpacity: function(opacity) {
+ var newValue = that.value;
+ if (opacity != null && opacity != '') {
+ var color = new RGBColor(that.value);
+ if (color.ok) {
+ newValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacity + ')';
+ }
+ }
+ return new svg.Property(that.name, newValue);
+ }
+ }
+
+ // definition extensions
+ this.Definition = {
+ // get the definition from the definitions table
+ getDefinition: function() {
+ var name = that.value.replace(/^(url\()?#([^\)]+)\)?$/, '$2');
+ return svg.Definitions[name];
+ },
+
+ isUrl: function() {
+ return that.value.indexOf('url(') == 0
+ },
+
+ getFillStyle: function(e) {
+ var def = this.getDefinition();
+
+ // gradient
+ if (def != null && def.createGradient) {
+ return def.createGradient(svg.ctx, e);
+ }
+
+ // pattern
+ if (def != null && def.createPattern) {
+ return def.createPattern(svg.ctx, e);
+ }
+
+ return null;
+ }
+ }
+
+ // length extensions
+ this.Length = {
+ DPI: function(viewPort) {
+ return 96.0; // TODO: compute?
+ },
+
+ EM: function(viewPort) {
+ var em = 12;
+
+ var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
+ if (fontSize.hasValue()) em = fontSize.Length.toPixels(viewPort);
+
+ return em;
+ },
+
+ // get the length as pixels
+ toPixels: function(viewPort) {
+ if (!that.hasValue()) return 0;
+ var s = that.value+'';
+ if (s.match(/em$/)) return that.numValue() * this.EM(viewPort);
+ if (s.match(/ex$/)) return that.numValue() * this.EM(viewPort) / 2.0;
+ if (s.match(/px$/)) return that.numValue();
+ if (s.match(/pt$/)) return that.numValue() * 1.25;
+ if (s.match(/pc$/)) return that.numValue() * 15;
+ if (s.match(/cm$/)) return that.numValue() * this.DPI(viewPort) / 2.54;
+ if (s.match(/mm$/)) return that.numValue() * this.DPI(viewPort) / 25.4;
+ if (s.match(/in$/)) return that.numValue() * this.DPI(viewPort);
+ if (s.match(/%$/)) return that.numValue() * svg.ViewPort.ComputeSize(viewPort);
+ return that.numValue();
+ }
+ }
+
+ // time extensions
+ this.Time = {
+ // get the time as milliseconds
+ toMilliseconds: function() {
+ if (!that.hasValue()) return 0;
+ var s = that.value+'';
+ if (s.match(/s$/)) return that.numValue() * 1000;
+ if (s.match(/ms$/)) return that.numValue();
+ return that.numValue();
+ }
+ }
+
+ // angle extensions
+ this.Angle = {
+ // get the angle as radians
+ toRadians: function() {
+ if (!that.hasValue()) return 0;
+ var s = that.value+'';
+ if (s.match(/deg$/)) return that.numValue() * (Math.PI / 180.0);
+ if (s.match(/grad$/)) return that.numValue() * (Math.PI / 200.0);
+ if (s.match(/rad$/)) return that.numValue();
+ return that.numValue() * (Math.PI / 180.0);
+ }
+ }
+ }
+
+ // fonts
+ svg.Font = new (function() {
+ this.Styles = ['normal','italic','oblique','inherit'];
+ this.Variants = ['normal','small-caps','inherit'];
+ this.Weights = ['normal','bold','bolder','lighter','100','200','300','400','500','600','700','800','900','inherit'];
+
+ this.CreateFont = function(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) {
+ var f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);
+ return {
+ fontFamily: fontFamily || f.fontFamily,
+ fontSize: fontSize || f.fontSize,
+ fontStyle: fontStyle || f.fontStyle,
+ fontWeight: fontWeight || f.fontWeight,
+ fontVariant: fontVariant || f.fontVariant,
+ toString: function () { return [this.fontStyle, this.fontVariant, this.fontWeight, this.fontSize, this.fontFamily].join(' ') }
+ }
+ }
+
+ var that = this;
+ this.Parse = function(s) {
+ var f = {};
+ var d = svg.trim(svg.compressSpaces(s || '')).split(' ');
+ var set = { fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false }
+ var ff = '';
+ for (var i=0; i<d.length; i++) {
+ if (!set.fontStyle && that.Styles.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontStyle = d[i]; set.fontStyle = true; }
+ else if (!set.fontVariant && that.Variants.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontVariant = d[i]; set.fontStyle = set.fontVariant = true; }
+ else if (!set.fontWeight && that.Weights.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontWeight = d[i]; set.fontStyle = set.fontVariant = set.fontWeight = true; }
+ else if (!set.fontSize) { if (d[i] != 'inherit') f.fontSize = d[i].split('/')[0]; set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true; }
+ else { if (d[i] != 'inherit') ff += d[i]; }
+ } if (ff != '') f.fontFamily = ff;
+ return f;
+ }
+ });
+
+ // points and paths
+ svg.ToNumberArray = function(s) {
+ var a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');
+ for (var i=0; i<a.length; i++) {
+ a[i] = parseFloat(a[i]);
+ }
+ return a;
+ }
+ svg.Point = function(x, y) {
+ this.x = x;
+ this.y = y;
+
+ this.angleTo = function(p) {
+ return Math.atan2(p.y - this.y, p.x - this.x);
+ }
+
+ this.applyTransform = function(v) {
+ var xp = this.x * v[0] + this.y * v[2] + v[4];
+ var yp = this.x * v[1] + this.y * v[3] + v[5];
+ this.x = xp;
+ this.y = yp;
+ }
+ }
+ svg.CreatePoint = function(s) {
+ var a = svg.ToNumberArray(s);
+ return new svg.Point(a[0], a[1]);
+ }
+ svg.CreatePath = function(s) {
+ var a = svg.ToNumberArray(s);
+ var path = [];
+ for (var i=0; i<a.length; i+=2) {
+ path.push(new svg.Point(a[i], a[i+1]));
+ }
+ return path;
+ }
+
+ // bounding box
+ svg.BoundingBox = function(x1, y1, x2, y2) { // pass in initial points if you want
+ this.x1 = Number.NaN;
+ this.y1 = Number.NaN;
+ this.x2 = Number.NaN;
+ this.y2 = Number.NaN;
+
+ this.x = function() { return this.x1; }
+ this.y = function() { return this.y1; }
+ this.width = function() { return this.x2 - this.x1; }
+ this.height = function() { return this.y2 - this.y1; }
+
+ this.addPoint = function(x, y) {
+ if (x != null) {
+ if (isNaN(this.x1) || isNaN(this.x2)) {
+ this.x1 = x;
+ this.x2 = x;
+ }
+ if (x < this.x1) this.x1 = x;
+ if (x > this.x2) this.x2 = x;
+ }
+
+ if (y != null) {
+ if (isNaN(this.y1) || isNaN(this.y2)) {
+ this.y1 = y;
+ this.y2 = y;
+ }
+ if (y < this.y1) this.y1 = y;
+ if (y > this.y2) this.y2 = y;
+ }
+ }
+ this.addX = function(x) { this.addPoint(x, null); }
+ this.addY = function(y) { this.addPoint(null, y); }
+
+ this.addBoundingBox = function(bb) {
+ this.addPoint(bb.x1, bb.y1);
+ this.addPoint(bb.x2, bb.y2);
+ }
+
+ this.addQuadraticCurve = function(p0x, p0y, p1x, p1y, p2x, p2y) {
+ var cp1x = p0x + 2/3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)
+ var cp1y = p0y + 2/3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)
+ var cp2x = cp1x + 1/3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)
+ var cp2y = cp1y + 1/3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)
+ this.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y, cp2y, p2x, p2y);
+ }
+
+ this.addBezierCurve = function(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
+ // from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
+ var p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y];
+ this.addPoint(p0[0], p0[1]);
+ this.addPoint(p3[0], p3[1]);
+
+ for (i=0; i<=1; i++) {
+ var f = function(t) {
+ return Math.pow(1-t, 3) * p0[i]
+ + 3 * Math.pow(1-t, 2) * t * p1[i]
+ + 3 * (1-t) * Math.pow(t, 2) * p2[i]
+ + Math.pow(t, 3) * p3[i];
+ }
+
+ var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];
+ var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];
+ var c = 3 * p1[i] - 3 * p0[i];
+
+ if (a == 0) {
+ if (b == 0) continue;
+ var t = -c / b;
+ if (0 < t && t < 1) {
+ if (i == 0) this.addX(f(t));
+ if (i == 1) this.addY(f(t));
+ }
+ continue;
+ }
+
+ var b2ac = Math.pow(b, 2) - 4 * c * a;
+ if (b2ac < 0) continue;
+ var t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
+ if (0 < t1 && t1 < 1) {
+ if (i == 0) this.addX(f(t1));
+ if (i == 1) this.addY(f(t1));
+ }
+ var t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
+ if (0 < t2 && t2 < 1) {
+ if (i == 0) this.addX(f(t2));
+ if (i == 1) this.addY(f(t2));
+ }
+ }
+ }
+
+ this.isPointInBox = function(x, y) {
+ return (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2);
+ }
+
+ this.addPoint(x1, y1);
+ this.addPoint(x2, y2);
+ }
+
+ // transforms
+ svg.Transform = function(v) {
+ var that = this;
+ this.Type = {}
+
+ // translate
+ this.Type.translate = function(s) {
+ this.p = svg.CreatePoint(s);
+ this.apply = function(ctx) {
+ ctx.translate(this.p.x || 0.0, this.p.y || 0.0);
+ }
+ this.applyToPoint = function(p) {
+ p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
+ }
+ }
+
+ // rotate
+ this.Type.rotate = function(s) {
+ var a = svg.ToNumberArray(s);
+ this.angle = new svg.Property('angle', a[0]);
+ this.cx = a[1] || 0;
+ this.cy = a[2] || 0;
+ this.apply = function(ctx) {
+ ctx.translate(this.cx, this.cy);
+ ctx.rotate(this.angle.Angle.toRadians());
+ ctx.translate(-this.cx, -this.cy);
+ }
+ this.applyToPoint = function(p) {
+ var a = this.angle.Angle.toRadians();
+ p.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);
+ p.applyTransform([Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]);
+ p.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);
+ }
+ }
+
+ this.Type.scale = function(s) {
+ this.p = svg.CreatePoint(s);
+ this.apply = function(ctx) {
+ ctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0);
+ }
+ this.applyToPoint = function(p) {
+ p.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]);
+ }
+ }
+
+ this.Type.matrix = function(s) {
+ this.m = svg.ToNumberArray(s);
+ this.apply = function(ctx) {
+ ctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);
+ }
+ this.applyToPoint = function(p) {
+ p.applyTransform(this.m);
+ }
+ }
+
+ this.Type.SkewBase = function(s) {
+ this.base = that.Type.matrix;
+ this.base(s);
+ this.angle = new svg.Property('angle', s);
+ }
+ this.Type.SkewBase.prototype = new this.Type.matrix;
+
+ this.Type.skewX = function(s) {
+ this.base = that.Type.SkewBase;
+ this.base(s);
+ this.m = [1, 0, Math.tan(this.angle.Angle.toRadians()), 1, 0, 0];
+ }
+ this.Type.skewX.prototype = new this.Type.SkewBase;
+
+ this.Type.skewY = function(s) {
+ this.base = that.Type.SkewBase;
+ this.base(s);
+ this.m = [1, Math.tan(this.angle.Angle.toRadians()), 0, 1, 0, 0];
+ }
+ this.Type.skewY.prototype = new this.Type.SkewBase;
+
+ this.transforms = [];
+
+ this.apply = function(ctx) {
+ for (var i=0; i<this.transforms.length; i++) {
+ this.transforms[i].apply(ctx);
+ }
+ }
+
+ this.applyToPoint = function(p) {
+ for (var i=0; i<this.transforms.length; i++) {
+ this.transforms[i].applyToPoint(p);
+ }
+ }
+
+ var data = svg.trim(svg.compressSpaces(v)).split(/\s(?=[a-z])/);
+ for (var i=0; i<data.length; i++) {
+ var type = data[i].split('(')[0];
+ var s = data[i].split('(')[1].replace(')','');
+ var transform = new this.Type[type](s);
+ this.transforms.push(transform);
+ }
+ }
+
+ // aspect ratio
+ svg.AspectRatio = function(ctx, aspectRatio, width, desiredWidth, height, desiredHeight, minX, minY, refX, refY) {
+ // aspect ratio - http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
+ aspectRatio = svg.compressSpaces(aspectRatio);
+ aspectRatio = aspectRatio.replace(/^defer\s/,''); // ignore defer
+ var align = aspectRatio.split(' ')[0] || 'xMidYMid';
+ var meetOrSlice = aspectRatio.split(' ')[1] || 'meet';
+
+ // calculate scale
+ var scaleX = width / desiredWidth;
+ var scaleY = height / desiredHeight;
+ var scaleMin = Math.min(scaleX, scaleY);
+ var scaleMax = Math.max(scaleX, scaleY);
+ if (meetOrSlice == 'meet') { desiredWidth *= scaleMin; desiredHeight *= scaleMin; }
+ if (meetOrSlice == 'slice') { desiredWidth *= scaleMax; desiredHeight *= scaleMax; }
+
+ refX = new svg.Property('refX', refX);
+ refY = new svg.Property('refY', refY);
+ if (refX.hasValue() && refY.hasValue()) {
+ ctx.translate(-scaleMin * refX.Length.toPixels('x'), -scaleMin * refY.Length.toPixels('y'));
+ }
+ else {
+ // align
+ if (align.match(/^xMid/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width / 2.0 - desiredWidth / 2.0, 0);
+ if (align.match(/YMid$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height / 2.0 - desiredHeight / 2.0);
+ if (align.match(/^xMax/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width - desiredWidth, 0);
+ if (align.match(/YMax$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height - desiredHeight);
+ }
+
+ // scale
+ if (align == 'none') ctx.scale(scaleX, scaleY);
+ else if (meetOrSlice == 'meet') ctx.scale(scaleMin, scaleMin);
+ else if (meetOrSlice == 'slice') ctx.scale(scaleMax, scaleMax);
+
+ // translate
+ ctx.translate(minX == null ? 0 : -minX, minY == null ? 0 : -minY);
+ }
+
+ // elements
+ svg.Element = {}
+
+ svg.Element.ElementBase = function(node) {
+ this.attributes = {};
+ this.styles = {};
+ this.children = [];
+
+ // get or create attribute
+ this.attribute = function(name, createIfNotExists) {
+ var a = this.attributes[name];
+ if (a != null) return a;
+
+ a = new svg.Property(name, '');
+ if (createIfNotExists == true) this.attributes[name] = a;
+ return a;
+ }
+
+ // get or create style, crawls up node tree
+ this.style = function(name, createIfNotExists) {
+ var s = this.styles[name];
+ if (s != null) return s;
+
+ var a = this.attribute(name);
+ if (a != null && a.hasValue()) {
+ return a;
+ }
+
+ var p = this.parent;
+ if (p != null) {
+ var ps = p.style(name);
+ if (ps != null && ps.hasValue()) {
+ return ps;
+ }
+ }
+
+ s = new svg.Property(name, '');
+ if (createIfNotExists == true) this.styles[name] = s;
+ return s;
+ }
+
+ // base render
+ this.render = function(ctx) {
+ // don't render display=none
+ if (this.style('display').value == 'none') return;
+
+ // don't render visibility=hidden
+ if (this.attribute('visibility').value == 'hidden') return;
+
+ ctx.save();
+ this.setContext(ctx);
+ // mask
+ if (this.attribute('mask').hasValue()) {
+ var mask = this.attribute('mask').Definition.getDefinition();
+ if (mask != null) mask.apply(ctx, this);
+ }
+ else if (this.style('filter').hasValue()) {
+ var filter = this.style('filter').Definition.getDefinition();
+ if (filter != null) filter.apply(ctx, this);
+ }
+ else this.renderChildren(ctx);
+ this.clearContext(ctx);
+ ctx.restore();
+ }
+
+ // base set context
+ this.setContext = function(ctx) {
+ // OVERRIDE ME!
+ }
+
+ // base clear context
+ this.clearContext = function(ctx) {
+ // OVERRIDE ME!
+ }
+
+ // base render children
+ this.renderChildren = function(ctx) {
+ for (var i=0; i<this.children.length; i++) {
+ this.children[i].render(ctx);
+ }
+ }
+
+ this.addChild = function(childNode, create) {
+ var child = childNode;
+ if (create) child = svg.CreateElement(childNode);
+ child.parent = this;
+ this.children.push(child);
+ }
+
+ if (node != null && node.nodeType == 1) { //ELEMENT_NODE
+ // add children
+ for (var i=0; i<node.childNodes.length; i++) {
+ var childNode = node.childNodes[i];
+ if (childNode.nodeType == 1) this.addChild(childNode, true); //ELEMENT_NODE
+ }
+
+ // add attributes
+ for (var i=0; i<node.attributes.length; i++) {
+ var attribute = node.attributes[i];
+ this.attributes[attribute.nodeName] = new svg.Property(attribute.nodeName, attribute.nodeValue);
+ }
+
+ // add tag styles
+ var styles = svg.Styles[node.nodeName];
+ if (styles != null) {
+ for (var name in styles) {
+ this.styles[name] = styles[name];
+ }
+ }
+
+ // add class styles
+ if (this.attribute('class').hasValue()) {
+ var classes = svg.compressSpaces(this.attribute('class').value).split(' ');
+ for (var j=0; j<classes.length; j++) {
+ styles = svg.Styles['.'+classes[j]];
+ if (styles != null) {
+ for (var name in styles) {
+ this.styles[name] = styles[name];
+ }
+ }
+ styles = svg.Styles[node.nodeName+'.'+classes[j]];
+ if (styles != null) {
+ for (var name in styles) {
+ this.styles[name] = styles[name];
+ }
+ }
+ }
+ }
+
+ // add inline styles
+ if (this.attribute('style').hasValue()) {
+ var styles = this.attribute('style').value.split(';');
+ for (var i=0; i<styles.length; i++) {
+ if (svg.trim(styles[i]) != '') {
+ var style = styles[i].split(':');
+ var name = svg.trim(style[0]);
+ var value = svg.trim(style[1]);
+ this.styles[name] = new svg.Property(name, value);
+ }
+ }
+ }
+
+ // add id
+ if (this.attribute('id').hasValue()) {
+ if (svg.Definitions[this.attribute('id').value] == null) {
+ svg.Definitions[this.attribute('id').value] = this;
+ }
+ }
+ }
+ }
+
+ svg.Element.RenderedElementBase = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.setContext = function(ctx) {
+ // fill
+ if (this.style('fill').Definition.isUrl()) {
+ var fs = this.style('fill').Definition.getFillStyle(this);
+ if (fs != null) ctx.fillStyle = fs;
+ }
+ else if (this.style('fill').hasValue()) {
+ var fillStyle = this.style('fill');
+ if (this.style('fill-opacity').hasValue()) fillStyle = fillStyle.Color.addOpacity(this.style('fill-opacity').value);
+ ctx.fillStyle = (fillStyle.value == 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);
+ }
+
+ // stroke
+ if (this.style('stroke').Definition.isUrl()) {
+ var fs = this.style('stroke').Definition.getFillStyle(this);
+ if (fs != null) ctx.strokeStyle = fs;
+ }
+ else if (this.style('stroke').hasValue()) {
+ var strokeStyle = this.style('stroke');
+ if (this.style('stroke-opacity').hasValue()) strokeStyle = strokeStyle.Color.addOpacity(this.style('stroke-opacity').value);
+ ctx.strokeStyle = (strokeStyle.value == 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);
+ }
+ if (this.style('stroke-width').hasValue()) ctx.lineWidth = this.style('stroke-width').Length.toPixels();
+ if (this.style('stroke-linecap').hasValue()) ctx.lineCap = this.style('stroke-linecap').value;
+ if (this.style('stroke-linejoin').hasValue()) ctx.lineJoin = this.style('stroke-linejoin').value;
+ if (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;
+
+ // font
+ if (typeof(ctx.font) != 'undefined') {
+ ctx.font = svg.Font.CreateFont(
+ this.style('font-style').value,
+ this.style('font-variant').value,
+ this.style('font-weight').value,
+ this.style('font-size').hasValue() ? this.style('font-size').Length.toPixels() + 'px' : '',
+ this.style('font-family').value).toString();
+ }
+
+ // transform
+ if (this.attribute('transform').hasValue()) {
+ var transform = new svg.Transform(this.attribute('transform').value);
+ transform.apply(ctx);
+ }
+
+ // clip
+ if (this.attribute('clip-path').hasValue()) {
+ var clip = this.attribute('clip-path').Definition.getDefinition();
+ if (clip != null) clip.apply(ctx);
+ }
+
+ // opacity
+ if (this.style('opacity').hasValue()) {
+ ctx.globalAlpha = this.style('opacity').numValue();
+ }
+ }
+ }
+ svg.Element.RenderedElementBase.prototype = new svg.Element.ElementBase;
+
+ svg.Element.PathElementBase = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.path = function(ctx) {
+ if (ctx != null) ctx.beginPath();
+ return new svg.BoundingBox();
+ }
+
+ this.renderChildren = function(ctx) {
+ this.path(ctx);
+ svg.Mouse.checkPath(this, ctx);
+ if (ctx.fillStyle != '') ctx.fill();
+ if (ctx.strokeStyle != '') ctx.stroke();
+
+ var markers = this.getMarkers();
+ if (markers != null) {
+ if (this.style('marker-start').Definition.isUrl()) {
+ var marker = this.style('marker-start').Definition.getDefinition();
+ marker.render(ctx, markers[0][0], markers[0][1]);
+ }
+ if (this.style('marker-mid').Definition.isUrl()) {
+ var marker = this.style('marker-mid').Definition.getDefinition();
+ for (var i=1;i<markers.length-1;i++) {
+ marker.render(ctx, markers[i][0], markers[i][1]);
+ }
+ }
+ if (this.style('marker-end').Definition.isUrl()) {
+ var marker = this.style('marker-end').Definition.getDefinition();
+ marker.render(ctx, markers[markers.length-1][0], markers[markers.length-1][1]);
+ }
+ }
+ }
+
+ this.getBoundingBox = function() {
+ return this.path();
+ }
+
+ this.getMarkers = function() {
+ return null;
+ }
+ }
+ svg.Element.PathElementBase.prototype = new svg.Element.RenderedElementBase;
+
+ // svg element
+ svg.Element.svg = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.baseClearContext = this.clearContext;
+ this.clearContext = function(ctx) {
+ this.baseClearContext(ctx);
+ svg.ViewPort.RemoveCurrent();
+ }
+
+ this.baseSetContext = this.setContext;
+ this.setContext = function(ctx) {
+ // initial values
+ ctx.strokeStyle = 'rgba(0,0,0,0)';
+ ctx.lineCap = 'butt';
+ ctx.lineJoin = 'miter';
+ ctx.miterLimit = 4;
+
+ this.baseSetContext(ctx);
+
+ // create new view port
+ if (this.attribute('x').hasValue() && this.attribute('y').hasValue()) {
+ ctx.translate(this.attribute('x').Length.toPixels('x'), this.attribute('y').Length.toPixels('y'));
+ }
+
+ var width = svg.ViewPort.width();
+ var height = svg.ViewPort.height();
+ if (typeof(this.root) == 'undefined' && this.attribute('width').hasValue() && this.attribute('height').hasValue()) {
+ width = this.attribute('width').Length.toPixels('x');
+ height = this.attribute('height').Length.toPixels('y');
+
+ var x = 0;
+ var y = 0;
+ if (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {
+ x = -this.attribute('refX').Length.toPixels('x');
+ y = -this.attribute('refY').Length.toPixels('y');
+ }
+
+ ctx.beginPath();
+ ctx.moveTo(x, y);
+ ctx.lineTo(width, y);
+ ctx.lineTo(width, height);
+ ctx.lineTo(x, height);
+ ctx.closePath();
+ ctx.clip();
+ }
+ svg.ViewPort.SetCurrent(width, height);
+
+ // viewbox
+ if (this.attribute('viewBox').hasValue()) {
+ var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
+ var minX = viewBox[0];
+ var minY = viewBox[1];
+ width = viewBox[2];
+ height = viewBox[3];
+
+ svg.AspectRatio(ctx,
+ this.attribute('preserveAspectRatio').value,
+ svg.ViewPort.width(),
+ width,
+ svg.ViewPort.height(),
+ height,
+ minX,
+ minY,
+ this.attribute('refX').value,
+ this.attribute('refY').value);
+
+ svg.ViewPort.RemoveCurrent();
+ svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);
+ }
+ }
+ }
+ svg.Element.svg.prototype = new svg.Element.RenderedElementBase;
+
+ // rect element
+ svg.Element.rect = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ this.path = function(ctx) {
+ var x = this.attribute('x').Length.toPixels('x');
+ var y = this.attribute('y').Length.toPixels('y');
+ var width = this.attribute('width').Length.toPixels('x');
+ var height = this.attribute('height').Length.toPixels('y');
+ var rx = this.attribute('rx').Length.toPixels('x');
+ var ry = this.attribute('ry').Length.toPixels('y');
+ if (this.attribute('rx').hasValue() && !this.attribute('ry').hasValue()) ry = rx;
+ if (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;
+
+ if (ctx != null) {
+ ctx.beginPath();
+ ctx.moveTo(x + rx, y);
+ ctx.lineTo(x + width - rx, y);
+ ctx.quadraticCurveTo(x + width, y, x + width, y + ry)
+ ctx.lineTo(x + width, y + height - ry);
+ ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height)
+ ctx.lineTo(x + rx, y + height);
+ ctx.quadraticCurveTo(x, y + height, x, y + height - ry)
+ ctx.lineTo(x, y + ry);
+ ctx.quadraticCurveTo(x, y, x + rx, y)
+ ctx.closePath();
+ }
+
+ return new svg.BoundingBox(x, y, x + width, y + height);
+ }
+ }
+ svg.Element.rect.prototype = new svg.Element.PathElementBase;
+
+ // circle element
+ svg.Element.circle = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ this.path = function(ctx) {
+ var cx = this.attribute('cx').Length.toPixels('x');
+ var cy = this.attribute('cy').Length.toPixels('y');
+ var r = this.attribute('r').Length.toPixels();
+
+ if (ctx != null) {
+ ctx.beginPath();
+ ctx.arc(cx, cy, r, 0, Math.PI * 2, true);
+ ctx.closePath();
+ }
+
+ return new svg.BoundingBox(cx - r, cy - r, cx + r, cy + r);
+ }
+ }
+ svg.Element.circle.prototype = new svg.Element.PathElementBase;
+
+ // ellipse element
+ svg.Element.ellipse = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ this.path = function(ctx) {
+ var KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);
+ var rx = this.attribute('rx').Length.toPixels('x');
+ var ry = this.attribute('ry').Length.toPixels('y');
+ var cx = this.attribute('cx').Length.toPixels('x');
+ var cy = this.attribute('cy').Length.toPixels('y');
+
+ if (ctx != null) {
+ ctx.beginPath();
+ ctx.moveTo(cx, cy - ry);
+ ctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry, cx + rx, cy - (KAPPA * ry), cx + rx, cy);
+ ctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);
+ ctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);
+ ctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);
+ ctx.closePath();
+ }
+
+ return new svg.BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);
+ }
+ }
+ svg.Element.ellipse.prototype = new svg.Element.PathElementBase;
+
+ // line element
+ svg.Element.line = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ this.getPoints = function() {
+ return [
+ new svg.Point(this.attribute('x1').Length.toPixels('x'), this.attribute('y1').Length.toPixels('y')),
+ new svg.Point(this.attribute('x2').Length.toPixels('x'), this.attribute('y2').Length.toPixels('y'))];
+ }
+
+ this.path = function(ctx) {
+ var points = this.getPoints();
+
+ if (ctx != null) {
+ ctx.beginPath();
+ ctx.moveTo(points[0].x, points[0].y);
+ ctx.lineTo(points[1].x, points[1].y);
+ }
+
+ return new svg.BoundingBox(points[0].x, points[0].y, points[1].x, points[1].y);
+ }
+
+ this.getMarkers = function() {
+ var points = this.getPoints();
+ var a = points[0].angleTo(points[1]);
+ return [[points[0], a], [points[1], a]];
+ }
+ }
+ svg.Element.line.prototype = new svg.Element.PathElementBase;
+
+ // polyline element
+ svg.Element.polyline = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ this.points = svg.CreatePath(this.attribute('points').value);
+ this.path = function(ctx) {
+ var bb = new svg.BoundingBox(this.points[0].x, this.points[0].y);
+ if (ctx != null) {
+ ctx.beginPath();
+ ctx.moveTo(this.points[0].x, this.points[0].y);
+ }
+ for (var i=1; i<this.points.length; i++) {
+ bb.addPoint(this.points[i].x, this.points[i].y);
+ if (ctx != null) ctx.lineTo(this.points[i].x, this.points[i].y);
+ }
+ return bb;
+ }
+
+ this.getMarkers = function() {
+ var markers = [];
+ for (var i=0; i<this.points.length - 1; i++) {
+ markers.push([this.points[i], this.points[i].angleTo(this.points[i+1])]);
+ }
+ markers.push([this.points[this.points.length-1], markers[markers.length-1][1]]);
+ return markers;
+ }
+ }
+ svg.Element.polyline.prototype = new svg.Element.PathElementBase;
+
+ // polygon element
+ svg.Element.polygon = function(node) {
+ this.base = svg.Element.polyline;
+ this.base(node);
+
+ this.basePath = this.path;
+ this.path = function(ctx) {
+ var bb = this.basePath(ctx);
+ if (ctx != null) {
+ ctx.lineTo(this.points[0].x, this.points[0].y);
+ ctx.closePath();
+ }
+ return bb;
+ }
+ }
+ svg.Element.polygon.prototype = new svg.Element.polyline;
+
+ // path element
+ svg.Element.path = function(node) {
+ this.base = svg.Element.PathElementBase;
+ this.base(node);
+
+ var d = this.attribute('d').value;
+ // TODO: convert to real lexer based on http://www.w3.org/TR/SVG11/paths.html#PathDataBNF
+ d = d.replace(/,/gm,' '); // get rid of all commas
+ d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
+ d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands
+ d = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([^\s])/gm,'$1 $2'); // separate commands from points
+ d = d.replace(/([^\s])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from points
+ d = d.replace(/([0-9])([+\-])/gm,'$1 $2'); // separate digits when no comma
+ d = d.replace(/(\.[0-9]*)(\.)/gm,'$1 $2'); // separate digits when no comma
+ d = d.replace(/([Aa](\s+[0-9]+){3})\s+([01])\s*([01])/gm,'$1 $3 $4 '); // shorthand elliptical arc path syntax
+ d = svg.compressSpaces(d); // compress multiple spaces
+ d = svg.trim(d);
+ this.PathParser = new (function(d) {
+ this.tokens = d.split(' ');
+
+ this.reset = function() {
+ this.i = -1;
+ this.command = '';
+ this.previousCommand = '';
+ this.start = new svg.Point(0, 0);
+ this.control = new svg.Point(0, 0);
+ this.current = new svg.Point(0, 0);
+ this.points = [];
+ this.angles = [];
+ }
+
+ this.isEnd = function() {
+ return this.i >= this.tokens.length - 1;
+ }
+
+ this.isCommandOrEnd = function() {
+ if (this.isEnd()) return true;
+ return this.tokens[this.i + 1].match(/^[A-Za-z]$/) != null;
+ }
+
+ this.isRelativeCommand = function() {
+ return this.command == this.command.toLowerCase();
+ }
+
+ this.getToken = function() {
+ this.i = this.i + 1;
+ return this.tokens[this.i];
+ }
+
+ this.getScalar = function() {
+ return parseFloat(this.getToken());
+ }
+
+ this.nextCommand = function() {
+ this.previousCommand = this.command;
+ this.command = this.getToken();
+ }
+
+ this.getPoint = function() {
+ var p = new svg.Point(this.getScalar(), this.getScalar());
+ return this.makeAbsolute(p);
+ }
+
+ this.getAsControlPoint = function() {
+ var p = this.getPoint();
+ this.control = p;
+ return p;
+ }
+
+ this.getAsCurrentPoint = function() {
+ var p = this.getPoint();
+ this.current = p;
+ return p;
+ }
+
+ this.getReflectedControlPoint = function() {
+ if (this.previousCommand.toLowerCase() != 'c' && this.previousCommand.toLowerCase() != 's') {
+ return this.current;
+ }
+
+ // reflect point
+ var p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y);
+ return p;
+ }
+
+ this.makeAbsolute = function(p) {
+ if (this.isRelativeCommand()) {
+ p.x = this.current.x + p.x;
+ p.y = this.current.y + p.y;
+ }
+ return p;
+ }
+
+ this.addMarker = function(p, from, priorTo) {
+ // if the last angle isn't filled in because we didn't have this point yet ...
+ if (priorTo != null && this.angles.length > 0 && this.angles[this.angles.length-1] == null) {
+ this.angles[this.angles.length-1] = this.points[this.points.length-1].angleTo(priorTo);
+ }
+ this.addMarkerAngle(p, from == null ? null : from.angleTo(p));
+ }
+
+ this.addMarkerAngle = function(p, a) {
+ this.points.push(p);
+ this.angles.push(a);
+ }
+
+ this.getMarkerPoints = function() { return this.points; }
+ this.getMarkerAngles = function() {
+ for (var i=0; i<this.angles.length; i++) {
+ if (this.angles[i] == null) {
+ for (var j=i+1; j<this.angles.length; j++) {
+ if (this.angles[j] != null) {
+ this.angles[i] = this.angles[j];
+ break;
+ }
+ }
+ }
+ }
+ return this.angles;
+ }
+ })(d);
+
+ this.path = function(ctx) {
+ var pp = this.PathParser;
+ pp.reset();
+
+ var bb = new svg.BoundingBox();
+ if (ctx != null) ctx.beginPath();
+ while (!pp.isEnd()) {
+ pp.nextCommand();
+ switch (pp.command.toUpperCase()) {
+ case 'M':
+ var p = pp.getAsCurrentPoint();
+ pp.addMarker(p);
+ bb.addPoint(p.x, p.y);
+ if (ctx != null) ctx.moveTo(p.x, p.y);
+ pp.start = pp.current;
+ while (!pp.isCommandOrEnd()) {
+ var p = pp.getAsCurrentPoint();
+ pp.addMarker(p, pp.start);
+ bb.addPoint(p.x, p.y);
+ if (ctx != null) ctx.lineTo(p.x, p.y);
+ }
+ break;
+ case 'L':
+ while (!pp.isCommandOrEnd()) {
+ var c = pp.current;
+ var p = pp.getAsCurrentPoint();
+ pp.addMarker(p, c);
+ bb.addPoint(p.x, p.y);
+ if (ctx != null) ctx.lineTo(p.x, p.y);
+ }
+ break;
+ case 'H':
+ while (!pp.isCommandOrEnd()) {
+ var newP = new svg.Point((pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar(), pp.current.y);
+ pp.addMarker(newP, pp.current);
+ pp.current = newP;
+ bb.addPoint(pp.current.x, pp.current.y);
+ if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
+ }
+ break;
+ case 'V':
+ while (!pp.isCommandOrEnd()) {
+ var newP = new svg.Point(pp.current.x, (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar());
+ pp.addMarker(newP, pp.current);
+ pp.current = newP;
+ bb.addPoint(pp.current.x, pp.current.y);
+ if (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);
+ }
+ break;
+ case 'C':
+ while (!pp.isCommandOrEnd()) {
+ var curr = pp.current;
+ var p1 = pp.getPoint();
+ var cntrl = pp.getAsControlPoint();
+ var cp = pp.getAsCurrentPoint();
+ pp.addMarker(cp, cntrl, p1);
+ bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ }
+ break;
+ case 'S':
+ while (!pp.isCommandOrEnd()) {
+ var curr = pp.current;
+ var p1 = pp.getReflectedControlPoint();
+ var cntrl = pp.getAsControlPoint();
+ var cp = pp.getAsCurrentPoint();
+ pp.addMarker(cp, cntrl, p1);
+ bb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ if (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ }
+ break;
+ case 'Q':
+ while (!pp.isCommandOrEnd()) {
+ var curr = pp.current;
+ var cntrl = pp.getAsControlPoint();
+ var cp = pp.getAsCurrentPoint();
+ pp.addMarker(cp, cntrl, cntrl);
+ bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
+ }
+ break;
+ case 'T':
+ while (!pp.isCommandOrEnd()) {
+ var curr = pp.current;
+ var cntrl = pp.getReflectedControlPoint();
+ pp.control = cntrl;
+ var cp = pp.getAsCurrentPoint();
+ pp.addMarker(cp, cntrl, cntrl);
+ bb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);
+ if (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);
+ }
+ break;
+ case 'A':
+ while (!pp.isCommandOrEnd()) {
+ var curr = pp.current;
+ var rx = pp.getScalar();
+ var ry = pp.getScalar();
+ var xAxisRotation = pp.getScalar() * (Math.PI / 180.0);
+ var largeArcFlag = pp.getScalar();
+ var sweepFlag = pp.getScalar();
+ var cp = pp.getAsCurrentPoint();
+
+ // Conversion from endpoint to center parameterization
+ // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+ // x1', y1'
+ var currp = new svg.Point(
+ Math.cos(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.sin(xAxisRotation) * (curr.y - cp.y) / 2.0,
+ -Math.sin(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.cos(xAxisRotation) * (curr.y - cp.y) / 2.0
+ );
+ // adjust radii
+ var l = Math.pow(currp.x,2)/Math.pow(rx,2)+Math.pow(currp.y,2)/Math.pow(ry,2);
+ if (l > 1) {
+ rx *= Math.sqrt(l);
+ ry *= Math.sqrt(l);
+ }
+ // cx', cy'
+ var s = (largeArcFlag == sweepFlag ? -1 : 1) * Math.sqrt(
+ ((Math.pow(rx,2)*Math.pow(ry,2))-(Math.pow(rx,2)*Math.pow(currp.y,2))-(Math.pow(ry,2)*Math.pow(currp.x,2))) /
+ (Math.pow(rx,2)*Math.pow(currp.y,2)+Math.pow(ry,2)*Math.pow(currp.x,2))
+ );
+ if (isNaN(s)) s = 0;
+ var cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx);
+ // cx, cy
+ var centp = new svg.Point(
+ (curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y,
+ (curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y
+ );
+ // vector magnitude
+ var m = function(v) { return Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2)); }
+ // ratio between two vectors
+ var r = function(u, v) { return (u[0]*v[0]+u[1]*v[1]) / (m(u)*m(v)) }
+ // angle between two vectors
+ var a = function(u, v) { return (u[0]*v[1] < u[1]*v[0] ? -1 : 1) * Math.acos(r(u,v)); }
+ // initial angle
+ var a1 = a([1,0], [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]);
+ // angle delta
+ var u = [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry];
+ var v = [(-currp.x-cpp.x)/rx,(-currp.y-cpp.y)/ry];
+ var ad = a(u, v);
+ if (r(u,v) <= -1) ad = Math.PI;
+ if (r(u,v) >= 1) ad = 0;
+
+ if (sweepFlag == 0 && ad > 0) ad = ad - 2 * Math.PI;
+ if (sweepFlag == 1 && ad < 0) ad = ad + 2 * Math.PI;
+
+ // for markers
+ var halfWay = new svg.Point(
+ centp.x - rx * Math.cos((a1 + ad) / 2),
+ centp.y - ry * Math.sin((a1 + ad) / 2)
+ );
+ pp.addMarkerAngle(halfWay, (a1 + ad) / 2 + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);
+ pp.addMarkerAngle(cp, ad + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);
+
+ bb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better
+ if (ctx != null) {
+ var r = rx > ry ? rx : ry;
+ var sx = rx > ry ? 1 : rx / ry;
+ var sy = rx > ry ? ry / rx : 1;
+
+ ctx.translate(centp.x, centp.y);
+ ctx.rotate(xAxisRotation);
+ ctx.scale(sx, sy);
+ ctx.arc(0, 0, r, a1, a1 + ad, 1 - sweepFlag);
+ ctx.scale(1/sx, 1/sy);
+ ctx.rotate(-xAxisRotation);
+ ctx.translate(-centp.x, -centp.y);
+ }
+ }
+ break;
+ case 'Z':
+ if (ctx != null) ctx.closePath();
+ pp.current = pp.start;
+ }
+ }
+
+ return bb;
+ }
+
+ this.getMarkers = function() {
+ var points = this.PathParser.getMarkerPoints();
+ var angles = this.PathParser.getMarkerAngles();
+
+ var markers = [];
+ for (var i=0; i<points.length; i++) {
+ markers.push([points[i], angles[i]]);
+ }
+ return markers;
+ }
+ }
+ svg.Element.path.prototype = new svg.Element.PathElementBase;
+
+ // pattern element
+ svg.Element.pattern = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.createPattern = function(ctx, element) {
+ // render me using a temporary svg element
+ var tempSvg = new svg.Element.svg();
+ tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
+ tempSvg.attributes['x'] = new svg.Property('x', this.attribute('x').value);
+ tempSvg.attributes['y'] = new svg.Property('y', this.attribute('y').value);
+ tempSvg.attributes['width'] = new svg.Property('width', this.attribute('width').value);
+ tempSvg.attributes['height'] = new svg.Property('height', this.attribute('height').value);
+ tempSvg.children = this.children;
+
+ var c = document.createElement('canvas');
+ c.width = this.attribute('width').Length.toPixels('x');
+ c.height = this.attribute('height').Length.toPixels('y');
+ tempSvg.render(c.getContext('2d'));
+ return ctx.createPattern(c, 'repeat');
+ }
+ }
+ svg.Element.pattern.prototype = new svg.Element.ElementBase;
+
+ // marker element
+ svg.Element.marker = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.baseRender = this.render;
+ this.render = function(ctx, point, angle) {
+ ctx.translate(point.x, point.y);
+ if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(angle);
+ if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);
+ ctx.save();
+
+ // render me using a temporary svg element
+ var tempSvg = new svg.Element.svg();
+ tempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);
+ tempSvg.attributes['refX'] = new svg.Property('refX', this.attribute('refX').value);
+ tempSvg.attributes['refY'] = new svg.Property('refY', this.attribute('refY').value);
+ tempSvg.attributes['width'] = new svg.Property('width', this.attribute('markerWidth').value);
+ tempSvg.attributes['height'] = new svg.Property('height', this.attribute('markerHeight').value);
+ tempSvg.attributes['fill'] = new svg.Property('fill', this.attribute('fill').valueOrDefault('black'));
+ tempSvg.attributes['stroke'] = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none'));
+ tempSvg.children = this.children;
+ tempSvg.render(ctx);
+
+ ctx.restore();
+ if (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(1/ctx.lineWidth, 1/ctx.lineWidth);
+ if (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(-angle);
+ ctx.translate(-point.x, -point.y);
+ }
+ }
+ svg.Element.marker.prototype = new svg.Element.ElementBase;
+
+ // definitions element
+ svg.Element.defs = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.render = function(ctx) {
+ // NOOP
+ }
+ }
+ svg.Element.defs.prototype = new svg.Element.ElementBase;
+
+ // base for gradients
+ svg.Element.GradientBase = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.gradientUnits = this.attribute('gradientUnits').valueOrDefault('objectBoundingBox');
+
+ this.stops = [];
+ for (var i=0; i<this.children.length; i++) {
+ var child = this.children[i];
+ this.stops.push(child);
+ }
+
+ this.getGradient = function() {
+ // OVERRIDE ME!
+ }
+
+ this.createGradient = function(ctx, element) {
+ var stopsContainer = this;
+ if (this.attribute('xlink:href').hasValue()) {
+ stopsContainer = this.attribute('xlink:href').Definition.getDefinition();
+ }
+
+ var g = this.getGradient(ctx, element);
+ for (var i=0; i<stopsContainer.stops.length; i++) {
+ g.addColorStop(stopsContainer.stops[i].offset, stopsContainer.stops[i].color);
+ }
+
+ if (this.attribute('gradientTransform').hasValue()) {
+ // render as transformed pattern on temporary canvas
+ var rootView = svg.ViewPort.viewPorts[0];
+
+ var rect = new svg.Element.rect();
+ rect.attributes['x'] = new svg.Property('x', -svg.MAX_VIRTUAL_PIXELS/3.0);
+ rect.attributes['y'] = new svg.Property('y', -svg.MAX_VIRTUAL_PIXELS/3.0);
+ rect.attributes['width'] = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);
+ rect.attributes['height'] = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);
+
+ var group = new svg.Element.g();
+ group.attributes['transform'] = new svg.Property('transform', this.attribute('gradientTransform').value);
+ group.children = [ rect ];
+
+ var tempSvg = new svg.Element.svg();
+ tempSvg.attributes['x'] = new svg.Property('x', 0);
+ tempSvg.attributes['y'] = new svg.Property('y', 0);
+ tempSvg.attributes['width'] = new svg.Property('width', rootView.width);
+ tempSvg.attributes['height'] = new svg.Property('height', rootView.height);
+ tempSvg.children = [ group ];
+
+ var c = document.createElement('canvas');
+ c.width = rootView.width;
+ c.height = rootView.height;
+ var tempCtx = c.getContext('2d');
+ tempCtx.fillStyle = g;
+ tempSvg.render(tempCtx);
+ return tempCtx.createPattern(c, 'no-repeat');
+ }
+
+ return g;
+ }
+ }
+ svg.Element.GradientBase.prototype = new svg.Element.ElementBase;
+
+ // linear gradient element
+ svg.Element.linearGradient = function(node) {
+ this.base = svg.Element.GradientBase;
+ this.base(node);
+
+ this.getGradient = function(ctx, element) {
+ var bb = element.getBoundingBox();
+
+ var x1 = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.x() + bb.width() * this.attribute('x1').numValue()
+ : this.attribute('x1').Length.toPixels('x'));
+ var y1 = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.y() + bb.height() * this.attribute('y1').numValue()
+ : this.attribute('y1').Length.toPixels('y'));
+ var x2 = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.x() + bb.width() * this.attribute('x2').numValue()
+ : this.attribute('x2').Length.toPixels('x'));
+ var y2 = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.y() + bb.height() * this.attribute('y2').numValue()
+ : this.attribute('y2').Length.toPixels('y'));
+
+ return ctx.createLinearGradient(x1, y1, x2, y2);
+ }
+ }
+ svg.Element.linearGradient.prototype = new svg.Element.GradientBase;
+
+ // radial gradient element
+ svg.Element.radialGradient = function(node) {
+ this.base = svg.Element.GradientBase;
+ this.base(node);
+
+ this.getGradient = function(ctx, element) {
+ var bb = element.getBoundingBox();
+
+ var cx = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.x() + bb.width() * this.attribute('cx').numValue()
+ : this.attribute('cx').Length.toPixels('x'));
+ var cy = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.y() + bb.height() * this.attribute('cy').numValue()
+ : this.attribute('cy').Length.toPixels('y'));
+
+ var fx = cx;
+ var fy = cy;
+ if (this.attribute('fx').hasValue()) {
+ fx = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.x() + bb.width() * this.attribute('fx').numValue()
+ : this.attribute('fx').Length.toPixels('x'));
+ }
+ if (this.attribute('fy').hasValue()) {
+ fy = (this.gradientUnits == 'objectBoundingBox'
+ ? bb.y() + bb.height() * this.attribute('fy').numValue()
+ : this.attribute('fy').Length.toPixels('y'));
+ }
+
+ var r = (this.gradientUnits == 'objectBoundingBox'
+ ? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()
+ : this.attribute('r').Length.toPixels());
+
+ return ctx.createRadialGradient(fx, fy, 0, cx, cy, r);
+ }
+ }
+ svg.Element.radialGradient.prototype = new svg.Element.GradientBase;
+
+ // gradient stop element
+ svg.Element.stop = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.offset = this.attribute('offset').numValue();
+
+ var stopColor = this.style('stop-color');
+ if (this.style('stop-opacity').hasValue()) stopColor = stopColor.Color.addOpacity(this.style('stop-opacity').value);
+ this.color = stopColor.value;
+ }
+ svg.Element.stop.prototype = new svg.Element.ElementBase;
+
+ // animation base element
+ svg.Element.AnimateBase = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ svg.Animations.push(this);
+
+ this.duration = 0.0;
+ this.begin = this.attribute('begin').Time.toMilliseconds();
+ this.maxDuration = this.begin + this.attribute('dur').Time.toMilliseconds();
+
+ this.getProperty = function() {
+ var attributeType = this.attribute('attributeType').value;
+ var attributeName = this.attribute('attributeName').value;
+
+ if (attributeType == 'CSS') {
+ return this.parent.style(attributeName, true);
+ }
+ return this.parent.attribute(attributeName, true);
+ };
+
+ this.initialValue = null;
+ this.removed = false;
+
+ this.calcValue = function() {
+ // OVERRIDE ME!
+ return '';
+ }
+
+ this.update = function(delta) {
+ // set initial value
+ if (this.initialValue == null) {
+ this.initialValue = this.getProperty().value;
+ }
+
+ // if we're past the end time
+ if (this.duration > this.maxDuration) {
+ // loop for indefinitely repeating animations
+ if (this.attribute('repeatCount').value == 'indefinite') {
+ this.duration = 0.0
+ }
+ else if (this.attribute('fill').valueOrDefault('remove') == 'remove' && !this.removed) {
+ this.removed = true;
+ this.getProperty().value = this.initialValue;
+ return true;
+ }
+ else {
+ return false; // no updates made
+ }
+ }
+ this.duration = this.duration + delta;
+
+ // if we're past the begin time
+ var updated = false;
+ if (this.begin < this.duration) {
+ var newValue = this.calcValue(); // tween
+
+ if (this.attribute('type').hasValue()) {
+ // for transform, etc.
+ var type = this.attribute('type').value;
+ newValue = type + '(' + newValue + ')';
+ }
+
+ this.getProperty().value = newValue;
+ updated = true;
+ }
+
+ return updated;
+ }
+
+ // fraction of duration we've covered
+ this.progress = function() {
+ return ((this.duration - this.begin) / (this.maxDuration - this.begin));
+ }
+ }
+ svg.Element.AnimateBase.prototype = new svg.Element.ElementBase;
+
+ // animate element
+ svg.Element.animate = function(node) {
+ this.base = svg.Element.AnimateBase;
+ this.base(node);
+
+ this.calcValue = function() {
+ var from = this.attribute('from').numValue();
+ var to = this.attribute('to').numValue();
+
+ // tween value linearly
+ return from + (to - from) * this.progress();
+ };
+ }
+ svg.Element.animate.prototype = new svg.Element.AnimateBase;
+
+ // animate color element
+ svg.Element.animateColor = function(node) {
+ this.base = svg.Element.AnimateBase;
+ this.base(node);
+
+ this.calcValue = function() {
+ var from = new RGBColor(this.attribute('from').value);
+ var to = new RGBColor(this.attribute('to').value);
+
+ if (from.ok && to.ok) {
+ // tween color linearly
+ var r = from.r + (to.r - from.r) * this.progress();
+ var g = from.g + (to.g - from.g) * this.progress();
+ var b = from.b + (to.b - from.b) * this.progress();
+ return 'rgb('+parseInt(r,10)+','+parseInt(g,10)+','+parseInt(b,10)+')';
+ }
+ return this.attribute('from').value;
+ };
+ }
+ svg.Element.animateColor.prototype = new svg.Element.AnimateBase;
+
+ // animate transform element
+ svg.Element.animateTransform = function(node) {
+ this.base = svg.Element.animate;
+ this.base(node);
+ }
+ svg.Element.animateTransform.prototype = new svg.Element.animate;
+
+ // font element
+ svg.Element.font = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.horizAdvX = this.attribute('horiz-adv-x').numValue();
+
+ this.isRTL = false;
+ this.isArabic = false;
+ this.fontFace = null;
+ this.missingGlyph = null;
+ this.glyphs = [];
+ for (var i=0; i<this.children.length; i++) {
+ var child = this.children[i];
+ if (child.type == 'font-face') {
+ this.fontFace = child;
+ if (child.style('font-family').hasValue()) {
+ svg.Definitions[child.style('font-family').value] = this;
+ }
+ }
+ else if (child.type == 'missing-glyph') this.missingGlyph = child;
+ else if (child.type == 'glyph') {
+ if (child.arabicForm != '') {
+ this.isRTL = true;
+ this.isArabic = true;
+ if (typeof(this.glyphs[child.unicode]) == 'undefined') this.glyphs[child.unicode] = [];
+ this.glyphs[child.unicode][child.arabicForm] = child;
+ }
+ else {
+ this.glyphs[child.unicode] = child;
+ }
+ }
+ }
+ }
+ svg.Element.font.prototype = new svg.Element.ElementBase;
+
+ // font-face element
+ svg.Element.fontface = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.ascent = this.attribute('ascent').value;
+ this.descent = this.attribute('descent').value;
+ this.unitsPerEm = this.attribute('units-per-em').numValue();
+ }
+ svg.Element.fontface.prototype = new svg.Element.ElementBase;
+
+ // missing-glyph element
+ svg.Element.missingglyph = function(node) {
+ this.base = svg.Element.path;
+ this.base(node);
+
+ this.horizAdvX = 0;
+ }
+ svg.Element.missingglyph.prototype = new svg.Element.path;
+
+ // glyph element
+ svg.Element.glyph = function(node) {
+ this.base = svg.Element.path;
+ this.base(node);
+
+ this.horizAdvX = this.attribute('horiz-adv-x').numValue();
+ this.unicode = this.attribute('unicode').value;
+ this.arabicForm = this.attribute('arabic-form').value;
+ }
+ svg.Element.glyph.prototype = new svg.Element.path;
+
+ // text element
+ svg.Element.text = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ if (node != null) {
+ // add children
+ this.children = [];
+ for (var i=0; i<node.childNodes.length; i++) {
+ var childNode = node.childNodes[i];
+ if (childNode.nodeType == 1) { // capture tspan and tref nodes
+ this.addChild(childNode, true);
+ }
+ else if (childNode.nodeType == 3) { // capture text
+ this.addChild(new svg.Element.tspan(childNode), false);
+ }
+ }
+ }
+
+ this.baseSetContext = this.setContext;
+ this.setContext = function(ctx) {
+ this.baseSetContext(ctx);
+ if (this.style('dominant-baseline').hasValue()) ctx.textBaseline = this.style('dominant-baseline').value;
+ if (this.style('alignment-baseline').hasValue()) ctx.textBaseline = this.style('alignment-baseline').value;
+ }
+
+ this.renderChildren = function(ctx) {
+ var textAnchor = this.style('text-anchor').valueOrDefault('start');
+ var x = this.attribute('x').Length.toPixels('x');
+ var y = this.attribute('y').Length.toPixels('y');
+ for (var i=0; i<this.children.length; i++) {
+ var child = this.children[i];
+
+ if (child.attribute('x').hasValue()) {
+ child.x = child.attribute('x').Length.toPixels('x');
+ }
+ else {
+ if (child.attribute('dx').hasValue()) x += child.attribute('dx').Length.toPixels('x');
+ child.x = x;
+ }
+
+ var childLength = child.measureText(ctx);
+ if (textAnchor != 'start' && (i==0 || child.attribute('x').hasValue())) { // new group?
+ // loop through rest of children
+ var groupLength = childLength;
+ for (var j=i+1; j<this.children.length; j++) {
+ var childInGroup = this.children[j];
+ if (childInGroup.attribute('x').hasValue()) break; // new group
+ groupLength += childInGroup.measureText(ctx);
+ }
+ child.x -= (textAnchor == 'end' ? groupLength : groupLength / 2.0);
+ }
+ x = child.x + childLength;
+
+ if (child.attribute('y').hasValue()) {
+ child.y = child.attribute('y').Length.toPixels('y');
+ }
+ else {
+ if (child.attribute('dy').hasValue()) y += child.attribute('dy').Length.toPixels('y');
+ child.y = y;
+ }
+ y = child.y;
+
+ child.render(ctx);
+ }
+ }
+ }
+ svg.Element.text.prototype = new svg.Element.RenderedElementBase;
+
+ // text base
+ svg.Element.TextElementBase = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.getGlyph = function(font, text, i) {
+ var c = text[i];
+ var glyph = null;
+ if (font.isArabic) {
+ var arabicForm = 'isolated';
+ if ((i==0 || text[i-1]==' ') && i<text.length-2 && text[i+1]!=' ') arabicForm = 'terminal';
+ if (i>0 && text[i-1]!=' ' && i<text.length-2 && text[i+1]!=' ') arabicForm = 'medial';
+ if (i>0 && text[i-1]!=' ' && (i == text.length-1 || text[i+1]==' ')) arabicForm = 'initial';
+ if (typeof(font.glyphs[c]) != 'undefined') {
+ glyph = font.glyphs[c][arabicForm];
+ if (glyph == null && font.glyphs[c].type == 'glyph') glyph = font.glyphs[c];
+ }
+ }
+ else {
+ glyph = font.glyphs[c];
+ }
+ if (glyph == null) glyph = font.missingGlyph;
+ return glyph;
+ }
+
+ this.renderChildren = function(ctx) {
+ var customFont = this.parent.style('font-family').Definition.getDefinition();
+ if (customFont != null) {
+ var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
+ var fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle);
+ var text = this.getText();
+ if (customFont.isRTL) text = text.split("").reverse().join("");
+
+ var dx = svg.ToNumberArray(this.parent.attribute('dx').value);
+ for (var i=0; i<text.length; i++) {
+ var glyph = this.getGlyph(customFont, text, i);
+ var scale = fontSize / customFont.fontFace.unitsPerEm;
+ ctx.translate(this.x, this.y);
+ ctx.scale(scale, -scale);
+ var lw = ctx.lineWidth;
+ ctx.lineWidth = ctx.lineWidth * customFont.fontFace.unitsPerEm / fontSize;
+ if (fontStyle == 'italic') ctx.transform(1, 0, .4, 1, 0, 0);
+ glyph.render(ctx);
+ if (fontStyle == 'italic') ctx.transform(1, 0, -.4, 1, 0, 0);
+ ctx.lineWidth = lw;
+ ctx.scale(1/scale, -1/scale);
+ ctx.translate(-this.x, -this.y);
+
+ this.x += fontSize * (glyph.horizAdvX || customFont.horizAdvX) / customFont.fontFace.unitsPerEm;
+ if (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {
+ this.x += dx[i];
+ }
+ }
+ return;
+ }
+
+ if (ctx.strokeStyle != '') ctx.strokeText(svg.compressSpaces(this.getText()), this.x, this.y);
+ if (ctx.fillStyle != '') ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);
+ }
+
+ this.getText = function() {
+ // OVERRIDE ME
+ }
+
+ this.measureText = function(ctx) {
+ var customFont = this.parent.style('font-family').Definition.getDefinition();
+ if (customFont != null) {
+ var fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);
+ var measure = 0;
+ var text = this.getText();
+ if (customFont.isRTL) text = text.split("").reverse().join("");
+ var dx = svg.ToNumberArray(this.parent.attribute('dx').value);
+ for (var i=0; i<text.length; i++) {
+ var glyph = this.getGlyph(customFont, text, i);
+ measure += (glyph.horizAdvX || customFont.horizAdvX) * fontSize / customFont.fontFace.unitsPerEm;
+ if (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {
+ measure += dx[i];
+ }
+ }
+ return measure;
+ }
+
+ var textToMeasure = svg.compressSpaces(this.getText());
+ if (!ctx.measureText) return textToMeasure.length * 10;
+
+ ctx.save();
+ this.setContext(ctx);
+ var width = ctx.measureText(textToMeasure).width;
+ ctx.restore();
+ return width;
+ }
+ }
+ svg.Element.TextElementBase.prototype = new svg.Element.RenderedElementBase;
+
+ // tspan
+ svg.Element.tspan = function(node) {
+ this.base = svg.Element.TextElementBase;
+ this.base(node);
+
+ this.text = node.nodeType == 3 ? node.nodeValue : // text
+ node.childNodes.length > 0 ? node.childNodes[0].nodeValue : // element
+ node.text;
+ this.getText = function() {
+ return this.text;
+ }
+ }
+ svg.Element.tspan.prototype = new svg.Element.TextElementBase;
+
+ // tref
+ svg.Element.tref = function(node) {
+ this.base = svg.Element.TextElementBase;
+ this.base(node);
+
+ this.getText = function() {
+ var element = this.attribute('xlink:href').Definition.getDefinition();
+ if (element != null) return element.children[0].getText();
+ }
+ }
+ svg.Element.tref.prototype = new svg.Element.TextElementBase;
+
+ // a element
+ svg.Element.a = function(node) {
+ this.base = svg.Element.TextElementBase;
+ this.base(node);
+
+ this.hasText = true;
+ for (var i=0; i<node.childNodes.length; i++) {
+ if (node.childNodes[i].nodeType != 3) this.hasText = false;
+ }
+
+ // this might contain text
+ this.text = this.hasText ? node.childNodes[0].nodeValue : '';
+ this.getText = function() {
+ return this.text;
+ }
+
+ this.baseRenderChildren = this.renderChildren;
+ this.renderChildren = function(ctx) {
+ if (this.hasText) {
+ // render as text element
+ this.baseRenderChildren(ctx);
+ var fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);
+ svg.Mouse.checkBoundingBox(this, new svg.BoundingBox(this.x, this.y - fontSize.Length.toPixels('y'), this.x + this.measureText(ctx), this.y));
+ }
+ else {
+ // render as temporary group
+ var g = new svg.Element.g();
+ g.children = this.children;
+ g.parent = this;
+ g.render(ctx);
+ }
+ }
+
+ this.onclick = function() {
+ window.open(this.attribute('xlink:href').value);
+ }
+
+ this.onmousemove = function() {
+ svg.ctx.canvas.style.cursor = 'pointer';
+ }
+ }
+ svg.Element.a.prototype = new svg.Element.TextElementBase;
+
+ // image element
+ svg.Element.image = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ svg.Images.push(this);
+ this.img = document.createElement('img');
+ this.loaded = false;
+ var that = this;
+ this.img.onload = function() { that.loaded = true; }
+ this.img.src = this.attribute('xlink:href').value;
+
+ this.renderChildren = function(ctx) {
+ var x = this.attribute('x').Length.toPixels('x');
+ var y = this.attribute('y').Length.toPixels('y');
+
+ var width = this.attribute('width').Length.toPixels('x');
+ var height = this.attribute('height').Length.toPixels('y');
+ if (width == 0 || height == 0) return;
+
+ ctx.save();
+ ctx.translate(x, y);
+ svg.AspectRatio(ctx,
+ this.attribute('preserveAspectRatio').value,
+ width,
+ this.img.width,
+ height,
+ this.img.height,
+ 0,
+ 0);
+ ctx.drawImage(this.img, 0, 0);
+ ctx.restore();
+ }
+ }
+ svg.Element.image.prototype = new svg.Element.RenderedElementBase;
+
+ // group element
+ svg.Element.g = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.getBoundingBox = function() {
+ var bb = new svg.BoundingBox();
+ for (var i=0; i<this.children.length; i++) {
+ bb.addBoundingBox(this.children[i].getBoundingBox());
+ }
+ return bb;
+ };
+ }
+ svg.Element.g.prototype = new svg.Element.RenderedElementBase;
+
+ // symbol element
+ svg.Element.symbol = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.baseSetContext = this.setContext;
+ this.setContext = function(ctx) {
+ this.baseSetContext(ctx);
+
+ // viewbox
+ if (this.attribute('viewBox').hasValue()) {
+ var viewBox = svg.ToNumberArray(this.attribute('viewBox').value);
+ var minX = viewBox[0];
+ var minY = viewBox[1];
+ width = viewBox[2];
+ height = viewBox[3];
+
+ svg.AspectRatio(ctx,
+ this.attribute('preserveAspectRatio').value,
+ this.attribute('width').Length.toPixels('x'),
+ width,
+ this.attribute('height').Length.toPixels('y'),
+ height,
+ minX,
+ minY);
+
+ svg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);
+ }
+ }
+ }
+ svg.Element.symbol.prototype = new svg.Element.RenderedElementBase;
+
+ // style element
+ svg.Element.style = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ // text, or spaces then CDATA
+ var css = node.childNodes[0].nodeValue + (node.childNodes.length > 1 ? node.childNodes[1].nodeValue : '');
+ css = css.replace(/(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(^[\s]*\/\/.*)/gm, ''); // remove comments
+ css = svg.compressSpaces(css); // replace whitespace
+ var cssDefs = css.split('}');
+ for (var i=0; i<cssDefs.length; i++) {
+ if (svg.trim(cssDefs[i]) != '') {
+ var cssDef = cssDefs[i].split('{');
+ var cssClasses = cssDef[0].split(',');
+ var cssProps = cssDef[1].split(';');
+ for (var j=0; j<cssClasses.length; j++) {
+ var cssClass = svg.trim(cssClasses[j]);
+ if (cssClass != '') {
+ var props = {};
+ for (var k=0; k<cssProps.length; k++) {
+ var prop = cssProps[k].indexOf(':');
+ var name = cssProps[k].substr(0, prop);
+ var value = cssProps[k].substr(prop + 1, cssProps[k].length - prop);
+ if (name != null && value != null) {
+ props[svg.trim(name)] = new svg.Property(svg.trim(name), svg.trim(value));
+ }
+ }
+ svg.Styles[cssClass] = props;
+ if (cssClass == '@font-face') {
+ var fontFamily = props['font-family'].value.replace(/"/g,'');
+ var srcs = props['src'].value.split(',');
+ for (var s=0; s<srcs.length; s++) {
+ if (srcs[s].indexOf('format("svg")') > 0) {
+ var urlStart = srcs[s].indexOf('url');
+ var urlEnd = srcs[s].indexOf(')', urlStart);
+ var url = srcs[s].substr(urlStart + 5, urlEnd - urlStart - 6);
+ var doc = svg.parseXml(svg.ajax(url));
+ var fonts = doc.getElementsByTagName('font');
+ for (var f=0; f<fonts.length; f++) {
+ var font = svg.CreateElement(fonts[f]);
+ svg.Definitions[fontFamily] = font;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ svg.Element.style.prototype = new svg.Element.ElementBase;
+
+ // use element
+ svg.Element.use = function(node) {
+ this.base = svg.Element.RenderedElementBase;
+ this.base(node);
+
+ this.baseSetContext = this.setContext;
+ this.setContext = function(ctx) {
+ this.baseSetContext(ctx);
+ if (this.attribute('x').hasValue()) ctx.translate(this.attribute('x').Length.toPixels('x'), 0);
+ if (this.attribute('y').hasValue()) ctx.translate(0, this.attribute('y').Length.toPixels('y'));
+ }
+
+ this.getDefinition = function() {
+ var element = this.attribute('xlink:href').Definition.getDefinition();
+ if (this.attribute('width').hasValue()) element.attribute('width', true).value = this.attribute('width').value;
+ if (this.attribute('height').hasValue()) element.attribute('height', true).value = this.attribute('height').value;
+ return element;
+ }
+
+ this.path = function(ctx) {
+ var element = this.getDefinition();
+ if (element != null) element.path(ctx);
+ }
+
+ this.renderChildren = function(ctx) {
+ var element = this.getDefinition();
+ if (element != null) element.render(ctx);
+ }
+ }
+ svg.Element.use.prototype = new svg.Element.RenderedElementBase;
+
+ // mask element
+ svg.Element.mask = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.apply = function(ctx, element) {
+ // render as temp svg
+ var x = this.attribute('x').Length.toPixels('x');
+ var y = this.attribute('y').Length.toPixels('y');
+ var width = this.attribute('width').Length.toPixels('x');
+ var height = this.attribute('height').Length.toPixels('y');
+
+ // temporarily remove mask to avoid recursion
+ var mask = element.attribute('mask').value;
+ element.attribute('mask').value = '';
+
+ var cMask = document.createElement('canvas');
+ cMask.width = x + width;
+ cMask.height = y + height;
+ var maskCtx = cMask.getContext('2d');
+ this.renderChildren(maskCtx);
+
+ var c = document.createElement('canvas');
+ c.width = x + width;
+ c.height = y + height;
+ var tempCtx = c.getContext('2d');
+ element.render(tempCtx);
+ tempCtx.globalCompositeOperation = 'destination-in';
+ tempCtx.fillStyle = maskCtx.createPattern(cMask, 'no-repeat');
+ tempCtx.fillRect(0, 0, x + width, y + height);
+
+ ctx.fillStyle = tempCtx.createPattern(c, 'no-repeat');
+ ctx.fillRect(0, 0, x + width, y + height);
+
+ // reassign mask
+ element.attribute('mask').value = mask;
+ }
+
+ this.render = function(ctx) {
+ // NO RENDER
+ }
+ }
+ svg.Element.mask.prototype = new svg.Element.ElementBase;
+
+ // clip element
+ svg.Element.clipPath = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.apply = function(ctx) {
+ for (var i=0; i<this.children.length; i++) {
+ if (this.children[i].path) {
+ this.children[i].path(ctx);
+ ctx.clip();
+ }
+ }
+ }
+
+ this.render = function(ctx) {
+ // NO RENDER
+ }
+ }
+ svg.Element.clipPath.prototype = new svg.Element.ElementBase;
+
+ // filters
+ svg.Element.filter = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ this.apply = function(ctx, element) {
+ // render as temp svg
+ var bb = element.getBoundingBox();
+ var x = this.attribute('x').Length.toPixels('x');
+ var y = this.attribute('y').Length.toPixels('y');
+ if (x == 0 || y == 0) {
+ x = bb.x1;
+ y = bb.y1;
+ }
+ var width = this.attribute('width').Length.toPixels('x');
+ var height = this.attribute('height').Length.toPixels('y');
+ if (width == 0 || height == 0) {
+ width = bb.width();
+ height = bb.height();
+ }
+
+ // temporarily remove filter to avoid recursion
+ var filter = element.style('filter').value;
+ element.style('filter').value = '';
+
+ // max filter distance
+ var extraPercent = .20;
+ var px = extraPercent * width;
+ var py = extraPercent * height;
+
+ var c = document.createElement('canvas');
+ c.width = width + 2*px;
+ c.height = height + 2*py;
+ var tempCtx = c.getContext('2d');
+ tempCtx.translate(-x + px, -y + py);
+ element.render(tempCtx);
+
+ // apply filters
+ for (var i=0; i<this.children.length; i++) {
+ this.children[i].apply(tempCtx, 0, 0, width + 2*px, height + 2*py);
+ }
+
+ // render on me
+ ctx.drawImage(c, 0, 0, width + 2*px, height + 2*py, x - px, y - py, width + 2*px, height + 2*py);
+
+ // reassign filter
+ element.style('filter', true).value = filter;
+ }
+
+ this.render = function(ctx) {
+ // NO RENDER
+ }
+ }
+ svg.Element.filter.prototype = new svg.Element.ElementBase;
+
+ svg.Element.feGaussianBlur = function(node) {
+ this.base = svg.Element.ElementBase;
+ this.base(node);
+
+ function make_fgauss(sigma) {
+ sigma = Math.max(sigma, 0.01);
+ var len = Math.ceil(sigma * 4.0) + 1;
+ mask = [];
+ for (var i = 0; i < len; i++) {
+ mask[i] = Math.exp(-0.5 * (i / sigma) * (i / sigma));
+ }
+ return mask;
+ }
+
+ function normalize(mask) {
+ var sum = 0;
+ for (var i = 1; i < mask.length; i++) {
+ sum += Math.abs(mask[i]);
+ }
+ sum = 2 * sum + Math.abs(mask[0]);
+ for (var i = 0; i < mask.length; i++) {
+ mask[i] /= sum;
+ }
+ return mask;
+ }
+
+ function convolve_even(src, dst, mask, width, height) {
+ for (var y = 0; y < height; y++) {
+ for (var x = 0; x < width; x++) {
+ var a = imGet(src, x, y, width, height, 3)/255;
+ for (var rgba = 0; rgba < 4; rgba++) {
+ var sum = mask[0] * (a==0?255:imGet(src, x, y, width, height, rgba)) * (a==0||rgba==3?1:a);
+ for (var i = 1; i < mask.length; i++) {
+ var a1 = imGet(src, Math.max(x-i,0), y, width, height, 3)/255;
+ var a2 = imGet(src, Math.min(x+i, width-1), y, width, height, 3)/255;
+ sum += mask[i] *
+ ((a1==0?255:imGet(src, Math.max(x-i,0), y, width, height, rgba)) * (a1==0||rgba==3?1:a1) +
+ (a2==0?255:imGet(src, Math.min(x+i, width-1), y, width, height, rgba)) * (a2==0||rgba==3?1:a2));
+ }
+ imSet(dst, y, x, height, width, rgba, sum);
+ }
+ }
+ }
+ }
+
+ function imGet(img, x, y, width, height, rgba) {
+ return img[y*width*4 + x*4 + rgba];
+ }
+
+ function imSet(img, x, y, width, height, rgba, val) {
+ img[y*width*4 + x*4 + rgba] = val;
+ }
+
+ function blur(ctx, width, height, sigma)
+ {
+ var srcData = ctx.getImageData(0, 0, width, height);
+ var mask = make_fgauss(sigma);
+ mask = normalize(mask);
+ tmp = [];
+ convolve_even(srcData.data, tmp, mask, width, height);
+ convolve_even(tmp, srcData.data, mask, height, width);
+ ctx.clearRect(0, 0, width, height);
+ ctx.putImageData(srcData, 0, 0);
+ }
+
+ this.apply = function(ctx, x, y, width, height) {
+ // assuming x==0 && y==0 for now
+ blur(ctx, width, height, this.attribute('stdDeviation').numValue());
+ }
+ }
+ svg.Element.filter.prototype = new svg.Element.feGaussianBlur;
+
+ // title element, do nothing
+ svg.Element.title = function(node) {
+ }
+ svg.Element.title.prototype = new svg.Element.ElementBase;
+
+ // desc element, do nothing
+ svg.Element.desc = function(node) {
+ }
+ svg.Element.desc.prototype = new svg.Element.ElementBase;
+
+ svg.Element.MISSING = function(node) {
+ console.log('ERROR: Element \'' + node.nodeName + '\' not yet implemented.');
+ }
+ svg.Element.MISSING.prototype = new svg.Element.ElementBase;
+
+ // element factory
+ svg.CreateElement = function(node) {
+ var className = node.nodeName.replace(/^[^:]+:/,''); // remove namespace
+ className = className.replace(/\-/g,''); // remove dashes
+ var e = null;
+ if (typeof(svg.Element[className]) != 'undefined') {
+ e = new svg.Element[className](node);
+ }
+ else {
+ e = new svg.Element.MISSING(node);
+ }
+
+ e.type = node.nodeName;
+ return e;
+ }
+
+ // load from url
+ svg.load = function(ctx, url) {
+ svg.loadXml(ctx, svg.ajax(url));
+ }
+
+ // load from xml
+ svg.loadXml = function(ctx, xml) {
+ svg.loadXmlDoc(ctx, svg.parseXml(xml));
+ }
+
+ svg.loadXmlDoc = function(ctx, dom) {
+ svg.init(ctx);
+
+ var mapXY = function(p) {
+ var e = ctx.canvas;
+ while (e) {
+ p.x -= e.offsetLeft;
+ p.y -= e.offsetTop;
+ e = e.offsetParent;
+ }
+ if (window.scrollX) p.x += window.scrollX;
+ if (window.scrollY) p.y += window.scrollY;
+ return p;
+ }
+
+ // bind mouse
+ if (svg.opts['ignoreMouse'] != true) {
+ ctx.canvas.onclick = function(e) {
+ var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
+ svg.Mouse.onclick(p.x, p.y);
+ };
+ ctx.canvas.onmousemove = function(e) {
+ var p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));
+ svg.Mouse.onmousemove(p.x, p.y);
+ };
+ }
+
+ var e = svg.CreateElement(dom.documentElement);
+ e.root = true;
+
+ // render loop
+ var isFirstRender = true;
+ var draw = function() {
+ svg.ViewPort.Clear();
+ if (ctx.canvas.parentNode) svg.ViewPort.SetCurrent(ctx.canvas.parentNode.clientWidth, ctx.canvas.parentNode.clientHeight);
+
+ if (svg.opts['ignoreDimensions'] != true) {
+ // set canvas size
+ if (e.style('width').hasValue()) {
+ ctx.canvas.width = e.style('width').Length.toPixels('x');
+ ctx.canvas.style.width = ctx.canvas.width + 'px';
+ }
+ if (e.style('height').hasValue()) {
+ ctx.canvas.height = e.style('height').Length.toPixels('y');
+ ctx.canvas.style.height = ctx.canvas.height + 'px';
+ }
+ }
+ var cWidth = ctx.canvas.clientWidth || ctx.canvas.width;
+ var cHeight = ctx.canvas.clientHeight || ctx.canvas.height;
+ svg.ViewPort.SetCurrent(cWidth, cHeight);
+
+ if (svg.opts != null && svg.opts['offsetX'] != null) e.attribute('x', true).value = svg.opts['offsetX'];
+ if (svg.opts != null && svg.opts['offsetY'] != null) e.attribute('y', true).value = svg.opts['offsetY'];
+ if (svg.opts != null && svg.opts['scaleWidth'] != null && svg.opts['scaleHeight'] != null) {
+ var xRatio = 1, yRatio = 1;
+ if (e.attribute('width').hasValue()) xRatio = e.attribute('width').Length.toPixels('x') / svg.opts['scaleWidth'];
+ if (e.attribute('height').hasValue()) yRatio = e.attribute('height').Length.toPixels('y') / svg.opts['scaleHeight'];
+
+ e.attribute('width', true).value = svg.opts['scaleWidth'];
+ e.attribute('height', true).value = svg.opts['scaleHeight'];
+ e.attribute('viewBox', true).value = '0 0 ' + (cWidth * xRatio) + ' ' + (cHeight * yRatio);
+ e.attribute('preserveAspectRatio', true).value = 'none';
+ }
+
+ // clear and render
+ if (svg.opts['ignoreClear'] != true) {
+ ctx.clearRect(0, 0, cWidth, cHeight);
+ }
+ e.render(ctx);
+ if (isFirstRender) {
+ isFirstRender = false;
+ if (svg.opts != null && typeof(svg.opts['renderCallback']) == 'function') svg.opts['renderCallback']();
+ }
+ }
+
+ var waitingForImages = true;
+ if (svg.ImagesLoaded()) {
+ waitingForImages = false;
+ draw();
+ }
+ svg.intervalID = setInterval(function() {
+ var needUpdate = false;
+
+ if (waitingForImages && svg.ImagesLoaded()) {
+ waitingForImages = false;
+ needUpdate = true;
+ }
+
+ // need update from mouse events?
+ if (svg.opts['ignoreMouse'] != true) {
+ needUpdate = needUpdate | svg.Mouse.hasEvents();
+ }
+
+ // need update from animations?
+ if (svg.opts['ignoreAnimation'] != true) {
+ for (var i=0; i<svg.Animations.length; i++) {
+ needUpdate = needUpdate | svg.Animations[i].update(1000 / svg.FRAMERATE);
+ }
+ }
+
+ // need update from redraw?
+ if (svg.opts != null && typeof(svg.opts['forceRedraw']) == 'function') {
+ if (svg.opts['forceRedraw']() == true) needUpdate = true;
+ }
+
+ // render if needed
+ if (needUpdate) {
+ draw();
+ svg.Mouse.runEvents(); // run and clear our events
+ }
+ }, 1000 / svg.FRAMERATE);
+ }
+
+ svg.stop = function() {
+ if (svg.intervalID) {
+ clearInterval(svg.intervalID);
+ }
+ }
+
+ svg.Mouse = new (function() {
+ this.events = [];
+ this.hasEvents = function() { return this.events.length != 0; }
+
+ this.onclick = function(x, y) {
+ this.events.push({ type: 'onclick', x: x, y: y,
+ run: function(e) { if (e.onclick) e.onclick(); }
+ });
+ }
+
+ this.onmousemove = function(x, y) {
+ this.events.push({ type: 'onmousemove', x: x, y: y,
+ run: function(e) { if (e.onmousemove) e.onmousemove(); }
+ });
+ }
+
+ this.eventElements = [];
+
+ this.checkPath = function(element, ctx) {
+ for (var i=0; i<this.events.length; i++) {
+ var e = this.events[i];
+ if (ctx.isPointInPath && ctx.isPointInPath(e.x, e.y)) this.eventElements[i] = element;
+ }
+ }
+
+ this.checkBoundingBox = function(element, bb) {
+ for (var i=0; i<this.events.length; i++) {
+ var e = this.events[i];
+ if (bb.isPointInBox(e.x, e.y)) this.eventElements[i] = element;
+ }
+ }
+
+ this.runEvents = function() {
+ svg.ctx.canvas.style.cursor = '';
+
+ for (var i=0; i<this.events.length; i++) {
+ var e = this.events[i];
+ var element = this.eventElements[i];
+ while (element) {
+ e.run(element);
+ element = element.parent;
+ }
+ }
+
+ // done running, clear
+ this.events = [];
+ this.eventElements = [];
+ }
+ });
+
+ return svg;
+ }
+})();
+
+if (CanvasRenderingContext2D) {
+ CanvasRenderingContext2D.prototype.drawSvg = function(s, dx, dy, dw, dh) {
+ canvg(this.canvas, s, {
+ ignoreMouse: true,
+ ignoreAnimation: true,
+ ignoreDimensions: true,
+ ignoreClear: true,
+ offsetX: dx,
+ offsetY: dy,
+ scaleWidth: dw,
+ scaleHeight: dh
+ });
+ }
+}/**
+ * @license Highcharts JS v4.0.4 (2014-09-02)
+ * CanVGRenderer Extension module
+ *
+ * (c) 2011-2012 Torstein Honsi, Erik Olsson
+ *
+ * License: www.highcharts.com/license
+ */
+
+// JSLint options:
+/*global Highcharts */
+
+(function (Highcharts) { // encapsulate
+ var UNDEFINED,
+ DIV = 'div',
+ ABSOLUTE = 'absolute',
+ RELATIVE = 'relative',
+ HIDDEN = 'hidden',
+ VISIBLE = 'visible',
+ PX = 'px',
+ css = Highcharts.css,
+ CanVGRenderer = Highcharts.CanVGRenderer,
+ SVGRenderer = Highcharts.SVGRenderer,
+ extend = Highcharts.extend,
+ merge = Highcharts.merge,
+ addEvent = Highcharts.addEvent,
+ createElement = Highcharts.createElement,
+ discardElement = Highcharts.discardElement;
+
+ // Extend CanVG renderer on demand, inherit from SVGRenderer
+ extend(CanVGRenderer.prototype, SVGRenderer.prototype);
+
+ // Add additional functionality:
+ extend(CanVGRenderer.prototype, {
+ create: function (chart, container, chartWidth, chartHeight) {
+ this.setContainer(container, chartWidth, chartHeight);
+ this.configure(chart);
+ },
+ setContainer: function (container, chartWidth, chartHeight) {
+ var containerStyle = container.style,
+ containerParent = container.parentNode,
+ containerLeft = containerStyle.left,
+ containerTop = containerStyle.top,
+ containerOffsetWidth = container.offsetWidth,
+ containerOffsetHeight = container.offsetHeight,
+ canvas,
+ initialHiddenStyle = { visibility: HIDDEN, position: ABSOLUTE };
+
+ this.init.apply(this, [container, chartWidth, chartHeight]);
+
+ // add the canvas above it
+ canvas = createElement('canvas', {
+ width: containerOffsetWidth,
+ height: containerOffsetHeight
+ }, {
+ position: RELATIVE,
+ left: containerLeft,
+ top: containerTop
+ }, container);
+ this.canvas = canvas;
+
+ // Create the tooltip line and div, they are placed as siblings to
+ // the container (and as direct childs to the div specified in the html page)
+ this.ttLine = createElement(DIV, null, initialHiddenStyle, containerParent);
+ this.ttDiv = createElement(DIV, null, initialHiddenStyle, containerParent);
+ this.ttTimer = UNDEFINED;
+
+ // Move away the svg node to a new div inside the container's parent so we can hide it.
+ var hiddenSvg = createElement(DIV, {
+ width: containerOffsetWidth,
+ height: containerOffsetHeight
+ }, {
+ visibility: HIDDEN,
+ left: containerLeft,
+ top: containerTop
+ }, containerParent);
+ this.hiddenSvg = hiddenSvg;
+ hiddenSvg.appendChild(this.box);
+ },
+
+ /**
+ * Configures the renderer with the chart. Attach a listener to the event tooltipRefresh.
+ **/
+ configure: function (chart) {
+ var renderer = this,
+ options = chart.options.tooltip,
+ borderWidth = options.borderWidth,
+ tooltipDiv = renderer.ttDiv,
+ tooltipDivStyle = options.style,
+ tooltipLine = renderer.ttLine,
+ padding = parseInt(tooltipDivStyle.padding, 10);
+
+ // Add border styling from options to the style
+ tooltipDivStyle = merge(tooltipDivStyle, {
+ padding: padding + PX,
+ 'background-color': options.backgroundColor,
+ 'border-style': 'solid',
+ 'border-width': borderWidth + PX,
+ 'border-radius': options.borderRadius + PX
+ });
+
+ // Optionally add shadow
+ if (options.shadow) {
+ tooltipDivStyle = merge(tooltipDivStyle, {
+ 'box-shadow': '1px 1px 3px gray', // w3c
+ '-webkit-box-shadow': '1px 1px 3px gray' // webkit
+ });
+ }
+ css(tooltipDiv, tooltipDivStyle);
+
+ // Set simple style on the line
+ css(tooltipLine, {
+ 'border-left': '1px solid darkgray'
+ });
+
+ // This event is triggered when a new tooltip should be shown
+ addEvent(chart, 'tooltipRefresh', function (args) {
+ var chartContainer = chart.container,
+ offsetLeft = chartContainer.offsetLeft,
+ offsetTop = chartContainer.offsetTop,
+ position;
+
+ // Set the content of the tooltip
+ tooltipDiv.innerHTML = args.text;
+
+ // Compute the best position for the tooltip based on the divs size and container size.
+ position = chart.tooltip.getPosition(tooltipDiv.offsetWidth, tooltipDiv.offsetHeight, {plotX: args.x, plotY: args.y});
+
+ css(tooltipDiv, {
+ visibility: VISIBLE,
+ left: position.x + PX,
+ top: position.y + PX,
+ 'border-color': args.borderColor
+ });
+
+ // Position the tooltip line
+ css(tooltipLine, {
+ visibility: VISIBLE,
+ left: offsetLeft + args.x + PX,
+ top: offsetTop + chart.plotTop + PX,
+ height: chart.plotHeight + PX
+ });
+
+ // This timeout hides the tooltip after 3 seconds
+ // First clear any existing timer
+ if (renderer.ttTimer !== UNDEFINED) {
+ clearTimeout(renderer.ttTimer);
+ }
+
+ // Start a new timer that hides tooltip and line
+ renderer.ttTimer = setTimeout(function () {
+ css(tooltipDiv, { visibility: HIDDEN });
+ css(tooltipLine, { visibility: HIDDEN });
+ }, 3000);
+ });
+ },
+
+ /**
+ * Extend SVGRenderer.destroy to also destroy the elements added by CanVGRenderer.
+ */
+ destroy: function () {
+ var renderer = this;
+
+ // Remove the canvas
+ discardElement(renderer.canvas);
+
+ // Kill the timer
+ if (renderer.ttTimer !== UNDEFINED) {
+ clearTimeout(renderer.ttTimer);
+ }
+
+ // Remove the divs for tooltip and line
+ discardElement(renderer.ttLine);
+ discardElement(renderer.ttDiv);
+ discardElement(renderer.hiddenSvg);
+
+ // Continue with base class
+ return SVGRenderer.prototype.destroy.apply(renderer);
+ },
+
+ /**
+ * Take a color and return it if it's a string, do not make it a gradient even if it is a
+ * gradient. Currently canvg cannot render gradients (turns out black),
+ * see: http://code.google.com/p/canvg/issues/detail?id=104
+ *
+ * @param {Object} color The color or config object
+ */
+ color: function (color, elem, prop) {
+ if (color && color.linearGradient) {
+ // Pick the end color and forward to base implementation
+ color = color.stops[color.stops.length - 1][1];
+ }
+ return SVGRenderer.prototype.color.call(this, color, elem, prop);
+ },
+
+ /**
+ * Draws the SVG on the canvas or adds a draw invokation to the deferred list.
+ */
+ draw: function () {
+ var renderer = this;
+ window.canvg(renderer.canvas, renderer.hiddenSvg.innerHTML);
+ }
+ });
+}(Highcharts));
diff --git a/html/includes/js/modules/data.js b/html/includes/js/modules/data.js
new file mode 100644
index 0000000..8a90423
--- /dev/null
+++ b/html/includes/js/modules/data.js
@@ -0,0 +1,24 @@
+/*
+ Data plugin for Highcharts
+
+ (c) 2012-2014 Torstein Honsi
+
+ License: www.highcharts.com/license
+*/
+(function(g){var j=g.each,q=HighchartsAdapter.inArray,s=g.splat,l,n=function(a,b){this.init(a,b)};g.extend(n.prototype,{init:function(a,b){this.options=a;this.chartOptions=b;this.columns=a.columns||this.rowsToColumns(a.rows)||[];this.rawColumns=[];this.columns.length?this.dataFound():(this.parseCSV(),this.parseTable(),this.parseGoogleSpreadsheet())},getColumnDistribution:function(){var a=this.chartOptions,b=this.options,c=[],f=function(a){return(g.seriesTypes[a||"line"].prototype.pointArrayMap||[0]).length},
+e=a&&a.chart&&a.chart.type,d=[],i=[],p,h;j(a&&a.series||[],function(a){d.push(f(a.type||e))});j(b&&b.seriesMapping||[],function(a){c.push(a.x||0)});c.length===0&&c.push(0);j(b&&b.seriesMapping||[],function(b){var c=new l,m,j=d[p]||f(e),o=g.seriesTypes[((a&&a.series||[])[p]||{}).type||e||"line"].prototype.pointArrayMap||["y"];c.addColumnReader(b.x,"x");for(m in b)b.hasOwnProperty(m)&&m!=="x"&&c.addColumnReader(b[m],m);for(h=0;h<j;h++)c.hasReader(o[h])||c.addColumnReader(void 0,o[h]);i.push(c);p++});
+b=g.seriesTypes[e||"line"].prototype.pointArrayMap;b===void 0&&(b=["y"]);this.valueCount={global:f(e),xColumns:c,individual:d,seriesBuilders:i,globalPointArrayMap:b}},dataFound:function(){if(this.options.switchRowsAndColumns)this.columns=this.rowsToColumns(this.columns);this.getColumnDistribution();this.parseTypes();this.findHeaderRow();this.parsed()!==!1&&this.complete()},parseCSV:function(){var a=this,b=this.options,c=b.csv,f=this.columns,e=b.startRow||0,d=b.endRow||Number.MAX_VALUE,i=b.startColumn||
+0,p=b.endColumn||Number.MAX_VALUE,h,g,r=0;c&&(g=c.replace(/\r\n/g,"\n").replace(/\r/g,"\n").split(b.lineDelimiter||"\n"),h=b.itemDelimiter||(c.indexOf("\t")!==-1?"\t":","),j(g,function(b,c){var g=a.trim(b),t=g.indexOf("#")===0;c>=e&&c<=d&&!t&&g!==""&&(g=b.split(h),j(g,function(a,b){b>=i&&b<=p&&(f[b-i]||(f[b-i]=[]),f[b-i][r]=a)}),r+=1)}),this.dataFound())},parseTable:function(){var a=this.options,b=a.table,c=this.columns,f=a.startRow||0,e=a.endRow||Number.MAX_VALUE,d=a.startColumn||0,i=a.endColumn||
+Number.MAX_VALUE;b&&(typeof b==="string"&&(b=document.getElementById(b)),j(b.getElementsByTagName("tr"),function(a,b){b>=f&&b<=e&&j(a.children,function(a,e){if((a.tagName==="TD"||a.tagName==="TH")&&e>=d&&e<=i)c[e-d]||(c[e-d]=[]),c[e-d][b-f]=a.innerHTML})}),this.dataFound())},parseGoogleSpreadsheet:function(){var a=this,b=this.options,c=b.googleSpreadsheetKey,f=this.columns,e=b.startRow||0,d=b.endRow||Number.MAX_VALUE,i=b.startColumn||0,g=b.endColumn||Number.MAX_VALUE,h,j;c&&jQuery.ajax({dataType:"json",
+url:"https://spreadsheets.google.com/feeds/cells/"+c+"/"+(b.googleSpreadsheetWorksheet||"od6")+"/public/values?alt=json-in-script&callback=?",error:b.error,success:function(b){var b=b.feed.entry,c,l=b.length,o=0,n=0,k;for(k=0;k<l;k++)c=b[k],o=Math.max(o,c.gs$cell.col),n=Math.max(n,c.gs$cell.row);for(k=0;k<o;k++)if(k>=i&&k<=g)f[k-i]=[],f[k-i].length=Math.min(n,d-e);for(k=0;k<l;k++)if(c=b[k],h=c.gs$cell.row-1,j=c.gs$cell.col-1,j>=i&&j<=g&&h>=e&&h<=d)f[j-i][h-e]=c.content.$t;a.dataFound()}})},findHeaderRow:function(){var a=
+0;j(this.columns,function(b){b.isNumeric&&typeof b[0]!=="string"&&(a=null)});this.headerRow=a},trim:function(a){return typeof a==="string"?a.replace(/^\s+|\s+$/g,""):a},parseTypes:function(){for(var a=this.columns,b=this.rawColumns,c=a.length,f,e,d,i,g,h,j=[],l,m=this.chartOptions;c--;){f=a[c].length;b[c]=[];for(l=(g=q(c,this.valueCount.xColumns)!==-1)&&m&&m.xAxis&&s(m.xAxis)[0].type==="category";f--;)if(e=j[f]||a[c][f],d=parseFloat(e),i=b[c][f]=this.trim(e),l)a[c][f]=i;else if(i==d)a[c][f]=d,d>31536E6?
+a[c].isDatetime=!0:a[c].isNumeric=!0;else if(d=this.parseDate(e),g&&typeof d==="number"&&!isNaN(d)){if(j[f]=e,a[c][f]=d,a[c].isDatetime=!0,a[c][f+1]!==void 0){e=d>a[c][f+1];if(e!==h&&h!==void 0)this.alternativeFormat?(this.dateFormat=this.alternativeFormat,f=a[c].length,this.alternativeFormat=this.dateFormats[this.dateFormat].alternative):a[c].unsorted=!0;h=e}}else if(a[c][f]=i===""?null:i,f!==0&&(a[c].isDatetime||a[c].isNumeric))a[c].mixed=!0;g&&a[c].mixed&&(a[c]=b[c])}if(a[0].isDatetime&&h){b=typeof a[0][0]!==
+"number";for(c=0;c<a.length;c++)a[c].reverse(),b&&a[c].unshift(a[c].pop())}},dateFormats:{"YYYY-mm-dd":{regex:/^([0-9]{4})[\-\/\.]([0-9]{2})[\-\/\.]([0-9]{2})$/,parser:function(a){return Date.UTC(+a[1],a[2]-1,+a[3])}},"dd/mm/YYYY":{regex:/^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/,parser:function(a){return Date.UTC(+a[3],a[2]-1,+a[1])},alternative:"mm/dd/YYYY"},"mm/dd/YYYY":{regex:/^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/,parser:function(a){return Date.UTC(+a[3],a[1]-1,+a[2])}},
+"dd/mm/YY":{regex:/^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/,parser:function(a){return Date.UTC(+a[3]+2E3,a[2]-1,+a[1])},alternative:"mm/dd/YY"},"mm/dd/YY":{regex:/^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/,parser:function(a){console.log(a);return Date.UTC(+a[3]+2E3,a[1]-1,+a[2])}}},parseDate:function(a){var b=this.options.parseDate,c,f,e=this.options.dateFormat||this.dateFormat,d;b&&(c=b(a));if(typeof a==="string"){if(e)b=this.dateFormats[e],(d=a.match(b.regex))&&(c=b.parser(d));
+else for(f in this.dateFormats)if(b=this.dateFormats[f],d=a.match(b.regex)){this.dateFormat=f;this.alternativeFormat=b.alternative;c=b.parser(d);break}d||(d=Date.parse(a),typeof d==="object"&&d!==null&&d.getTime?c=d.getTime()-d.getTimezoneOffset()*6E4:typeof d==="number"&&!isNaN(d)&&(c=d-(new Date(d)).getTimezoneOffset()*6E4))}return c},rowsToColumns:function(a){var b,c,f,e,d;if(a){d=[];c=a.length;for(b=0;b<c;b++){e=a[b].length;for(f=0;f<e;f++)d[f]||(d[f]=[]),d[f][b]=a[b][f]}}return d},parsed:function(){if(this.options.parsed)return this.options.parsed.call(this,
+this.columns)},getFreeIndexes:function(a,b){var c,f,e=[],d=[],i;for(f=0;f<a;f+=1)e.push(!0);for(c=0;c<b.length;c+=1){i=b[c].getReferencedColumnIndexes();for(f=0;f<i.length;f+=1)e[i[f]]=!1}for(f=0;f<e.length;f+=1)e[f]&&d.push(f);return d},complete:function(){var a=this.columns,b,c=this.options,f,e,d,i,g=[],h;if(c.complete||c.afterComplete){for(d=0;d<a.length;d++)if(this.headerRow===0)a[d].name=a[d].shift();f=[];e=this.getFreeIndexes(a.length,this.valueCount.seriesBuilders);for(d=0;d<this.valueCount.seriesBuilders.length;d++)h=
+this.valueCount.seriesBuilders[d],h.populateColumns(e)&&g.push(h);for(;e.length>0;){h=new l;h.addColumnReader(0,"x");d=q(0,e);d!==-1&&e.splice(d,1);for(d=0;d<this.valueCount.global;d++)h.addColumnReader(void 0,this.valueCount.globalPointArrayMap[d]);h.populateColumns(e)&&g.push(h)}g.length>0&&g[0].readers.length>0&&(h=a[g[0].readers[0].columnIndex],h!==void 0&&(h.isDatetime?b="datetime":h.isNumeric||(b="category")));if(b==="category")for(d=0;d<g.length;d++){h=g[d];for(e=0;e<h.readers.length;e++)if(h.readers[e].configName===
+"x")h.readers[e].configName="name"}for(d=0;d<g.length;d++){h=g[d];e=[];for(i=0;i<a[0].length;i++)e[i]=h.read(a,i);f[d]={data:e};if(h.name)f[d].name=h.name}a={xAxis:{type:b},series:f};c.complete&&c.complete(a);c.afterComplete&&c.afterComplete(a)}}});g.Data=n;g.data=function(a,b){return new n(a,b)};g.wrap(g.Chart.prototype,"init",function(a,b,c){var f=this;b&&b.data?g.data(g.extend(b.data,{afterComplete:function(e){var d,i;if(b.hasOwnProperty("series"))if(typeof b.series==="object")for(d=Math.max(b.series.length,
+e.series.length);d--;)i=b.series[d]||{},b.series[d]=g.merge(i,e.series[d]);else delete b.series;b=g.merge(e,b);a.call(f,b,c)}}),b):a.call(f,b,c)});l=function(){this.readers=[];this.pointIsArray=!0};l.prototype.populateColumns=function(a){var b=!0;j(this.readers,function(b){if(b.columnIndex===void 0)b.columnIndex=a.shift()});j(this.readers,function(a){a.columnIndex===void 0&&(b=!1)});return b};l.prototype.read=function(a,b){var c=this.pointIsArray,f=c?[]:{},e;j(this.readers,function(d){var e=a[d.columnIndex][b];
+c?f.push(e):f[d.configName]=e});if(this.name===void 0&&this.readers.length>=2&&(e=this.getReferencedColumnIndexes(),e.length>=2))e.shift(),e.sort(),this.name=a[e.shift()].name;return f};l.prototype.addColumnReader=function(a,b){this.readers.push({columnIndex:a,configName:b});if(!(b==="x"||b==="y"||b===void 0))this.pointIsArray=!1};l.prototype.getReferencedColumnIndexes=function(){var a,b=[],c;for(a=0;a<this.readers.length;a+=1)c=this.readers[a],c.columnIndex!==void 0&&b.push(c.columnIndex);return b};
+l.prototype.hasReader=function(a){var b,c;for(b=0;b<this.readers.length;b+=1)if(c=this.readers[b],c.configName===a)return!0}})(Highcharts);
diff --git a/html/includes/js/modules/data.src.js b/html/includes/js/modules/data.src.js
new file mode 100644
index 0000000..471307f
--- /dev/null
+++ b/html/includes/js/modules/data.src.js
@@ -0,0 +1,1025 @@
+/**
+ * @license Data plugin for Highcharts
+ *
+ * (c) 2012-2014 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+/*
+ * The Highcharts Data plugin is a utility to ease parsing of input sources like
+ * CSV, HTML tables or grid views into basic configuration options for use
+ * directly in the Highcharts constructor.
+ *
+ * Demo: http://jsfiddle.net/highcharts/SnLFj/
+ *
+ * --- OPTIONS ---
+ *
+ * - columns : Array<Array<Mixed>>
+ * A two-dimensional array representing the input data on tabular form. This input can
+ * be used when the data is already parsed, for example from a grid view component.
+ * Each cell can be a string or number. If not switchRowsAndColumns is set, the columns
+ * are interpreted as series. See also the rows option.
+ *
+ * - complete : Function(chartOptions)
+ * The callback that is evaluated when the data is finished loading, optionally from an
+ * external source, and parsed. The first argument passed is a finished chart options
+ * object, containing the series. Thise options
+ * can be extended with additional options and passed directly to the chart constructor. This is
+ * related to the parsed callback, that goes in at an earlier stage.
+ *
+ * - csv : String
+ * A comma delimited string to be parsed. Related options are startRow, endRow, startColumn
+ * and endColumn to delimit what part of the table is used. The lineDelimiter and
+ * itemDelimiter options define the CSV delimiter formats.
+ *
+ * - dateFormat: String
+ * Which of the predefined date formats in Date.prototype.dateFormats to use to parse date
+ * columns, for example "dd/mm/YYYY" or "YYYY-mm-dd". Defaults to a best guess based on
+ * what format gives valid dates, and prefers ordered dates.
+ *
+ * - endColumn : Integer
+ * In tabular input data, the first row (indexed by 0) to use. Defaults to the last
+ * column containing data.
+ *
+ * - endRow : Integer
+ * In tabular input data, the last row (indexed by 0) to use. Defaults to the last row
+ * containing data.
+ *
+ * - googleSpreadsheetKey : String
+ * A Google Spreadsheet key. See https://developers.google.com/gdata/samples/spreadsheet_sample
+ * for general information on GS.
+ *
+ * - googleSpreadsheetWorksheet : String
+ * The Google Spreadsheet worksheet. The available id's can be read from
+ * https://spreadsheets.google.com/feeds/worksheets/{key}/public/basic
+ *
+ * - itemDelimiter : String
+ * Item or cell delimiter for parsing CSV. Defaults to the tab character "\t" if a tab character
+ * is found in the CSV string, if not it defaults to ",".
+ *
+ * - lineDelimiter : String
+ * Line delimiter for parsing CSV. Defaults to "\n".
+ *
+ * - parsed : Function
+ * A callback function to access the parsed columns, the two-dimentional input data
+ * array directly, before they are interpreted into series data and categories. See also
+ * the complete callback, that goes in on a later stage where the raw columns are interpreted
+ * into a Highcharts option structure. Return false to stop completion, or call this.complete()
+ * to continue async.
+ *
+ * - parseDate : Function
+ * A callback function to parse string representations of dates into JavaScript timestamps.
+ * Return an integer on success.
+ *
+ * - rows : Array<Array<Mixed>>
+ * The same as the columns input option, but defining rows intead of columns.
+ *
+ * - seriesMapping : Array<Object>
+ * An array containing object with Point property names along with what column id the
+ * property should be taken from.
+ *
+ * - startColumn : Integer
+ * In tabular input data, the first column (indexed by 0) to use.
+ *
+ * - startRow : Integer
+ * In tabular input data, the first row (indexed by 0) to use.
+ *
+ * - switchRowsAndColumns : Boolean
+ * Switch rows and columns of the input data, so that this.columns effectively becomes the
+ * rows of the data set, and the rows are interpreted as series.
+ *
+ * - table : String|HTMLElement
+ * A HTML table or the id of such to be parsed as input data. Related options ara startRow,
+ * endRow, startColumn and endColumn to delimit what part of the table is used.
+ */
+
+/*
+ * TODO:
+ * - Handle various date formats
+ * - http://jsfiddle.net/highcharts/114wejdx/
+ * - http://jsfiddle.net/highcharts/ryv67bkq/
+ */
+
+// JSLint options:
+/*global jQuery, HighchartsAdapter */
+
+(function (Highcharts) { // docs
+
+ // Utilities
+ var each = Highcharts.each,
+ inArray = HighchartsAdapter.inArray,
+ splat = Highcharts.splat,
+ SeriesBuilder;
+
+
+ // The Data constructor
+ var Data = function (dataOptions, chartOptions) {
+ this.init(dataOptions, chartOptions);
+ };
+
+ // Set the prototype properties
+ Highcharts.extend(Data.prototype, {
+
+ /**
+ * Initialize the Data object with the given options
+ */
+ init: function (options, chartOptions) {
+ this.options = options;
+ this.chartOptions = chartOptions;
+ this.columns = options.columns || this.rowsToColumns(options.rows) || [];
+
+ // This is a two-dimensional array holding the raw, trimmed string values
+ // with the same organisation as the columns array. It makes it possible
+ // for example to revert from interpreted timestamps to string-based
+ // categories.
+ this.rawColumns = [];
+
+ // No need to parse or interpret anything
+ if (this.columns.length) {
+ this.dataFound();
+
+ // Parse and interpret
+ } else {
+
+ // Parse a CSV string if options.csv is given
+ this.parseCSV();
+
+ // Parse a HTML table if options.table is given
+ this.parseTable();
+
+ // Parse a Google Spreadsheet
+ this.parseGoogleSpreadsheet();
+ }
+
+ },
+
+ /**
+ * Get the column distribution. For example, a line series takes a single column for
+ * Y values. A range series takes two columns for low and high values respectively,
+ * and an OHLC series takes four columns.
+ */
+ getColumnDistribution: function () {
+ var chartOptions = this.chartOptions,
+ options = this.options,
+ xColumns = [],
+ getValueCount = function (type) {
+ return (Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap || [0]).length;
+ },
+ getPointArrayMap = function (type) {
+ return Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap;
+ },
+ globalType = chartOptions && chartOptions.chart && chartOptions.chart.type,
+ individualCounts = [],
+ seriesBuilders = [],
+ seriesIndex,
+ i;
+
+ each((chartOptions && chartOptions.series) || [], function (series) {
+ individualCounts.push(getValueCount(series.type || globalType));
+ });
+
+ // Collect the x-column indexes from seriesMapping
+ each((options && options.seriesMapping) || [], function (mapping) {
+ xColumns.push(mapping.x || 0);
+ });
+
+ // If there are no defined series with x-columns, use the first column as x column
+ if (xColumns.length === 0) {
+ xColumns.push(0);
+ }
+
+ // Loop all seriesMappings and constructs SeriesBuilders from
+ // the mapping options.
+ each((options && options.seriesMapping) || [], function (mapping) {
+ var builder = new SeriesBuilder(),
+ name,
+ numberOfValueColumnsNeeded = individualCounts[seriesIndex] || getValueCount(globalType),
+ seriesArr = (chartOptions && chartOptions.series) || [],
+ series = seriesArr[seriesIndex] || {},
+ pointArrayMap = getPointArrayMap(series.type || globalType) || ['y'];
+
+ // Add an x reader from the x property or from an undefined column
+ // if the property is not set. It will then be auto populated later.
+ builder.addColumnReader(mapping.x, 'x');
+
+ // Add all column mappings
+ for (name in mapping) {
+ if (mapping.hasOwnProperty(name) && name !== 'x') {
+ builder.addColumnReader(mapping[name], name);
+ }
+ }
+
+ // Add missing columns
+ for (i = 0; i < numberOfValueColumnsNeeded; i++) {
+ if (!builder.hasReader(pointArrayMap[i])) {
+ //builder.addNextColumnReader(pointArrayMap[i]);
+ // Create and add a column reader for the next free column index
+ builder.addColumnReader(undefined, pointArrayMap[i]);
+ }
+ }
+
+ seriesBuilders.push(builder);
+ seriesIndex++;
+ });
+
+ var globalPointArrayMap = getPointArrayMap(globalType);
+ if (globalPointArrayMap === undefined) {
+ globalPointArrayMap = ['y'];
+ }
+
+ this.valueCount = {
+ global: getValueCount(globalType),
+ xColumns: xColumns,
+ individual: individualCounts,
+ seriesBuilders: seriesBuilders,
+ globalPointArrayMap: globalPointArrayMap
+ };
+ },
+
+ /**
+ * When the data is parsed into columns, either by CSV, table, GS or direct input,
+ * continue with other operations.
+ */
+ dataFound: function () {
+
+ if (this.options.switchRowsAndColumns) {
+ this.columns = this.rowsToColumns(this.columns);
+ }
+
+ // Interpret the info about series and columns
+ this.getColumnDistribution();
+
+ // Interpret the values into right types
+ this.parseTypes();
+
+ // Use first row for series names?
+ this.findHeaderRow();
+
+ // Handle columns if a handleColumns callback is given
+ if (this.parsed() !== false) {
+
+ // Complete if a complete callback is given
+ this.complete();
+ }
+
+ },
+
+ /**
+ * Parse a CSV input string
+ */
+ parseCSV: function () {
+ var self = this,
+ options = this.options,
+ csv = options.csv,
+ columns = this.columns,
+ startRow = options.startRow || 0,
+ endRow = options.endRow || Number.MAX_VALUE,
+ startColumn = options.startColumn || 0,
+ endColumn = options.endColumn || Number.MAX_VALUE,
+ itemDelimiter,
+ lines,
+ activeRowNo = 0;
+
+ if (csv) {
+
+ lines = csv
+ .replace(/\r\n/g, "\n") // Unix
+ .replace(/\r/g, "\n") // Mac
+ .split(options.lineDelimiter || "\n");
+
+ itemDelimiter = options.itemDelimiter || (csv.indexOf('\t') !== -1 ? '\t' : ',');
+
+ each(lines, function (line, rowNo) {
+ var trimmed = self.trim(line),
+ isComment = trimmed.indexOf('#') === 0,
+ isBlank = trimmed === '',
+ items;
+
+ if (rowNo >= startRow && rowNo <= endRow && !isComment && !isBlank) {
+ items = line.split(itemDelimiter);
+ each(items, function (item, colNo) {
+ if (colNo >= startColumn && colNo <= endColumn) {
+ if (!columns[colNo - startColumn]) {
+ columns[colNo - startColumn] = [];
+ }
+
+ columns[colNo - startColumn][activeRowNo] = item;
+ }
+ });
+ activeRowNo += 1;
+ }
+ });
+
+ this.dataFound();
+ }
+ },
+
+ /**
+ * Parse a HTML table
+ */
+ parseTable: function () {
+ var options = this.options,
+ table = options.table,
+ columns = this.columns,
+ startRow = options.startRow || 0,
+ endRow = options.endRow || Number.MAX_VALUE,
+ startColumn = options.startColumn || 0,
+ endColumn = options.endColumn || Number.MAX_VALUE;
+
+ if (table) {
+
+ if (typeof table === 'string') {
+ table = document.getElementById(table);
+ }
+
+ each(table.getElementsByTagName('tr'), function (tr, rowNo) {
+ if (rowNo >= startRow && rowNo <= endRow) {
+ each(tr.children, function (item, colNo) {
+ if ((item.tagName === 'TD' || item.tagName === 'TH') && colNo >= startColumn && colNo <= endColumn) {
+ if (!columns[colNo - startColumn]) {
+ columns[colNo - startColumn] = [];
+ }
+
+ columns[colNo - startColumn][rowNo - startRow] = item.innerHTML;
+ }
+ });
+ }
+ });
+
+ this.dataFound(); // continue
+ }
+ },
+
+ /**
+ */
+ parseGoogleSpreadsheet: function () {
+ var self = this,
+ options = this.options,
+ googleSpreadsheetKey = options.googleSpreadsheetKey,
+ columns = this.columns,
+ startRow = options.startRow || 0,
+ endRow = options.endRow || Number.MAX_VALUE,
+ startColumn = options.startColumn || 0,
+ endColumn = options.endColumn || Number.MAX_VALUE,
+ gr, // google row
+ gc; // google column
+
+ if (googleSpreadsheetKey) {
+ jQuery.ajax({
+ dataType: 'json',
+ url: 'https://spreadsheets.google.com/feeds/cells/' +
+ googleSpreadsheetKey + '/' + (options.googleSpreadsheetWorksheet || 'od6') +
+ '/public/values?alt=json-in-script&callback=?',
+ error: options.error,
+ success: function (json) {
+ // Prepare the data from the spreadsheat
+ var cells = json.feed.entry,
+ cell,
+ cellCount = cells.length,
+ colCount = 0,
+ rowCount = 0,
+ i;
+
+ // First, find the total number of columns and rows that
+ // are actually filled with data
+ for (i = 0; i < cellCount; i++) {
+ cell = cells[i];
+ colCount = Math.max(colCount, cell.gs$cell.col);
+ rowCount = Math.max(rowCount, cell.gs$cell.row);
+ }
+
+ // Set up arrays containing the column data
+ for (i = 0; i < colCount; i++) {
+ if (i >= startColumn && i <= endColumn) {
+ // Create new columns with the length of either end-start or rowCount
+ columns[i - startColumn] = [];
+
+ // Setting the length to avoid jslint warning
+ columns[i - startColumn].length = Math.min(rowCount, endRow - startRow);
+ }
+ }
+
+ // Loop over the cells and assign the value to the right
+ // place in the column arrays
+ for (i = 0; i < cellCount; i++) {
+ cell = cells[i];
+ gr = cell.gs$cell.row - 1; // rows start at 1
+ gc = cell.gs$cell.col - 1; // columns start at 1
+
+ // If both row and col falls inside start and end
+ // set the transposed cell value in the newly created columns
+ if (gc >= startColumn && gc <= endColumn &&
+ gr >= startRow && gr <= endRow) {
+ columns[gc - startColumn][gr - startRow] = cell.content.$t;
+ }
+ }
+ self.dataFound();
+ }
+ });
+ }
+ },
+
+ /**
+ * Find the header row. For now, we just check whether the first row contains
+ * numbers or strings. Later we could loop down and find the first row with
+ * numbers.
+ */
+ findHeaderRow: function () {
+ var headerRow = 0;
+ each(this.columns, function (column) {
+ if (column.isNumeric && typeof column[0] !== 'string') {
+ headerRow = null;
+ }
+ });
+ this.headerRow = headerRow;
+ },
+
+ /**
+ * Trim a string from whitespace
+ */
+ trim: function (str) {
+ return typeof str === 'string' ? str.replace(/^\s+|\s+$/g, '') : str;
+ },
+
+ /**
+ * Parse numeric cells in to number types and date types in to true dates.
+ */
+ parseTypes: function () {
+ var columns = this.columns,
+ rawColumns = this.rawColumns,
+ col = columns.length,
+ row,
+ val,
+ floatVal,
+ trimVal,
+ isXColumn,
+ dateVal,
+ descending,
+ backup = [],
+ diff,
+ hasHeaderRow,
+ forceCategory,
+ chartOptions = this.chartOptions;
+
+ while (col--) {
+ row = columns[col].length;
+ rawColumns[col] = [];
+ isXColumn = inArray(col, this.valueCount.xColumns) !== -1;
+ forceCategory = isXColumn && chartOptions && chartOptions.xAxis && splat(chartOptions.xAxis)[0].type === 'category';
+ while (row--) {
+ val = backup[row] || columns[col][row];
+ floatVal = parseFloat(val);
+ trimVal = rawColumns[col][row] = this.trim(val);
+
+ // Disable number or date parsing by setting the X axis type to category
+ if (forceCategory) {
+ columns[col][row] = trimVal;
+
+ /*jslint eqeq: true*/
+ } else if (trimVal == floatVal) { // is numeric
+ /*jslint eqeq: false*/
+ columns[col][row] = floatVal;
+
+ // If the number is greater than milliseconds in a year, assume datetime
+ if (floatVal > 365 * 24 * 3600 * 1000) {
+ columns[col].isDatetime = true;
+ } else {
+ columns[col].isNumeric = true;
+ }
+
+ } else { // string, continue to determine if it is a date string or really a string
+ dateVal = this.parseDate(val);
+ // Only allow parsing of dates if this column is an x-column
+ if (isXColumn && typeof dateVal === 'number' && !isNaN(dateVal)) { // is date
+ backup[row] = val;
+ columns[col][row] = dateVal;
+ columns[col].isDatetime = true;
+
+ // Check if the dates are uniformly descending or ascending. If they
+ // are not, chances are that they are a different time format, so check
+ // for alternative.
+ if (columns[col][row + 1] !== undefined) {
+ diff = dateVal > columns[col][row + 1];
+ if (diff !== descending && descending !== undefined) {
+ if (this.alternativeFormat) {
+ this.dateFormat = this.alternativeFormat;
+ row = columns[col].length;
+ this.alternativeFormat = this.dateFormats[this.dateFormat].alternative;
+ } else {
+ columns[col].unsorted = true;
+ }
+ }
+ descending = diff;
+ }
+
+ } else { // string
+ columns[col][row] = trimVal === '' ? null : trimVal;
+ if (row !== 0 && (columns[col].isDatetime || columns[col].isNumeric)) {
+ columns[col].mixed = true;
+ }
+ }
+ }
+ }
+
+ // If strings are intermixed with numbers or dates in a parsed column, it is an indication
+ // that parsing went wrong or the data was not intended to display as numbers or dates and
+ // parsing is too aggressive. Fall back to categories. Demonstrated in the
+ // highcharts/demo/column-drilldown sample.
+ if (isXColumn && columns[col].mixed) {
+ columns[col] = rawColumns[col];
+ }
+ }
+
+ // If the 0 column is date and descending, reverse all columns.
+ // TODO: probably this should apply to xColumns, not 0 column alone.
+ if (columns[0].isDatetime && descending) {
+ hasHeaderRow = typeof columns[0][0] !== 'number';
+ for (col = 0; col < columns.length; col++) {
+ columns[col].reverse();
+ if (hasHeaderRow) {
+ columns[col].unshift(columns[col].pop());
+ }
+ }
+ }
+ },
+
+ /**
+ * A collection of available date formats, extendable from the outside to support
+ * custom date formats.
+ */
+ dateFormats: {
+ 'YYYY-mm-dd': {
+ regex: /^([0-9]{4})[\-\/\.]([0-9]{2})[\-\/\.]([0-9]{2})$/,
+ parser: function (match) {
+ return Date.UTC(+match[1], match[2] - 1, +match[3]);
+ }
+ },
+ 'dd/mm/YYYY': {
+ regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/,
+ parser: function (match) {
+ return Date.UTC(+match[3], match[2] - 1, +match[1]);
+ },
+ alternative: 'mm/dd/YYYY' // different format with the same regex
+ },
+ 'mm/dd/YYYY': {
+ regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{4})$/,
+ parser: function (match) {
+ return Date.UTC(+match[3], match[1] - 1, +match[2]);
+ }
+ },
+ 'dd/mm/YY': {
+ regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/,
+ parser: function (match) {
+ return Date.UTC(+match[3] + 2000, match[2] - 1, +match[1]);
+ },
+ alternative: 'mm/dd/YY' // different format with the same regex
+ },
+ 'mm/dd/YY': {
+ regex: /^([0-9]{1,2})[\-\/\.]([0-9]{1,2})[\-\/\.]([0-9]{2})$/,
+ parser: function (match) {
+ console.log(match)
+ return Date.UTC(+match[3] + 2000, match[1] - 1, +match[2]);
+ }
+ }
+ },
+
+ /**
+ * Parse a date and return it as a number. Overridable through options.parseDate.
+ */
+ parseDate: function (val) {
+ var parseDate = this.options.parseDate,
+ ret,
+ key,
+ format,
+ dateFormat = this.options.dateFormat || this.dateFormat,
+ match;
+
+ if (parseDate) {
+ ret = parseDate(val);
+ }
+
+ if (typeof val === 'string') {
+ // Auto-detect the date format the first time
+ if (!dateFormat) {
+ for (key in this.dateFormats) {
+ format = this.dateFormats[key];
+ match = val.match(format.regex);
+ if (match) {
+ this.dateFormat = dateFormat = key;
+ this.alternativeFormat = format.alternative;
+ ret = format.parser(match);
+ break;
+ }
+ }
+ // Next time, use the one previously found
+ } else {
+ format = this.dateFormats[dateFormat];
+ match = val.match(format.regex);
+ if (match) {
+ ret = format.parser(match);
+ }
+ }
+ // Fall back to Date.parse
+ if (!match) {
+ match = Date.parse(val);
+ // External tools like Date.js and MooTools extend Date object and
+ // returns a date.
+ if (typeof match === 'object' && match !== null && match.getTime) {
+ ret = match.getTime() - match.getTimezoneOffset() * 60000;
+
+ // Timestamp
+ } else if (typeof match === 'number' && !isNaN(match)) {
+ ret = match - (new Date(match)).getTimezoneOffset() * 60000;
+ }
+ }
+ }
+ return ret;
+ },
+
+ /**
+ * Reorganize rows into columns
+ */
+ rowsToColumns: function (rows) {
+ var row,
+ rowsLength,
+ col,
+ colsLength,
+ columns;
+
+ if (rows) {
+ columns = [];
+ rowsLength = rows.length;
+ for (row = 0; row < rowsLength; row++) {
+ colsLength = rows[row].length;
+ for (col = 0; col < colsLength; col++) {
+ if (!columns[col]) {
+ columns[col] = [];
+ }
+ columns[col][row] = rows[row][col];
+ }
+ }
+ }
+ return columns;
+ },
+
+ /**
+ * A hook for working directly on the parsed columns
+ */
+ parsed: function () {
+ if (this.options.parsed) {
+ return this.options.parsed.call(this, this.columns);
+ }
+ },
+
+ getFreeIndexes: function (numberOfColumns, seriesBuilders) {
+ var s,
+ i,
+ freeIndexes = [],
+ freeIndexValues = [],
+ referencedIndexes;
+
+ // Add all columns as free
+ for (i = 0; i < numberOfColumns; i = i + 1) {
+ freeIndexes.push(true);
+ }
+
+ // Loop all defined builders and remove their referenced columns
+ for (s = 0; s < seriesBuilders.length; s = s + 1) {
+ referencedIndexes = seriesBuilders[s].getReferencedColumnIndexes();
+
+ for (i = 0; i < referencedIndexes.length; i = i + 1) {
+ freeIndexes[referencedIndexes[i]] = false;
+ }
+ }
+
+ // Collect the values for the free indexes
+ for (i = 0; i < freeIndexes.length; i = i + 1) {
+ if (freeIndexes[i]) {
+ freeIndexValues.push(i);
+ }
+ }
+
+ return freeIndexValues;
+ },
+
+ /**
+ * If a complete callback function is provided in the options, interpret the
+ * columns into a Highcharts options object.
+ */
+ complete: function () {
+
+ var columns = this.columns,
+ xColumns = [],
+ type,
+ options = this.options,
+ series,
+ data,
+ i,
+ j,
+ r,
+ seriesIndex,
+ chartOptions,
+ allSeriesBuilders = [],
+ builder,
+ freeIndexes,
+ typeCol,
+ index;
+
+ xColumns.length = columns.length;
+ if (options.complete || options.afterComplete) {
+
+ // Get the names and shift the top row
+ for (i = 0; i < columns.length; i++) {
+ if (this.headerRow === 0) {
+ columns[i].name = columns[i].shift();
+ }
+ }
+
+ // Use the next columns for series
+ series = [];
+ freeIndexes = this.getFreeIndexes(columns.length, this.valueCount.seriesBuilders);
+
+ // Populate defined series
+ for (seriesIndex = 0; seriesIndex < this.valueCount.seriesBuilders.length; seriesIndex++) {
+ builder = this.valueCount.seriesBuilders[seriesIndex];
+
+ // If the builder can be populated with remaining columns, then add it to allBuilders
+ if (builder.populateColumns(freeIndexes)) {
+ allSeriesBuilders.push(builder);
+ }
+ }
+
+ // Populate dynamic series
+ while (freeIndexes.length > 0) {
+ builder = new SeriesBuilder();
+ builder.addColumnReader(0, 'x');
+
+ // Mark index as used (not free)
+ index = inArray(0, freeIndexes);
+ if (index !== -1) {
+ freeIndexes.splice(index, 1);
+ }
+
+ for (i = 0; i < this.valueCount.global; i++) {
+ // Create and add a column reader for the next free column index
+ builder.addColumnReader(undefined, this.valueCount.globalPointArrayMap[i]);
+ }
+
+ // If the builder can be populated with remaining columns, then add it to allBuilders
+ if (builder.populateColumns(freeIndexes)) {
+ allSeriesBuilders.push(builder);
+ }
+ }
+
+ // Get the data-type from the first series x column
+ if (allSeriesBuilders.length > 0 && allSeriesBuilders[0].readers.length > 0) {
+ typeCol = columns[allSeriesBuilders[0].readers[0].columnIndex];
+ if (typeCol !== undefined) {
+ if (typeCol.isDatetime) {
+ type = 'datetime';
+ } else if (!typeCol.isNumeric) {
+ type = 'category';
+ }
+ }
+ }
+ // Axis type is category, then the "x" column should be called "name"
+ if (type === 'category') {
+ for (seriesIndex = 0; seriesIndex < allSeriesBuilders.length; seriesIndex++) {
+ builder = allSeriesBuilders[seriesIndex];
+ for (r = 0; r < builder.readers.length; r++) {
+ if (builder.readers[r].configName === 'x') {
+ builder.readers[r].configName = 'name';
+ }
+ }
+ }
+ }
+
+ // Read data for all builders
+ for (seriesIndex = 0; seriesIndex < allSeriesBuilders.length; seriesIndex++) {
+ builder = allSeriesBuilders[seriesIndex];
+
+ // Iterate down the cells of each column and add data to the series
+ data = [];
+ for (j = 0; j < columns[0].length; j++) { // TODO: which column's length should we use here
+ data[j] = builder.read(columns, j);
+ }
+
+ // Add the series
+ series[seriesIndex] = {
+ data: data
+ };
+ if (builder.name) {
+ series[seriesIndex].name = builder.name;
+ }
+ }
+
+
+
+ // Do the callback
+ chartOptions = {
+ xAxis: {
+ type: type
+ },
+ series: series
+ };
+ if (options.complete) {
+ options.complete(chartOptions);
+ }
+
+ // The afterComplete hook is used internally to avoid conflict with the externally
+ // available complete option.
+ if (options.afterComplete) {
+ options.afterComplete(chartOptions);
+ }
+ }
+ }
+ });
+
+ // Register the Data prototype and data function on Highcharts
+ Highcharts.Data = Data;
+ Highcharts.data = function (options, chartOptions) {
+ return new Data(options, chartOptions);
+ };
+
+ // Extend Chart.init so that the Chart constructor accepts a new configuration
+ // option group, data.
+ Highcharts.wrap(Highcharts.Chart.prototype, 'init', function (proceed, userOptions, callback) {
+ var chart = this;
+
+ if (userOptions && userOptions.data) {
+ Highcharts.data(Highcharts.extend(userOptions.data, {
+
+ afterComplete: function (dataOptions) {
+ var i, series;
+
+ // Merge series configs
+ if (userOptions.hasOwnProperty('series')) {
+ if (typeof userOptions.series === 'object') {
+ i = Math.max(userOptions.series.length, dataOptions.series.length);
+ while (i--) {
+ series = userOptions.series[i] || {};
+ userOptions.series[i] = Highcharts.merge(series, dataOptions.series[i]);
+ }
+ } else { // Allow merging in dataOptions.series (#2856)
+ delete userOptions.series;
+ }
+ }
+
+ // Do the merge
+ userOptions = Highcharts.merge(dataOptions, userOptions);
+
+ proceed.call(chart, userOptions, callback);
+ }
+ }), userOptions);
+ } else {
+ proceed.call(chart, userOptions, callback);
+ }
+ });
+
+ /**
+ * Creates a new SeriesBuilder. A SeriesBuilder consists of a number
+ * of ColumnReaders that reads columns and give them a name.
+ * Ex: A series builder can be constructed to read column 3 as 'x' and
+ * column 7 and 8 as 'y1' and 'y2'.
+ * The output would then be points/rows of the form {x: 11, y1: 22, y2: 33}
+ *
+ * The name of the builder is taken from the second column. In the above
+ * example it would be the column with index 7.
+ * @constructor
+ */
+ SeriesBuilder = function () {
+ this.readers = [];
+ this.pointIsArray = true;
+ };
+
+ /**
+ * Populates readers with column indexes. A reader can be added without
+ * a specific index and for those readers the index is taken sequentially
+ * from the free columns (this is handled by the ColumnCursor instance).
+ * @returns {boolean}
+ */
+ SeriesBuilder.prototype.populateColumns = function (freeIndexes) {
+ var builder = this,
+ enoughColumns = true;
+
+ // Loop each reader and give it an index if its missing.
+ // The freeIndexes.shift() will return undefined if there
+ // are no more columns.
+ each(builder.readers, function (reader) {
+ if (reader.columnIndex === undefined) {
+ reader.columnIndex = freeIndexes.shift();
+ }
+ });
+
+ // Now, all readers should have columns mapped. If not
+ // then return false to signal that this series should
+ // not be added.
+ each(builder.readers, function (reader) {
+ if (reader.columnIndex === undefined) {
+ enoughColumns = false;
+ }
+ });
+
+ return enoughColumns;
+ };
+
+ /**
+ * Reads a row from the dataset and returns a point or array depending
+ * on the names of the readers.
+ * @param columns
+ * @param rowIndex
+ * @returns {Array | Object}
+ */
+ SeriesBuilder.prototype.read = function (columns, rowIndex) {
+ var builder = this,
+ pointIsArray = builder.pointIsArray,
+ point = pointIsArray ? [] : {},
+ columnIndexes;
+
+ // Loop each reader and ask it to read its value.
+ // Then, build an array or point based on the readers names.
+ each(builder.readers, function (reader) {
+ var value = columns[reader.columnIndex][rowIndex];
+ if (pointIsArray) {
+ point.push(value);
+ } else {
+ point[reader.configName] = value;
+ }
+ });
+
+ // The name comes from the first column (excluding the x column)
+ if (this.name === undefined && builder.readers.length >= 2) {
+ columnIndexes = builder.getReferencedColumnIndexes();
+ if (columnIndexes.length >= 2) {
+ // remove the first one (x col)
+ columnIndexes.shift();
+
+ // Sort the remaining
+ columnIndexes.sort();
+
+ // Now use the lowest index as name column
+ this.name = columns[columnIndexes.shift()].name;
+ }
+ }
+
+ return point;
+ };
+
+ /**
+ * Creates and adds ColumnReader from the given columnIndex and configName.
+ * ColumnIndex can be undefined and in that case the reader will be given
+ * an index when columns are populated.
+ * @param columnIndex {Number | undefined}
+ * @param configName
+ */
+ SeriesBuilder.prototype.addColumnReader = function (columnIndex, configName) {
+ this.readers.push({
+ columnIndex: columnIndex,
+ configName: configName
+ });
+
+ if (!(configName === 'x' || configName === 'y' || configName === undefined)) {
+ this.pointIsArray = false;
+ }
+ };
+
+ /**
+ * Returns an array of column indexes that the builder will use when
+ * reading data.
+ * @returns {Array}
+ */
+ SeriesBuilder.prototype.getReferencedColumnIndexes = function () {
+ var i,
+ referencedColumnIndexes = [],
+ columnReader;
+
+ for (i = 0; i < this.readers.length; i = i + 1) {
+ columnReader = this.readers[i];
+ if (columnReader.columnIndex !== undefined) {
+ referencedColumnIndexes.push(columnReader.columnIndex);
+ }
+ }
+
+ return referencedColumnIndexes;
+ };
+
+ /**
+ * Returns true if the builder has a reader for the given configName.
+ * @param configName
+ * @returns {boolean}
+ */
+ SeriesBuilder.prototype.hasReader = function (configName) {
+ var i, columnReader;
+ for (i = 0; i < this.readers.length; i = i + 1) {
+ columnReader = this.readers[i];
+ if (columnReader.configName === configName) {
+ return true;
+ }
+ }
+ // Else return undefined
+ };
+
+
+
+}(Highcharts));
diff --git a/html/includes/js/modules/drilldown.js b/html/includes/js/modules/drilldown.js
new file mode 100644
index 0000000..4388a1e
--- /dev/null
+++ b/html/includes/js/modules/drilldown.js
@@ -0,0 +1,15 @@
+(function(g){function t(a,b,d){return"rgba("+[Math.round(a[0]+(b[0]-a[0])*d),Math.round(a[1]+(b[1]-a[1])*d),Math.round(a[2]+(b[2]-a[2])*d),a[3]+(b[3]-a[3])*d].join(",")+")"}var u=function(){},o=g.getOptions(),i=g.each,p=g.extend,z=g.format,A=g.pick,q=g.wrap,l=g.Chart,n=g.seriesTypes,v=n.pie,m=n.column,w=HighchartsAdapter.fireEvent,x=HighchartsAdapter.inArray,r=[];p(o.lang,{drillUpText:"◁ Back to {series.name}"});o.drilldown={activeAxisLabelStyle:{cursor:"pointer",color:"#0d233a",fontWeight:"bold",
+textDecoration:"underline"},activeDataLabelStyle:{cursor:"pointer",color:"#0d233a",fontWeight:"bold",textDecoration:"underline"},animation:{duration:500},drillUpButton:{position:{align:"right",x:-10,y:10}}};g.SVGRenderer.prototype.Element.prototype.fadeIn=function(a){this.attr({opacity:0.1,visibility:"inherit"}).animate({opacity:A(this.newOpacity,1)},a||{duration:250})};l.prototype.addSeriesAsDrilldown=function(a,b){this.addSingleSeriesAsDrilldown(a,b);this.applyDrilldown()};l.prototype.addSingleSeriesAsDrilldown=
+function(a,b){var d=a.series,c=d.xAxis,f=d.yAxis,h;h=a.color||d.color;var e,y=[],g=[],k;k=d.levelNumber||0;b=p({color:h},b);e=x(a,d.points);i(d.chart.series,function(a){if(a.xAxis===c)y.push(a),g.push(a.userOptions),a.levelNumber=a.levelNumber||k});h={levelNumber:k,seriesOptions:d.userOptions,levelSeriesOptions:g,levelSeries:y,shapeArgs:a.shapeArgs,bBox:a.graphic.getBBox(),color:h,lowerSeriesOptions:b,pointOptions:d.options.data[e],pointIndex:e,oldExtremes:{xMin:c&&c.userMin,xMax:c&&c.userMax,yMin:f&&
+f.userMin,yMax:f&&f.userMax}};if(!this.drilldownLevels)this.drilldownLevels=[];this.drilldownLevels.push(h);h=h.lowerSeries=this.addSeries(b,!1);h.levelNumber=k+1;if(c)c.oldPos=c.pos,c.userMin=c.userMax=null,f.userMin=f.userMax=null;if(d.type===h.type)h.animate=h.animateDrilldown||u,h.options.animation=!0};l.prototype.applyDrilldown=function(){var a=this.drilldownLevels,b;if(a&&a.length>0)b=a[a.length-1].levelNumber,i(this.drilldownLevels,function(a){a.levelNumber===b&&i(a.levelSeries,function(a){a.levelNumber===
+b&&a.remove(!1)})});this.redraw();this.showDrillUpButton()};l.prototype.getDrilldownBackText=function(){var a=this.drilldownLevels;if(a&&a.length>0)return a=a[a.length-1],a.series=a.seriesOptions,z(this.options.lang.drillUpText,a)};l.prototype.showDrillUpButton=function(){var a=this,b=this.getDrilldownBackText(),d=a.options.drilldown.drillUpButton,c,f;this.drillUpButton?this.drillUpButton.attr({text:b}).align():(f=(c=d.theme)&&c.states,this.drillUpButton=this.renderer.button(b,null,null,function(){a.drillUp()},
+c,f&&f.hover,f&&f.select).attr({align:d.position.align,zIndex:9}).add().align(d.position,!1,d.relativeTo||"plotBox"))};l.prototype.drillUp=function(){for(var a=this,b=a.drilldownLevels,d=b[b.length-1].levelNumber,c=b.length,f=a.series,h=f.length,e,g,j,k,l=function(b){var c;i(f,function(a){a.userOptions===b&&(c=a)});c=c||a.addSeries(b,!1);if(c.type===g.type&&c.animateDrillupTo)c.animate=c.animateDrillupTo;b===e.seriesOptions&&(j=c)};c--;)if(e=b[c],e.levelNumber===d){b.pop();g=e.lowerSeries;if(!g.chart)for(;h--;)if(f[h].options.id===
+e.lowerSeriesOptions.id){g=f[h];break}g.xData=[];i(e.levelSeriesOptions,l);w(a,"drillup",{seriesOptions:e.seriesOptions});if(j.type===g.type)j.drilldownLevel=e,j.options.animation=a.options.drilldown.animation,g.animateDrillupFrom&&g.animateDrillupFrom(e);j.levelNumber=d;g.remove(!1);if(j.xAxis)k=e.oldExtremes,j.xAxis.setExtremes(k.xMin,k.xMax,!1),j.yAxis.setExtremes(k.yMin,k.yMax,!1)}this.redraw();this.drilldownLevels.length===0?this.drillUpButton=this.drillUpButton.destroy():this.drillUpButton.attr({text:this.getDrilldownBackText()}).align();
+r.length=[]};m.prototype.supportsDrilldown=!0;m.prototype.animateDrillupTo=function(a){if(!a){var b=this,d=b.drilldownLevel;i(this.points,function(a){a.graphic.hide();a.dataLabel&&a.dataLabel.hide();a.connector&&a.connector.hide()});setTimeout(function(){i(b.points,function(a,b){var h=b===(d&&d.pointIndex)?"show":"fadeIn",e=h==="show"?!0:void 0;a.graphic[h](e);if(a.dataLabel)a.dataLabel[h](e);if(a.connector)a.connector[h](e)})},Math.max(this.chart.options.drilldown.animation.duration-50,0));this.animate=
+u}};m.prototype.animateDrilldown=function(a){var b=this,d=this.chart.drilldownLevels,c=this.chart.drilldownLevels[this.chart.drilldownLevels.length-1].shapeArgs,f=this.chart.options.drilldown.animation;if(!a)i(d,function(a){if(b.userOptions===a.lowerSeriesOptions)c=a.shapeArgs}),c.x+=this.xAxis.oldPos-this.xAxis.pos,i(this.points,function(a){a.graphic&&a.graphic.attr(c).animate(a.shapeArgs,f);a.dataLabel&&a.dataLabel.fadeIn(f)}),this.animate=null};m.prototype.animateDrillupFrom=function(a){var b=
+this.chart.options.drilldown.animation,d=this.group,c=this;i(c.trackerGroups,function(a){if(c[a])c[a].on("mouseover")});delete this.group;i(this.points,function(c){var h=c.graphic,e=g.Color(c.color).rgba,i=g.Color(a.color).rgba,j=function(){h.destroy();d&&(d=d.destroy())};h&&(delete c.graphic,b?h.animate(a.shapeArgs,g.merge(b,{step:function(a,b){b.prop==="start"&&e.length===4&&i.length===4&&this.attr({fill:t(e,i,b.pos)})},complete:j})):(h.attr(a.shapeArgs),j()))})};v&&p(v.prototype,{supportsDrilldown:!0,
+animateDrillupTo:m.prototype.animateDrillupTo,animateDrillupFrom:m.prototype.animateDrillupFrom,animateDrilldown:function(a){var b=this.chart.drilldownLevels[this.chart.drilldownLevels.length-1],d=this.chart.options.drilldown.animation,c=b.shapeArgs,f=c.start,h=(c.end-f)/this.points.length,e=g.Color(b.color).rgba;if(!a)i(this.points,function(a,b){var i=g.Color(a.color).rgba;a.graphic.attr(g.merge(c,{start:f+b*h,end:f+(b+1)*h}))[d?"animate":"attr"](a.shapeArgs,g.merge(d,{step:function(a,b){b.prop===
+"start"&&e.length===4&&i.length===4&&this.attr({fill:t(e,i,b.pos)})}}))}),this.animate=null}});g.Point.prototype.doDrilldown=function(a){for(var b=this.series.chart,d=b.options.drilldown,c=(d.series||[]).length,f;c--&&!f;)d.series[c].id===this.drilldown&&x(this.drilldown,r)===-1&&(f=d.series[c],r.push(this.drilldown));w(b,"drilldown",{point:this,seriesOptions:f});f&&(a?b.addSingleSeriesAsDrilldown(this,f):b.addSeriesAsDrilldown(this,f))};q(g.Point.prototype,"init",function(a,b,d,c){var f=a.call(this,
+b,d,c),h=b.chart,e=(a=b.xAxis&&b.xAxis.ticks[c])&&a.label;if(f.drilldown){if(g.addEvent(f,"click",function(){f.doDrilldown()}),e){if(!e.basicStyles)e.basicStyles=g.merge(e.styles);e.addClass("highcharts-drilldown-axis-label").css(h.options.drilldown.activeAxisLabelStyle).on("click",function(){i(e.ddPoints,function(a){a.doDrilldown&&a.doDrilldown(!0)});h.applyDrilldown()});if(!e.ddPoints)e.ddPoints=[];e.ddPoints.push(f)}}else if(e&&e.basicStyles)e.styles={},e.css(e.basicStyles);return f});q(g.Series.prototype,
+"drawDataLabels",function(a){var b=this.chart.options.drilldown.activeDataLabelStyle;a.call(this);i(this.points,function(a){if(a.drilldown&&a.dataLabel)a.dataLabel.attr({"class":"highcharts-drilldown-data-label"}).css(b).on("click",function(){a.doDrilldown()})})});var s,o=function(a){a.call(this);i(this.points,function(a){a.drilldown&&a.graphic&&a.graphic.attr({"class":"highcharts-drilldown-point"}).css({cursor:"pointer"})})};for(s in n)n[s].prototype.supportsDrilldown&&q(n[s].prototype,"drawTracker",
+o)})(Highcharts);
diff --git a/html/includes/js/modules/drilldown.src.js b/html/includes/js/modules/drilldown.src.js
new file mode 100644
index 0000000..cd3522c
--- /dev/null
+++ b/html/includes/js/modules/drilldown.src.js
@@ -0,0 +1,606 @@
+/**
+ * Highcharts Drilldown plugin
+ *
+ * Author: Torstein Honsi
+ * License: MIT License
+ *
+ * Demo: http://jsfiddle.net/highcharts/Vf3yT/
+ */
+
+/*global HighchartsAdapter*/
+(function (H) {
+
+ "use strict";
+
+ var noop = function () {},
+ defaultOptions = H.getOptions(),
+ each = H.each,
+ extend = H.extend,
+ format = H.format,
+ pick = H.pick,
+ wrap = H.wrap,
+ Chart = H.Chart,
+ seriesTypes = H.seriesTypes,
+ PieSeries = seriesTypes.pie,
+ ColumnSeries = seriesTypes.column,
+ fireEvent = HighchartsAdapter.fireEvent,
+ inArray = HighchartsAdapter.inArray,
+ dupes = [];
+
+ // Utilities
+ function tweenColors(startColor, endColor, pos) {
+ var rgba = [
+ Math.round(startColor[0] + (endColor[0] - startColor[0]) * pos),
+ Math.round(startColor[1] + (endColor[1] - startColor[1]) * pos),
+ Math.round(startColor[2] + (endColor[2] - startColor[2]) * pos),
+ startColor[3] + (endColor[3] - startColor[3]) * pos
+ ];
+ return 'rgba(' + rgba.join(',') + ')';
+ }
+
+ // Add language
+ extend(defaultOptions.lang, {
+ drillUpText: '◁ Back to {series.name}'
+ });
+ defaultOptions.drilldown = {
+ activeAxisLabelStyle: {
+ cursor: 'pointer',
+ color: '#0d233a',
+ fontWeight: 'bold',
+ textDecoration: 'underline'
+ },
+ activeDataLabelStyle: {
+ cursor: 'pointer',
+ color: '#0d233a',
+ fontWeight: 'bold',
+ textDecoration: 'underline'
+ },
+ animation: {
+ duration: 500
+ },
+ drillUpButton: {
+ position: {
+ align: 'right',
+ x: -10,
+ y: 10
+ }
+ // relativeTo: 'plotBox'
+ // theme
+ }
+ };
+
+ /**
+ * A general fadeIn method
+ */
+ H.SVGRenderer.prototype.Element.prototype.fadeIn = function (animation) {
+ this
+ .attr({
+ opacity: 0.1,
+ visibility: 'inherit'
+ })
+ .animate({
+ opacity: pick(this.newOpacity, 1) // newOpacity used in maps
+ }, animation || {
+ duration: 250
+ });
+ };
+
+ Chart.prototype.addSeriesAsDrilldown = function (point, ddOptions) {
+ this.addSingleSeriesAsDrilldown(point, ddOptions);
+ this.applyDrilldown();
+ };
+ Chart.prototype.addSingleSeriesAsDrilldown = function (point, ddOptions) {
+ var oldSeries = point.series,
+ xAxis = oldSeries.xAxis,
+ yAxis = oldSeries.yAxis,
+ newSeries,
+ color = point.color || oldSeries.color,
+ pointIndex,
+ levelSeries = [],
+ levelSeriesOptions = [],
+ level,
+ levelNumber;
+
+ levelNumber = oldSeries.levelNumber || 0;
+
+ ddOptions = extend({
+ color: color
+ }, ddOptions);
+ pointIndex = inArray(point, oldSeries.points);
+
+ // Record options for all current series
+ each(oldSeries.chart.series, function (series) {
+ if (series.xAxis === xAxis) {
+ levelSeries.push(series);
+ levelSeriesOptions.push(series.userOptions);
+ series.levelNumber = series.levelNumber || levelNumber; // #3182
+ }
+ });
+
+ // Add a record of properties for each drilldown level
+ level = {
+ levelNumber: levelNumber,
+ seriesOptions: oldSeries.userOptions,
+ levelSeriesOptions: levelSeriesOptions,
+ levelSeries: levelSeries,
+ shapeArgs: point.shapeArgs,
+ bBox: point.graphic.getBBox(),
+ color: color,
+ lowerSeriesOptions: ddOptions,
+ pointOptions: oldSeries.options.data[pointIndex],
+ pointIndex: pointIndex,
+ oldExtremes: {
+ xMin: xAxis && xAxis.userMin,
+ xMax: xAxis && xAxis.userMax,
+ yMin: yAxis && yAxis.userMin,
+ yMax: yAxis && yAxis.userMax
+ }
+ };
+
+ // Generate and push it to a lookup array
+ if (!this.drilldownLevels) {
+ this.drilldownLevels = [];
+ }
+ this.drilldownLevels.push(level);
+
+ newSeries = level.lowerSeries = this.addSeries(ddOptions, false);
+ newSeries.levelNumber = levelNumber + 1;
+ if (xAxis) {
+ xAxis.oldPos = xAxis.pos;
+ xAxis.userMin = xAxis.userMax = null;
+ yAxis.userMin = yAxis.userMax = null;
+ }
+
+ // Run fancy cross-animation on supported and equal types
+ if (oldSeries.type === newSeries.type) {
+ newSeries.animate = newSeries.animateDrilldown || noop;
+ newSeries.options.animation = true;
+ }
+ };
+
+ Chart.prototype.applyDrilldown = function () {
+ var drilldownLevels = this.drilldownLevels,
+ levelToRemove;
+
+ if (drilldownLevels && drilldownLevels.length > 0) { // #3352, async loading
+ levelToRemove = drilldownLevels[drilldownLevels.length - 1].levelNumber;
+ each(this.drilldownLevels, function (level) {
+ if (level.levelNumber === levelToRemove) {
+ each(level.levelSeries, function (series) {
+ if (series.levelNumber === levelToRemove) { // Not removed, not added as part of a multi-series drilldown
+ series.remove(false);
+ }
+ });
+ }
+ });
+ }
+
+ this.redraw();
+ this.showDrillUpButton();
+ };
+
+ Chart.prototype.getDrilldownBackText = function () {
+ var drilldownLevels = this.drilldownLevels,
+ lastLevel;
+ if (drilldownLevels && drilldownLevels.length > 0) { // #3352, async loading
+ lastLevel = drilldownLevels[drilldownLevels.length - 1];
+ lastLevel.series = lastLevel.seriesOptions;
+ return format(this.options.lang.drillUpText, lastLevel);
+ }
+
+ };
+
+ Chart.prototype.showDrillUpButton = function () {
+ var chart = this,
+ backText = this.getDrilldownBackText(),
+ buttonOptions = chart.options.drilldown.drillUpButton,
+ attr,
+ states;
+
+
+ if (!this.drillUpButton) {
+ attr = buttonOptions.theme;
+ states = attr && attr.states;
+
+ this.drillUpButton = this.renderer.button(
+ backText,
+ null,
+ null,
+ function () {
+ chart.drillUp();
+ },
+ attr,
+ states && states.hover,
+ states && states.select
+ )
+ .attr({
+ align: buttonOptions.position.align,
+ zIndex: 9
+ })
+ .add()
+ .align(buttonOptions.position, false, buttonOptions.relativeTo || 'plotBox');
+ } else {
+ this.drillUpButton.attr({
+ text: backText
+ })
+ .align();
+ }
+ };
+
+ Chart.prototype.drillUp = function () {
+ var chart = this,
+ drilldownLevels = chart.drilldownLevels,
+ levelNumber = drilldownLevels[drilldownLevels.length - 1].levelNumber,
+ i = drilldownLevels.length,
+ chartSeries = chart.series,
+ seriesI = chartSeries.length,
+ level,
+ oldSeries,
+ newSeries,
+ oldExtremes,
+ addSeries = function (seriesOptions) {
+ var addedSeries;
+ each(chartSeries, function (series) {
+ if (series.userOptions === seriesOptions) {
+ addedSeries = series;
+ }
+ });
+
+ addedSeries = addedSeries || chart.addSeries(seriesOptions, false);
+ if (addedSeries.type === oldSeries.type && addedSeries.animateDrillupTo) {
+ addedSeries.animate = addedSeries.animateDrillupTo;
+ }
+ if (seriesOptions === level.seriesOptions) {
+ newSeries = addedSeries;
+ }
+ };
+
+ while (i--) {
+
+ level = drilldownLevels[i];
+ if (level.levelNumber === levelNumber) {
+ drilldownLevels.pop();
+
+ // Get the lower series by reference or id
+ oldSeries = level.lowerSeries;
+ if (!oldSeries.chart) { // #2786
+ while (seriesI--) {
+ if (chartSeries[seriesI].options.id === level.lowerSeriesOptions.id) {
+ oldSeries = chartSeries[seriesI];
+ break;
+ }
+ }
+ }
+ oldSeries.xData = []; // Overcome problems with minRange (#2898)
+
+ each(level.levelSeriesOptions, addSeries);
+
+ fireEvent(chart, 'drillup', { seriesOptions: level.seriesOptions });
+
+ if (newSeries.type === oldSeries.type) {
+ newSeries.drilldownLevel = level;
+ newSeries.options.animation = chart.options.drilldown.animation;
+
+ if (oldSeries.animateDrillupFrom) {
+ oldSeries.animateDrillupFrom(level);
+ }
+ }
+ newSeries.levelNumber = levelNumber;
+
+ oldSeries.remove(false);
+
+ // Reset the zoom level of the upper series
+ if (newSeries.xAxis) {
+ oldExtremes = level.oldExtremes;
+ newSeries.xAxis.setExtremes(oldExtremes.xMin, oldExtremes.xMax, false);
+ newSeries.yAxis.setExtremes(oldExtremes.yMin, oldExtremes.yMax, false);
+ }
+ }
+ }
+
+ this.redraw();
+
+ if (this.drilldownLevels.length === 0) {
+ this.drillUpButton = this.drillUpButton.destroy();
+ } else {
+ this.drillUpButton.attr({
+ text: this.getDrilldownBackText()
+ })
+ .align();
+ }
+
+ dupes.length = []; // #3315
+ };
+
+
+ ColumnSeries.prototype.supportsDrilldown = true;
+
+ /**
+ * When drilling up, keep the upper series invisible until the lower series has
+ * moved into place
+ */
+ ColumnSeries.prototype.animateDrillupTo = function (init) {
+ if (!init) {
+ var newSeries = this,
+ level = newSeries.drilldownLevel;
+
+ each(this.points, function (point) {
+ point.graphic.hide();
+ if (point.dataLabel) {
+ point.dataLabel.hide();
+ }
+ if (point.connector) {
+ point.connector.hide();
+ }
+ });
+
+
+ // Do dummy animation on first point to get to complete
+ setTimeout(function () {
+ each(newSeries.points, function (point, i) {
+ // Fade in other points
+ var verb = i === (level && level.pointIndex) ? 'show' : 'fadeIn',
+ inherit = verb === 'show' ? true : undefined;
+ point.graphic[verb](inherit);
+ if (point.dataLabel) {
+ point.dataLabel[verb](inherit);
+ }
+ if (point.connector) {
+ point.connector[verb](inherit);
+ }
+ });
+ }, Math.max(this.chart.options.drilldown.animation.duration - 50, 0));
+
+ // Reset
+ this.animate = noop;
+ }
+
+ };
+
+ ColumnSeries.prototype.animateDrilldown = function (init) {
+ var series = this,
+ drilldownLevels = this.chart.drilldownLevels,
+ animateFrom = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1].shapeArgs,
+ animationOptions = this.chart.options.drilldown.animation;
+
+ if (!init) {
+ each(drilldownLevels, function (level) {
+ if (series.userOptions === level.lowerSeriesOptions) {
+ animateFrom = level.shapeArgs;
+ }
+ });
+
+ animateFrom.x += (this.xAxis.oldPos - this.xAxis.pos);
+
+ each(this.points, function (point) {
+ if (point.graphic) {
+ point.graphic
+ .attr(animateFrom)
+ .animate(point.shapeArgs, animationOptions);
+ }
+ if (point.dataLabel) {
+ point.dataLabel.fadeIn(animationOptions);
+ }
+ });
+ this.animate = null;
+ }
+
+ };
+
+ /**
+ * When drilling up, pull out the individual point graphics from the lower series
+ * and animate them into the origin point in the upper series.
+ */
+ ColumnSeries.prototype.animateDrillupFrom = function (level) {
+ var animationOptions = this.chart.options.drilldown.animation,
+ group = this.group,
+ series = this;
+
+ // Cancel mouse events on the series group (#2787)
+ each(series.trackerGroups, function (key) {
+ if (series[key]) { // we don't always have dataLabelsGroup
+ series[key].on('mouseover');
+ }
+ });
+
+
+ delete this.group;
+ each(this.points, function (point) {
+ var graphic = point.graphic,
+ startColor = H.Color(point.color).rgba,
+ endColor = H.Color(level.color).rgba,
+ complete = function () {
+ graphic.destroy();
+ if (group) {
+ group = group.destroy();
+ }
+ };
+
+ if (graphic) {
+
+ delete point.graphic;
+
+ if (animationOptions) {
+ /*jslint unparam: true*/
+ graphic.animate(level.shapeArgs, H.merge(animationOptions, {
+ step: function (val, fx) {
+ if (fx.prop === 'start' && startColor.length === 4 && endColor.length === 4) {
+ this.attr({
+ fill: tweenColors(startColor, endColor, fx.pos)
+ });
+ }
+ },
+ complete: complete
+ }));
+ /*jslint unparam: false*/
+ } else {
+ graphic.attr(level.shapeArgs);
+ complete();
+ }
+ }
+ });
+ };
+
+ if (PieSeries) {
+ extend(PieSeries.prototype, {
+ supportsDrilldown: true,
+ animateDrillupTo: ColumnSeries.prototype.animateDrillupTo,
+ animateDrillupFrom: ColumnSeries.prototype.animateDrillupFrom,
+
+ animateDrilldown: function (init) {
+ var level = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1],
+ animationOptions = this.chart.options.drilldown.animation,
+ animateFrom = level.shapeArgs,
+ start = animateFrom.start,
+ angle = animateFrom.end - start,
+ startAngle = angle / this.points.length,
+ startColor = H.Color(level.color).rgba;
+
+ if (!init) {
+ each(this.points, function (point, i) {
+ var endColor = H.Color(point.color).rgba;
+
+ /*jslint unparam: true*/
+ point.graphic
+ .attr(H.merge(animateFrom, {
+ start: start + i * startAngle,
+ end: start + (i + 1) * startAngle
+ }))[animationOptions ? 'animate' : 'attr'](point.shapeArgs, H.merge(animationOptions, {
+ step: function (val, fx) {
+ if (fx.prop === 'start' && startColor.length === 4 && endColor.length === 4) {
+ this.attr({
+ fill: tweenColors(startColor, endColor, fx.pos)
+ });
+ }
+ }
+ }));
+ /*jslint unparam: false*/
+ });
+ this.animate = null;
+ }
+ }
+ });
+ }
+
+ H.Point.prototype.doDrilldown = function (_holdRedraw) {
+ var series = this.series,
+ chart = series.chart,
+ drilldown = chart.options.drilldown,
+ i = (drilldown.series || []).length,
+ seriesOptions;
+
+ while (i-- && !seriesOptions) {
+ if (drilldown.series[i].id === this.drilldown && inArray(this.drilldown, dupes) === -1) {
+ seriesOptions = drilldown.series[i];
+ dupes.push(this.drilldown);
+ }
+ }
+
+ // Fire the event. If seriesOptions is undefined, the implementer can check for
+ // seriesOptions, and call addSeriesAsDrilldown async if necessary.
+ fireEvent(chart, 'drilldown', {
+ point: this,
+ seriesOptions: seriesOptions
+ });
+
+ if (seriesOptions) {
+ if (_holdRedraw) {
+ chart.addSingleSeriesAsDrilldown(this, seriesOptions);
+ } else {
+ chart.addSeriesAsDrilldown(this, seriesOptions);
+ }
+ }
+
+ };
+
+ wrap(H.Point.prototype, 'init', function (proceed, series, options, x) {
+ var point = proceed.call(this, series, options, x),
+ chart = series.chart,
+ tick = series.xAxis && series.xAxis.ticks[x],
+ tickLabel = tick && tick.label;
+
+ if (point.drilldown) {
+
+ // Add the click event to the point
+ H.addEvent(point, 'click', function () {
+ point.doDrilldown();
+ });
+ /*wrap(point, 'importEvents', function (proceed) { // wrapping importEvents makes point.click event work
+ if (!this.hasImportedEvents) {
+ proceed.call(this);
+ H.addEvent(this, 'click', function () {
+ this.doDrilldown();
+ });
+ }
+ });*/
+
+ // Make axis labels clickable
+ if (tickLabel) {
+ if (!tickLabel.basicStyles) {
+ tickLabel.basicStyles = H.merge(tickLabel.styles);
+ }
+ tickLabel
+ .addClass('highcharts-drilldown-axis-label')
+ .css(chart.options.drilldown.activeAxisLabelStyle)
+ .on('click', function () {
+ each(tickLabel.ddPoints, function (point) {
+ if (point.doDrilldown) {
+ point.doDrilldown(true);
+ }
+ });
+ chart.applyDrilldown();
+ });
+ if (!tickLabel.ddPoints) {
+ tickLabel.ddPoints = [];
+ }
+ tickLabel.ddPoints.push(point);
+
+ }
+ } else if (tickLabel && tickLabel.basicStyles) {
+ tickLabel.styles = {}; // reset for full overwrite of styles
+ tickLabel.css(tickLabel.basicStyles);
+ }
+
+ return point;
+ });
+
+ wrap(H.Series.prototype, 'drawDataLabels', function (proceed) {
+ var css = this.chart.options.drilldown.activeDataLabelStyle;
+
+ proceed.call(this);
+
+ each(this.points, function (point) {
+ if (point.drilldown && point.dataLabel) {
+ point.dataLabel
+ .attr({
+ 'class': 'highcharts-drilldown-data-label'
+ })
+ .css(css)
+ .on('click', function () {
+ point.doDrilldown();
+ });
+ }
+ });
+ });
+
+ // Mark the trackers with a pointer
+ var type,
+ drawTrackerWrapper = function (proceed) {
+ proceed.call(this);
+ each(this.points, function (point) {
+ if (point.drilldown && point.graphic) {
+ point.graphic
+ .attr({
+ 'class': 'highcharts-drilldown-point'
+ })
+ .css({ cursor: 'pointer' });
+ }
+ });
+ };
+ for (type in seriesTypes) {
+ if (seriesTypes[type].prototype.supportsDrilldown) {
+ wrap(seriesTypes[type].prototype, 'drawTracker', drawTrackerWrapper);
+ }
+ }
+
+}(Highcharts));
diff --git a/html/includes/js/modules/exporting.js b/html/includes/js/modules/exporting.js
new file mode 100644
index 0000000..cf150ea
--- /dev/null
+++ b/html/includes/js/modules/exporting.js
@@ -0,0 +1,23 @@
+/*
+ Highcharts JS v4.0.4 (2014-09-02)
+ Exporting module
+
+ (c) 2010-2014 Torstein Honsi
+
+ License: www.highcharts.com/license
+*/
+(function(f){var A=f.Chart,t=f.addEvent,B=f.removeEvent,l=f.createElement,o=f.discardElement,v=f.css,k=f.merge,r=f.each,p=f.extend,D=Math.max,j=document,C=window,E=f.isTouchDevice,F=f.Renderer.prototype.symbols,s=f.getOptions(),y;p(s.lang,{printChart:"Print chart",downloadPNG:"Download PNG image",downloadJPEG:"Download JPEG image",downloadPDF:"Download PDF document",downloadSVG:"Download SVG vector image",contextButtonTitle:"Chart context menu"});s.navigation={menuStyle:{border:"1px solid #A0A0A0",
+background:"#FFFFFF",padding:"5px 0"},menuItemStyle:{padding:"0 10px",background:"none",color:"#303030",fontSize:E?"14px":"11px"},menuItemHoverStyle:{background:"#4572A5",color:"#FFFFFF"},buttonOptions:{symbolFill:"#E0E0E0",symbolSize:14,symbolStroke:"#666",symbolStrokeWidth:3,symbolX:12.5,symbolY:10.5,align:"right",buttonSpacing:3,height:22,theme:{fill:"white",stroke:"none"},verticalAlign:"top",width:24}};s.exporting={type:"image/png",url:"http://export.highcharts.com/",buttons:{contextButton:{menuClassName:"highcharts-contextmenu",
+symbol:"menu",_titleKey:"contextButtonTitle",menuItems:[{textKey:"printChart",onclick:function(){this.print()}},{separator:!0},{textKey:"downloadPNG",onclick:function(){this.exportChart()}},{textKey:"downloadJPEG",onclick:function(){this.exportChart({type:"image/jpeg"})}},{textKey:"downloadPDF",onclick:function(){this.exportChart({type:"application/pdf"})}},{textKey:"downloadSVG",onclick:function(){this.exportChart({type:"image/svg+xml"})}}]}}};f.post=function(b,a,d){var c,b=l("form",k({method:"post",
+action:b,enctype:"multipart/form-data"},d),{display:"none"},j.body);for(c in a)l("input",{type:"hidden",name:c,value:a[c]},null,b);b.submit();o(b)};p(A.prototype,{getSVG:function(b){var a=this,d,c,z,h,g=k(a.options,b);if(!j.createElementNS)j.createElementNS=function(a,b){return j.createElement(b)};b=l("div",null,{position:"absolute",top:"-9999em",width:a.chartWidth+"px",height:a.chartHeight+"px"},j.body);c=a.renderTo.style.width;h=a.renderTo.style.height;c=g.exporting.sourceWidth||g.chart.width||
+/px$/.test(c)&&parseInt(c,10)||600;h=g.exporting.sourceHeight||g.chart.height||/px$/.test(h)&&parseInt(h,10)||400;p(g.chart,{animation:!1,renderTo:b,forExport:!0,width:c,height:h});g.exporting.enabled=!1;g.series=[];r(a.series,function(a){z=k(a.options,{animation:!1,enableMouseTracking:!1,showCheckbox:!1,visible:a.visible});z.isInternal||g.series.push(z)});d=new f.Chart(g,a.callback);r(["xAxis","yAxis"],function(b){r(a[b],function(a,c){var g=d[b][c],f=a.getExtremes(),h=f.userMin,f=f.userMax;g&&(h!==
+void 0||f!==void 0)&&g.setExtremes(h,f,!0,!1)})});c=d.container.innerHTML;g=null;d.destroy();o(b);c=c.replace(/zIndex="[^"]+"/g,"").replace(/isShadow="[^"]+"/g,"").replace(/symbolName="[^"]+"/g,"").replace(/jQuery[0-9]+="[^"]+"/g,"").replace(/url\([^#]+#/g,"url(#").replace(/<svg /,'<svg xmlns:xlink="http://www.w3.org/1999/xlink" ').replace(/ href=/g," xlink:href=").replace(/\n/," ").replace(/<\/svg>.*?$/,"</svg>").replace(/(fill|stroke)="rgba\(([ 0-9]+,[ 0-9]+,[ 0-9]+),([ 0-9\.]+)\)"/g,'$1="rgb($2)" $1-opacity="$3"').replace(/&nbsp;/g,
+" ").replace(/&shy;/g,"­").replace(/<IMG /g,"<image ").replace(/height=([^" ]+)/g,'height="$1"').replace(/width=([^" ]+)/g,'width="$1"').replace(/hc-svg-href="([^"]+)">/g,'xlink:href="$1"/>').replace(/id=([^" >]+)/g,'id="$1"').replace(/class=([^" >]+)/g,'class="$1"').replace(/ transform /g," ").replace(/:(path|rect)/g,"$1").replace(/style="([^"]+)"/g,function(a){return a.toLowerCase()});return c=c.replace(/(url\(#highcharts-[0-9]+)&quot;/g,"$1").replace(/&quot;/g,"'")},exportChart:function(b,a){var b=
+b||{},d=this.options.exporting,d=this.getSVG(k({chart:{borderRadius:0}},d.chartOptions,a,{exporting:{sourceWidth:b.sourceWidth||d.sourceWidth,sourceHeight:b.sourceHeight||d.sourceHeight}})),b=k(this.options.exporting,b);f.post(b.url,{filename:b.filename||"chart",type:b.type,width:b.width||0,scale:b.scale||2,svg:d},b.formAttributes)},print:function(){var b=this,a=b.container,d=[],c=a.parentNode,f=j.body,h=f.childNodes;if(!b.isPrinting)b.isPrinting=!0,r(h,function(a,b){if(a.nodeType===1)d[b]=a.style.display,
+a.style.display="none"}),f.appendChild(a),C.focus(),C.print(),setTimeout(function(){c.appendChild(a);r(h,function(a,b){if(a.nodeType===1)a.style.display=d[b]});b.isPrinting=!1},1E3)},contextMenu:function(b,a,d,c,f,h,g){var e=this,k=e.options.navigation,q=k.menuItemStyle,m=e.chartWidth,n=e.chartHeight,j="cache-"+b,i=e[j],u=D(f,h),w,x,o,s=function(a){e.pointer.inClass(a.target,b)||x()};if(!i)e[j]=i=l("div",{className:b},{position:"absolute",zIndex:1E3,padding:u+"px"},e.container),w=l("div",null,p({MozBoxShadow:"3px 3px 10px #888",
+WebkitBoxShadow:"3px 3px 10px #888",boxShadow:"3px 3px 10px #888"},k.menuStyle),i),x=function(){v(i,{display:"none"});g&&g.setState(0);e.openMenu=!1},t(i,"mouseleave",function(){o=setTimeout(x,500)}),t(i,"mouseenter",function(){clearTimeout(o)}),t(document,"mouseup",s),t(e,"destroy",function(){B(document,"mouseup",s)}),r(a,function(a){if(a){var b=a.separator?l("hr",null,null,w):l("div",{onmouseover:function(){v(this,k.menuItemHoverStyle)},onmouseout:function(){v(this,q)},onclick:function(){x();a.onclick.apply(e,
+arguments)},innerHTML:a.text||e.options.lang[a.textKey]},p({cursor:"pointer"},q),w);e.exportDivElements.push(b)}}),e.exportDivElements.push(w,i),e.exportMenuWidth=i.offsetWidth,e.exportMenuHeight=i.offsetHeight;a={display:"block"};d+e.exportMenuWidth>m?a.right=m-d-f-u+"px":a.left=d-u+"px";c+h+e.exportMenuHeight>n&&g.alignOptions.verticalAlign!=="top"?a.bottom=n-c-u+"px":a.top=c+h-u+"px";v(i,a);e.openMenu=!0},addButton:function(b){var a=this,d=a.renderer,c=k(a.options.navigation.buttonOptions,b),j=
+c.onclick,h=c.menuItems,g,e,l={stroke:c.symbolStroke,fill:c.symbolFill},q=c.symbolSize||12;if(!a.btnCount)a.btnCount=0;if(!a.exportDivElements)a.exportDivElements=[],a.exportSVGElements=[];if(c.enabled!==!1){var m=c.theme,n=m.states,o=n&&n.hover,n=n&&n.select,i;delete m.states;j?i=function(){j.apply(a,arguments)}:h&&(i=function(){a.contextMenu(e.menuClassName,h,e.translateX,e.translateY,e.width,e.height,e);e.setState(2)});c.text&&c.symbol?m.paddingLeft=f.pick(m.paddingLeft,25):c.text||p(m,{width:c.width,
+height:c.height,padding:0});e=d.button(c.text,0,0,i,m,o,n).attr({title:a.options.lang[c._titleKey],"stroke-linecap":"round"});e.menuClassName=b.menuClassName||"highcharts-menu-"+a.btnCount++;c.symbol&&(g=d.symbol(c.symbol,c.symbolX-q/2,c.symbolY-q/2,q,q).attr(p(l,{"stroke-width":c.symbolStrokeWidth||1,zIndex:1})).add(e));e.add().align(p(c,{width:e.width,x:f.pick(c.x,y)}),!0,"spacingBox");y+=(e.width+c.buttonSpacing)*(c.align==="right"?-1:1);a.exportSVGElements.push(e,g)}},destroyExport:function(b){var b=
+b.target,a,d;for(a=0;a<b.exportSVGElements.length;a++)if(d=b.exportSVGElements[a])d.onclick=d.ontouchstart=null,b.exportSVGElements[a]=d.destroy();for(a=0;a<b.exportDivElements.length;a++)d=b.exportDivElements[a],B(d,"mouseleave"),b.exportDivElements[a]=d.onmouseout=d.onmouseover=d.ontouchstart=d.onclick=null,o(d)}});F.menu=function(b,a,d,c){return["M",b,a+2.5,"L",b+d,a+2.5,"M",b,a+c/2+0.5,"L",b+d,a+c/2+0.5,"M",b,a+c-1.5,"L",b+d,a+c-1.5]};A.prototype.callbacks.push(function(b){var a,d=b.options.exporting,
+c=d.buttons;y=0;if(d.enabled!==!1){for(a in c)b.addButton(c[a]);t(b,"destroy",b.destroyExport)}})})(Highcharts);
diff --git a/html/includes/js/modules/exporting.src.js b/html/includes/js/modules/exporting.src.js
new file mode 100644
index 0000000..2111990
--- /dev/null
+++ b/html/includes/js/modules/exporting.src.js
@@ -0,0 +1,719 @@
+/**
+ * @license Highcharts JS v4.0.4 (2014-09-02)
+ * Exporting module
+ *
+ * (c) 2010-2014 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+// JSLint options:
+/*global Highcharts, document, window, Math, setTimeout */
+
+(function (Highcharts) { // encapsulate
+
+// create shortcuts
+var Chart = Highcharts.Chart,
+ addEvent = Highcharts.addEvent,
+ removeEvent = Highcharts.removeEvent,
+ createElement = Highcharts.createElement,
+ discardElement = Highcharts.discardElement,
+ css = Highcharts.css,
+ merge = Highcharts.merge,
+ each = Highcharts.each,
+ extend = Highcharts.extend,
+ math = Math,
+ mathMax = math.max,
+ doc = document,
+ win = window,
+ isTouchDevice = Highcharts.isTouchDevice,
+ M = 'M',
+ L = 'L',
+ DIV = 'div',
+ HIDDEN = 'hidden',
+ NONE = 'none',
+ PREFIX = 'highcharts-',
+ ABSOLUTE = 'absolute',
+ PX = 'px',
+ UNDEFINED,
+ symbols = Highcharts.Renderer.prototype.symbols,
+ defaultOptions = Highcharts.getOptions(),
+ buttonOffset;
+
+ // Add language
+ extend(defaultOptions.lang, {
+ printChart: 'Print chart',
+ downloadPNG: 'Download PNG image',
+ downloadJPEG: 'Download JPEG image',
+ downloadPDF: 'Download PDF document',
+ downloadSVG: 'Download SVG vector image',
+ contextButtonTitle: 'Chart context menu'
+ });
+
+// Buttons and menus are collected in a separate config option set called 'navigation'.
+// This can be extended later to add control buttons like zoom and pan right click menus.
+defaultOptions.navigation = {
+ menuStyle: {
+ border: '1px solid #A0A0A0',
+ background: '#FFFFFF',
+ padding: '5px 0'
+ },
+ menuItemStyle: {
+ padding: '0 10px',
+ background: NONE,
+ color: '#303030',
+ fontSize: isTouchDevice ? '14px' : '11px'
+ },
+ menuItemHoverStyle: {
+ background: '#4572A5',
+ color: '#FFFFFF'
+ },
+
+ buttonOptions: {
+ symbolFill: '#E0E0E0',
+ symbolSize: 14,
+ symbolStroke: '#666',
+ symbolStrokeWidth: 3,
+ symbolX: 12.5,
+ symbolY: 10.5,
+ align: 'right',
+ buttonSpacing: 3,
+ height: 22,
+ // text: null,
+ theme: {
+ fill: 'white', // capture hover
+ stroke: 'none'
+ },
+ verticalAlign: 'top',
+ width: 24
+ }
+};
+
+
+
+// Add the export related options
+defaultOptions.exporting = {
+ //enabled: true,
+ //filename: 'chart',
+ type: 'image/png',
+ url: 'http://export.highcharts.com/',
+ //width: undefined,
+ //scale: 2
+ buttons: {
+ contextButton: {
+ menuClassName: PREFIX + 'contextmenu',
+ //x: -10,
+ symbol: 'menu',
+ _titleKey: 'contextButtonTitle',
+ menuItems: [{
+ textKey: 'printChart',
+ onclick: function () {
+ this.print();
+ }
+ }, {
+ separator: true
+ }, {
+ textKey: 'downloadPNG',
+ onclick: function () {
+ this.exportChart();
+ }
+ }, {
+ textKey: 'downloadJPEG',
+ onclick: function () {
+ this.exportChart({
+ type: 'image/jpeg'
+ });
+ }
+ }, {
+ textKey: 'downloadPDF',
+ onclick: function () {
+ this.exportChart({
+ type: 'application/pdf'
+ });
+ }
+ }, {
+ textKey: 'downloadSVG',
+ onclick: function () {
+ this.exportChart({
+ type: 'image/svg+xml'
+ });
+ }
+ }
+ // Enable this block to add "View SVG" to the dropdown menu
+ /*
+ ,{
+
+ text: 'View SVG',
+ onclick: function () {
+ var svg = this.getSVG()
+ .replace(/</g, '\n&lt;')
+ .replace(/>/g, '&gt;');
+
+ doc.body.innerHTML = '<pre>' + svg + '</pre>';
+ }
+ } // */
+ ]
+ }
+ }
+};
+
+// Add the Highcharts.post utility
+Highcharts.post = function (url, data, formAttributes) {
+ var name,
+ form;
+
+ // create the form
+ form = createElement('form', merge({
+ method: 'post',
+ action: url,
+ enctype: 'multipart/form-data'
+ }, formAttributes), {
+ display: NONE
+ }, doc.body);
+
+ // add the data
+ for (name in data) {
+ createElement('input', {
+ type: HIDDEN,
+ name: name,
+ value: data[name]
+ }, null, form);
+ }
+
+ // submit
+ form.submit();
+
+ // clean up
+ discardElement(form);
+};
+
+extend(Chart.prototype, {
+
+ /**
+ * Return an SVG representation of the chart
+ *
+ * @param additionalOptions {Object} Additional chart options for the generated SVG representation
+ */
+ getSVG: function (additionalOptions) {
+ var chart = this,
+ chartCopy,
+ sandbox,
+ svg,
+ seriesOptions,
+ sourceWidth,
+ sourceHeight,
+ cssWidth,
+ cssHeight,
+ options = merge(chart.options, additionalOptions); // copy the options and add extra options
+
+ // IE compatibility hack for generating SVG content that it doesn't really understand
+ if (!doc.createElementNS) {
+ /*jslint unparam: true*//* allow unused parameter ns in function below */
+ doc.createElementNS = function (ns, tagName) {
+ return doc.createElement(tagName);
+ };
+ /*jslint unparam: false*/
+ }
+
+ // create a sandbox where a new chart will be generated
+ sandbox = createElement(DIV, null, {
+ position: ABSOLUTE,
+ top: '-9999em',
+ width: chart.chartWidth + PX,
+ height: chart.chartHeight + PX
+ }, doc.body);
+
+ // get the source size
+ cssWidth = chart.renderTo.style.width;
+ cssHeight = chart.renderTo.style.height;
+ sourceWidth = options.exporting.sourceWidth ||
+ options.chart.width ||
+ (/px$/.test(cssWidth) && parseInt(cssWidth, 10)) ||
+ 600;
+ sourceHeight = options.exporting.sourceHeight ||
+ options.chart.height ||
+ (/px$/.test(cssHeight) && parseInt(cssHeight, 10)) ||
+ 400;
+
+ // override some options
+ extend(options.chart, {
+ animation: false,
+ renderTo: sandbox,
+ forExport: true,
+ width: sourceWidth,
+ height: sourceHeight
+ });
+ options.exporting.enabled = false; // hide buttons in print
+
+ // prepare for replicating the chart
+ options.series = [];
+ each(chart.series, function (serie) {
+ seriesOptions = merge(serie.options, {
+ animation: false, // turn off animation
+ enableMouseTracking: false,
+ showCheckbox: false,
+ visible: serie.visible
+ });
+
+ if (!seriesOptions.isInternal) { // used for the navigator series that has its own option set
+ options.series.push(seriesOptions);
+ }
+ });
+
+ // generate the chart copy
+ chartCopy = new Highcharts.Chart(options, chart.callback);
+
+ // reflect axis extremes in the export
+ each(['xAxis', 'yAxis'], function (axisType) {
+ each(chart[axisType], function (axis, i) {
+ var axisCopy = chartCopy[axisType][i],
+ extremes = axis.getExtremes(),
+ userMin = extremes.userMin,
+ userMax = extremes.userMax;
+
+ if (axisCopy && (userMin !== UNDEFINED || userMax !== UNDEFINED)) {
+ axisCopy.setExtremes(userMin, userMax, true, false);
+ }
+ });
+ });
+
+ // get the SVG from the container's innerHTML
+ svg = chartCopy.container.innerHTML;
+
+ // free up memory
+ options = null;
+ chartCopy.destroy();
+ discardElement(sandbox);
+
+ // sanitize
+ svg = svg
+ .replace(/zIndex="[^"]+"/g, '')
+ .replace(/isShadow="[^"]+"/g, '')
+ .replace(/symbolName="[^"]+"/g, '')
+ .replace(/jQuery[0-9]+="[^"]+"/g, '')
+ .replace(/url\([^#]+#/g, 'url(#')
+ .replace(/<svg /, '<svg xmlns:xlink="http://www.w3.org/1999/xlink" ')
+ .replace(/ href=/g, ' xlink:href=')
+ .replace(/\n/, ' ')
+ // Any HTML added to the container after the SVG (#894)
+ .replace(/<\/svg>.*?$/, '</svg>')
+ // Batik doesn't support rgba fills and strokes (#3095)
+ .replace(/(fill|stroke)="rgba\(([ 0-9]+,[ 0-9]+,[ 0-9]+),([ 0-9\.]+)\)"/g, '$1="rgb($2)" $1-opacity="$3"')
+ /* This fails in IE < 8
+ .replace(/([0-9]+)\.([0-9]+)/g, function(s1, s2, s3) { // round off to save weight
+ return s2 +'.'+ s3[0];
+ })*/
+
+ // Replace HTML entities, issue #347
+ .replace(/&nbsp;/g, '\u00A0') // no-break space
+ .replace(/&shy;/g, '\u00AD') // soft hyphen
+
+ // IE specific
+ .replace(/<IMG /g, '<image ')
+ .replace(/height=([^" ]+)/g, 'height="$1"')
+ .replace(/width=([^" ]+)/g, 'width="$1"')
+ .replace(/hc-svg-href="([^"]+)">/g, 'xlink:href="$1"/>')
+ .replace(/id=([^" >]+)/g, 'id="$1"')
+ .replace(/class=([^" >]+)/g, 'class="$1"')
+ .replace(/ transform /g, ' ')
+ .replace(/:(path|rect)/g, '$1')
+ .replace(/style="([^"]+)"/g, function (s) {
+ return s.toLowerCase();
+ });
+
+ // IE9 beta bugs with innerHTML. Test again with final IE9.
+ svg = svg.replace(/(url\(#highcharts-[0-9]+)&quot;/g, '$1')
+ .replace(/&quot;/g, "'");
+
+ return svg;
+ },
+
+ /**
+ * Submit the SVG representation of the chart to the server
+ * @param {Object} options Exporting options. Possible members are url, type, width and formAttributes.
+ * @param {Object} chartOptions Additional chart options for the SVG representation of the chart
+ */
+ exportChart: function (options, chartOptions) {
+ options = options || {};
+
+ var chart = this,
+ chartExportingOptions = chart.options.exporting,
+ svg = chart.getSVG(merge(
+ { chart: { borderRadius: 0 } },
+ chartExportingOptions.chartOptions,
+ chartOptions,
+ {
+ exporting: {
+ sourceWidth: options.sourceWidth || chartExportingOptions.sourceWidth,
+ sourceHeight: options.sourceHeight || chartExportingOptions.sourceHeight
+ }
+ }
+ ));
+
+ // merge the options
+ options = merge(chart.options.exporting, options);
+
+ // do the post
+ Highcharts.post(options.url, {
+ filename: options.filename || 'chart',
+ type: options.type,
+ width: options.width || 0, // IE8 fails to post undefined correctly, so use 0
+ scale: options.scale || 2,
+ svg: svg
+ }, options.formAttributes);
+
+ },
+
+ /**
+ * Print the chart
+ */
+ print: function () {
+
+ var chart = this,
+ container = chart.container,
+ origDisplay = [],
+ origParent = container.parentNode,
+ body = doc.body,
+ childNodes = body.childNodes;
+
+ if (chart.isPrinting) { // block the button while in printing mode
+ return;
+ }
+
+ chart.isPrinting = true;
+
+ // hide all body content
+ each(childNodes, function (node, i) {
+ if (node.nodeType === 1) {
+ origDisplay[i] = node.style.display;
+ node.style.display = NONE;
+ }
+ });
+
+ // pull out the chart
+ body.appendChild(container);
+
+ // print
+ win.focus(); // #1510
+ win.print();
+
+ // allow the browser to prepare before reverting
+ setTimeout(function () {
+
+ // put the chart back in
+ origParent.appendChild(container);
+
+ // restore all body content
+ each(childNodes, function (node, i) {
+ if (node.nodeType === 1) {
+ node.style.display = origDisplay[i];
+ }
+ });
+
+ chart.isPrinting = false;
+
+ }, 1000);
+
+ },
+
+ /**
+ * Display a popup menu for choosing the export type
+ *
+ * @param {String} className An identifier for the menu
+ * @param {Array} items A collection with text and onclicks for the items
+ * @param {Number} x The x position of the opener button
+ * @param {Number} y The y position of the opener button
+ * @param {Number} width The width of the opener button
+ * @param {Number} height The height of the opener button
+ */
+ contextMenu: function (className, items, x, y, width, height, button) {
+ var chart = this,
+ navOptions = chart.options.navigation,
+ menuItemStyle = navOptions.menuItemStyle,
+ chartWidth = chart.chartWidth,
+ chartHeight = chart.chartHeight,
+ cacheName = 'cache-' + className,
+ menu = chart[cacheName],
+ menuPadding = mathMax(width, height), // for mouse leave detection
+ boxShadow = '3px 3px 10px #888',
+ innerMenu,
+ hide,
+ hideTimer,
+ menuStyle,
+ docMouseUpHandler = function (e) {
+ if (!chart.pointer.inClass(e.target, className)) {
+ hide();
+ }
+ };
+
+ // create the menu only the first time
+ if (!menu) {
+
+ // create a HTML element above the SVG
+ chart[cacheName] = menu = createElement(DIV, {
+ className: className
+ }, {
+ position: ABSOLUTE,
+ zIndex: 1000,
+ padding: menuPadding + PX
+ }, chart.container);
+
+ innerMenu = createElement(DIV, null,
+ extend({
+ MozBoxShadow: boxShadow,
+ WebkitBoxShadow: boxShadow,
+ boxShadow: boxShadow
+ }, navOptions.menuStyle), menu);
+
+ // hide on mouse out
+ hide = function () {
+ css(menu, { display: NONE });
+ if (button) {
+ button.setState(0);
+ }
+ chart.openMenu = false;
+ };
+
+ // Hide the menu some time after mouse leave (#1357)
+ addEvent(menu, 'mouseleave', function () {
+ hideTimer = setTimeout(hide, 500);
+ });
+ addEvent(menu, 'mouseenter', function () {
+ clearTimeout(hideTimer);
+ });
+
+
+ // Hide it on clicking or touching outside the menu (#2258, #2335, #2407)
+ addEvent(document, 'mouseup', docMouseUpHandler);
+ addEvent(chart, 'destroy', function () {
+ removeEvent(document, 'mouseup', docMouseUpHandler);
+ });
+
+
+ // create the items
+ each(items, function (item) {
+ if (item) {
+ var element = item.separator ?
+ createElement('hr', null, null, innerMenu) :
+ createElement(DIV, {
+ onmouseover: function () {
+ css(this, navOptions.menuItemHoverStyle);
+ },
+ onmouseout: function () {
+ css(this, menuItemStyle);
+ },
+ onclick: function () {
+ hide();
+ item.onclick.apply(chart, arguments);
+ },
+ innerHTML: item.text || chart.options.lang[item.textKey]
+ }, extend({
+ cursor: 'pointer'
+ }, menuItemStyle), innerMenu);
+
+
+ // Keep references to menu divs to be able to destroy them
+ chart.exportDivElements.push(element);
+ }
+ });
+
+ // Keep references to menu and innerMenu div to be able to destroy them
+ chart.exportDivElements.push(innerMenu, menu);
+
+ chart.exportMenuWidth = menu.offsetWidth;
+ chart.exportMenuHeight = menu.offsetHeight;
+ }
+
+ menuStyle = { display: 'block' };
+
+ // if outside right, right align it
+ if (x + chart.exportMenuWidth > chartWidth) {
+ menuStyle.right = (chartWidth - x - width - menuPadding) + PX;
+ } else {
+ menuStyle.left = (x - menuPadding) + PX;
+ }
+ // if outside bottom, bottom align it
+ if (y + height + chart.exportMenuHeight > chartHeight && button.alignOptions.verticalAlign !== 'top') {
+ menuStyle.bottom = (chartHeight - y - menuPadding) + PX;
+ } else {
+ menuStyle.top = (y + height - menuPadding) + PX;
+ }
+
+ css(menu, menuStyle);
+ chart.openMenu = true;
+ },
+
+ /**
+ * Add the export button to the chart
+ */
+ addButton: function (options) {
+ var chart = this,
+ renderer = chart.renderer,
+ btnOptions = merge(chart.options.navigation.buttonOptions, options),
+ onclick = btnOptions.onclick,
+ menuItems = btnOptions.menuItems,
+ symbol,
+ button,
+ symbolAttr = {
+ stroke: btnOptions.symbolStroke,
+ fill: btnOptions.symbolFill
+ },
+ symbolSize = btnOptions.symbolSize || 12;
+ if (!chart.btnCount) {
+ chart.btnCount = 0;
+ }
+
+ // Keeps references to the button elements
+ if (!chart.exportDivElements) {
+ chart.exportDivElements = [];
+ chart.exportSVGElements = [];
+ }
+
+ if (btnOptions.enabled === false) {
+ return;
+ }
+
+
+ var attr = btnOptions.theme,
+ states = attr.states,
+ hover = states && states.hover,
+ select = states && states.select,
+ callback;
+
+ delete attr.states;
+
+ if (onclick) {
+ callback = function () {
+ onclick.apply(chart, arguments);
+ };
+
+ } else if (menuItems) {
+ callback = function () {
+ chart.contextMenu(
+ button.menuClassName,
+ menuItems,
+ button.translateX,
+ button.translateY,
+ button.width,
+ button.height,
+ button
+ );
+ button.setState(2);
+ };
+ }
+
+
+ if (btnOptions.text && btnOptions.symbol) {
+ attr.paddingLeft = Highcharts.pick(attr.paddingLeft, 25);
+
+ } else if (!btnOptions.text) {
+ extend(attr, {
+ width: btnOptions.width,
+ height: btnOptions.height,
+ padding: 0
+ });
+ }
+
+ button = renderer.button(btnOptions.text, 0, 0, callback, attr, hover, select)
+ .attr({
+ title: chart.options.lang[btnOptions._titleKey],
+ 'stroke-linecap': 'round'
+ });
+ button.menuClassName = options.menuClassName || PREFIX + 'menu-' + chart.btnCount++;
+
+ if (btnOptions.symbol) {
+ symbol = renderer.symbol(
+ btnOptions.symbol,
+ btnOptions.symbolX - (symbolSize / 2),
+ btnOptions.symbolY - (symbolSize / 2),
+ symbolSize,
+ symbolSize
+ )
+ .attr(extend(symbolAttr, {
+ 'stroke-width': btnOptions.symbolStrokeWidth || 1,
+ zIndex: 1
+ })).add(button);
+ }
+
+ button.add()
+ .align(extend(btnOptions, {
+ width: button.width,
+ x: Highcharts.pick(btnOptions.x, buttonOffset) // #1654
+ }), true, 'spacingBox');
+
+ buttonOffset += (button.width + btnOptions.buttonSpacing) * (btnOptions.align === 'right' ? -1 : 1);
+
+ chart.exportSVGElements.push(button, symbol);
+
+ },
+
+ /**
+ * Destroy the buttons.
+ */
+ destroyExport: function (e) {
+ var chart = e.target,
+ i,
+ elem;
+
+ // Destroy the extra buttons added
+ for (i = 0; i < chart.exportSVGElements.length; i++) {
+ elem = chart.exportSVGElements[i];
+
+ // Destroy and null the svg/vml elements
+ if (elem) { // #1822
+ elem.onclick = elem.ontouchstart = null;
+ chart.exportSVGElements[i] = elem.destroy();
+ }
+ }
+
+ // Destroy the divs for the menu
+ for (i = 0; i < chart.exportDivElements.length; i++) {
+ elem = chart.exportDivElements[i];
+
+ // Remove the event handler
+ removeEvent(elem, 'mouseleave');
+
+ // Remove inline events
+ chart.exportDivElements[i] = elem.onmouseout = elem.onmouseover = elem.ontouchstart = elem.onclick = null;
+
+ // Destroy the div by moving to garbage bin
+ discardElement(elem);
+ }
+ }
+});
+
+
+symbols.menu = function (x, y, width, height) {
+ var arr = [
+ M, x, y + 2.5,
+ L, x + width, y + 2.5,
+ M, x, y + height / 2 + 0.5,
+ L, x + width, y + height / 2 + 0.5,
+ M, x, y + height - 1.5,
+ L, x + width, y + height - 1.5
+ ];
+ return arr;
+};
+
+// Add the buttons on chart load
+Chart.prototype.callbacks.push(function (chart) {
+ var n,
+ exportingOptions = chart.options.exporting,
+ buttons = exportingOptions.buttons;
+
+ buttonOffset = 0;
+
+ if (exportingOptions.enabled !== false) {
+
+ for (n in buttons) {
+ chart.addButton(buttons[n]);
+ }
+
+ // Destroy the export elements at chart destroy
+ addEvent(chart, 'destroy', chart.destroyExport);
+ }
+
+});
+
+
+}(Highcharts));
diff --git a/html/includes/js/modules/funnel.js b/html/includes/js/modules/funnel.js
new file mode 100644
index 0000000..efaf891
--- /dev/null
+++ b/html/includes/js/modules/funnel.js
@@ -0,0 +1,13 @@
+/*
+
+ Highcharts funnel module
+
+ (c) 2010-2014 Torstein Honsi
+
+ License: www.highcharts.com/license
+*/
+(function(b){var d=b.getOptions(),v=d.plotOptions,q=b.seriesTypes,E=b.merge,D=function(){},A=b.each;v.funnel=E(v.pie,{animation:!1,center:["50%","50%"],width:"90%",neckWidth:"30%",height:"100%",neckHeight:"25%",reversed:!1,dataLabels:{connectorWidth:1,connectorColor:"#606060"},size:!0,states:{select:{color:"#C0C0C0",borderColor:"#000000",shadow:!1}}});q.funnel=b.extendClass(q.pie,{type:"funnel",animate:D,singularTooltips:!0,translate:function(){var a=function(j,a){return/%$/.test(j)?a*parseInt(j,
+10)/100:parseInt(j,10)},B=0,f=this.chart,c=this.options,g=c.reversed,b=f.plotWidth,n=f.plotHeight,o=0,f=c.center,h=a(f[0],b),d=a(f[1],n),q=a(c.width,b),k,r,e=a(c.height,n),s=a(c.neckWidth,b),t=a(c.neckHeight,n),w=e-t,a=this.data,x,y,v=c.dataLabels.position==="left"?1:0,z,l,C,p,i,u,m;this.getWidthAt=r=function(j){return j>e-t||e===t?s:s+(q-s)*((e-t-j)/(e-t))};this.getX=function(j,a){return h+(a?-1:1)*(r(g?n-j:j)/2+c.dataLabels.distance)};this.center=[h,d,e];this.centerX=h;A(a,function(a){B+=a.y});
+A(a,function(a){m=null;y=B?a.y/B:0;l=d-e/2+o*e;i=l+y*e;k=r(l);z=h-k/2;C=z+k;k=r(i);p=h-k/2;u=p+k;l>w?(z=p=h-s/2,C=u=h+s/2):i>w&&(m=i,k=r(w),p=h-k/2,u=p+k,i=w);g&&(l=e-l,i=e-i,m=m?e-m:null);x=["M",z,l,"L",C,l,u,i];m&&x.push(u,m,p,m);x.push(p,i,"Z");a.shapeType="path";a.shapeArgs={d:x};a.percentage=y*100;a.plotX=h;a.plotY=(l+(m||i))/2;a.tooltipPos=[h,a.plotY];a.slice=D;a.half=v;o+=y})},drawPoints:function(){var a=this,b=a.options,f=a.chart.renderer;A(a.data,function(c){var g=c.graphic,d=c.shapeArgs;
+g?g.animate(d):c.graphic=f.path(d).attr({fill:c.color,stroke:b.borderColor,"stroke-width":b.borderWidth}).add(a.group)})},sortByAngle:function(a){a.sort(function(a,b){return a.plotY-b.plotY})},drawDataLabels:function(){var a=this.data,b=this.options.dataLabels.distance,f,c,g,d=a.length,n,o;for(this.center[2]-=2*b;d--;)g=a[d],c=(f=g.half)?1:-1,o=g.plotY,n=this.getX(o,f),g.labelPos=[0,o,n+(b-5)*c,o,n+b*c,o,f?"right":"left",0];q.pie.prototype.drawDataLabels.call(this)}});d.plotOptions.pyramid=b.merge(d.plotOptions.funnel,
+{neckWidth:"0%",neckHeight:"0%",reversed:!0});b.seriesTypes.pyramid=b.extendClass(b.seriesTypes.funnel,{type:"pyramid"})})(Highcharts);
diff --git a/html/includes/js/modules/funnel.src.js b/html/includes/js/modules/funnel.src.js
new file mode 100644
index 0000000..102d0ad
--- /dev/null
+++ b/html/includes/js/modules/funnel.src.js
@@ -0,0 +1,310 @@
+/**
+ * @license
+ * Highcharts funnel module
+ *
+ * (c) 2010-2014 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+/*global Highcharts */
+(function (Highcharts) {
+
+'use strict';
+
+// create shortcuts
+var defaultOptions = Highcharts.getOptions(),
+ defaultPlotOptions = defaultOptions.plotOptions,
+ seriesTypes = Highcharts.seriesTypes,
+ merge = Highcharts.merge,
+ noop = function () {},
+ each = Highcharts.each;
+
+// set default options
+defaultPlotOptions.funnel = merge(defaultPlotOptions.pie, {
+ animation: false,
+ center: ['50%', '50%'],
+ width: '90%',
+ neckWidth: '30%',
+ height: '100%',
+ neckHeight: '25%',
+ reversed: false,
+ dataLabels: {
+ //position: 'right',
+ connectorWidth: 1,
+ connectorColor: '#606060'
+ },
+ size: true, // to avoid adapting to data label size in Pie.drawDataLabels
+ states: {
+ select: {
+ color: '#C0C0C0',
+ borderColor: '#000000',
+ shadow: false
+ }
+ }
+});
+
+
+seriesTypes.funnel = Highcharts.extendClass(seriesTypes.pie, {
+
+ type: 'funnel',
+ animate: noop,
+ singularTooltips: true,
+
+ /**
+ * Overrides the pie translate method
+ */
+ translate: function () {
+
+ var
+ // Get positions - either an integer or a percentage string must be given
+ getLength = function (length, relativeTo) {
+ return (/%$/).test(length) ?
+ relativeTo * parseInt(length, 10) / 100 :
+ parseInt(length, 10);
+ },
+
+ sum = 0,
+ series = this,
+ chart = series.chart,
+ options = series.options,
+ reversed = options.reversed,
+ plotWidth = chart.plotWidth,
+ plotHeight = chart.plotHeight,
+ cumulative = 0, // start at top
+ center = options.center,
+ centerX = getLength(center[0], plotWidth),
+ centerY = getLength(center[1], plotHeight),
+ width = getLength(options.width, plotWidth),
+ tempWidth,
+ getWidthAt,
+ height = getLength(options.height, plotHeight),
+ neckWidth = getLength(options.neckWidth, plotWidth),
+ neckHeight = getLength(options.neckHeight, plotHeight),
+ neckY = height - neckHeight,
+ data = series.data,
+ path,
+ fraction,
+ half = options.dataLabels.position === 'left' ? 1 : 0,
+
+ x1,
+ y1,
+ x2,
+ x3,
+ y3,
+ x4,
+ y5;
+
+ // Return the width at a specific y coordinate
+ series.getWidthAt = getWidthAt = function (y) {
+ return y > height - neckHeight || height === neckHeight ?
+ neckWidth :
+ neckWidth + (width - neckWidth) * ((height - neckHeight - y) / (height - neckHeight));
+ };
+ series.getX = function (y, half) {
+ return centerX + (half ? -1 : 1) * ((getWidthAt(reversed ? plotHeight - y : y) / 2) + options.dataLabels.distance);
+ };
+
+ // Expose
+ series.center = [centerX, centerY, height];
+ series.centerX = centerX;
+
+ /*
+ * Individual point coordinate naming:
+ *
+ * x1,y1 _________________ x2,y1
+ * \ /
+ * \ /
+ * \ /
+ * \ /
+ * \ /
+ * x3,y3 _________ x4,y3
+ *
+ * Additional for the base of the neck:
+ *
+ * | |
+ * | |
+ * | |
+ * x3,y5 _________ x4,y5
+ */
+
+
+
+
+ // get the total sum
+ each(data, function (point) {
+ sum += point.y;
+ });
+
+ each(data, function (point) {
+ // set start and end positions
+ y5 = null;
+ fraction = sum ? point.y / sum : 0;
+ y1 = centerY - height / 2 + cumulative * height;
+ y3 = y1 + fraction * height;
+ //tempWidth = neckWidth + (width - neckWidth) * ((height - neckHeight - y1) / (height - neckHeight));
+ tempWidth = getWidthAt(y1);
+ x1 = centerX - tempWidth / 2;
+ x2 = x1 + tempWidth;
+ tempWidth = getWidthAt(y3);
+ x3 = centerX - tempWidth / 2;
+ x4 = x3 + tempWidth;
+
+ // the entire point is within the neck
+ if (y1 > neckY) {
+ x1 = x3 = centerX - neckWidth / 2;
+ x2 = x4 = centerX + neckWidth / 2;
+
+ // the base of the neck
+ } else if (y3 > neckY) {
+ y5 = y3;
+
+ tempWidth = getWidthAt(neckY);
+ x3 = centerX - tempWidth / 2;
+ x4 = x3 + tempWidth;
+
+ y3 = neckY;
+ }
+
+ if (reversed) {
+ y1 = height - y1;
+ y3 = height - y3;
+ y5 = (y5 ? height - y5 : null);
+ }
+ // save the path
+ path = [
+ 'M',
+ x1, y1,
+ 'L',
+ x2, y1,
+ x4, y3
+ ];
+ if (y5) {
+ path.push(x4, y5, x3, y5);
+ }
+ path.push(x3, y3, 'Z');
+
+ // prepare for using shared dr
+ point.shapeType = 'path';
+ point.shapeArgs = { d: path };
+
+
+ // for tooltips and data labels
+ point.percentage = fraction * 100;
+ point.plotX = centerX;
+ point.plotY = (y1 + (y5 || y3)) / 2;
+
+ // Placement of tooltips and data labels
+ point.tooltipPos = [
+ centerX,
+ point.plotY
+ ];
+
+ // Slice is a noop on funnel points
+ point.slice = noop;
+
+ // Mimicking pie data label placement logic
+ point.half = half;
+
+ cumulative += fraction;
+ });
+ },
+ /**
+ * Draw a single point (wedge)
+ * @param {Object} point The point object
+ * @param {Object} color The color of the point
+ * @param {Number} brightness The brightness relative to the color
+ */
+ drawPoints: function () {
+ var series = this,
+ options = series.options,
+ chart = series.chart,
+ renderer = chart.renderer;
+
+ each(series.data, function (point) {
+
+ var graphic = point.graphic,
+ shapeArgs = point.shapeArgs;
+
+ if (!graphic) { // Create the shapes
+ point.graphic = renderer.path(shapeArgs).
+ attr({
+ fill: point.color,
+ stroke: options.borderColor,
+ 'stroke-width': options.borderWidth
+ }).
+ add(series.group);
+
+ } else { // Update the shapes
+ graphic.animate(shapeArgs);
+ }
+ });
+ },
+
+ /**
+ * Funnel items don't have angles (#2289)
+ */
+ sortByAngle: function (points) {
+ points.sort(function (a, b) {
+ return a.plotY - b.plotY;
+ });
+ },
+
+ /**
+ * Extend the pie data label method
+ */
+ drawDataLabels: function () {
+ var data = this.data,
+ labelDistance = this.options.dataLabels.distance,
+ leftSide,
+ sign,
+ point,
+ i = data.length,
+ x,
+ y;
+
+ // In the original pie label anticollision logic, the slots are distributed
+ // from one labelDistance above to one labelDistance below the pie. In funnels
+ // we don't want this.
+ this.center[2] -= 2 * labelDistance;
+
+ // Set the label position array for each point.
+ while (i--) {
+ point = data[i];
+ leftSide = point.half;
+ sign = leftSide ? 1 : -1;
+ y = point.plotY;
+ x = this.getX(y, leftSide);
+
+ // set the anchor point for data labels
+ point.labelPos = [
+ 0, // first break of connector
+ y, // a/a
+ x + (labelDistance - 5) * sign, // second break, right outside point shape
+ y, // a/a
+ x + labelDistance * sign, // landing point for connector
+ y, // a/a
+ leftSide ? 'right' : 'left', // alignment
+ 0 // center angle
+ ];
+ }
+
+ seriesTypes.pie.prototype.drawDataLabels.call(this);
+ }
+
+});
+
+/**
+ * Pyramid series type.
+ * A pyramid series is a special type of funnel, without neck and reversed by default.
+ */
+defaultOptions.plotOptions.pyramid = Highcharts.merge(defaultOptions.plotOptions.funnel, {
+ neckWidth: '0%',
+ neckHeight: '0%',
+ reversed: true
+});
+Highcharts.seriesTypes.pyramid = Highcharts.extendClass(Highcharts.seriesTypes.funnel, {
+ type: 'pyramid'
+});
+
+}(Highcharts));
diff --git a/html/includes/js/modules/heatmap.js b/html/includes/js/modules/heatmap.js
new file mode 100644
index 0000000..fc9b856
--- /dev/null
+++ b/html/includes/js/modules/heatmap.js
@@ -0,0 +1,23 @@
+/*
+ Highcharts JS v4.0.4 (2014-09-02)
+
+ (c) 2011-2014 Torstein Honsi
+
+ License: www.highcharts.com/license
+*/
+(function(h){var k=h.Axis,y=h.Chart,l=h.Color,z=h.Legend,t=h.LegendSymbolMixin,u=h.Series,v=h.SVGRenderer,w=h.getOptions(),i=h.each,r=h.extend,A=h.extendClass,m=h.merge,o=h.pick,x=h.numberFormat,p=h.seriesTypes,s=h.wrap,n=function(){},q=h.ColorAxis=function(){this.isColorAxis=!0;this.init.apply(this,arguments)};r(q.prototype,k.prototype);r(q.prototype,{defaultColorAxisOptions:{lineWidth:0,gridLineWidth:1,tickPixelInterval:72,startOnTick:!0,endOnTick:!0,offset:0,marker:{animation:{duration:50},color:"gray",
+width:0.01},labels:{overflow:"justify"},minColor:"#EFEFFF",maxColor:"#003875",tickLength:5},init:function(b,a){var c=b.options.legend.layout!=="vertical",d;d=m(this.defaultColorAxisOptions,{side:c?2:1,reversed:!c},a,{isX:c,opposite:!c,showEmpty:!1,title:null,isColor:!0});k.prototype.init.call(this,b,d);a.dataClasses&&this.initDataClasses(a);this.initStops(a);this.isXAxis=!0;this.horiz=c;this.zoomEnabled=!1},tweenColors:function(b,a,c){var d=a.rgba[3]!==1||b.rgba[3]!==1;return(d?"rgba(":"rgb(")+Math.round(a.rgba[0]+
+(b.rgba[0]-a.rgba[0])*(1-c))+","+Math.round(a.rgba[1]+(b.rgba[1]-a.rgba[1])*(1-c))+","+Math.round(a.rgba[2]+(b.rgba[2]-a.rgba[2])*(1-c))+(d?","+(a.rgba[3]+(b.rgba[3]-a.rgba[3])*(1-c)):"")+")"},initDataClasses:function(b){var a=this,c=this.chart,d,e=0,f=this.options,g=b.dataClasses.length;this.dataClasses=d=[];this.legendItems=[];i(b.dataClasses,function(b,h){var i,b=m(b);d.push(b);if(!b.color)f.dataClassColor==="category"?(i=c.options.colors,b.color=i[e++],e===i.length&&(e=0)):b.color=a.tweenColors(l(f.minColor),
+l(f.maxColor),g<2?0.5:h/(g-1))})},initStops:function(b){this.stops=b.stops||[[0,this.options.minColor],[1,this.options.maxColor]];i(this.stops,function(a){a.color=l(a[1])})},setOptions:function(b){k.prototype.setOptions.call(this,b);this.options.crosshair=this.options.marker;this.coll="colorAxis"},setAxisSize:function(){var b=this.legendSymbol,a=this.chart,c,d,e;if(b)this.left=c=b.attr("x"),this.top=d=b.attr("y"),this.width=e=b.attr("width"),this.height=b=b.attr("height"),this.right=a.chartWidth-
+c-e,this.bottom=a.chartHeight-d-b,this.len=this.horiz?e:b,this.pos=this.horiz?c:d},toColor:function(b,a){var c,d=this.stops,e,f=this.dataClasses,g,j;if(f)for(j=f.length;j--;){if(g=f[j],e=g.from,d=g.to,(e===void 0||b>=e)&&(d===void 0||b<=d)){c=g.color;if(a)a.dataClass=j;break}}else{this.isLog&&(b=this.val2lin(b));c=1-(this.max-b)/(this.max-this.min||1);for(j=d.length;j--;)if(c>d[j][0])break;e=d[j]||d[j+1];d=d[j+1]||e;c=1-(d[0]-c)/(d[0]-e[0]||1);c=this.tweenColors(e.color,d.color,c)}return c},getOffset:function(){var b=
+this.legendGroup,a=this.chart.axisOffset[this.side];if(b){k.prototype.getOffset.call(this);if(!this.axisGroup.parentGroup)this.axisGroup.add(b),this.gridGroup.add(b),this.labelGroup.add(b),this.added=!0;this.chart.axisOffset[this.side]=a}},setLegendColor:function(){var b,a=this.options;b=this.horiz?[0,0,1,0]:[0,0,0,1];this.legendColor={linearGradient:{x1:b[0],y1:b[1],x2:b[2],y2:b[3]},stops:a.stops||[[0,a.minColor],[1,a.maxColor]]}},drawLegendSymbol:function(b,a){var c=b.padding,d=b.options,e=this.horiz,
+f=o(d.symbolWidth,e?200:12),g=o(d.symbolHeight,e?12:200),j=o(d.labelPadding,e?16:30),d=o(d.itemDistance,10);this.setLegendColor();a.legendSymbol=this.chart.renderer.rect(0,b.baseline-11,f,g).attr({zIndex:1}).add(a.legendGroup);a.legendSymbol.getBBox();this.legendItemWidth=f+c+(e?d:j);this.legendItemHeight=g+c+(e?j:0)},setState:n,visible:!0,setVisible:n,getSeriesExtremes:function(){var b;if(this.series.length)b=this.series[0],this.dataMin=b.valueMin,this.dataMax=b.valueMax},drawCrosshair:function(b,
+a){var c=!this.cross,d=a&&a.plotX,e=a&&a.plotY,f,g=this.pos,j=this.len;if(a)f=this.toPixels(a.value),f<g?f=g-2:f>g+j&&(f=g+j+2),a.plotX=f,a.plotY=this.len-f,k.prototype.drawCrosshair.call(this,b,a),a.plotX=d,a.plotY=e,!c&&this.cross&&this.cross.attr({fill:this.crosshair.color}).add(this.labelGroup)},getPlotLinePath:function(b,a,c,d,e){return e?this.horiz?["M",e-4,this.top-6,"L",e+4,this.top-6,e,this.top,"Z"]:["M",this.left,e,"L",this.left-6,e+6,this.left-6,e-6,"Z"]:k.prototype.getPlotLinePath.call(this,
+b,a,c,d)},update:function(b,a){i(this.series,function(a){a.isDirtyData=!0});k.prototype.update.call(this,b,a);this.legendItem&&(this.setLegendColor(),this.chart.legend.colorizeItem(this,!0))},getDataClassLegendSymbols:function(){var b=this,a=this.chart,c=this.legendItems,d=a.options.legend,e=d.valueDecimals,f=d.valueSuffix||"",g;c.length||i(this.dataClasses,function(d,h){var k=!0,l=d.from,m=d.to;g="";l===void 0?g="< ":m===void 0&&(g="> ");l!==void 0&&(g+=x(l,e)+f);l!==void 0&&m!==void 0&&(g+=" - ");
+m!==void 0&&(g+=x(m,e)+f);c.push(r({chart:a,name:g,options:{},drawLegendSymbol:t.drawRectangle,visible:!0,setState:n,setVisible:function(){k=this.visible=!k;i(b.series,function(a){i(a.points,function(a){a.dataClass===h&&a.setVisible(k)})});a.legend.colorizeItem(this,k)}},d))});return c},name:""});i(["fill","stroke"],function(b){HighchartsAdapter.addAnimSetter(b,function(a){a.elem.attr(b,q.prototype.tweenColors(l(a.start),l(a.end),a.pos))})});s(y.prototype,"getAxes",function(b){var a=this.options.colorAxis;
+b.call(this);this.colorAxis=[];a&&new q(this,a)});s(z.prototype,"getAllItems",function(b){var a=[],c=this.chart.colorAxis[0];c&&(c.options.dataClasses?a=a.concat(c.getDataClassLegendSymbols()):a.push(c),i(c.series,function(a){a.options.showInLegend=!1}));return a.concat(b.call(this))});h={pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color",dashstyle:"dashStyle"},pointArrayMap:["value"],axisTypes:["xAxis","yAxis","colorAxis"],optionalAxis:"colorAxis",trackerGroups:["group",
+"markerGroup","dataLabelsGroup"],getSymbol:n,parallelArrays:["x","y","value"],colorKey:"value",translateColors:function(){var b=this,a=this.options.nullColor,c=this.colorAxis,d=this.colorKey;i(this.data,function(e){var f=e[d];if(f=f===null?a:c&&f!==void 0?c.toColor(f,e):e.color||b.color)e.color=f})}};s(v.prototype,"buildText",function(b,a){var c=a.styles&&a.styles.HcTextStroke;b.call(this,a);c&&a.applyTextStroke&&a.applyTextStroke(c)});v.prototype.Element.prototype.applyTextStroke=function(b){var a=
+this.element,c,d,b=b.split(" ");c=a.getElementsByTagName("tspan");d=a.firstChild;this.ySetter=this.xSetter;i([].slice.call(c),function(c,f){var g;f===0&&(c.setAttribute("x",a.getAttribute("x")),(f=a.getAttribute("y"))!==null&&c.setAttribute("y",f));g=c.cloneNode(1);g.setAttribute("stroke",b[1]);g.setAttribute("stroke-width",b[0]);g.setAttribute("stroke-linejoin","round");a.insertBefore(g,d)})};w.plotOptions.heatmap=m(w.plotOptions.scatter,{animation:!1,borderWidth:0,nullColor:"#F8F8F8",dataLabels:{formatter:function(){return this.point.value},
+verticalAlign:"middle",crop:!1,overflow:!1,style:{color:"white",fontWeight:"bold",HcTextStroke:"1px rgba(0,0,0,0.5)"}},marker:null,tooltip:{pointFormat:"{point.x}, {point.y}: {point.value}<br/>"},states:{normal:{animation:!0},hover:{brightness:0.2}}});p.heatmap=A(p.scatter,m(h,{type:"heatmap",pointArrayMap:["y","value"],hasPointSpecificOptions:!0,supportsDrilldown:!0,getExtremesFromAll:!0,init:function(){p.scatter.prototype.init.apply(this,arguments);this.pointRange=this.options.colsize||1;this.yAxis.axisPointRange=
+this.options.rowsize||1},translate:function(){var b=this.options,a=this.xAxis,c=this.yAxis;this.generatePoints();i(this.points,function(d){var e=(b.colsize||1)/2,f=(b.rowsize||1)/2,g=Math.round(a.len-a.translate(d.x-e,0,1,0,1)),e=Math.round(a.len-a.translate(d.x+e,0,1,0,1)),h=Math.round(c.translate(d.y-f,0,1,0,1)),f=Math.round(c.translate(d.y+f,0,1,0,1));d.plotX=(g+e)/2;d.plotY=(h+f)/2;d.shapeType="rect";d.shapeArgs={x:Math.min(g,e),y:Math.min(h,f),width:Math.abs(e-g),height:Math.abs(f-h)}});this.translateColors();
+this.chart.hasRendered&&i(this.points,function(a){a.shapeArgs.fill=a.options.color||a.color})},drawPoints:p.column.prototype.drawPoints,animate:n,getBox:n,drawLegendSymbol:t.drawRectangle,getExtremes:function(){u.prototype.getExtremes.call(this,this.valueData);this.valueMin=this.dataMin;this.valueMax=this.dataMax;u.prototype.getExtremes.call(this)}}))})(Highcharts);
diff --git a/html/includes/js/modules/heatmap.src.js b/html/includes/js/modules/heatmap.src.js
new file mode 100644
index 0000000..253ca2b
--- /dev/null
+++ b/html/includes/js/modules/heatmap.src.js
@@ -0,0 +1,687 @@
+/**
+ * @license Highcharts JS v4.0.4 (2014-09-02)
+ *
+ * (c) 2011-2014 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+/*global HighchartsAdapter*/
+(function (Highcharts) {
+
+
+var UNDEFINED,
+ Axis = Highcharts.Axis,
+ Chart = Highcharts.Chart,
+ Color = Highcharts.Color,
+ Legend = Highcharts.Legend,
+ LegendSymbolMixin = Highcharts.LegendSymbolMixin,
+ Series = Highcharts.Series,
+ SVGRenderer = Highcharts.SVGRenderer,
+
+ defaultOptions = Highcharts.getOptions(),
+ each = Highcharts.each,
+ extend = Highcharts.extend,
+ extendClass = Highcharts.extendClass,
+ merge = Highcharts.merge,
+ pick = Highcharts.pick,
+ numberFormat = Highcharts.numberFormat,
+ seriesTypes = Highcharts.seriesTypes,
+ wrap = Highcharts.wrap,
+ noop = function () {};
+
+
+
+
+/**
+ * The ColorAxis object for inclusion in gradient legends
+ */
+var ColorAxis = Highcharts.ColorAxis = function () {
+ this.isColorAxis = true;
+ this.init.apply(this, arguments);
+};
+extend(ColorAxis.prototype, Axis.prototype);
+extend(ColorAxis.prototype, {
+ defaultColorAxisOptions: {
+ lineWidth: 0,
+ gridLineWidth: 1,
+ tickPixelInterval: 72,
+ startOnTick: true,
+ endOnTick: true,
+ offset: 0,
+ marker: {
+ animation: {
+ duration: 50
+ },
+ color: 'gray',
+ width: 0.01
+ },
+ labels: {
+ overflow: 'justify'
+ },
+ minColor: '#EFEFFF',
+ maxColor: '#003875',
+ tickLength: 5
+ },
+ init: function (chart, userOptions) {
+ var horiz = chart.options.legend.layout !== 'vertical',
+ options;
+
+ // Build the options
+ options = merge(this.defaultColorAxisOptions, {
+ side: horiz ? 2 : 1,
+ reversed: !horiz
+ }, userOptions, {
+ isX: horiz,
+ opposite: !horiz,
+ showEmpty: false,
+ title: null,
+ isColor: true
+ });
+
+ Axis.prototype.init.call(this, chart, options);
+
+ // Base init() pushes it to the xAxis array, now pop it again
+ //chart[this.isXAxis ? 'xAxis' : 'yAxis'].pop();
+
+ // Prepare data classes
+ if (userOptions.dataClasses) {
+ this.initDataClasses(userOptions);
+ }
+ this.initStops(userOptions);
+
+ // Override original axis properties
+ this.isXAxis = true;
+ this.horiz = horiz;
+ this.zoomEnabled = false;
+ },
+
+ /*
+ * Return an intermediate color between two colors, according to pos where 0
+ * is the from color and 1 is the to color
+ */
+ tweenColors: function (from, to, pos) {
+ // Check for has alpha, because rgba colors perform worse due to lack of
+ // support in WebKit.
+ var hasAlpha = (to.rgba[3] !== 1 || from.rgba[3] !== 1);
+ return (hasAlpha ? 'rgba(' : 'rgb(') +
+ Math.round(to.rgba[0] + (from.rgba[0] - to.rgba[0]) * (1 - pos)) + ',' +
+ Math.round(to.rgba[1] + (from.rgba[1] - to.rgba[1]) * (1 - pos)) + ',' +
+ Math.round(to.rgba[2] + (from.rgba[2] - to.rgba[2]) * (1 - pos)) +
+ (hasAlpha ? (',' + (to.rgba[3] + (from.rgba[3] - to.rgba[3]) * (1 - pos))) : '') + ')';
+ },
+
+ initDataClasses: function (userOptions) {
+ var axis = this,
+ chart = this.chart,
+ dataClasses,
+ colorCounter = 0,
+ options = this.options,
+ len = userOptions.dataClasses.length;
+ this.dataClasses = dataClasses = [];
+ this.legendItems = [];
+
+ each(userOptions.dataClasses, function (dataClass, i) {
+ var colors;
+
+ dataClass = merge(dataClass);
+ dataClasses.push(dataClass);
+ if (!dataClass.color) {
+ if (options.dataClassColor === 'category') {
+ colors = chart.options.colors;
+ dataClass.color = colors[colorCounter++];
+ // loop back to zero
+ if (colorCounter === colors.length) {
+ colorCounter = 0;
+ }
+ } else {
+ dataClass.color = axis.tweenColors(
+ Color(options.minColor),
+ Color(options.maxColor),
+ len < 2 ? 0.5 : i / (len - 1) // #3219
+ );
+ }
+ }
+ });
+ },
+
+ initStops: function (userOptions) {
+ this.stops = userOptions.stops || [
+ [0, this.options.minColor],
+ [1, this.options.maxColor]
+ ];
+ each(this.stops, function (stop) {
+ stop.color = Color(stop[1]);
+ });
+ },
+
+ /**
+ * Extend the setOptions method to process extreme colors and color
+ * stops.
+ */
+ setOptions: function (userOptions) {
+ Axis.prototype.setOptions.call(this, userOptions);
+
+ this.options.crosshair = this.options.marker;
+ this.coll = 'colorAxis';
+ },
+
+ setAxisSize: function () {
+ var symbol = this.legendSymbol,
+ chart = this.chart,
+ x,
+ y,
+ width,
+ height;
+
+ if (symbol) {
+ this.left = x = symbol.attr('x');
+ this.top = y = symbol.attr('y');
+ this.width = width = symbol.attr('width');
+ this.height = height = symbol.attr('height');
+ this.right = chart.chartWidth - x - width;
+ this.bottom = chart.chartHeight - y - height;
+
+ this.len = this.horiz ? width : height;
+ this.pos = this.horiz ? x : y;
+ }
+ },
+
+ /**
+ * Translate from a value to a color
+ */
+ toColor: function (value, point) {
+ var pos,
+ stops = this.stops,
+ from,
+ to,
+ color,
+ dataClasses = this.dataClasses,
+ dataClass,
+ i;
+
+ if (dataClasses) {
+ i = dataClasses.length;
+ while (i--) {
+ dataClass = dataClasses[i];
+ from = dataClass.from;
+ to = dataClass.to;
+ if ((from === UNDEFINED || value >= from) && (to === UNDEFINED || value <= to)) {
+ color = dataClass.color;
+ if (point) {
+ point.dataClass = i;
+ }
+ break;
+ }
+ }
+
+ } else {
+
+ if (this.isLog) {
+ value = this.val2lin(value);
+ }
+ pos = 1 - ((this.max - value) / ((this.max - this.min) || 1));
+ i = stops.length;
+ while (i--) {
+ if (pos > stops[i][0]) {
+ break;
+ }
+ }
+ from = stops[i] || stops[i + 1];
+ to = stops[i + 1] || from;
+
+ // The position within the gradient
+ pos = 1 - (to[0] - pos) / ((to[0] - from[0]) || 1);
+
+ color = this.tweenColors(
+ from.color,
+ to.color,
+ pos
+ );
+ }
+ return color;
+ },
+
+ getOffset: function () {
+ var group = this.legendGroup,
+ sideOffset = this.chart.axisOffset[this.side];
+
+ if (group) {
+
+ Axis.prototype.getOffset.call(this);
+
+ if (!this.axisGroup.parentGroup) {
+
+ // Move the axis elements inside the legend group
+ this.axisGroup.add(group);
+ this.gridGroup.add(group);
+ this.labelGroup.add(group);
+
+ this.added = true;
+ }
+ // Reset it to avoid color axis reserving space
+ this.chart.axisOffset[this.side] = sideOffset;
+ }
+ },
+
+ /**
+ * Create the color gradient
+ */
+ setLegendColor: function () {
+ var grad,
+ horiz = this.horiz,
+ options = this.options;
+
+ grad = horiz ? [0, 0, 1, 0] : [0, 0, 0, 1];
+ this.legendColor = {
+ linearGradient: { x1: grad[0], y1: grad[1], x2: grad[2], y2: grad[3] },
+ stops: options.stops || [
+ [0, options.minColor],
+ [1, options.maxColor]
+ ]
+ };
+ },
+
+ /**
+ * The color axis appears inside the legend and has its own legend symbol
+ */
+ drawLegendSymbol: function (legend, item) {
+ var padding = legend.padding,
+ legendOptions = legend.options,
+ horiz = this.horiz,
+ box,
+ width = pick(legendOptions.symbolWidth, horiz ? 200 : 12),
+ height = pick(legendOptions.symbolHeight, horiz ? 12 : 200),
+ labelPadding = pick(legendOptions.labelPadding, horiz ? 16 : 30),
+ itemDistance = pick(legendOptions.itemDistance, 10);
+
+ this.setLegendColor();
+
+ // Create the gradient
+ item.legendSymbol = this.chart.renderer.rect(
+ 0,
+ legend.baseline - 11,
+ width,
+ height
+ ).attr({
+ zIndex: 1
+ }).add(item.legendGroup);
+ box = item.legendSymbol.getBBox();
+
+ // Set how much space this legend item takes up
+ this.legendItemWidth = width + padding + (horiz ? itemDistance : labelPadding);
+ this.legendItemHeight = height + padding + (horiz ? labelPadding : 0);
+ },
+ /**
+ * Fool the legend
+ */
+ setState: noop,
+ visible: true,
+ setVisible: noop,
+ getSeriesExtremes: function () {
+ var series;
+ if (this.series.length) {
+ series = this.series[0];
+ this.dataMin = series.valueMin;
+ this.dataMax = series.valueMax;
+ }
+ },
+ drawCrosshair: function (e, point) {
+ var newCross = !this.cross,
+ plotX = point && point.plotX,
+ plotY = point && point.plotY,
+ crossPos,
+ axisPos = this.pos,
+ axisLen = this.len;
+
+ if (point) {
+ crossPos = this.toPixels(point.value);
+ if (crossPos < axisPos) {
+ crossPos = axisPos - 2;
+ } else if (crossPos > axisPos + axisLen) {
+ crossPos = axisPos + axisLen + 2;
+ }
+
+ point.plotX = crossPos;
+ point.plotY = this.len - crossPos;
+ Axis.prototype.drawCrosshair.call(this, e, point);
+ point.plotX = plotX;
+ point.plotY = plotY;
+
+ if (!newCross && this.cross) {
+ this.cross
+ .attr({
+ fill: this.crosshair.color
+ })
+ .add(this.labelGroup);
+ }
+ }
+ },
+ getPlotLinePath: function (a, b, c, d, pos) {
+ if (pos) { // crosshairs only
+ return this.horiz ?
+ ['M', pos - 4, this.top - 6, 'L', pos + 4, this.top - 6, pos, this.top, 'Z'] :
+ ['M', this.left, pos, 'L', this.left - 6, pos + 6, this.left - 6, pos - 6, 'Z'];
+ } else {
+ return Axis.prototype.getPlotLinePath.call(this, a, b, c, d);
+ }
+ },
+
+ update: function (newOptions, redraw) {
+ each(this.series, function (series) {
+ series.isDirtyData = true; // Needed for Axis.update when choropleth colors change
+ });
+ Axis.prototype.update.call(this, newOptions, redraw);
+ if (this.legendItem) {
+ this.setLegendColor();
+ this.chart.legend.colorizeItem(this, true);
+ }
+ },
+
+ /**
+ * Get the legend item symbols for data classes
+ */
+ getDataClassLegendSymbols: function () {
+ var axis = this,
+ chart = this.chart,
+ legendItems = this.legendItems,
+ legendOptions = chart.options.legend,
+ valueDecimals = legendOptions.valueDecimals,
+ valueSuffix = legendOptions.valueSuffix || '',
+ name;
+
+ if (!legendItems.length) {
+ each(this.dataClasses, function (dataClass, i) {
+ var vis = true,
+ from = dataClass.from,
+ to = dataClass.to;
+
+ // Assemble the default name. This can be overridden by legend.options.labelFormatter
+ name = '';
+ if (from === UNDEFINED) {
+ name = '< ';
+ } else if (to === UNDEFINED) {
+ name = '> ';
+ }
+ if (from !== UNDEFINED) {
+ name += numberFormat(from, valueDecimals) + valueSuffix;
+ }
+ if (from !== UNDEFINED && to !== UNDEFINED) {
+ name += ' - ';
+ }
+ if (to !== UNDEFINED) {
+ name += numberFormat(to, valueDecimals) + valueSuffix;
+ }
+
+ // Add a mock object to the legend items
+ legendItems.push(extend({
+ chart: chart,
+ name: name,
+ options: {},
+ drawLegendSymbol: LegendSymbolMixin.drawRectangle,
+ visible: true,
+ setState: noop,
+ setVisible: function () {
+ vis = this.visible = !vis;
+ each(axis.series, function (series) {
+ each(series.points, function (point) {
+ if (point.dataClass === i) {
+ point.setVisible(vis);
+ }
+ });
+ });
+
+ chart.legend.colorizeItem(this, vis);
+ }
+ }, dataClass));
+ });
+ }
+ return legendItems;
+ },
+ name: '' // Prevents 'undefined' in legend in IE8
+});
+
+/**
+ * Handle animation of the color attributes directly
+ */
+each(['fill', 'stroke'], function (prop) {
+ HighchartsAdapter.addAnimSetter(prop, function (fx) {
+ fx.elem.attr(prop, ColorAxis.prototype.tweenColors(Color(fx.start), Color(fx.end), fx.pos));
+ });
+});
+
+/**
+ * Extend the chart getAxes method to also get the color axis
+ */
+wrap(Chart.prototype, 'getAxes', function (proceed) {
+
+ var options = this.options,
+ colorAxisOptions = options.colorAxis;
+
+ proceed.call(this);
+
+ this.colorAxis = [];
+ if (colorAxisOptions) {
+ proceed = new ColorAxis(this, colorAxisOptions); // Fake assignment for jsLint
+ }
+});
+
+
+/**
+ * Wrap the legend getAllItems method to add the color axis. This also removes the
+ * axis' own series to prevent them from showing up individually.
+ */
+wrap(Legend.prototype, 'getAllItems', function (proceed) {
+ var allItems = [],
+ colorAxis = this.chart.colorAxis[0];
+
+ if (colorAxis) {
+
+ // Data classes
+ if (colorAxis.options.dataClasses) {
+ allItems = allItems.concat(colorAxis.getDataClassLegendSymbols());
+ // Gradient legend
+ } else {
+ // Add this axis on top
+ allItems.push(colorAxis);
+ }
+
+ // Don't add the color axis' series
+ each(colorAxis.series, function (series) {
+ series.options.showInLegend = false;
+ });
+ }
+
+ return allItems.concat(proceed.call(this));
+});/**
+ * Mixin for maps and heatmaps
+ */
+var colorSeriesMixin = {
+
+ pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
+ stroke: 'borderColor',
+ 'stroke-width': 'borderWidth',
+ fill: 'color',
+ dashstyle: 'dashStyle'
+ },
+ pointArrayMap: ['value'],
+ axisTypes: ['xAxis', 'yAxis', 'colorAxis'],
+ optionalAxis: 'colorAxis',
+ trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],
+ getSymbol: noop,
+ parallelArrays: ['x', 'y', 'value'],
+ colorKey: 'value',
+
+ /**
+ * In choropleth maps, the color is a result of the value, so this needs translation too
+ */
+ translateColors: function () {
+ var series = this,
+ nullColor = this.options.nullColor,
+ colorAxis = this.colorAxis,
+ colorKey = this.colorKey;
+
+ each(this.data, function (point) {
+ var value = point[colorKey],
+ color;
+
+ color = value === null ? nullColor : (colorAxis && value !== undefined) ? colorAxis.toColor(value, point) : point.color || series.color;
+
+ if (color) {
+ point.color = color;
+ }
+ });
+ }
+};
+
+
+/**
+ * Wrap the buildText method and add the hook for add text stroke
+ */
+wrap(SVGRenderer.prototype, 'buildText', function (proceed, wrapper) {
+
+ var textStroke = wrapper.styles && wrapper.styles.HcTextStroke;
+
+ proceed.call(this, wrapper);
+
+ // Apply the text stroke
+ if (textStroke && wrapper.applyTextStroke) {
+ wrapper.applyTextStroke(textStroke);
+ }
+});
+
+/**
+ * Apply an outside text stroke to data labels, based on the custom CSS property, HcTextStroke.
+ * Consider moving this to Highcharts core, also makes sense on stacked columns etc.
+ */
+SVGRenderer.prototype.Element.prototype.applyTextStroke = function (textStroke) {
+ var elem = this.element,
+ tspans,
+ firstChild;
+
+ textStroke = textStroke.split(' ');
+ tspans = elem.getElementsByTagName('tspan');
+ firstChild = elem.firstChild;
+
+ // In order to get the right y position of the clones,
+ // copy over the y setter
+ this.ySetter = this.xSetter;
+
+ each([].slice.call(tspans), function (tspan, y) {
+ var clone;
+ if (y === 0) {
+ tspan.setAttribute('x', elem.getAttribute('x'));
+ if ((y = elem.getAttribute('y')) !== null) {
+ tspan.setAttribute('y', y);
+ }
+ }
+ clone = tspan.cloneNode(1);
+ clone.setAttribute('stroke', textStroke[1]);
+ clone.setAttribute('stroke-width', textStroke[0]);
+ clone.setAttribute('stroke-linejoin', 'round');
+ elem.insertBefore(clone, firstChild);
+ });
+};
+/**
+ * Extend the default options with map options
+ */
+defaultOptions.plotOptions.heatmap = merge(defaultOptions.plotOptions.scatter, {
+ animation: false,
+ borderWidth: 0,
+ nullColor: '#F8F8F8',
+ dataLabels: {
+ formatter: function () { // #2945
+ return this.point.value;
+ },
+ verticalAlign: 'middle',
+ crop: false,
+ overflow: false,
+ style: {
+ color: 'white',
+ fontWeight: 'bold',
+ HcTextStroke: '1px rgba(0,0,0,0.5)'
+ }
+ },
+ marker: null,
+ tooltip: {
+ pointFormat: '{point.x}, {point.y}: {point.value}<br/>'
+ },
+ states: {
+ normal: {
+ animation: true
+ },
+ hover: {
+ brightness: 0.2
+ }
+ }
+});
+
+// The Heatmap series type
+seriesTypes.heatmap = extendClass(seriesTypes.scatter, merge(colorSeriesMixin, {
+ type: 'heatmap',
+ pointArrayMap: ['y', 'value'],
+ hasPointSpecificOptions: true,
+ supportsDrilldown: true,
+ getExtremesFromAll: true,
+ init: function () {
+ seriesTypes.scatter.prototype.init.apply(this, arguments);
+ this.pointRange = this.options.colsize || 1;
+ this.yAxis.axisPointRange = this.options.rowsize || 1; // general point range
+ },
+ translate: function () {
+ var series = this,
+ options = series.options,
+ xAxis = series.xAxis,
+ yAxis = series.yAxis;
+
+ series.generatePoints();
+
+ each(series.points, function (point) {
+ var xPad = (options.colsize || 1) / 2,
+ yPad = (options.rowsize || 1) / 2,
+ x1 = Math.round(xAxis.len - xAxis.translate(point.x - xPad, 0, 1, 0, 1)),
+ x2 = Math.round(xAxis.len - xAxis.translate(point.x + xPad, 0, 1, 0, 1)),
+ y1 = Math.round(yAxis.translate(point.y - yPad, 0, 1, 0, 1)),
+ y2 = Math.round(yAxis.translate(point.y + yPad, 0, 1, 0, 1));
+
+ // Set plotX and plotY for use in K-D-Tree and more
+ point.plotX = (x1 + x2) / 2;
+ point.plotY = (y1 + y2) / 2;
+
+ point.shapeType = 'rect';
+ point.shapeArgs = {
+ x: Math.min(x1, x2),
+ y: Math.min(y1, y2),
+ width: Math.abs(x2 - x1),
+ height: Math.abs(y2 - y1)
+ };
+ });
+
+ series.translateColors();
+
+ // Make sure colors are updated on colorAxis update (#2893)
+ if (this.chart.hasRendered) {
+ each(series.points, function (point) {
+ point.shapeArgs.fill = point.options.color || point.color; // #3311
+ });
+ }
+ },
+ drawPoints: seriesTypes.column.prototype.drawPoints,
+ animate: noop,
+ getBox: noop,
+ drawLegendSymbol: LegendSymbolMixin.drawRectangle,
+
+ getExtremes: function () {
+ // Get the extremes from the value data
+ Series.prototype.getExtremes.call(this, this.valueData);
+ this.valueMin = this.dataMin;
+ this.valueMax = this.dataMax;
+
+ // Get the extremes from the y data
+ Series.prototype.getExtremes.call(this);
+ }
+
+}));
+
+
+}(Highcharts));
diff --git a/html/includes/js/modules/multicolor_series.js b/html/includes/js/modules/multicolor_series.js
new file mode 100644
index 0000000..ca70ae6
--- /dev/null
+++ b/html/includes/js/modules/multicolor_series.js
@@ -0,0 +1,567 @@
+(function(H){
+ var each = H.each,
+ seriesTypes = H.seriesTypes,
+ mathFloor = Math.floor,
+ mathMax = Math.max,
+ mathMin = Math.min,
+ mathAbs = Math.abs,
+ UNDEFINED,
+ NORMAL_STATE = '',
+ HOVER_STATE = 'hover',
+ SELECT_STATE = 'select',
+ VISIBLE = 'visible',
+ HIDDEN = 'hidden',
+ PREFIX = 'highcharts-',
+ NONE = 'none',
+ hasTouch = document.documentElement.ontouchstart !== UNDEFINED,
+ TRACKER_FILL = 'rgba(192,192,192,' + (H.hasSVG ? 0.0001 : 0.002) + ')', // invisible but clickable
+ M = 'M',
+ L = 'L';
+
+ // handle unsorted data, throw error anyway
+ function error(code, stop) {
+ var msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code;
+ if (stop) {
+ throw msg;
+ } else if (window.console) {
+ console.log(msg);
+ }
+ }
+
+ /***
+ If replacing L and M in trakcer will be necessary use that getPath():
+
+ function getPath(arr){
+ var ret = [];
+ each(arr, function(el, ind) {
+ var len = el[0].length;
+ for(var i = 0; i < len; i++){
+ var p = el[0][i];
+ if(p == M && ind != 0 && i == 0) {
+ p = L;
+ }
+ ret.push(p);
+ }
+ });
+ return ret;
+ }
+ ***/
+
+
+ function getPath(arr){
+ var ret = [];
+ each(arr, function(el) {
+ ret = ret.concat(el[0]);
+ });
+ return ret;
+ }
+
+ /***
+ *
+ * ColoredLine series type
+ *
+ ***/
+
+ seriesTypes.coloredline = Highcharts.extendClass(seriesTypes.line);
+ H.seriesTypes.coloredline.prototype.processData = function(force) {
+
+ var series = this,
+ processedXData = series.xData, // copied during slice operation below
+ processedYData = series.yData,
+ dataLength = processedXData.length,
+ croppedData,
+ cropStart = 0,
+ cropped,
+ distance,
+ closestPointRange,
+ xAxis = series.xAxis,
+ i, // loop variable
+ options = series.options,
+ cropThreshold = options.cropThreshold,
+ isCartesian = series.isCartesian;
+
+ // If the series data or axes haven't changed, don't go through this. Return false to pass
+ // the message on to override methods like in data grouping.
+ if (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) {
+ return false;
+ }
+
+ // Find the closest distance between processed points
+ for (i = processedXData.length - 1; i >= 0; i--) {
+ distance = processedXData[i] - processedXData[i - 1];
+ if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) {
+ closestPointRange = distance;
+
+ // Unsorted data is not supported by the line tooltip, as well as data grouping and
+ // navigation in Stock charts (#725) and width calculation of columns (#1900)
+ } else if (distance < 0 && series.requireSorting) {
+ error(15);
+ }
+ }
+
+ // Record the properties
+ series.cropped = cropped; // undefined or true
+ series.cropStart = cropStart;
+ series.processedXData = processedXData;
+ series.processedYData = processedYData;
+
+ if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC
+ series.pointRange = closestPointRange || 1;
+ }
+ series.closestPointRange = closestPointRange;
+ };
+
+ H.seriesTypes.coloredline.prototype.setTooltipPoints = function (renew) {
+ var series = this,
+ points = [],
+ pointsLength,
+ low,
+ high,
+ xAxis = series.xAxis,
+ xExtremes = xAxis && xAxis.getExtremes(),
+ axisLength = xAxis ? (xAxis.tooltipLen || xAxis.len) : series.chart.plotSizeX, // tooltipLen and tooltipPosName used in polar
+ point,
+ pointX,
+ nextPoint,
+ i,
+ tooltipPoints = []; // a lookup array for each pixel in the x dimension
+
+ // don't waste resources if tracker is disabled
+ if (series.options.enableMouseTracking === false) {
+ return;
+ }
+
+ // renew
+ if (renew) {
+ series.tooltipPoints = null;
+ }
+ // concat segments to overcome null values
+ each(series.points, function (segment) {
+ if(segment.y !== null){
+ points = points.concat(segment);
+ }
+ });
+ each(series.segments, function (segment) {
+ each(segment.points, function(point) {
+ if(point.y !== null){
+ points = points.concat(point);
+ }
+ });
+ });
+ // Reverse the points in case the X axis is reversed
+ if (xAxis && xAxis.reversed) {
+ points = points.reverse();
+ }
+
+ // Polar needs additional shaping
+ if (series.orderTooltipPoints) {
+ series.orderTooltipPoints(points);
+ }
+
+ // Assign each pixel position to the nearest point
+ pointsLength = points.length;
+ for (i = 0; i < pointsLength; i++) {
+ point = points[i];
+ pointX = point.x;
+ if (pointX >= xExtremes.min && pointX <= xExtremes.max) { // #1149
+ nextPoint = points[i + 1];
+
+ // Set this range's low to the last range's high plus one
+ low = high === UNDEFINED ? 0 : high + 1;
+ // Now find the new high
+ high = points[i + 1] ?
+ mathMin(mathMax(0, mathFloor( // #2070
+ (point.clientX + (nextPoint ? (nextPoint.wrappedClientX || nextPoint.clientX) : axisLength)) / 2 )), axisLength) :
+ axisLength;
+
+ while (low >= 0 && low <= high) {
+ tooltipPoints[low++] = point;
+ }
+ }
+ }
+ series.tooltipPoints = tooltipPoints;
+ };
+
+ H.seriesTypes.coloredline.prototype.drawTracker = function () {
+ var series = this,
+ options = series.options,
+ trackByArea = options.trackByArea,
+ trackerPath = [].concat(trackByArea ? series.areaPath : getPath(series.graphPath)),
+ trackerPathLength = trackerPath.length,
+ chart = series.chart,
+ pointer = chart.pointer,
+ renderer = chart.renderer,
+ snap = chart.options.tooltip.snap,
+ tracker = series.tracker,
+ cursor = options.cursor,
+ css = cursor && { cursor: cursor },
+ singlePoints = series.singlePoints,
+ singlePoint,
+ i,
+ onMouseOver = function () {
+ if (chart.hoverSeries !== series) {
+ series.onMouseOver();
+ }
+ };
+ // Extend end points. A better way would be to use round linecaps,
+ // but those are not clickable in VML.
+ if (trackerPathLength && !trackByArea) {
+ i = trackerPathLength + 1;
+ while (i--) {
+ if (trackerPath[i] === M) { // extend left side
+ trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L);
+ }
+ if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side
+ trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]);
+ }
+ }
+ }
+
+ // handle single points
+ for (i = 0; i < singlePoints.length; i++) {
+ singlePoint = singlePoints[i];
+ if(singlePoint.plotX && singlePoint.plotY) {
+ trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,
+ L, singlePoint.plotX + snap, singlePoint.plotY);
+ }
+ }
+
+
+
+ // draw the tracker
+ if (tracker) {
+ tracker.attr({ d: trackerPath });
+
+ } else { // create
+
+ series.tracker = renderer.path(trackerPath)
+ .attr({
+ 'stroke-linejoin': 'round', // #1225
+ visibility: series.visible ? VISIBLE : HIDDEN,
+ stroke: TRACKER_FILL,
+ fill: trackByArea ? TRACKER_FILL : NONE,
+ 'stroke-width' : options.lineWidth + (trackByArea ? 0 : 2 * snap),
+ zIndex: 2
+ })
+ .add(series.group);
+
+ // The tracker is added to the series group, which is clipped, but is covered
+ // by the marker group. So the marker group also needs to capture events.
+ each([series.tracker, series.markerGroup], function (tracker) {
+ tracker.addClass(PREFIX + 'tracker')
+ .on('mouseover', onMouseOver)
+ .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })
+ .css(css);
+
+ if (hasTouch) {
+ tracker.on('touchstart', onMouseOver);
+ }
+ });
+ }
+
+ };
+
+ H.seriesTypes.coloredline.prototype.setState = function (state) {
+ var series = this,
+ options = series.options,
+ graph = series.graph,
+ graphNeg = series.graphNeg,
+ stateOptions = options.states,
+ lineWidth = options.lineWidth,
+ attribs;
+
+ state = state || NORMAL_STATE;
+
+ if (series.state !== state) {
+ series.state = state;
+
+ if (stateOptions[state] && stateOptions[state].enabled === false) {
+ return;
+ }
+
+ if (state) {
+ lineWidth = stateOptions[state].lineWidth || lineWidth + 1;
+ }
+
+ if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
+ attribs = {
+ 'stroke-width': lineWidth
+ };
+ // use attr because animate will cause any other animation on the graph to stop
+ each(graph, function(seg, i){
+ seg.attr(attribs);
+ });
+ }
+ }
+ };
+
+ /***
+ *
+ * The main change to get multi color isFinite changes segments array.
+ * From array of points to object with color and array of points.
+ *
+ ***/
+ H.seriesTypes.coloredline.prototype.getSegments = function(f){
+ var series = this,
+ lastColor = 0,
+ segments = [],
+ i,
+ points = series.points,
+ pointsLength = points.length;
+
+ if (pointsLength) { // no action required for []
+
+ // if connect nulls, just remove null points
+ if (series.options.connectNulls) {
+ each(points, function (point, i) {
+ if (points[i].y === null) {
+ points.splice(i, 1);
+ }
+ });
+ each(points, function (point, i) {
+ if (i > 0 && points[i].segmentColor != points[i-1].segmentColor){
+ segments.push({
+ points: points.slice(lastColor, i + 1),
+ color: points[i - 1].segmentColor
+ });
+ lastColor = i;
+ }
+ });
+
+ if (points.length && segments.length === 0) {
+ segments = [points];
+ }
+
+ // else, split on null points or different colors
+ } else {
+ var previousColor = null;
+ each(points, function (point, i) {
+ var colorChanged = i > 0 && point.segmentColor != points[i-1].segmentColor && points[i].segmentColor != previousColor,
+ colorExists = points[i-1] && points[i-1].segmentColor ? true : false;
+
+ // handle first point
+ if(!previousColor && point.segmetColor){
+ previousColor = point.segmentColor;
+ }
+
+ if(colorChanged){
+ var p = points.slice(lastColor, i + 1);
+ if(p.length > 0) {
+ segments.push({
+ points: p,
+ color: colorExists ? points[i-1].segmentColor : previousColor
+ });
+ lastColor = i;
+ }
+ } else if (i === pointsLength - 1){
+ var next = i + 1;
+ if(point.y === null) {
+ next--;
+ }
+ var p = points.slice(lastColor, next);
+ if(p.length > 0) {
+ segments.push({
+ points: p,
+ color: colorExists ? points[i-1].segmentColor : previousColor
+ });
+ lastColor = i;
+ }
+
+ }
+
+ // store previous color
+ if(point){
+ previousColor = point.segmentColor;
+ }
+ });
+ }
+ }
+ // register it
+ series.segments = segments;
+ };
+
+ H.seriesTypes.coloredline.prototype.getGraphPath = function(f){
+
+ //var ret = f.apply(this, Array.prototype.slice.call(arguments, 1));
+ var series = this,
+ graphPath = [],
+ segmentPath,
+ singlePoints = []; // used in drawTracker
+ // Divide into segments and build graph and area paths
+ each(series.segments, function (segment) {
+ segmentPath = series.getSegmentPath(segment.points);
+ // add the segment to the graph, or a single point for tracking
+ if (segment.points.length > 1) {
+ graphPath.push([segmentPath, segment.color]);
+ } else {
+ singlePoints.push(segment.points);
+ }
+ });
+
+ // Record it for use in drawGraph and drawTracker, and return graphPath
+ series.singlePoints = singlePoints;
+ series.graphPath = graphPath;
+
+ return graphPath;
+ };
+
+ H.seriesTypes.coloredline.prototype.drawGraph = function(f){
+ var series = this,
+ options = series.options,
+ props = [['graph', options.lineColor || series.color]],
+ lineWidth = options.lineWidth,
+ dashStyle = options.dashStyle,
+ roundCap = options.linecap !== 'square',
+ graphPath = series.getGraphPath(),
+ graphPathLength = graphPath.length,
+ graphSegmentsLength = 0,
+ negativeColor = options.negativeColor;
+
+ // draw the graph
+ each(props, function (prop, i) {
+ var graphKey = prop[0],
+ graph = series[graphKey];
+
+ if (graph) {// cancel running animations, #459
+ // do we have animation
+ each(graphPath, function(segment, j) {
+ // update color and path
+
+ if(series[graphKey][j]){
+ series[graphKey][j].attr({ d: segment[0], stroke: segment[1] });
+ } else {
+ series[graphKey][j] = getSegment(segment, prop, i);
+ }
+ });
+
+ } else if (lineWidth && graphPath.length) { // #1487
+ graph = [];
+ each(graphPath, function(segment, j) {
+ graph[j] = getSegment(segment, prop, i);
+ });
+ series[graphKey] = graph;
+ //add destroying elements
+ series[graphKey].destroy = function(){
+ for(g in series[graphKey]){
+ var el = series[graphKey][g];
+ if(el && el.destroy){
+ el.destroy();
+ }
+ }
+ };
+ }
+ graphSegmentsLength = series.graph.length;
+
+ for(var j = graphSegmentsLength; j >= graphPathLength; j --){
+ if(series[graphKey][j]) {
+ series[graphKey][j].destroy();
+ series[graphKey].splice(j, 1);
+ }
+ }
+ });
+
+
+ function getSegment(segment, prop, i){
+ var attribs = {
+ stroke: prop[1],
+ 'stroke-width': lineWidth,
+ zIndex: 1 // #1069
+ },
+ item;
+ if (dashStyle) {
+ attribs.dashstyle = dashStyle;
+ } else if (roundCap) {
+ attribs['stroke-linecap'] = attribs['stroke-linejoin'] = 'round';
+ }
+ if(segment[1]){
+ attribs.stroke = segment[1];
+ }
+
+ item = series.chart.renderer.path(segment[0])
+ .attr(attribs)
+ .add(series.group)
+ .shadow(!i && options.shadow);
+
+ return item;
+ }
+
+ };
+
+
+ /***
+ *
+ * ColoredArea series type
+ *
+ ***/
+ seriesTypes.coloredarea = Highcharts.extendClass(seriesTypes.coloredline);
+
+ H.seriesTypes.coloredarea.prototype.init = function(chart, options) {
+ options.threshold = options.threshold || null;
+ H.Series.prototype.init.call(this, chart, options);
+ };
+
+ H.seriesTypes.coloredarea.prototype.closeSegment = H.seriesTypes.area.prototype.closeSegment;
+
+ H.seriesTypes.coloredarea.prototype.drawGraph = function(f) {
+ H.seriesTypes.coloredline.prototype.drawGraph.call(this, f);
+ var series = this,
+ options = this.options,
+ props = [['graph', options.lineColor || series.color]];
+
+ each(props, function(prop, i) {
+ var graphKey = prop[0],
+ graph = series[graphKey];
+
+ if (graph) {// cancel running animations, #459
+ // do we have animation
+ each(series.graphPath, function(segment, j) {
+ // update color and path
+
+ if(series[graphKey][j]){
+ series[graphKey][j].attr({ fill: segment[1] });
+ }
+ });
+
+ }
+ });
+ };
+
+ H.seriesTypes.coloredarea.prototype.getSegmentPath = function(segment){
+ var path;
+
+ seriesTypes.area.prototype.getSegmentPath.call(this, segment);
+
+ path = [].concat(this.areaPath);
+ this.areaPath = [];
+
+ return path;
+ };
+
+ H.seriesTypes.coloredarea.prototype.getGraphPath = function() {
+ var series = this,
+ graphPath = [],
+ segmentPath,
+ singlePoints = []; // used in drawTracker
+ // Divide into segments and build graph and area paths
+
+ this.areaPath = [];
+ each(series.segments, function (segment) {
+ segmentPath = series.getSegmentPath(segment.points);
+ // add the segment to the graph, or a single point for tracking
+ if (segment.points.length > 1) {
+ graphPath.push([segmentPath, segment.color]);
+ } else {
+ singlePoints.push(segment.points);
+ }
+ });
+
+ // Record it for use in drawGraph and drawTracker, and return graphPath
+ series.singlePoints = singlePoints;
+ series.graphPath = graphPath;
+ return graphPath;
+
+ };
+
+ H.seriesTypes.coloredarea.prototype.drawLegendSymbol = H.LegendSymbolMixin.drawRectangle;
+
+})(Highcharts);
diff --git a/html/includes/js/modules/no-data-to-display.js b/html/includes/js/modules/no-data-to-display.js
new file mode 100644
index 0000000..c96c4d1
--- /dev/null
+++ b/html/includes/js/modules/no-data-to-display.js
@@ -0,0 +1,12 @@
+/*
+ Highcharts JS v4.0.4 (2014-09-02)
+ Plugin for displaying a message when there is no data visible in chart.
+
+ (c) 2010-2014 Highsoft AS
+ Author: Oystein Moseng
+
+ License: www.highcharts.com/license
+*/
+(function(c){function f(){return!!this.points.length}function g(){this.hasData()?this.hideNoData():this.showNoData()}var d=c.seriesTypes,e=c.Chart.prototype,h=c.getOptions(),i=c.extend;i(h.lang,{noData:"No data to display"});h.noData={position:{x:0,y:0,align:"center",verticalAlign:"middle"},attr:{},style:{fontWeight:"bold",fontSize:"12px",color:"#60606a"}};if(d.pie)d.pie.prototype.hasData=f;if(d.gauge)d.gauge.prototype.hasData=f;if(d.waterfall)d.waterfall.prototype.hasData=f;c.Series.prototype.hasData=
+function(){return this.dataMax!==void 0&&this.dataMin!==void 0};e.showNoData=function(a){var b=this.options,a=a||b.lang.noData,b=b.noData;if(!this.noDataLabel)this.noDataLabel=this.renderer.label(a,0,0,null,null,null,null,null,"no-data").attr(b.attr).css(b.style).add(),this.noDataLabel.align(i(this.noDataLabel.getBBox(),b.position),!1,"plotBox")};e.hideNoData=function(){if(this.noDataLabel)this.noDataLabel=this.noDataLabel.destroy()};e.hasData=function(){for(var a=this.series,b=a.length;b--;)if(a[b].hasData()&&
+!a[b].options.isInternal)return!0;return!1};e.callbacks.push(function(a){c.addEvent(a,"load",g);c.addEvent(a,"redraw",g)})})(Highcharts);
diff --git a/html/includes/js/modules/no-data-to-display.src.js b/html/includes/js/modules/no-data-to-display.src.js
new file mode 100644
index 0000000..9badd16
--- /dev/null
+++ b/html/includes/js/modules/no-data-to-display.src.js
@@ -0,0 +1,130 @@
+/**
+ * @license Highcharts JS v4.0.4 (2014-09-02)
+ * Plugin for displaying a message when there is no data visible in chart.
+ *
+ * (c) 2010-2014 Highsoft AS
+ * Author: Oystein Moseng
+ *
+ * License: www.highcharts.com/license
+ */
+
+(function (H) {
+
+ var seriesTypes = H.seriesTypes,
+ chartPrototype = H.Chart.prototype,
+ defaultOptions = H.getOptions(),
+ extend = H.extend;
+
+ // Add language option
+ extend(defaultOptions.lang, {
+ noData: 'No data to display'
+ });
+
+ // Add default display options for message
+ defaultOptions.noData = {
+ position: {
+ x: 0,
+ y: 0,
+ align: 'center',
+ verticalAlign: 'middle'
+ },
+ attr: {
+ },
+ style: {
+ fontWeight: 'bold',
+ fontSize: '12px',
+ color: '#60606a'
+ }
+ };
+
+ /**
+ * Define hasData functions for series. These return true if there are data points on this series within the plot area
+ */
+ function hasDataPie() {
+ return !!this.points.length; /* != 0 */
+ }
+
+ if (seriesTypes.pie) {
+ seriesTypes.pie.prototype.hasData = hasDataPie;
+ }
+
+ if (seriesTypes.gauge) {
+ seriesTypes.gauge.prototype.hasData = hasDataPie;
+ }
+
+ if (seriesTypes.waterfall) {
+ seriesTypes.waterfall.prototype.hasData = hasDataPie;
+ }
+
+ H.Series.prototype.hasData = function () {
+ return this.dataMax !== undefined && this.dataMin !== undefined;
+ };
+
+ /**
+ * Display a no-data message.
+ *
+ * @param {String} str An optional message to show in place of the default one
+ */
+ chartPrototype.showNoData = function (str) {
+ var chart = this,
+ options = chart.options,
+ text = str || options.lang.noData,
+ noDataOptions = options.noData;
+
+ if (!chart.noDataLabel) {
+ chart.noDataLabel = chart.renderer.label(text, 0, 0, null, null, null, null, null, 'no-data')
+ .attr(noDataOptions.attr)
+ .css(noDataOptions.style)
+ .add();
+ chart.noDataLabel.align(extend(chart.noDataLabel.getBBox(), noDataOptions.position), false, 'plotBox');
+ }
+ };
+
+ /**
+ * Hide no-data message
+ */
+ chartPrototype.hideNoData = function () {
+ var chart = this;
+ if (chart.noDataLabel) {
+ chart.noDataLabel = chart.noDataLabel.destroy();
+ }
+ };
+
+ /**
+ * Returns true if there are data points within the plot area now
+ */
+ chartPrototype.hasData = function () {
+ var chart = this,
+ series = chart.series,
+ i = series.length;
+
+ while (i--) {
+ if (series[i].hasData() && !series[i].options.isInternal) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ /**
+ * Show no-data message if there is no data in sight. Otherwise, hide it.
+ */
+ function handleNoData() {
+ var chart = this;
+ if (chart.hasData()) {
+ chart.hideNoData();
+ } else {
+ chart.showNoData();
+ }
+ }
+
+ /**
+ * Add event listener to handle automatic display of no-data message
+ */
+ chartPrototype.callbacks.push(function (chart) {
+ H.addEvent(chart, 'load', handleNoData);
+ H.addEvent(chart, 'redraw', handleNoData);
+ });
+
+}(Highcharts));
diff --git a/html/includes/js/modules/solid-gauge.js b/html/includes/js/modules/solid-gauge.js
new file mode 100644
index 0000000..85e8c85
--- /dev/null
+++ b/html/includes/js/modules/solid-gauge.js
@@ -0,0 +1,13 @@
+/*
+ Highcharts JS v4.0.4 (2014-09-02)
+ Solid angular gauge module
+
+ (c) 2010-2014 Torstein Honsi
+
+ License: www.highcharts.com/license
+*/
+(function(a){var k=a.getOptions().plotOptions,q=a.pInt,r=a.pick,l=a.each,n;k.solidgauge=a.merge(k.gauge,{colorByPoint:!0});n={initDataClasses:function(b){var h=this,e=this.chart,c,m=0,f=this.options;this.dataClasses=c=[];l(b.dataClasses,function(g,d){var i,g=a.merge(g);c.push(g);if(!g.color)f.dataClassColor==="category"?(i=e.options.colors,g.color=i[m++],m===i.length&&(m=0)):g.color=h.tweenColors(a.Color(f.minColor),a.Color(f.maxColor),d/(b.dataClasses.length-1))})},initStops:function(b){this.stops=
+b.stops||[[0,this.options.minColor],[1,this.options.maxColor]];l(this.stops,function(b){b.color=a.Color(b[1])})},toColor:function(b,h){var e,c=this.stops,a,f=this.dataClasses,g,d;if(f)for(d=f.length;d--;){if(g=f[d],a=g.from,c=g.to,(a===void 0||b>=a)&&(c===void 0||b<=c)){e=g.color;if(h)h.dataClass=d;break}}else{this.isLog&&(b=this.val2lin(b));e=1-(this.max-b)/(this.max-this.min);for(d=c.length;d--;)if(e>c[d][0])break;a=c[d]||c[d+1];c=c[d+1]||a;e=1-(c[0]-e)/(c[0]-a[0]||1);e=this.tweenColors(a.color,
+c.color,e)}return e},tweenColors:function(b,a,e){var c=a.rgba[3]!==1||b.rgba[3]!==1;return b.rgba.length===0||a.rgba.length===0?"none":(c?"rgba(":"rgb(")+Math.round(a.rgba[0]+(b.rgba[0]-a.rgba[0])*(1-e))+","+Math.round(a.rgba[1]+(b.rgba[1]-a.rgba[1])*(1-e))+","+Math.round(a.rgba[2]+(b.rgba[2]-a.rgba[2])*(1-e))+(c?","+(a.rgba[3]+(b.rgba[3]-a.rgba[3])*(1-e)):"")+")"}};a.seriesTypes.solidgauge=a.extendClass(a.seriesTypes.gauge,{type:"solidgauge",bindAxes:function(){var b;a.seriesTypes.gauge.prototype.bindAxes.call(this);
+b=this.yAxis;a.extend(b,n);b.options.dataClasses&&b.initDataClasses(b.options);b.initStops(b.options)},drawPoints:function(){var b=this,h=b.yAxis,e=h.center,c=b.options,m=b.chart.renderer;a.each(b.points,function(f){var g=f.graphic,d=h.startAngleRad+h.translate(f.y,null,null,null,!0),i=q(r(c.radius,100))*e[2]/200,o=q(r(c.innerRadius,60))*e[2]/200,p=h.toColor(f.y,f),k;if(p!=="none")k=f.color,f.color=p;c.wrap===!1&&(d=Math.max(h.startAngleRad,Math.min(h.endAngleRad,d)));var d=d*180/Math.PI,j=d/(180/
+Math.PI),l=h.startAngleRad,d=Math.min(j,l),j=Math.max(j,l);j-d>2*Math.PI&&(j=d+2*Math.PI);i={x:e[0],y:e[1],r:i,innerR:o,start:d,end:j};g?(o=i.d,g.attr({fill:f.color}).animate(i,{step:function(b,c){g.attr("fill",n.tweenColors(a.Color(k),a.Color(p),c.pos))}}),i.d=o):f.graphic=m.arc(i).attr({stroke:c.borderColor||"none","stroke-width":c.borderWidth||0,fill:f.color,"sweep-flag":0}).add(b.group)})},animate:null})})(Highcharts);
diff --git a/html/includes/js/modules/solid-gauge.src.js b/html/includes/js/modules/solid-gauge.src.js
new file mode 100644
index 0000000..6fd14c9
--- /dev/null
+++ b/html/includes/js/modules/solid-gauge.src.js
@@ -0,0 +1,234 @@
+/**
+ * @license Highcharts JS v4.0.4 (2014-09-02)
+ * Solid angular gauge module
+ *
+ * (c) 2010-2014 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+/*global Highcharts*/
+(function (H) {
+ "use strict";
+
+ var defaultPlotOptions = H.getOptions().plotOptions,
+ pInt = H.pInt,
+ pick = H.pick,
+ each = H.each,
+ colorAxisMethods,
+ UNDEFINED;
+
+ // The default options
+ defaultPlotOptions.solidgauge = H.merge(defaultPlotOptions.gauge, {
+ colorByPoint: true
+ });
+
+
+ // These methods are defined in the ColorAxis object, and copied here.
+ // If we implement an AMD system we should make ColorAxis a dependency.
+ colorAxisMethods = {
+
+
+ initDataClasses: function (userOptions) {
+ var axis = this,
+ chart = this.chart,
+ dataClasses,
+ colorCounter = 0,
+ options = this.options;
+ this.dataClasses = dataClasses = [];
+
+ each(userOptions.dataClasses, function (dataClass, i) {
+ var colors;
+
+ dataClass = H.merge(dataClass);
+ dataClasses.push(dataClass);
+ if (!dataClass.color) {
+ if (options.dataClassColor === 'category') {
+ colors = chart.options.colors;
+ dataClass.color = colors[colorCounter++];
+ // loop back to zero
+ if (colorCounter === colors.length) {
+ colorCounter = 0;
+ }
+ } else {
+ dataClass.color = axis.tweenColors(H.Color(options.minColor), H.Color(options.maxColor), i / (userOptions.dataClasses.length - 1));
+ }
+ }
+ });
+ },
+
+ initStops: function (userOptions) {
+ this.stops = userOptions.stops || [
+ [0, this.options.minColor],
+ [1, this.options.maxColor]
+ ];
+ each(this.stops, function (stop) {
+ stop.color = H.Color(stop[1]);
+ });
+ },
+ /**
+ * Translate from a value to a color
+ */
+ toColor: function (value, point) {
+ var pos,
+ stops = this.stops,
+ from,
+ to,
+ color,
+ dataClasses = this.dataClasses,
+ dataClass,
+ i;
+
+ if (dataClasses) {
+ i = dataClasses.length;
+ while (i--) {
+ dataClass = dataClasses[i];
+ from = dataClass.from;
+ to = dataClass.to;
+ if ((from === UNDEFINED || value >= from) && (to === UNDEFINED || value <= to)) {
+ color = dataClass.color;
+ if (point) {
+ point.dataClass = i;
+ }
+ break;
+ }
+ }
+
+ } else {
+
+ if (this.isLog) {
+ value = this.val2lin(value);
+ }
+ pos = 1 - ((this.max - value) / (this.max - this.min));
+ i = stops.length;
+ while (i--) {
+ if (pos > stops[i][0]) {
+ break;
+ }
+ }
+ from = stops[i] || stops[i + 1];
+ to = stops[i + 1] || from;
+
+ // The position within the gradient
+ pos = 1 - (to[0] - pos) / ((to[0] - from[0]) || 1);
+
+ color = this.tweenColors(
+ from.color,
+ to.color,
+ pos
+ );
+ }
+ return color;
+ },
+ tweenColors: function (from, to, pos) {
+ // Check for has alpha, because rgba colors perform worse due to lack of
+ // support in WebKit.
+ var hasAlpha = (to.rgba[3] !== 1 || from.rgba[3] !== 1);
+
+ if (from.rgba.length === 0 || to.rgba.length === 0) {
+ return 'none';
+ }
+ return (hasAlpha ? 'rgba(' : 'rgb(') +
+ Math.round(to.rgba[0] + (from.rgba[0] - to.rgba[0]) * (1 - pos)) + ',' +
+ Math.round(to.rgba[1] + (from.rgba[1] - to.rgba[1]) * (1 - pos)) + ',' +
+ Math.round(to.rgba[2] + (from.rgba[2] - to.rgba[2]) * (1 - pos)) +
+ (hasAlpha ? (',' + (to.rgba[3] + (from.rgba[3] - to.rgba[3]) * (1 - pos))) : '') + ')';
+ }
+ };
+
+ // The series prototype
+ H.seriesTypes.solidgauge = H.extendClass(H.seriesTypes.gauge, {
+ type: 'solidgauge',
+
+ bindAxes: function () {
+ var axis;
+ H.seriesTypes.gauge.prototype.bindAxes.call(this);
+
+ axis = this.yAxis;
+ H.extend(axis, colorAxisMethods);
+
+ // Prepare data classes
+ if (axis.options.dataClasses) {
+ axis.initDataClasses(axis.options);
+ }
+ axis.initStops(axis.options);
+ },
+
+ /**
+ * Draw the points where each point is one needle
+ */
+ drawPoints: function () {
+ var series = this,
+ yAxis = series.yAxis,
+ center = yAxis.center,
+ options = series.options,
+ renderer = series.chart.renderer;
+
+ H.each(series.points, function (point) {
+ var graphic = point.graphic,
+ rotation = yAxis.startAngleRad + yAxis.translate(point.y, null, null, null, true),
+ radius = (pInt(pick(options.radius, 100)) * center[2]) / 200,
+ innerRadius = (pInt(pick(options.innerRadius, 60)) * center[2]) / 200,
+ shapeArgs,
+ d,
+ toColor = yAxis.toColor(point.y, point),
+ fromColor;
+
+ if (toColor !== 'none') {
+ fromColor = point.color;
+ point.color = toColor;
+ }
+
+ // Handle the wrap option
+ if (options.wrap === false) {
+ rotation = Math.max(yAxis.startAngleRad, Math.min(yAxis.endAngleRad, rotation));
+ }
+ rotation = rotation * 180 / Math.PI;
+
+ var angle1 = rotation / (180 / Math.PI),
+ angle2 = yAxis.startAngleRad,
+ minAngle = Math.min(angle1, angle2),
+ maxAngle = Math.max(angle1, angle2);
+
+ if (maxAngle - minAngle > 2 * Math.PI) {
+ maxAngle = minAngle + 2 * Math.PI;
+ }
+
+ shapeArgs = {
+ x: center[0],
+ y: center[1],
+ r: radius,
+ innerR: innerRadius,
+ start: minAngle,
+ end: maxAngle
+ };
+
+ if (graphic) {
+ d = shapeArgs.d;
+
+ /*jslint unparam: true*/
+ graphic.attr({
+ fill: point.color
+ }).animate(shapeArgs, {
+ step: function (value, fx) {
+ graphic.attr('fill', colorAxisMethods.tweenColors(H.Color(fromColor), H.Color(toColor), fx.pos));
+ }
+ });
+ /*jslint unparam: false*/
+ shapeArgs.d = d; // animate alters it
+ } else {
+ point.graphic = renderer.arc(shapeArgs)
+ .attr({
+ stroke: options.borderColor || 'none',
+ 'stroke-width': options.borderWidth || 0,
+ fill: point.color,
+ 'sweep-flag': 0
+ })
+ .add(series.group);
+ }
+ });
+ },
+ animate: null
+ });
+
+}(Highcharts));