diff --git a/.gitignore b/.gitignore index 8186eacd4d..a7f8111306 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ project/project project/target public/compiled public/vendor/bar-rating +public/vendor/highcharts-4.2.5 public/vendor/hopscotch public/vendor/stockfish.wasm public/vendor/stockfish-mv.wasm diff --git a/public/vendor/highcharts-4.2.5/highcharts-3d.js b/public/vendor/highcharts-4.2.5/highcharts-3d.js deleted file mode 100644 index bec09a2902..0000000000 --- a/public/vendor/highcharts-4.2.5/highcharts-3d.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - Highcharts JS v4.2.5 (2016-05-06) - - 3D features for Highcharts JS - - @license: www.highcharts.com/license -*/ -(function(d){typeof module==="object"&&module.exports?module.exports=d:d(Highcharts)})(function(d){function o(c,b,a){var e,f,g=b.options.chart.options3d,d=!1,j=b.scale3d||1;a?(d=b.inverted,a=b.plotWidth/2,b=b.plotHeight/2,e=g.depth/2,f=s(g.depth,1)*s(g.viewDistance,0)):(a=b.plotLeft+b.plotWidth/2,b=b.plotTop+b.plotHeight/2,e=g.depth/2,f=s(g.depth,1)*s(g.viewDistance,0));var k=[],i=a,m=b,x=e,y=f,a=B*(d?g.beta:-g.beta),g=B*(d?-g.alpha:g.alpha),q=r(a),p=l(a),n=r(g),u=l(g),t,z,v,w,C,o;A(c,function(a){t= -(d?a.y:a.x)-i;z=(d?a.x:a.y)-m;v=(a.z||0)-x;w=p*t-q*v;C=-q*n*t+u*z-p*n*v;o=q*u*t+n*z+p*u*v;y>0&&yf&&g-f>n/2+ -1.0E-4?(k=k.concat(u(c,b,a,e,f,f+n/2,d,j)),k=k.concat(u(c,b,a,e,f+n/2,g,d,j))):gn/2+1.0E-4?(k=k.concat(u(c,b,a,e,f,f-n/2,d,j)),k=k.concat(u(c,b,a,e,f-n/2,g,d,j))):(k=g-f,k=["C",c+a*l(f)-a*F*k*r(f)+d,b+e*r(f)+e*F*k*l(f)+j,c+a*l(g)+a*F*k*r(g)+d,b+e*r(g)-e*F*k*l(g)+j,c+a*l(g)+d,b+e*r(g)+j]);return k}function J(c){if(this.chart.is3d()){var b=this.chart.options.plotOptions.column.grouping;if(b!==void 0&&!b&&this.group.zIndex!==void 0&&!this.zIndexSet)this.group.attr({zIndex:this.group.zIndex*10}), -this.zIndexSet=!0;var a=this.options,e=this.options.states;this.borderWidth=a.borderWidth=D(a.edgeWidth)?a.edgeWidth:1;d.each(this.data,function(b){if(b.y!==null)b=b.pointAttr,this.borderColor=d.pick(a.edgeColor,b[""].fill),b[""].stroke=this.borderColor,b.hover.stroke=d.pick(e.hover.edgeColor,this.borderColor),b.select.stroke=d.pick(e.select.edgeColor,this.borderColor)})}c.apply(this,[].slice.call(arguments,1))}var M=d.animObject,A=d.each,N=d.extend,O=d.inArray,G=d.merge,s=d.pick,K=d.wrap,n=Math.PI, -B=n/180,r=Math.sin,l=Math.cos,L=Math.round;d.perspective=o;var F=4*(Math.sqrt(2)-1)/3/(n/2);d.SVGRenderer.prototype.toLinePath=function(c,b){var a=[];d.each(c,function(b){a.push("L",b.x,b.y)});c.length&&(a[0]="M",b&&a.push("Z"));return a};d.SVGRenderer.prototype.cuboid=function(c){var b=this.g(),c=this.cuboidPath(c);b.front=this.path(c[0]).attr({zIndex:c[3],"stroke-linejoin":"round"}).add(b);b.top=this.path(c[1]).attr({zIndex:c[4],"stroke-linejoin":"round"}).add(b);b.side=this.path(c[2]).attr({zIndex:c[5], -"stroke-linejoin":"round"}).add(b);b.fillSetter=function(a){var b=d.Color(a).brighten(0.1).get(),c=d.Color(a).brighten(-0.1).get();this.front.attr({fill:a});this.top.attr({fill:b});this.side.attr({fill:c});this.color=a;return this};b.opacitySetter=function(a){this.front.attr({opacity:a});this.top.attr({opacity:a});this.side.attr({opacity:a});return this};b.attr=function(a){if(a.shapeArgs||D(a.x))a=this.renderer.cuboidPath(a.shapeArgs||a),this.front.attr({d:a[0],zIndex:a[3]}),this.top.attr({d:a[1], -zIndex:a[4]}),this.side.attr({d:a[2],zIndex:a[5]});else return d.SVGElement.prototype.attr.call(this,a);return this};b.animate=function(a,b,c){D(a.x)&&D(a.y)?(a=this.renderer.cuboidPath(a),this.front.attr({zIndex:a[3]}).animate({d:a[0]},b,c),this.top.attr({zIndex:a[4]}).animate({d:a[1]},b,c),this.side.attr({zIndex:a[5]}).animate({d:a[2]},b,c),this.attr({zIndex:-a[6]})):a.opacity?(this.front.animate(a,b,c),this.top.animate(a,b,c),this.side.animate(a,b,c)):d.SVGElement.prototype.animate.call(this,a, -b,c);return this};b.destroy=function(){this.front.destroy();this.top.destroy();this.side.destroy();return null};b.attr({zIndex:-c[6]});return b};d.SVGRenderer.prototype.cuboidPath=function(c){function b(a){return i[a]}var a=c.x,e=c.y,f=c.z,g=c.height,h=c.width,j=c.depth,k=d.map,i=[{x:a,y:e,z:f},{x:a+h,y:e,z:f},{x:a+h,y:e+g,z:f},{x:a,y:e+g,z:f},{x:a,y:e+g,z:f+j},{x:a+h,y:e+g,z:f+j},{x:a+h,y:e,z:f+j},{x:a,y:e,z:f+j}],i=o(i,d.charts[this.chartIndex],c.insidePlotArea),f=function(a,c){var e=[],a=k(a,b), -c=k(c,b);I(a)<0?e=a:I(c)<0&&(e=c);return e},c=f([3,2,1,0],[7,6,5,4]),a=[4,5,2,3],e=f([1,6,7,0],a),f=f([1,2,5,6],[0,7,4,3]);return[this.toLinePath(c,!0),this.toLinePath(e,!0),this.toLinePath(f,!0),E(c),E(e),E(f),E(k(a,b))*9E9]};d.SVGRenderer.prototype.arc3d=function(c){function b(a){var b=!1,c={},e;for(e in a)O(e,f)!==-1&&(c[e]=a[e],delete a[e],b=!0);return b?c:!1}var a=this.g(),e=a.renderer,f="x,y,r,innerR,start,end".split(","),c=G(c);c.alpha*=B;c.beta*=B;a.top=e.path();a.side1=e.path();a.side2=e.path(); -a.inn=e.path();a.out=e.path();a.onAdd=function(){var b=a.parentGroup;a.top.add(a);a.out.add(b);a.inn.add(b);a.side1.add(b);a.side2.add(b)};a.setPaths=function(b){var c=a.renderer.arc3dPath(b),e=c.zTop*100;a.attribs=b;a.top.attr({d:c.top,zIndex:c.zTop});a.inn.attr({d:c.inn,zIndex:c.zInn});a.out.attr({d:c.out,zIndex:c.zOut});a.side1.attr({d:c.side1,zIndex:c.zSide1});a.side2.attr({d:c.side2,zIndex:c.zSide2});a.zIndex=e;a.attr({zIndex:e});b.center&&(a.top.setRadialReference(b.center),delete b.center)}; -a.setPaths(c);a.fillSetter=function(a){var b=d.Color(a).brighten(-0.1).get();this.fill=a;this.side1.attr({fill:b});this.side2.attr({fill:b});this.inn.attr({fill:b});this.out.attr({fill:b});this.top.attr({fill:a});return this};A(["opacity","translateX","translateY","visibility"],function(b){a[b+"Setter"]=function(b,c){a[c]=b;A(["out","inn","side1","side2","top"],function(e){a[e].attr(c,b)})}});K(a,"attr",function(c,e,d){var f;if(typeof e==="object"&&(f=b(e)))N(a.attribs,f),a.setPaths(a.attribs);return c.call(this, -e,d)});K(a,"animate",function(a,c,e,d){var f,m=this.attribs,l;delete c.center;delete c.z;delete c.depth;delete c.alpha;delete c.beta;e=M(s(e,this.renderer.globalAnimation));if(e.duration&&(c=G(c),f=b(c)))l=f,e.step=function(a,b){function c(a){return m[a]+(s(l[a],m[a])-m[a])*b.pos}b.elem.setPaths(G(m,{x:c("x"),y:c("y"),r:c("r"),innerR:c("innerR"),start:c("start"),end:c("end")}))};return a.call(this,c,e,d)});a.destroy=function(){this.top.destroy();this.out.destroy();this.inn.destroy();this.side1.destroy(); -this.side2.destroy();d.SVGElement.prototype.destroy.call(this)};a.hide=function(){this.top.hide();this.out.hide();this.inn.hide();this.side1.hide();this.side2.hide()};a.show=function(){this.top.show();this.out.show();this.inn.show();this.side1.show();this.side2.show()};return a};d.SVGRenderer.prototype.arc3dPath=function(c){function b(a){a%=2*n;a>n&&(a=2*n-a);return a}var a=c.x,e=c.y,d=c.start,g=c.end-1.0E-5,h=c.r,j=c.innerR,k=c.depth,i=c.alpha,m=c.beta,x=l(d),y=r(d),c=l(g),q=r(g),p=h*l(m);h*=l(i); -var o=j*l(m),s=j*l(i),j=k*r(m),t=k*r(i),k=["M",a+p*x,e+h*y],k=k.concat(u(a,e,p,h,d,g,0,0)),k=k.concat(["L",a+o*c,e+s*q]),k=k.concat(u(a,e,o,s,g,d,0,0)),k=k.concat(["Z"]),z=m>0?n/2:0,m=i>0?0:n/2,z=d>-z?d:g>-z?-z:d,v=gw&&dn-m&&da&&(n=Math.min(n,1-Math.abs((h+m)/(a+m))%1));jd&&(n=d<0?Math.min(n,(k+l)/(-d+k+l)):Math.min(n,1-(k+l)/(d+l)%1));i0?4:-1}).css({stroke:g.color}).add()):(d={x:m+(b.yAxis[0].opposite?0:-f.size),y:l+(b.xAxis[0].opposite?-g.size:0),z:j,width:i+f.size,height:k+g.size,depth:h.size,insidePlotArea:!1},this.backFrame?this.backFrame.animate(d):this.backFrame=a.cuboid(d).attr({fill:h.color,zIndex:-3}).css({stroke:h.color}).add(),b={x:m+(b.yAxis[0].opposite?i:-f.size),y:l+(b.xAxis[0].opposite?-g.size:0),z:0,width:f.size,height:k+g.size,depth:j,insidePlotArea:!1},this.sideFrame?this.sideFrame.animate(b): -this.sideFrame=a.cuboid(b).attr({fill:f.color,zIndex:-2}).css({stroke:f.color}).add())}});d.wrap(d.Axis.prototype,"getPlotLinePath",function(c){var b=c.apply(this,[].slice.call(arguments,1));if(!this.chart.is3d())return b;if(b===null)return b;var a=this.chart,d=a.options.chart.options3d,a=this.isZAxis?a.plotWidth:d.depth,d=this.opposite;this.horiz&&(d=!d);b=[this.swapZ({x:b[1],y:b[2],z:d?a:0}),this.swapZ({x:b[1],y:b[2],z:a}),this.swapZ({x:b[4],y:b[5],z:a}),this.swapZ({x:b[4],y:b[5],z:d?0:a})];b=o(b, -this.chart,!1);return b=this.chart.renderer.toLinePath(b,!1)});d.wrap(d.Axis.prototype,"getLinePath",function(c){return this.chart.is3d()?[]:c.apply(this,[].slice.call(arguments,1))});d.wrap(d.Axis.prototype,"getPlotBandPath",function(c){if(!this.chart.is3d())return c.apply(this,[].slice.call(arguments,1));var b=arguments,a=b[1],b=this.getPlotLinePath(b[2]);(a=this.getPlotLinePath(a))&&b?a.push("L",b[10],b[11],"L",b[7],b[8],"L",b[4],b[5],"L",b[1],b[2]):a=null;return a});d.wrap(d.Tick.prototype,"getMarkPath", -function(c){var b=c.apply(this,[].slice.call(arguments,1));if(!this.axis.chart.is3d())return b;b=[this.axis.swapZ({x:b[1],y:b[2],z:0}),this.axis.swapZ({x:b[4],y:b[5],z:0})];b=o(b,this.axis.chart,!1);return b=["M",b[0].x,b[0].y,"L",b[1].x,b[1].y]});d.wrap(d.Tick.prototype,"getLabelPosition",function(c){var b=c.apply(this,[].slice.call(arguments,1));if(!this.axis.chart.is3d())return b;var a=o([this.axis.swapZ({x:b.x,y:b.y,z:0})],this.axis.chart,!1)[0];a.x-=!this.axis.horiz&&this.axis.opposite?this.axis.transA: -0;a.old=b;return a});d.wrap(d.Tick.prototype,"handleOverflow",function(c,b){if(this.axis.chart.is3d())b=b.old;return c.call(this,b)});d.wrap(d.Axis.prototype,"getTitlePosition",function(c){var b=this.chart.is3d(),a,d;if(b)d=this.axisTitleMargin,this.axisTitleMargin=0;a=c.apply(this,[].slice.call(arguments,1));if(b)a=o([this.swapZ({x:a.x,y:a.y,z:0})],this.chart,!1)[0],a[this.horiz?"y":"x"]+=(this.horiz?1:-1)*(this.opposite?-1:1)*d,this.axisTitleMargin=d;return a});d.wrap(d.Axis.prototype,"drawCrosshair", -function(c){var b=arguments;this.chart.is3d()&&b[2]&&(b[2]={plotX:b[2].plotXold||b[2].plotX,plotY:b[2].plotYold||b[2].plotY});c.apply(this,[].slice.call(b,1))});d.Axis.prototype.swapZ=function(c,b){if(this.isZAxis){var a=b?0:this.chart.plotLeft,d=this.chart;return{x:a+(d.yAxis[0].opposite?c.z:d.xAxis[0].width-c.z),y:c.y,z:c.x-a}}return c};var H=d.ZAxis=function(){this.isZAxis=!0;this.init.apply(this,arguments)};d.extend(H.prototype,d.Axis.prototype);d.extend(H.prototype,{setOptions:function(c){c= -d.merge({offset:0,lineWidth:0},c);d.Axis.prototype.setOptions.call(this,c);this.coll="zAxis"},setAxisSize:function(){d.Axis.prototype.setAxisSize.call(this);this.width=this.len=this.chart.options.chart.options3d.depth;this.right=this.chart.chartWidth-this.width-this.left},getSeriesExtremes:function(){var c=this,b=c.chart;c.hasVisibleSeries=!1;c.dataMin=c.dataMax=c.ignoreMinPadding=c.ignoreMaxPadding=null;c.buildStacks&&c.buildStacks();d.each(c.series,function(a){if(a.visible||!b.options.chart.ignoreHiddenSeries)if(c.hasVisibleSeries= -!0,a=a.zData,a.length)c.dataMin=Math.min(s(c.dataMin,a[0]),Math.min.apply(null,a)),c.dataMax=Math.max(s(c.dataMax,a[0]),Math.max.apply(null,a))})}});d.wrap(d.Chart.prototype,"getAxes",function(c){var b=this,a=this.options,a=a.zAxis=d.splat(a.zAxis||{});c.call(this);if(b.is3d())this.zAxis=[],d.each(a,function(a,c){a.index=c;a.isX=!0;(new H(b,a)).setScale()})});d.wrap(d.seriesTypes.column.prototype,"translate",function(c){c.apply(this,[].slice.call(arguments,1));if(this.chart.is3d()){var b=this.chart, -a=this.options,e=a.depth||25,f=(a.stacking?a.stack||0:this._i)*(e+(a.groupZPadding||1));a.grouping!==!1&&(f=0);f+=a.groupZPadding||1;d.each(this.data,function(a){if(a.y!==null){var c=a.shapeArgs,d=a.tooltipPos;a.shapeType="cuboid";c.z=f;c.depth=e;c.insidePlotArea=!0;d=o([{x:d[0],y:d[1],z:f}],b,!0)[0];a.tooltipPos=[d.x,d.y]}});this.z=f}});d.wrap(d.seriesTypes.column.prototype,"animate",function(c){if(this.chart.is3d()){var b=arguments[1],a=this.yAxis,e=this,f=this.yAxis.reversed;if(d.svg)b?d.each(e.data, -function(b){if(b.y!==null&&(b.height=b.shapeArgs.height,b.shapey=b.shapeArgs.y,b.shapeArgs.height=1,!f))b.shapeArgs.y=b.stackY?b.plotY+a.translate(b.stackY):b.plotY+(b.negative?-b.height:b.height)}):(d.each(e.data,function(a){if(a.y!==null)a.shapeArgs.height=a.height,a.shapeArgs.y=a.shapey,a.graphic&&a.graphic.animate(a.shapeArgs,e.options.animation)}),this.drawDataLabels(),e.animate=null)}else c.apply(this,[].slice.call(arguments,1))});d.wrap(d.seriesTypes.column.prototype,"init",function(c){c.apply(this, -[].slice.call(arguments,1));if(this.chart.is3d()){var b=this.options,a=b.grouping,d=b.stacking,f=s(this.yAxis.options.reversedStacks,!0),g=0;if(a===void 0||a){a=this.chart.retrieveStacks(d);g=b.stack||0;for(d=0;d=a.min&&g<=a.max:!1,e.push({x:f.plotX,y:f.plotY,z:f.plotZ});b=o(e,b,!0);for(h=0;h{point.x}
y: {point.y}
z: {point.z}
":"x: {point.x}
y: {point.y}
z: {point.z}
";return c});if(d.VMLRenderer)d.setOptions({animate:!1}),d.VMLRenderer.prototype.cuboid=d.SVGRenderer.prototype.cuboid, -d.VMLRenderer.prototype.cuboidPath=d.SVGRenderer.prototype.cuboidPath,d.VMLRenderer.prototype.toLinePath=d.SVGRenderer.prototype.toLinePath,d.VMLRenderer.prototype.createElement3D=d.SVGRenderer.prototype.createElement3D,d.VMLRenderer.prototype.arc3d=function(c){c=d.SVGRenderer.prototype.arc3d.call(this,c);c.css({zIndex:c.zIndex});return c},d.VMLRenderer.prototype.arc3dPath=d.SVGRenderer.prototype.arc3dPath,d.wrap(d.Axis.prototype,"render",function(c){c.apply(this,[].slice.call(arguments,1));this.sideFrame&& -(this.sideFrame.css({zIndex:0}),this.sideFrame.front.attr({fill:this.sideFrame.color}));this.bottomFrame&&(this.bottomFrame.css({zIndex:1}),this.bottomFrame.front.attr({fill:this.bottomFrame.color}));this.backFrame&&(this.backFrame.css({zIndex:0}),this.backFrame.front.attr({fill:this.backFrame.color}))})}); diff --git a/public/vendor/highcharts-4.2.5/highcharts-3d.src.js b/public/vendor/highcharts-4.2.5/highcharts-3d.src.js deleted file mode 100644 index f507ba8405..0000000000 --- a/public/vendor/highcharts-4.2.5/highcharts-3d.src.js +++ /dev/null @@ -1,1817 +0,0 @@ -// ==ClosureCompiler== -// @compilation_level SIMPLE_OPTIMIZATIONS - -/** - * @license Highcharts JS v4.2.5 (2016-05-06) - * - * 3D features for Highcharts JS - * - * @license: www.highcharts.com/license - */ - -(function (factory) { - if (typeof module === 'object' && module.exports) { - module.exports = factory; - } else { - factory(Highcharts); - } -}(function (Highcharts) { -/** - Shorthands for often used function - */ - var animObject = Highcharts.animObject, - each = Highcharts.each, - extend = Highcharts.extend, - inArray = Highcharts.inArray, - merge = Highcharts.merge, - pick = Highcharts.pick, - wrap = Highcharts.wrap; - /** - * Mathematical Functionility - */ - var PI = Math.PI, - deg2rad = (PI / 180), // degrees to radians - sin = Math.sin, - cos = Math.cos, - round = Math.round; - - /** - * Transforms a given array of points according to the angles in chart.options. - * Parameters: - * - points: the array of points - * - chart: the chart - * - insidePlotArea: wether to verifiy the points are inside the plotArea - * Returns: - * - an array of transformed points - */ - function perspective(points, chart, insidePlotArea) { - var options3d = chart.options.chart.options3d, - inverted = false, - origin, - scale = chart.scale3d || 1; - - if (insidePlotArea) { - inverted = chart.inverted; - origin = { - x: chart.plotWidth / 2, - y: chart.plotHeight / 2, - z: options3d.depth / 2, - vd: pick(options3d.depth, 1) * pick(options3d.viewDistance, 0) - }; - } else { - origin = { - x: chart.plotLeft + (chart.plotWidth / 2), - y: chart.plotTop + (chart.plotHeight / 2), - z: options3d.depth / 2, - vd: pick(options3d.depth, 1) * pick(options3d.viewDistance, 0) - }; - } - - var result = [], - xe = origin.x, - ye = origin.y, - ze = origin.z, - vd = origin.vd, - angle1 = deg2rad * (inverted ? options3d.beta : -options3d.beta), - angle2 = deg2rad * (inverted ? -options3d.alpha : options3d.alpha), - s1 = sin(angle1), - c1 = cos(angle1), - s2 = sin(angle2), - c2 = cos(angle2); - - var x, y, z, px, py, pz; - - // Transform each point - each(points, function (point) { - x = (inverted ? point.y : point.x) - xe; - y = (inverted ? point.x : point.y) - ye; - z = (point.z || 0) - ze; - - // Apply 3-D rotation - // Euler Angles (XYZ): cosA = cos(Alfa|Roll), cosB = cos(Beta|Pitch), cosG = cos(Gamma|Yaw) - // - // Composite rotation: - // | cosB * cosG | cosB * sinG | -sinB | - // | sinA * sinB * cosG - cosA * sinG | sinA * sinB * sinG + cosA * cosG | sinA * cosB | - // | cosA * sinB * cosG + sinA * sinG | cosA * sinB * sinG - sinA * cosG | cosA * cosB | - // - // Now, Gamma/Yaw is not used (angle=0), so we assume cosG = 1 and sinG = 0, so we get: - // | cosB | 0 | - sinB | - // | sinA * sinB | cosA | sinA * cosB | - // | cosA * sinB | - sinA | cosA * cosB | - // - // But in browsers, y is reversed, so we get sinA => -sinA. The general result is: - // | cosB | 0 | - sinB | | x | | px | - // | - sinA * sinB | cosA | - sinA * cosB | x | y | = | py | - // | cosA * sinB | sinA | cosA * cosB | | z | | pz | - // - // Result: - px = c1 * x - s1 * z; - py = -s1 * s2 * x + c2 * y - c1 * s2 * z; - pz = s1 * c2 * x + s2 * y + c1 * c2 * z; - - - // Apply perspective - if ((vd > 0) && (vd < Number.POSITIVE_INFINITY)) { - px = px * (vd / (pz + ze + vd)); - py = py * (vd / (pz + ze + vd)); - } - - - //Apply translation - px = px * scale + xe; - py = py * scale + ye; - pz = pz * scale + ze; - - - result.push({ - x: (inverted ? py : px), - y: (inverted ? px : py), - z: pz - }); - }); - return result; - } - // Make function acessible to plugins - Highcharts.perspective = perspective; - /*** - EXTENSION TO THE SVG-RENDERER TO ENABLE 3D SHAPES - ***/ - ////// HELPER METHODS ////// - - var dFactor = (4 * (Math.sqrt(2) - 1) / 3) / (PI / 2); - - function defined(obj) { - return obj !== undefined && obj !== null; - } - - //Shoelace algorithm -- http://en.wikipedia.org/wiki/Shoelace_formula - function shapeArea(vertexes) { - var area = 0, - i, - j; - for (i = 0; i < vertexes.length; i++) { - j = (i + 1) % vertexes.length; - area += vertexes[i].x * vertexes[j].y - vertexes[j].x * vertexes[i].y; - } - return area / 2; - } - - function averageZ(vertexes) { - var z = 0, - i; - for (i = 0; i < vertexes.length; i++) { - z += vertexes[i].z; - } - return vertexes.length ? z / vertexes.length : 0; - } - - /** Method to construct a curved path - * Can 'wrap' around more then 180 degrees - */ - function curveTo(cx, cy, rx, ry, start, end, dx, dy) { - var result = []; - if ((end > start) && (end - start > PI / 2 + 0.0001)) { - result = result.concat(curveTo(cx, cy, rx, ry, start, start + (PI / 2), dx, dy)); - result = result.concat(curveTo(cx, cy, rx, ry, start + (PI / 2), end, dx, dy)); - } else if ((end < start) && (start - end > PI / 2 + 0.0001)) { - result = result.concat(curveTo(cx, cy, rx, ry, start, start - (PI / 2), dx, dy)); - result = result.concat(curveTo(cx, cy, rx, ry, start - (PI / 2), end, dx, dy)); - } else { - var arcAngle = end - start; - result = [ - 'C', - cx + (rx * cos(start)) - ((rx * dFactor * arcAngle) * sin(start)) + dx, - cy + (ry * sin(start)) + ((ry * dFactor * arcAngle) * cos(start)) + dy, - cx + (rx * cos(end)) + ((rx * dFactor * arcAngle) * sin(end)) + dx, - cy + (ry * sin(end)) - ((ry * dFactor * arcAngle) * cos(end)) + dy, - - cx + (rx * cos(end)) + dx, - cy + (ry * sin(end)) + dy - ]; - } - return result; - } - - Highcharts.SVGRenderer.prototype.toLinePath = function (points, closed) { - var result = []; - - // Put "L x y" for each point - Highcharts.each(points, function (point) { - result.push('L', point.x, point.y); - }); - - if (points.length) { - // Set the first element to M - result[0] = 'M'; - - // If it is a closed line, add Z - if (closed) { - result.push('Z'); - } - } - - return result; - }; - - ////// CUBOIDS ////// - Highcharts.SVGRenderer.prototype.cuboid = function (shapeArgs) { - - var result = this.g(), - paths = this.cuboidPath(shapeArgs); - - // create the 3 sides - result.front = this.path(paths[0]).attr({ zIndex: paths[3], 'stroke-linejoin': 'round' }).add(result); - result.top = this.path(paths[1]).attr({ zIndex: paths[4], 'stroke-linejoin': 'round' }).add(result); - result.side = this.path(paths[2]).attr({ zIndex: paths[5], 'stroke-linejoin': 'round' }).add(result); - - // apply the fill everywhere, the top a bit brighter, the side a bit darker - result.fillSetter = function (color) { - var c0 = color, - c1 = Highcharts.Color(color).brighten(0.1).get(), - c2 = Highcharts.Color(color).brighten(-0.1).get(); - - this.front.attr({ fill: c0 }); - this.top.attr({ fill: c1 }); - this.side.attr({ fill: c2 }); - - this.color = color; - return this; - }; - - // apply opacaity everywhere - result.opacitySetter = function (opacity) { - this.front.attr({ opacity: opacity }); - this.top.attr({ opacity: opacity }); - this.side.attr({ opacity: opacity }); - return this; - }; - - result.attr = function (args) { - if (args.shapeArgs || defined(args.x)) { - var shapeArgs = args.shapeArgs || args; - var paths = this.renderer.cuboidPath(shapeArgs); - this.front.attr({ d: paths[0], zIndex: paths[3] }); - this.top.attr({ d: paths[1], zIndex: paths[4] }); - this.side.attr({ d: paths[2], zIndex: paths[5] }); - } else { - return Highcharts.SVGElement.prototype.attr.call(this, args); // getter returns value - } - - return this; - }; - - result.animate = function (args, duration, complete) { - if (defined(args.x) && defined(args.y)) { - var paths = this.renderer.cuboidPath(args); - this.front.attr({ zIndex: paths[3] }).animate({ d: paths[0] }, duration, complete); - this.top.attr({ zIndex: paths[4] }).animate({ d: paths[1] }, duration, complete); - this.side.attr({ zIndex: paths[5] }).animate({ d: paths[2] }, duration, complete); - this.attr({ - zIndex: -paths[6] // #4774 - }); - } else if (args.opacity) { - this.front.animate(args, duration, complete); - this.top.animate(args, duration, complete); - this.side.animate(args, duration, complete); - } else { - Highcharts.SVGElement.prototype.animate.call(this, args, duration, complete); - } - return this; - }; - - // destroy all children - result.destroy = function () { - this.front.destroy(); - this.top.destroy(); - this.side.destroy(); - - return null; - }; - - // Apply the Z index to the cuboid group - result.attr({ zIndex: -paths[6] }); - - return result; - }; - - /** - * Generates a cuboid - */ - Highcharts.SVGRenderer.prototype.cuboidPath = function (shapeArgs) { - var x = shapeArgs.x, - y = shapeArgs.y, - z = shapeArgs.z, - h = shapeArgs.height, - w = shapeArgs.width, - d = shapeArgs.depth, - chart = Highcharts.charts[this.chartIndex], - map = Highcharts.map; - - // The 8 corners of the cube - var pArr = [ - { x: x, y: y, z: z }, - { x: x + w, y: y, z: z }, - { x: x + w, y: y + h, z: z }, - { x: x, y: y + h, z: z }, - { x: x, y: y + h, z: z + d }, - { x: x + w, y: y + h, z: z + d }, - { x: x + w, y: y, z: z + d }, - { x: x, y: y, z: z + d } - ]; - - // apply perspective - pArr = perspective(pArr, chart, shapeArgs.insidePlotArea); - - // helper method to decide which side is visible - function mapPath(i) { - return pArr[i]; - } - var pickShape = function (path1, path2) { - var ret = []; - path1 = map(path1, mapPath); - path2 = map(path2, mapPath); - if (shapeArea(path1) < 0) { - ret = path1; - } else if (shapeArea(path2) < 0) { - ret = path2; - } - return ret; - }; - - // front or back - var front = [3, 2, 1, 0]; - var back = [7, 6, 5, 4]; - var path1 = pickShape(front, back); - - // top or bottom - var top = [1, 6, 7, 0]; - var bottom = [4, 5, 2, 3]; - var path2 = pickShape(top, bottom); - - // side - var right = [1, 2, 5, 6]; - var left = [0, 7, 4, 3]; - var path3 = pickShape(right, left); - - return [this.toLinePath(path1, true), this.toLinePath(path2, true), this.toLinePath(path3, true), averageZ(path1), averageZ(path2), averageZ(path3), averageZ(map(bottom, mapPath)) * 9e9]; // #4774 - }; - - ////// SECTORS ////// - Highcharts.SVGRenderer.prototype.arc3d = function (attribs) { - - var wrapper = this.g(), - renderer = wrapper.renderer, - customAttribs = ['x', 'y', 'r', 'innerR', 'start', 'end']; - - /** - * Get custom attributes. Mutate the original object and return an object with only custom attr. - */ - function suckOutCustom(params) { - var hasCA = false, - ca = {}; - for (var key in params) { - if (inArray(key, customAttribs) !== -1) { - ca[key] = params[key]; - delete params[key]; - hasCA = true; - } - } - return hasCA ? ca : false; - } - - attribs = merge(attribs); - - attribs.alpha *= deg2rad; - attribs.beta *= deg2rad; - - // Create the different sub sections of the shape - wrapper.top = renderer.path(); - wrapper.side1 = renderer.path(); - wrapper.side2 = renderer.path(); - wrapper.inn = renderer.path(); - wrapper.out = renderer.path(); - - /** - * Add all faces - */ - wrapper.onAdd = function () { - var parent = wrapper.parentGroup; - wrapper.top.add(wrapper); - wrapper.out.add(parent); - wrapper.inn.add(parent); - wrapper.side1.add(parent); - wrapper.side2.add(parent); - }; - - /** - * Compute the transformed paths and set them to the composite shapes - */ - wrapper.setPaths = function (attribs) { - - var paths = wrapper.renderer.arc3dPath(attribs), - zIndex = paths.zTop * 100; - - wrapper.attribs = attribs; - - wrapper.top.attr({ d: paths.top, zIndex: paths.zTop }); - wrapper.inn.attr({ d: paths.inn, zIndex: paths.zInn }); - wrapper.out.attr({ d: paths.out, zIndex: paths.zOut }); - wrapper.side1.attr({ d: paths.side1, zIndex: paths.zSide1 }); - wrapper.side2.attr({ d: paths.side2, zIndex: paths.zSide2 }); - - - // show all children - wrapper.zIndex = zIndex; - wrapper.attr({ zIndex: zIndex }); - - // Set the radial gradient center the first time - if (attribs.center) { - wrapper.top.setRadialReference(attribs.center); - delete attribs.center; - } - }; - wrapper.setPaths(attribs); - - // Apply the fill to the top and a darker shade to the sides - wrapper.fillSetter = function (value) { - var darker = Highcharts.Color(value).brighten(-0.1).get(); - - this.fill = value; - - this.side1.attr({ fill: darker }); - this.side2.attr({ fill: darker }); - this.inn.attr({ fill: darker }); - this.out.attr({ fill: darker }); - this.top.attr({ fill: value }); - return this; - }; - - // Apply the same value to all. These properties cascade down to the children - // when set to the composite arc3d. - each(['opacity', 'translateX', 'translateY', 'visibility'], function (setter) { - wrapper[setter + 'Setter'] = function (value, key) { - wrapper[key] = value; - each(['out', 'inn', 'side1', 'side2', 'top'], function (el) { - wrapper[el].attr(key, value); - }); - }; - }); - - /** - * Override attr to remove shape attributes and use those to set child paths - */ - wrap(wrapper, 'attr', function (proceed, params, val) { - var ca; - if (typeof params === 'object') { - ca = suckOutCustom(params); - if (ca) { - extend(wrapper.attribs, ca); - wrapper.setPaths(wrapper.attribs); - } - } - return proceed.call(this, params, val); - }); - - /** - * Override the animate function by sucking out custom parameters related to the shapes directly, - * and update the shapes from the animation step. - */ - wrap(wrapper, 'animate', function (proceed, params, animation, complete) { - var ca, - from = this.attribs, - to; - - // Attribute-line properties connected to 3D. These shouldn't have been in the - // attribs collection in the first place. - delete params.center; - delete params.z; - delete params.depth; - delete params.alpha; - delete params.beta; - - animation = animObject(pick(animation, this.renderer.globalAnimation)); - - if (animation.duration) { - params = merge(params); // Don't mutate the original object - ca = suckOutCustom(params); - - if (ca) { - to = ca; - animation.step = function (a, fx) { - function interpolate(key) { - return from[key] + (pick(to[key], from[key]) - from[key]) * fx.pos; - } - fx.elem.setPaths(merge(from, { - x: interpolate('x'), - y: interpolate('y'), - r: interpolate('r'), - innerR: interpolate('innerR'), - start: interpolate('start'), - end: interpolate('end') - })); - }; - } - } - return proceed.call(this, params, animation, complete); - }); - - // destroy all children - wrapper.destroy = function () { - this.top.destroy(); - this.out.destroy(); - this.inn.destroy(); - this.side1.destroy(); - this.side2.destroy(); - - Highcharts.SVGElement.prototype.destroy.call(this); - }; - // hide all children - wrapper.hide = function () { - this.top.hide(); - this.out.hide(); - this.inn.hide(); - this.side1.hide(); - this.side2.hide(); - }; - wrapper.show = function () { - this.top.show(); - this.out.show(); - this.inn.show(); - this.side1.show(); - this.side2.show(); - }; - return wrapper; - }; - - /** - * Generate the paths required to draw a 3D arc - */ - Highcharts.SVGRenderer.prototype.arc3dPath = function (shapeArgs) { - var cx = shapeArgs.x, // x coordinate of the center - cy = shapeArgs.y, // y coordinate of the center - start = shapeArgs.start, // start angle - end = shapeArgs.end - 0.00001, // end angle - r = shapeArgs.r, // radius - ir = shapeArgs.innerR, // inner radius - d = shapeArgs.depth, // depth - alpha = shapeArgs.alpha, // alpha rotation of the chart - beta = shapeArgs.beta; // beta rotation of the chart - - // Derived Variables - var cs = cos(start), // cosinus of the start angle - ss = sin(start), // sinus of the start angle - ce = cos(end), // cosinus of the end angle - se = sin(end), // sinus of the end angle - rx = r * cos(beta), // x-radius - ry = r * cos(alpha), // y-radius - irx = ir * cos(beta), // x-radius (inner) - iry = ir * cos(alpha), // y-radius (inner) - dx = d * sin(beta), // distance between top and bottom in x - dy = d * sin(alpha); // distance between top and bottom in y - - // TOP - var top = ['M', cx + (rx * cs), cy + (ry * ss)]; - top = top.concat(curveTo(cx, cy, rx, ry, start, end, 0, 0)); - top = top.concat([ - 'L', cx + (irx * ce), cy + (iry * se) - ]); - top = top.concat(curveTo(cx, cy, irx, iry, end, start, 0, 0)); - top = top.concat(['Z']); - // OUTSIDE - var b = (beta > 0 ? PI / 2 : 0), - a = (alpha > 0 ? 0 : PI / 2); - - var start2 = start > -b ? start : (end > -b ? -b : start), - end2 = end < PI - a ? end : (start < PI - a ? PI - a : end), - midEnd = 2 * PI - a; - - // When slice goes over bottom middle, need to add both, left and right outer side. - // Additionally, when we cross right hand edge, create sharp edge. Outer shape/wall: - // - // ------- - // / ^ \ - // 4) / / \ \ 1) - // / / \ \ - // / / \ \ - // (c)=> ==== ==== <=(d) - // \ \ / / - // \ \<=(a)/ / - // \ \ / / <=(b) - // 3) \ v / 2) - // ------- - // - // (a) - inner side - // (b) - outer side - // (c) - left edge (sharp) - // (d) - right edge (sharp) - // 1..n - rendering order for startAngle = 0, when set to e.g 90, order changes clockwise (1->2, 2->3, n->1) and counterclockwise for negative startAngle - - var out = ['M', cx + (rx * cos(start2)), cy + (ry * sin(start2))]; - out = out.concat(curveTo(cx, cy, rx, ry, start2, end2, 0, 0)); - - if (end > midEnd && start < midEnd) { // When shape is wide, it can cross both, (c) and (d) edges, when using startAngle - // Go to outer side - out = out.concat([ - 'L', cx + (rx * cos(end2)) + dx, cy + (ry * sin(end2)) + dy - ]); - // Curve to the right edge of the slice (d) - out = out.concat(curveTo(cx, cy, rx, ry, end2, midEnd, dx, dy)); - // Go to the inner side - out = out.concat([ - 'L', cx + (rx * cos(midEnd)), cy + (ry * sin(midEnd)) - ]); - // Curve to the true end of the slice - out = out.concat(curveTo(cx, cy, rx, ry, midEnd, end, 0, 0)); - // Go to the outer side - out = out.concat([ - 'L', cx + (rx * cos(end)) + dx, cy + (ry * sin(end)) + dy - ]); - // Go back to middle (d) - out = out.concat(curveTo(cx, cy, rx, ry, end, midEnd, dx, dy)); - out = out.concat([ - 'L', cx + (rx * cos(midEnd)), cy + (ry * sin(midEnd)) - ]); - // Go back to the left edge - out = out.concat(curveTo(cx, cy, rx, ry, midEnd, end2, 0, 0)); - } else if (end > PI - a && start < PI - a) { // But shape can cross also only (c) edge: - // Go to outer side - out = out.concat([ - 'L', cx + (rx * cos(end2)) + dx, cy + (ry * sin(end2)) + dy - ]); - // Curve to the true end of the slice - out = out.concat(curveTo(cx, cy, rx, ry, end2, end, dx, dy)); - // Go to the inner side - out = out.concat([ - 'L', cx + (rx * cos(end)), cy + (ry * sin(end)) - ]); - // Go back to the artifical end2 - out = out.concat(curveTo(cx, cy, rx, ry, end, end2, 0, 0)); - } - - out = out.concat([ - 'L', cx + (rx * cos(end2)) + dx, cy + (ry * sin(end2)) + dy - ]); - out = out.concat(curveTo(cx, cy, rx, ry, end2, start2, dx, dy)); - out = out.concat(['Z']); - - // INSIDE - var inn = ['M', cx + (irx * cs), cy + (iry * ss)]; - inn = inn.concat(curveTo(cx, cy, irx, iry, start, end, 0, 0)); - inn = inn.concat([ - 'L', cx + (irx * cos(end)) + dx, cy + (iry * sin(end)) + dy - ]); - inn = inn.concat(curveTo(cx, cy, irx, iry, end, start, dx, dy)); - inn = inn.concat(['Z']); - - // SIDES - var side1 = [ - 'M', cx + (rx * cs), cy + (ry * ss), - 'L', cx + (rx * cs) + dx, cy + (ry * ss) + dy, - 'L', cx + (irx * cs) + dx, cy + (iry * ss) + dy, - 'L', cx + (irx * cs), cy + (iry * ss), - 'Z' - ]; - var side2 = [ - 'M', cx + (rx * ce), cy + (ry * se), - 'L', cx + (rx * ce) + dx, cy + (ry * se) + dy, - 'L', cx + (irx * ce) + dx, cy + (iry * se) + dy, - 'L', cx + (irx * ce), cy + (iry * se), - 'Z' - ]; - - // correction for changed position of vanishing point caused by alpha and beta rotations - var angleCorr = Math.atan2(dy, -dx), - angleEnd = Math.abs(end + angleCorr), - angleStart = Math.abs(start + angleCorr), - angleMid = Math.abs((start + end) / 2 + angleCorr); - - // set to 0-PI range - function toZeroPIRange(angle) { - angle = angle % (2 * PI); - if (angle > PI) { - angle = 2 * PI - angle; - } - return angle; - } - angleEnd = toZeroPIRange(angleEnd); - angleStart = toZeroPIRange(angleStart); - angleMid = toZeroPIRange(angleMid); - - // *1e5 is to compensate pInt in zIndexSetter - var incPrecision = 1e5, - a1 = angleMid * incPrecision, - a2 = angleStart * incPrecision, - a3 = angleEnd * incPrecision; - - return { - top: top, - zTop: PI * incPrecision + 1, // max angle is PI, so this is allways higher - out: out, - zOut: Math.max(a1, a2, a3), - inn: inn, - zInn: Math.max(a1, a2, a3), - side1: side1, - zSide1: a3 * 0.99, // to keep below zOut and zInn in case of same values - side2: side2, - zSide2: a2 * 0.99 - }; - }; - /*** - EXTENSION FOR 3D CHARTS - ***/ - // Shorthand to check the is3d flag - Highcharts.Chart.prototype.is3d = function () { - return this.options.chart.options3d && this.options.chart.options3d.enabled; // #4280 - }; - - /** - * Extend the getMargins method to calculate scale of the 3D view. That is required to - * fit chart's 3D projection into the actual plotting area. Reported as #4933. - */ - Highcharts.wrap(Highcharts.Chart.prototype, 'getMargins', function (proceed) { - var chart = this, - options3d = chart.options.chart.options3d, - bbox3d = { - minX: Number.MAX_VALUE, - maxX: -Number.MAX_VALUE, - minY: Number.MAX_VALUE, - maxY: -Number.MAX_VALUE - }, - plotLeft = chart.plotLeft, - plotRight = chart.plotWidth + plotLeft, - plotTop = chart.plotTop, - plotBottom = chart.plotHeight + plotTop, - originX = plotLeft + chart.plotWidth / 2, - originY = plotTop + chart.plotHeight / 2, - scale = 1, - corners = [], - i; - - proceed.apply(this, [].slice.call(arguments, 1)); - - if (this.is3d()) { - if (options3d.fitToPlot === true) { - // Clear previous scale in case of updates: - chart.scale3d = 1; - - // Top left corners: - corners = [{ - x: plotLeft, - y: plotTop, - z: 0 - }, { - x: plotLeft, - y: plotTop, - z: options3d.depth - }]; - - // Top right corners: - for (i = 0; i < 2; i++) { - corners.push({ - x: plotRight, - y: corners[i].y, - z: corners[i].z - }); - } - - // All bottom corners: - for (i = 0; i < 4; i++) { - corners.push({ - x: corners[i].x, - y: plotBottom, - z: corners[i].z - }); - } - - // Calculate 3D corners: - corners = perspective(corners, chart, false); - - // Get bounding box of 3D element: - each(corners, function (corner) { - bbox3d.minX = Math.min(bbox3d.minX, corner.x); - bbox3d.maxX = Math.max(bbox3d.maxX, corner.x); - bbox3d.minY = Math.min(bbox3d.minY, corner.y); - bbox3d.maxY = Math.max(bbox3d.maxY, corner.y); - }); - - // Left edge: - if (plotLeft > bbox3d.minX) { - scale = Math.min(scale, 1 - Math.abs((plotLeft + originX) / (bbox3d.minX + originX)) % 1); - } - - // Right edge: - if (plotRight < bbox3d.maxX) { - scale = Math.min(scale, (plotRight - originX) / (bbox3d.maxX - originX)); - } - - // Top edge: - if (plotTop > bbox3d.minY) { - if (bbox3d.minY < 0) { - scale = Math.min(scale, (plotTop + originY) / (-bbox3d.minY + plotTop + originY)); - } else { - scale = Math.min(scale, 1 - (plotTop + originY) / (bbox3d.minY + originY) % 1); - } - } - - // Bottom edge: - if (plotBottom < bbox3d.maxY) { - scale = Math.min(scale, Math.abs((plotBottom - originY) / (bbox3d.maxY - originY))); - } - - // Set scale, used later in perspective method(): - chart.scale3d = scale; - } - } - }); - - Highcharts.wrap(Highcharts.Chart.prototype, 'isInsidePlot', function (proceed) { - return this.is3d() || proceed.apply(this, [].slice.call(arguments, 1)); - }); - - var defaultChartOptions = Highcharts.getOptions(); - defaultChartOptions.chart.options3d = { - enabled: false, - alpha: 0, - beta: 0, - depth: 100, - fitToPlot: true, - viewDistance: 25, - frame: { - bottom: { size: 1, color: 'rgba(255,255,255,0)' }, - side: { size: 1, color: 'rgba(255,255,255,0)' }, - back: { size: 1, color: 'rgba(255,255,255,0)' } - } - }; - - Highcharts.wrap(Highcharts.Chart.prototype, 'init', function (proceed) { - var args = [].slice.call(arguments, 1), - plotOptions, - pieOptions; - - if (args[0].chart && args[0].chart.options3d && args[0].chart.options3d.enabled) { - // Normalize alpha and beta to (-360, 360) range - args[0].chart.options3d.alpha = (args[0].chart.options3d.alpha || 0) % 360; - args[0].chart.options3d.beta = (args[0].chart.options3d.beta || 0) % 360; - - plotOptions = args[0].plotOptions || {}; - pieOptions = plotOptions.pie || {}; - - pieOptions.borderColor = Highcharts.pick(pieOptions.borderColor, undefined); - } - proceed.apply(this, args); - }); - - Highcharts.wrap(Highcharts.Chart.prototype, 'setChartSize', function (proceed) { - proceed.apply(this, [].slice.call(arguments, 1)); - - if (this.is3d()) { - var inverted = this.inverted, - clipBox = this.clipBox, - margin = this.margin, - x = inverted ? 'y' : 'x', - y = inverted ? 'x' : 'y', - w = inverted ? 'height' : 'width', - h = inverted ? 'width' : 'height'; - - clipBox[x] = -(margin[3] || 0); - clipBox[y] = -(margin[0] || 0); - clipBox[w] = this.chartWidth + (margin[3] || 0) + (margin[1] || 0); - clipBox[h] = this.chartHeight + (margin[0] || 0) + (margin[2] || 0); - } - }); - - Highcharts.wrap(Highcharts.Chart.prototype, 'redraw', function (proceed) { - if (this.is3d()) { - // Set to force a redraw of all elements - this.isDirtyBox = true; - } - proceed.apply(this, [].slice.call(arguments, 1)); - }); - - // Draw the series in the reverse order (#3803, #3917) - Highcharts.wrap(Highcharts.Chart.prototype, 'renderSeries', function (proceed) { - var series, - i = this.series.length; - - if (this.is3d()) { - while (i--) { - series = this.series[i]; - series.translate(); - series.render(); - } - } else { - proceed.call(this); - } - }); - - Highcharts.Chart.prototype.retrieveStacks = function (stacking) { - var series = this.series, - stacks = {}, - stackNumber, - i = 1; - - Highcharts.each(this.series, function (s) { - stackNumber = pick(s.options.stack, (stacking ? 0 : series.length - 1 - s.index)); // #3841, #4532 - if (!stacks[stackNumber]) { - stacks[stackNumber] = { series: [s], position: i }; - i++; - } else { - stacks[stackNumber].series.push(s); - } - }); - - stacks.totalStacks = i + 1; - return stacks; - }; - - /*** - EXTENSION TO THE AXIS - ***/ - Highcharts.wrap(Highcharts.Axis.prototype, 'setOptions', function (proceed, userOptions) { - var options; - proceed.call(this, userOptions); - if (this.chart.is3d()) { - options = this.options; - options.tickWidth = Highcharts.pick(options.tickWidth, 0); - options.gridLineWidth = Highcharts.pick(options.gridLineWidth, 1); - } - }); - - Highcharts.wrap(Highcharts.Axis.prototype, 'render', function (proceed) { - proceed.apply(this, [].slice.call(arguments, 1)); - - // Do not do this if the chart is not 3D - if (!this.chart.is3d()) { - return; - } - - var chart = this.chart, - renderer = chart.renderer, - options3d = chart.options.chart.options3d, - frame = options3d.frame, - fbottom = frame.bottom, - fback = frame.back, - fside = frame.side, - depth = options3d.depth, - height = this.height, - width = this.width, - left = this.left, - top = this.top; - - if (this.isZAxis) { - return; - } - if (this.horiz) { - var bottomShape = { - x: left, - y: top + (chart.xAxis[0].opposite ? -fbottom.size : height), - z: 0, - width: width, - height: fbottom.size, - depth: depth, - insidePlotArea: false - }; - if (!this.bottomFrame) { - this.bottomFrame = renderer.cuboid(bottomShape).attr({ - fill: fbottom.color, - zIndex: (chart.yAxis[0].reversed && options3d.alpha > 0 ? 4 : -1) - }) - .css({ - stroke: fbottom.color - }).add(); - } else { - this.bottomFrame.animate(bottomShape); - } - } else { - // BACK - var backShape = { - x: left + (chart.yAxis[0].opposite ? 0 : -fside.size), - y: top + (chart.xAxis[0].opposite ? -fbottom.size : 0), - z: depth, - width: width + fside.size, - height: height + fbottom.size, - depth: fback.size, - insidePlotArea: false - }; - if (!this.backFrame) { - this.backFrame = renderer.cuboid(backShape).attr({ - fill: fback.color, - zIndex: -3 - }).css({ - stroke: fback.color - }).add(); - } else { - this.backFrame.animate(backShape); - } - var sideShape = { - x: left + (chart.yAxis[0].opposite ? width : -fside.size), - y: top + (chart.xAxis[0].opposite ? -fbottom.size : 0), - z: 0, - width: fside.size, - height: height + fbottom.size, - depth: depth, - insidePlotArea: false - }; - if (!this.sideFrame) { - this.sideFrame = renderer.cuboid(sideShape).attr({ - fill: fside.color, - zIndex: -2 - }).css({ - stroke: fside.color - }).add(); - } else { - this.sideFrame.animate(sideShape); - } - } - }); - - Highcharts.wrap(Highcharts.Axis.prototype, 'getPlotLinePath', function (proceed) { - var path = proceed.apply(this, [].slice.call(arguments, 1)); - - // Do not do this if the chart is not 3D - if (!this.chart.is3d()) { - return path; - } - - if (path === null) { - return path; - } - - var chart = this.chart, - options3d = chart.options.chart.options3d, - d = this.isZAxis ? chart.plotWidth : options3d.depth, - opposite = this.opposite; - if (this.horiz) { - opposite = !opposite; - } - var pArr = [ - this.swapZ({ x: path[1], y: path[2], z: (opposite ? d : 0) }), - this.swapZ({ x: path[1], y: path[2], z: d }), - this.swapZ({ x: path[4], y: path[5], z: d }), - this.swapZ({ x: path[4], y: path[5], z: (opposite ? 0 : d) }) - ]; - - pArr = perspective(pArr, this.chart, false); - path = this.chart.renderer.toLinePath(pArr, false); - - return path; - }); - - // Do not draw axislines in 3D - Highcharts.wrap(Highcharts.Axis.prototype, 'getLinePath', function (proceed) { - return this.chart.is3d() ? [] : proceed.apply(this, [].slice.call(arguments, 1)); - }); - - Highcharts.wrap(Highcharts.Axis.prototype, 'getPlotBandPath', function (proceed) { - // Do not do this if the chart is not 3D - if (!this.chart.is3d()) { - return proceed.apply(this, [].slice.call(arguments, 1)); - } - - var args = arguments, - from = args[1], - to = args[2], - toPath = this.getPlotLinePath(to), - path = this.getPlotLinePath(from); - - if (path && toPath) { - path.push( - 'L', - toPath[10], // These two do not exist in the regular getPlotLine - toPath[11], // ---- # 3005 - 'L', - toPath[7], - toPath[8], - 'L', - toPath[4], - toPath[5], - 'L', - toPath[1], - toPath[2] - ); - } else { // outside the axis area - path = null; - } - - return path; - }); - - /*** - EXTENSION TO THE TICKS - ***/ - - Highcharts.wrap(Highcharts.Tick.prototype, 'getMarkPath', function (proceed) { - var path = proceed.apply(this, [].slice.call(arguments, 1)); - - // Do not do this if the chart is not 3D - if (!this.axis.chart.is3d()) { - return path; - } - - var pArr = [ - this.axis.swapZ({ x: path[1], y: path[2], z: 0 }), - this.axis.swapZ({ x: path[4], y: path[5], z: 0 }) - ]; - - pArr = perspective(pArr, this.axis.chart, false); - path = [ - 'M', pArr[0].x, pArr[0].y, - 'L', pArr[1].x, pArr[1].y - ]; - return path; - }); - - Highcharts.wrap(Highcharts.Tick.prototype, 'getLabelPosition', function (proceed) { - var pos = proceed.apply(this, [].slice.call(arguments, 1)); - - // Do not do this if the chart is not 3D - if (!this.axis.chart.is3d()) { - return pos; - } - - var newPos = perspective([this.axis.swapZ({ x: pos.x, y: pos.y, z: 0 })], this.axis.chart, false)[0]; - newPos.x = newPos.x - (!this.axis.horiz && this.axis.opposite ? this.axis.transA : 0); //#3788 - newPos.old = pos; - return newPos; - }); - - Highcharts.wrap(Highcharts.Tick.prototype, 'handleOverflow', function (proceed, xy) { - if (this.axis.chart.is3d()) { - xy = xy.old; - } - return proceed.call(this, xy); - }); - - Highcharts.wrap(Highcharts.Axis.prototype, 'getTitlePosition', function (proceed) { - var is3d = this.chart.is3d(), - pos, - axisTitleMargin; - - // Pull out the axis title margin, that is not subject to the perspective - if (is3d) { - axisTitleMargin = this.axisTitleMargin; - this.axisTitleMargin = 0; - } - - pos = proceed.apply(this, [].slice.call(arguments, 1)); - - if (is3d) { - pos = perspective([this.swapZ({ x: pos.x, y: pos.y, z: 0 })], this.chart, false)[0]; - - // Re-apply the axis title margin outside the perspective - pos[this.horiz ? 'y' : 'x'] += (this.horiz ? 1 : -1) * // horizontal axis reverses the margin ... - (this.opposite ? -1 : 1) * // ... so does opposite axes - axisTitleMargin; - this.axisTitleMargin = axisTitleMargin; - } - return pos; - }); - - Highcharts.wrap(Highcharts.Axis.prototype, 'drawCrosshair', function (proceed) { - var args = arguments; - if (this.chart.is3d()) { - if (args[2]) { - args[2] = { - plotX: args[2].plotXold || args[2].plotX, - plotY: args[2].plotYold || args[2].plotY - }; - } - } - proceed.apply(this, [].slice.call(args, 1)); - }); - - /*** - Z-AXIS - ***/ - - Highcharts.Axis.prototype.swapZ = function (p, insidePlotArea) { - if (this.isZAxis) { - var plotLeft = insidePlotArea ? 0 : this.chart.plotLeft; - var chart = this.chart; - return { - x: plotLeft + (chart.yAxis[0].opposite ? p.z : chart.xAxis[0].width - p.z), - y: p.y, - z: p.x - plotLeft - }; - } - return p; - }; - - var ZAxis = Highcharts.ZAxis = function () { - this.isZAxis = true; - this.init.apply(this, arguments); - }; - Highcharts.extend(ZAxis.prototype, Highcharts.Axis.prototype); - Highcharts.extend(ZAxis.prototype, { - setOptions: function (userOptions) { - userOptions = Highcharts.merge({ - offset: 0, - lineWidth: 0 - }, userOptions); - Highcharts.Axis.prototype.setOptions.call(this, userOptions); - this.coll = 'zAxis'; - }, - setAxisSize: function () { - Highcharts.Axis.prototype.setAxisSize.call(this); - this.width = this.len = this.chart.options.chart.options3d.depth; - this.right = this.chart.chartWidth - this.width - this.left; - }, - getSeriesExtremes: function () { - var axis = this, - chart = axis.chart; - - axis.hasVisibleSeries = false; - - // Reset properties in case we're redrawing (#3353) - axis.dataMin = axis.dataMax = axis.ignoreMinPadding = axis.ignoreMaxPadding = null; - - if (axis.buildStacks) { - axis.buildStacks(); - } - - // loop through this axis' series - Highcharts.each(axis.series, function (series) { - - if (series.visible || !chart.options.chart.ignoreHiddenSeries) { - - var seriesOptions = series.options, - zData, - threshold = seriesOptions.threshold; - - axis.hasVisibleSeries = true; - - // Validate threshold in logarithmic axes - if (axis.isLog && threshold <= 0) { - threshold = null; - } - - zData = series.zData; - if (zData.length) { - axis.dataMin = Math.min(pick(axis.dataMin, zData[0]), Math.min.apply(null, zData)); - axis.dataMax = Math.max(pick(axis.dataMax, zData[0]), Math.max.apply(null, zData)); - } - } - }); - } - }); - - - /** - * Extend the chart getAxes method to also get the color axis - */ - Highcharts.wrap(Highcharts.Chart.prototype, 'getAxes', function (proceed) { - var chart = this, - options = this.options, - zAxisOptions = options.zAxis = Highcharts.splat(options.zAxis || {}); - - proceed.call(this); - - if (!chart.is3d()) { - return; - } - this.zAxis = []; - Highcharts.each(zAxisOptions, function (axisOptions, i) { - axisOptions.index = i; - axisOptions.isX = true; //Z-Axis is shown horizontally, so it's kind of a X-Axis - var zAxis = new ZAxis(chart, axisOptions); - zAxis.setScale(); - }); - }); - /*** - EXTENSION FOR 3D COLUMNS - ***/ - Highcharts.wrap(Highcharts.seriesTypes.column.prototype, 'translate', function (proceed) { - proceed.apply(this, [].slice.call(arguments, 1)); - - // Do not do this if the chart is not 3D - if (!this.chart.is3d()) { - return; - } - - var series = this, - chart = series.chart, - seriesOptions = series.options, - depth = seriesOptions.depth || 25; - - var stack = seriesOptions.stacking ? (seriesOptions.stack || 0) : series._i; - var z = stack * (depth + (seriesOptions.groupZPadding || 1)); - - if (seriesOptions.grouping !== false) { - z = 0; - } - - z += (seriesOptions.groupZPadding || 1); - - Highcharts.each(series.data, function (point) { - if (point.y !== null) { - var shapeArgs = point.shapeArgs, - tooltipPos = point.tooltipPos; - - point.shapeType = 'cuboid'; - shapeArgs.z = z; - shapeArgs.depth = depth; - shapeArgs.insidePlotArea = true; - - // Translate the tooltip position in 3d space - tooltipPos = perspective([{ x: tooltipPos[0], y: tooltipPos[1], z: z }], chart, true)[0]; - point.tooltipPos = [tooltipPos.x, tooltipPos.y]; - } - }); - // store for later use #4067 - series.z = z; - }); - - Highcharts.wrap(Highcharts.seriesTypes.column.prototype, 'animate', function (proceed) { - if (!this.chart.is3d()) { - proceed.apply(this, [].slice.call(arguments, 1)); - } else { - var args = arguments, - init = args[1], - yAxis = this.yAxis, - series = this, - reversed = this.yAxis.reversed; - - if (Highcharts.svg) { // VML is too slow anyway - if (init) { - Highcharts.each(series.data, function (point) { - if (point.y !== null) { - point.height = point.shapeArgs.height; - point.shapey = point.shapeArgs.y; //#2968 - point.shapeArgs.height = 1; - if (!reversed) { - if (point.stackY) { - point.shapeArgs.y = point.plotY + yAxis.translate(point.stackY); - } else { - point.shapeArgs.y = point.plotY + (point.negative ? -point.height : point.height); - } - } - } - }); - - } else { // run the animation - Highcharts.each(series.data, function (point) { - if (point.y !== null) { - point.shapeArgs.height = point.height; - point.shapeArgs.y = point.shapey; //#2968 - // null value do not have a graphic - if (point.graphic) { - point.graphic.animate(point.shapeArgs, series.options.animation); - } - } - }); - - // redraw datalabels to the correct position - this.drawDataLabels(); - - // delete this function to allow it only once - series.animate = null; - } - } - } - }); - - Highcharts.wrap(Highcharts.seriesTypes.column.prototype, 'init', function (proceed) { - proceed.apply(this, [].slice.call(arguments, 1)); - - if (this.chart.is3d()) { - var seriesOptions = this.options, - grouping = seriesOptions.grouping, - stacking = seriesOptions.stacking, - reversedStacks = pick(this.yAxis.options.reversedStacks, true), - z = 0; - - if (!(grouping !== undefined && !grouping)) { - var stacks = this.chart.retrieveStacks(stacking), - stack = seriesOptions.stack || 0, - i; // position within the stack - for (i = 0; i < stacks[stack].series.length; i++) { - if (stacks[stack].series[i] === this) { - break; - } - } - z = (10 * (stacks.totalStacks - stacks[stack].position)) + (reversedStacks ? i : -i); // #4369 - - // In case when axis is reversed, columns are also reversed inside the group (#3737) - if (!this.xAxis.reversed) { - z = (stacks.totalStacks * 10) - z; - } - } - - seriesOptions.zIndex = z; - } - }); - function draw3DPoints(proceed) { - // Do not do this if the chart is not 3D - if (this.chart.is3d()) { - var grouping = this.chart.options.plotOptions.column.grouping; - if (grouping !== undefined && !grouping && this.group.zIndex !== undefined && !this.zIndexSet) { - this.group.attr({ zIndex: this.group.zIndex * 10 }); - this.zIndexSet = true; // #4062 set zindex only once - } - - var options = this.options, - states = this.options.states; - - this.borderWidth = options.borderWidth = defined(options.edgeWidth) ? options.edgeWidth : 1; //#4055 - - Highcharts.each(this.data, function (point) { - if (point.y !== null) { - var pointAttr = point.pointAttr; - - // Set the border color to the fill color to provide a smooth edge - this.borderColor = Highcharts.pick(options.edgeColor, pointAttr[''].fill); - - pointAttr[''].stroke = this.borderColor; - pointAttr.hover.stroke = Highcharts.pick(states.hover.edgeColor, this.borderColor); - pointAttr.select.stroke = Highcharts.pick(states.select.edgeColor, this.borderColor); - } - }); - } - - proceed.apply(this, [].slice.call(arguments, 1)); - } - - Highcharts.wrap(Highcharts.Series.prototype, 'alignDataLabel', function (proceed) { - - // Only do this for 3D columns and columnranges - if (this.chart.is3d() && (this.type === 'column' || this.type === 'columnrange')) { - var series = this, - chart = series.chart; - - var args = arguments, - alignTo = args[4]; - - var pos = ({ x: alignTo.x, y: alignTo.y, z: series.z }); - pos = perspective([pos], chart, true)[0]; - alignTo.x = pos.x; - alignTo.y = pos.y; - } - - proceed.apply(this, [].slice.call(arguments, 1)); - }); - - if (Highcharts.seriesTypes.columnrange) { - Highcharts.wrap(Highcharts.seriesTypes.columnrange.prototype, 'drawPoints', draw3DPoints); - } - - Highcharts.wrap(Highcharts.seriesTypes.column.prototype, 'drawPoints', draw3DPoints); - - /*** - EXTENSION FOR 3D CYLINDRICAL COLUMNS - Not supported - ***/ - /* - var defaultOptions = Highcharts.getOptions(); - defaultOptions.plotOptions.cylinder = Highcharts.merge(defaultOptions.plotOptions.column); - var CylinderSeries = Highcharts.extendClass(Highcharts.seriesTypes.column, { - type: 'cylinder' - }); - Highcharts.seriesTypes.cylinder = CylinderSeries; - - Highcharts.wrap(Highcharts.seriesTypes.cylinder.prototype, 'translate', function (proceed) { - proceed.apply(this, [].slice.call(arguments, 1)); - - // Do not do this if the chart is not 3D - if (!this.chart.is3d()) { - return; - } - - var series = this, - chart = series.chart, - options = chart.options, - cylOptions = options.plotOptions.cylinder, - options3d = options.chart.options3d, - depth = cylOptions.depth || 0, - alpha = options3d.alpha; - - var z = cylOptions.stacking ? (this.options.stack || 0) * depth : series._i * depth; - z += depth / 2; - - if (cylOptions.grouping !== false) { z = 0; } - - Highcharts.each(series.data, function (point) { - var shapeArgs = point.shapeArgs; - point.shapeType = 'arc3d'; - shapeArgs.x += depth / 2; - shapeArgs.z = z; - shapeArgs.start = 0; - shapeArgs.end = 2 * PI; - shapeArgs.r = depth * 0.95; - shapeArgs.innerR = 0; - shapeArgs.depth = shapeArgs.height * (1 / sin((90 - alpha) * deg2rad)) - z; - shapeArgs.alpha = 90 - alpha; - shapeArgs.beta = 0; - }); - }); - */ - /*** - EXTENSION FOR 3D PIES - ***/ - - Highcharts.wrap(Highcharts.seriesTypes.pie.prototype, 'translate', function (proceed) { - proceed.apply(this, [].slice.call(arguments, 1)); - - // Do not do this if the chart is not 3D - if (!this.chart.is3d()) { - return; - } - - var series = this, - chart = series.chart, - options = chart.options, - seriesOptions = series.options, - depth = seriesOptions.depth || 0, - options3d = options.chart.options3d, - alpha = options3d.alpha, - beta = options3d.beta, - z = seriesOptions.stacking ? (seriesOptions.stack || 0) * depth : series._i * depth; - - z += depth / 2; - - if (seriesOptions.grouping !== false) { - z = 0; - } - - each(series.data, function (point) { - - var shapeArgs = point.shapeArgs, - angle; - - point.shapeType = 'arc3d'; - - shapeArgs.z = z; - shapeArgs.depth = depth * 0.75; - shapeArgs.alpha = alpha; - shapeArgs.beta = beta; - shapeArgs.center = series.center; - - angle = (shapeArgs.end + shapeArgs.start) / 2; - - point.slicedTranslation = { - translateX: round(cos(angle) * seriesOptions.slicedOffset * cos(alpha * deg2rad)), - translateY: round(sin(angle) * seriesOptions.slicedOffset * cos(alpha * deg2rad)) - }; - }); - }); - - Highcharts.wrap(Highcharts.seriesTypes.pie.prototype.pointClass.prototype, 'haloPath', function (proceed) { - var args = arguments; - return this.series.chart.is3d() ? [] : proceed.call(this, args[1]); - }); - - Highcharts.wrap(Highcharts.seriesTypes.pie.prototype, 'drawPoints', function (proceed) { - - var options = this.options, - states = options.states; - - // Do not do this if the chart is not 3D - if (this.chart.is3d()) { - // Set the border color to the fill color to provide a smooth edge - this.borderWidth = options.borderWidth = options.edgeWidth || 1; - this.borderColor = options.edgeColor = Highcharts.pick(options.edgeColor, options.borderColor, undefined); - - states.hover.borderColor = Highcharts.pick(states.hover.edgeColor, this.borderColor); - states.hover.borderWidth = Highcharts.pick(states.hover.edgeWidth, this.borderWidth); - states.select.borderColor = Highcharts.pick(states.select.edgeColor, this.borderColor); - states.select.borderWidth = Highcharts.pick(states.select.edgeWidth, this.borderWidth); - - each(this.data, function (point) { - var pointAttr = point.pointAttr; - pointAttr[''].stroke = point.series.borderColor || point.color; - pointAttr['']['stroke-width'] = point.series.borderWidth; - pointAttr.hover.stroke = states.hover.borderColor; - pointAttr.hover['stroke-width'] = states.hover.borderWidth; - pointAttr.select.stroke = states.select.borderColor; - pointAttr.select['stroke-width'] = states.select.borderWidth; - }); - } - - proceed.apply(this, [].slice.call(arguments, 1)); - - if (this.chart.is3d()) { - each(this.points, function (point) { - var graphic = point.graphic; - - // #4584 Check if has graphic - null points don't have it - if (graphic) { - // Hide null or 0 points (#3006, 3650) - graphic[point.y && point.visible ? 'show' : 'hide'](); - } - }); - } - }); - - Highcharts.wrap(Highcharts.seriesTypes.pie.prototype, 'drawDataLabels', function (proceed) { - if (this.chart.is3d()) { - var series = this, - chart = series.chart, - options3d = chart.options.chart.options3d; - each(series.data, function (point) { - var shapeArgs = point.shapeArgs, - r = shapeArgs.r, - a1 = (shapeArgs.alpha || options3d.alpha) * deg2rad, //#3240 issue with datalabels for 0 and null values - b1 = (shapeArgs.beta || options3d.beta) * deg2rad, - a2 = (shapeArgs.start + shapeArgs.end) / 2, - labelPos = point.labelPos, - labelIndexes = [0, 2, 4], // [x1, y1, x2, y2, x3, y3] - yOffset = (-r * (1 - cos(a1)) * sin(a2)), // + (sin(a2) > 0 ? sin(a1) * d : 0) - xOffset = r * (cos(b1) - 1) * cos(a2); - - // Apply perspective on label positions - each(labelIndexes, function (index) { - labelPos[index] += xOffset; - labelPos[index + 1] += yOffset; - }); - }); - } - - proceed.apply(this, [].slice.call(arguments, 1)); - }); - - Highcharts.wrap(Highcharts.seriesTypes.pie.prototype, 'addPoint', function (proceed) { - proceed.apply(this, [].slice.call(arguments, 1)); - if (this.chart.is3d()) { - // destroy (and rebuild) everything!!! - this.update(this.userOptions, true); // #3845 pass the old options - } - }); - - Highcharts.wrap(Highcharts.seriesTypes.pie.prototype, 'animate', function (proceed) { - if (!this.chart.is3d()) { - proceed.apply(this, [].slice.call(arguments, 1)); - } else { - var args = arguments, - init = args[1], - animation = this.options.animation, - attribs, - center = this.center, - group = this.group, - markerGroup = this.markerGroup; - - if (Highcharts.svg) { // VML is too slow anyway - - if (animation === true) { - animation = {}; - } - // Initialize the animation - if (init) { - - // Scale down the group and place it in the center - group.oldtranslateX = group.translateX; - group.oldtranslateY = group.translateY; - attribs = { - translateX: center[0], - translateY: center[1], - scaleX: 0.001, // #1499 - scaleY: 0.001 - }; - - group.attr(attribs); - if (markerGroup) { - markerGroup.attrSetters = group.attrSetters; - markerGroup.attr(attribs); - } - - // Run the animation - } else { - attribs = { - translateX: group.oldtranslateX, - translateY: group.oldtranslateY, - scaleX: 1, - scaleY: 1 - }; - group.animate(attribs, animation); - - if (markerGroup) { - markerGroup.animate(attribs, animation); - } - - // Delete this function to allow it only once - this.animate = null; - } - - } - } - }); - /*** - EXTENSION FOR 3D SCATTER CHART - ***/ - - Highcharts.wrap(Highcharts.seriesTypes.scatter.prototype, 'translate', function (proceed) { - //function translate3d(proceed) { - proceed.apply(this, [].slice.call(arguments, 1)); - - if (!this.chart.is3d()) { - return; - } - - var series = this, - chart = series.chart, - zAxis = Highcharts.pick(series.zAxis, chart.options.zAxis[0]), - rawPoints = [], - rawPoint, - projectedPoints, - projectedPoint, - zValue, - i; - - for (i = 0; i < series.data.length; i++) { - rawPoint = series.data[i]; - zValue = zAxis.isLog && zAxis.val2lin ? zAxis.val2lin(rawPoint.z) : rawPoint.z; // #4562 - rawPoint.plotZ = zAxis.translate(zValue); - - rawPoint.isInside = rawPoint.isInside ? (zValue >= zAxis.min && zValue <= zAxis.max) : false; - - rawPoints.push({ - x: rawPoint.plotX, - y: rawPoint.plotY, - z: rawPoint.plotZ - }); - } - - projectedPoints = perspective(rawPoints, chart, true); - - for (i = 0; i < series.data.length; i++) { - rawPoint = series.data[i]; - projectedPoint = projectedPoints[i]; - - rawPoint.plotXold = rawPoint.plotX; - rawPoint.plotYold = rawPoint.plotY; - - rawPoint.plotX = projectedPoint.x; - rawPoint.plotY = projectedPoint.y; - rawPoint.plotZ = projectedPoint.z; - - - } - - }); - - Highcharts.wrap(Highcharts.seriesTypes.scatter.prototype, 'init', function (proceed, chart, options) { - if (chart.is3d()) { - // add a third coordinate - this.axisTypes = ['xAxis', 'yAxis', 'zAxis']; - this.pointArrayMap = ['x', 'y', 'z']; - this.parallelArrays = ['x', 'y', 'z']; - - // Require direct touch rather than using the k-d-tree, because the k-d-tree currently doesn't - // take the xyz coordinate system into account (#4552) - this.directTouch = true; - } - - var result = proceed.apply(this, [chart, options]); - - if (this.chart.is3d()) { - // Set a new default tooltip formatter - var default3dScatterTooltip = 'x: {point.x}
y: {point.y}
z: {point.z}
'; - if (this.userOptions.tooltip) { - this.tooltipOptions.pointFormat = this.userOptions.tooltip.pointFormat || default3dScatterTooltip; - } else { - this.tooltipOptions.pointFormat = default3dScatterTooltip; - } - } - return result; - }); - /** - * Extension to the VML Renderer - */ - if (Highcharts.VMLRenderer) { - - Highcharts.setOptions({ animate: false }); - - Highcharts.VMLRenderer.prototype.cuboid = Highcharts.SVGRenderer.prototype.cuboid; - Highcharts.VMLRenderer.prototype.cuboidPath = Highcharts.SVGRenderer.prototype.cuboidPath; - - Highcharts.VMLRenderer.prototype.toLinePath = Highcharts.SVGRenderer.prototype.toLinePath; - - Highcharts.VMLRenderer.prototype.createElement3D = Highcharts.SVGRenderer.prototype.createElement3D; - - Highcharts.VMLRenderer.prototype.arc3d = function (shapeArgs) { - var result = Highcharts.SVGRenderer.prototype.arc3d.call(this, shapeArgs); - result.css({ zIndex: result.zIndex }); - return result; - }; - - Highcharts.VMLRenderer.prototype.arc3dPath = Highcharts.SVGRenderer.prototype.arc3dPath; - - Highcharts.wrap(Highcharts.Axis.prototype, 'render', function (proceed) { - proceed.apply(this, [].slice.call(arguments, 1)); - // VML doesn't support a negative z-index - if (this.sideFrame) { - this.sideFrame.css({ zIndex: 0 }); - this.sideFrame.front.attr({ fill: this.sideFrame.color }); - } - if (this.bottomFrame) { - this.bottomFrame.css({ zIndex: 1 }); - this.bottomFrame.front.attr({ fill: this.bottomFrame.color }); - } - if (this.backFrame) { - this.backFrame.css({ zIndex: 0 }); - this.backFrame.front.attr({ fill: this.backFrame.color }); - } - }); - - } - -})); diff --git a/public/vendor/highcharts-4.2.5/highcharts-more.js b/public/vendor/highcharts-4.2.5/highcharts-more.js deleted file mode 100644 index fcd9cc6b77..0000000000 --- a/public/vendor/highcharts-4.2.5/highcharts-more.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - Highcharts JS v4.2.5 (2016-05-06) - - (c) 2009-2016 Torstein Honsi - - License: www.highcharts.com/license -*/ -(function(m){typeof module==="object"&&module.exports?module.exports=m:m(Highcharts)})(function(m){function M(a,b,c){this.init(a,b,c)}var R=m.arrayMin,S=m.arrayMax,t=m.each,H=m.extend,I=m.isNumber,u=m.merge,T=m.map,o=m.pick,B=m.pInt,G=m.correctFloat,p=m.getOptions().plotOptions,i=m.seriesTypes,v=m.extendClass,N=m.splat,w=m.wrap,O=m.Axis,z=m.Tick,J=m.Point,U=m.Pointer,V=m.CenteredSeriesMixin,C=m.TrackerMixin,x=m.Series,y=Math,F=y.round,D=y.floor,P=y.max,W=m.Color,r=function(){};H(M.prototype,{init:function(a, -b,c){var d=this,g=d.defaultOptions;d.chart=b;d.options=a=u(g,b.angular?{background:{}}:void 0,a);(a=a.background)&&t([].concat(N(a)).reverse(),function(a){var b=a.backgroundColor,g=c.userOptions,a=u(d.defaultBackgroundOptions,a);if(b)a.backgroundColor=b;a.color=a.backgroundColor;c.options.plotBands.unshift(a);g.plotBands=g.plotBands||[];g.plotBands!==c.options.plotBands&&g.plotBands.unshift(a)})},defaultOptions:{center:["50%","50%"],size:"85%",startAngle:0},defaultBackgroundOptions:{shape:"circle", -borderWidth:1,borderColor:"silver",backgroundColor:{linearGradient:{x1:0,y1:0,x2:0,y2:1},stops:[[0,"#FFF"],[1,"#DDD"]]},from:-Number.MAX_VALUE,innerRadius:0,to:Number.MAX_VALUE,outerRadius:"105%"}});var A=O.prototype,z=z.prototype,X={getOffset:r,redraw:function(){this.isDirty=!1},render:function(){this.isDirty=!1},setScale:r,setCategories:r,setTitle:r},Q={isRadial:!0,defaultRadialGaugeOptions:{labels:{align:"center",x:0,y:null},minorGridLineWidth:0,minorTickInterval:"auto",minorTickLength:10,minorTickPosition:"inside", -minorTickWidth:1,tickLength:10,tickPosition:"inside",tickWidth:2,title:{rotation:0},zIndex:2},defaultRadialXOptions:{gridLineWidth:1,labels:{align:null,distance:15,x:0,y:null},maxPadding:0,minPadding:0,showLastLabel:!1,tickLength:0},defaultRadialYOptions:{gridLineInterpolation:"circle",labels:{align:"right",x:-3,y:-2},showLastLabel:!1,title:{x:4,text:null,rotation:90}},setOptions:function(a){a=this.options=u(this.defaultOptions,this.defaultRadialOptions,a);if(!a.plotBands)a.plotBands=[]},getOffset:function(){A.getOffset.call(this); -this.chart.axisOffset[this.side]=0;this.center=this.pane.center=V.getCenter.call(this.pane)},getLinePath:function(a,b){var c=this.center,b=o(b,c[2]/2-this.offset);return this.chart.renderer.symbols.arc(this.left+c[0],this.top+c[1],b,b,{start:this.startAngleRad,end:this.endAngleRad,open:!0,innerR:0})},setAxisTranslation:function(){A.setAxisTranslation.call(this);if(this.center)this.transA=this.isCircular?(this.endAngleRad-this.startAngleRad)/(this.max-this.min||1):this.center[2]/2/(this.max-this.min|| -1),this.minPixelPadding=this.isXAxis?this.transA*this.minPointOffset:0},beforeSetTickPositions:function(){this.autoConnect&&(this.max+=this.categories&&1||this.pointRange||this.closestPointRange||0)},setAxisSize:function(){A.setAxisSize.call(this);if(this.isRadial){this.center=this.pane.center=m.CenteredSeriesMixin.getCenter.call(this.pane);if(this.isCircular)this.sector=this.endAngleRad-this.startAngleRad;this.len=this.width=this.height=this.center[2]*o(this.sector,1)/2}},getPosition:function(a, -b){return this.postTranslate(this.isCircular?this.translate(a):0,o(this.isCircular?b:this.translate(a),this.center[2]/2)-this.offset)},postTranslate:function(a,b){var c=this.chart,d=this.center,a=this.startAngleRad+a;return{x:c.plotLeft+d[0]+Math.cos(a)*b,y:c.plotTop+d[1]+Math.sin(a)*b}},getPlotBandPath:function(a,b,c){var d=this.center,g=this.startAngleRad,e=d[2]/2,j=[o(c.outerRadius,"100%"),c.innerRadius,o(c.thickness,10)],l=/%$/,h,f=this.isCircular;this.options.gridLineInterpolation==="polygon"? -d=this.getPlotLinePath(a).concat(this.getPlotLinePath(b,!0)):(a=Math.max(a,this.min),b=Math.min(b,this.max),f||(j[0]=this.translate(a),j[1]=this.translate(b)),j=T(j,function(a){l.test(a)&&(a=B(a,10)*e/100);return a}),c.shape==="circle"||!f?(a=-Math.PI/2,b=Math.PI*1.5,h=!0):(a=g+this.translate(a),b=g+this.translate(b)),d=this.chart.renderer.symbols.arc(this.left+d[0],this.top+d[1],j[0],j[0],{start:Math.min(a,b),end:Math.max(a,b),innerR:o(j[1],j[0]-j[2]),open:h}));return d},getPlotLinePath:function(a, -b){var c=this,d=c.center,g=c.chart,e=c.getPosition(a),j,l,h;c.isCircular?h=["M",d[0]+g.plotLeft,d[1]+g.plotTop,"L",e.x,e.y]:c.options.gridLineInterpolation==="circle"?(a=c.translate(a))&&(h=c.getLinePath(0,a)):(t(g.xAxis,function(a){a.pane===c.pane&&(j=a)}),h=[],a=c.translate(a),d=j.tickPositions,j.autoConnect&&(d=d.concat([d[0]])),b&&(d=[].concat(d).reverse()),t(d,function(e,b){l=j.getPosition(e,a);h.push(b?"L":"M",l.x,l.y)}));return h},getTitlePosition:function(){var a=this.center,b=this.chart, -c=this.options.title;return{x:b.plotLeft+a[0]+(c.x||0),y:b.plotTop+a[1]-{high:0.5,middle:0.25,low:0}[c.align]*a[2]+(c.y||0)}}};w(A,"init",function(a,b,c){var k;var d=b.angular,g=b.polar,e=c.isX,j=d&&e,l,h;h=b.options;var f=c.pane||0;if(d){if(H(this,j?X:Q),l=!e)this.defaultRadialOptions=this.defaultRadialGaugeOptions}else if(g)H(this,Q),this.defaultRadialOptions=(l=e)?this.defaultRadialXOptions:u(this.defaultYAxisOptions,this.defaultRadialYOptions);if(d||g)b.inverted=!1,h.chart.zoomType=null;a.call(this, -b,c);if(!j&&(d||g)){a=this.options;if(!b.panes)b.panes=[];this.pane=(k=b.panes[f]=b.panes[f]||new M(N(h.pane)[f],b,this),b=k);h=b.options;this.startAngleRad=b=(h.startAngle-90)*Math.PI/180;this.endAngleRad=h=(o(h.endAngle,h.startAngle+360)-90)*Math.PI/180;this.offset=a.offset||0;if((this.isCircular=l)&&c.max===void 0&&h-b===2*Math.PI)this.autoConnect=!0}});w(A,"autoLabelAlign",function(a){if(!this.isRadial)return a.apply(this,[].slice.call(arguments,1))});w(z,"getPosition",function(a,b,c,d,g){var e= -this.axis;return e.getPosition?e.getPosition(c):a.call(this,b,c,d,g)});w(z,"getLabelPosition",function(a,b,c,d,g,e,j,l,h){var f=this.axis,k=e.y,n=20,s=e.align,i=(f.translate(this.pos)+f.startAngleRad+Math.PI/2)/Math.PI*180%360;f.isRadial?(a=f.getPosition(this.pos,f.center[2]/2+o(e.distance,-25)),e.rotation==="auto"?d.attr({rotation:i}):k===null&&(k=f.chart.renderer.fontMetrics(d.styles.fontSize).b-d.getBBox().height/2),s===null&&(f.isCircular?(this.label.getBBox().width>f.len*f.tickInterval/(f.max- -f.min)&&(n=0),s=i>n&&i<180-n?"left":i>180+n&&i<360-n?"right":"center"):s="center",d.attr({align:s})),a.x+=e.x,a.y+=k):a=a.call(this,b,c,d,g,e,j,l,h);return a});w(z,"getMarkPath",function(a,b,c,d,g,e,j){var l=this.axis;l.isRadial?(a=l.getPosition(this.pos,l.center[2]/2+d),b=["M",b,c,"L",a.x,a.y]):b=a.call(this,b,c,d,g,e,j);return b});p.arearange=u(p.area,{lineWidth:1,marker:null,threshold:null,tooltip:{pointFormat:'\u25cf {series.name}: {point.low} - {point.high}
'}, -trackByArea:!0,dataLabels:{align:null,verticalAlign:null,xLow:0,xHigh:0,yLow:0,yHigh:0},states:{hover:{halo:!1}}});i.arearange=v(i.area,{type:"arearange",pointArrayMap:["low","high"],dataLabelCollections:["dataLabel","dataLabelUpper"],toYData:function(a){return[a.low,a.high]},pointValKey:"low",deferTranslatePolar:!0,highToXY:function(a){var b=this.chart,c=this.xAxis.postTranslate(a.rectPlotX,this.yAxis.len-a.plotHigh);a.plotHighX=c.x-b.plotLeft;a.plotHigh=c.y-b.plotTop},translate:function(){var a= -this,b=a.yAxis;i.area.prototype.translate.apply(a);t(a.points,function(a){var d=a.low,g=a.high,e=a.plotY;g===null||d===null?a.isNull=!0:(a.plotLow=e,a.plotHigh=b.translate(g,0,1,0,1))});this.chart.polar&&t(this.points,function(b){a.highToXY(b)})},getGraphPath:function(){var a=this.points,b=[],c=[],d=a.length,g=x.prototype.getGraphPath,e,j,l;l=this.options;for(var h=l.step,d=a.length;d--;)e=a[d],!e.isNull&&(!a[d+1]||a[d+1].isNull)&&c.push({plotX:e.plotX,plotY:e.plotLow}),j={plotX:e.plotX,plotY:e.plotHigh, -isNull:e.isNull},c.push(j),b.push(j),!e.isNull&&(!a[d-1]||a[d-1].isNull)&&c.push({plotX:e.plotX,plotY:e.plotLow});a=g.call(this,a);if(h)h===!0&&(h="left"),l.step={left:"right",center:"center",right:"left"}[h];b=g.call(this,b);c=g.call(this,c);l.step=h;l=[].concat(a,b);!this.chart.polar&&c[0]==="M"&&(c[0]="L");this.areaPath=this.areaPath.concat(a,c);return l},drawDataLabels:function(){var a=this.data,b=a.length,c,d=[],g=x.prototype,e=this.options.dataLabels,j=e.align,l=e.verticalAlign,h=e.inside,f, -k,n=this.chart.inverted;if(e.enabled||this._hasPointLabels){for(c=b;c--;)if(f=a[c]){k=h?f.plotHighf.plotLow;f.y=f.high;f._plotY=f.plotY;f.plotY=f.plotHigh;d[c]=f.dataLabel;f.dataLabel=f.dataLabelUpper;f.below=k;if(n){if(!j)e.align=k?"right":"left"}else if(!l)e.verticalAlign=k?"top":"bottom";e.x=e.xHigh;e.y=e.yHigh}g.drawDataLabels&&g.drawDataLabels.apply(this,arguments);for(c=b;c--;)if(f=a[c]){k=h?f.plotHighf.plotLow;f.dataLabelUpper=f.dataLabel;f.dataLabel= -d[c];f.y=f.low;f.plotY=f._plotY;f.below=!k;if(n){if(!j)e.align=k?"left":"right"}else if(!l)e.verticalAlign=k?"bottom":"top";e.x=e.xLow;e.y=e.yLow}g.drawDataLabels&&g.drawDataLabels.apply(this,arguments)}e.align=j;e.verticalAlign=l},alignDataLabel:function(){i.column.prototype.alignDataLabel.apply(this,arguments)},setStackedPoints:r,getSymbol:r,drawPoints:r});p.areasplinerange=u(p.arearange);i.areasplinerange=v(i.arearange,{type:"areasplinerange",getPointSpline:i.spline.prototype.getPointSpline}); -(function(){var a=i.column.prototype;p.columnrange=u(p.column,p.arearange,{lineWidth:1,pointRange:null});i.columnrange=v(i.arearange,{type:"columnrange",translate:function(){var b=this,c=b.yAxis,d=b.xAxis,g=d.startAngleRad,e,j=b.chart,l=b.xAxis.isRadial,h;a.translate.apply(b);t(b.points,function(a){var k=a.shapeArgs,n=b.options.minPointLength,s,i;a.plotHigh=h=c.translate(a.high,0,1,0,1);a.plotLow=a.plotY;i=h;s=o(a.rectPlotY,a.plotY)-h;Math.abs(s)\u25cf {series.name}
Maximum: {point.high}
Upper quartile: {point.q3}
Median: {point.median}
Lower quartile: {point.q1}
Minimum: {point.low}
'}, -whiskerLength:"50%",whiskerWidth:2});i.boxplot=v(i.column,{type:"boxplot",pointArrayMap:["low","q1","median","q3","high"],toYData:function(a){return[a.low,a.q1,a.median,a.q3,a.high]},pointValKey:"high",pointAttrToOptions:{fill:"fillColor",stroke:"color","stroke-width":"lineWidth"},drawDataLabels:r,translate:function(){var a=this.yAxis,b=this.pointArrayMap;i.column.prototype.translate.apply(this);t(this.points,function(c){t(b,function(b){c[b]!==null&&(c[b+"Plot"]=a.translate(c[b],0,1,0,1))})})},drawPoints:function(){var a= -this,b=a.options,c=a.chart.renderer,d,g,e,j,l,h,f,k,n,i,m,K,L,p,u,r,w,v,x,y,C,B,z=a.doQuartiles!==!1,A,E=a.options.whiskerLength;t(a.points,function(q){n=q.graphic;C=q.shapeArgs;m={};p={};r={};B=q.color||a.color;if(q.plotY!==void 0)if(d=q.pointAttr[q.selected?"selected":""],w=C.width,v=D(C.x),x=v+w,y=F(w/2),g=D(z?q.q1Plot:q.lowPlot),e=D(z?q.q3Plot:q.lowPlot),j=D(q.highPlot),l=D(q.lowPlot),m.stroke=q.stemColor||b.stemColor||B,m["stroke-width"]=o(q.stemWidth,b.stemWidth,b.lineWidth),m.dashstyle=q.stemDashStyle|| -b.stemDashStyle,p.stroke=q.whiskerColor||b.whiskerColor||B,p["stroke-width"]=o(q.whiskerWidth,b.whiskerWidth,b.lineWidth),r.stroke=q.medianColor||b.medianColor||B,r["stroke-width"]=o(q.medianWidth,b.medianWidth,b.lineWidth),f=m["stroke-width"]%2/2,k=v+y+f,i=["M",k,e,"L",k,j,"M",k,g,"L",k,l],z&&(f=d["stroke-width"]%2/2,k=D(k)+f,g=D(g)+f,e=D(e)+f,v+=f,x+=f,K=["M",v,e,"L",v,g,"L",x,g,"L",x,e,"L",v,e,"z"]),E&&(f=p["stroke-width"]%2/2,j+=f,l+=f,A=/%$/.test(E)?y*parseFloat(E)/100:E/2,L=["M",k-A,j,"L",k+ -A,j,"M",k-A,l,"L",k+A,l]),f=r["stroke-width"]%2/2,h=F(q.medianPlot)+f,u=["M",v,h,"L",x,h],n)q.stem.animate({d:i}),E&&q.whiskers.animate({d:L}),z&&q.box.animate({d:K}),q.medianShape.animate({d:u});else{q.graphic=n=c.g().add(a.group);q.stem=c.path(i).attr(m).add(n);if(E)q.whiskers=c.path(L).attr(p).add(n);if(z)q.box=c.path(K).attr(d).add(n);q.medianShape=c.path(u).attr(r).add(n)}})},setStackedPoints:r});p.errorbar=u(p.boxplot,{color:"#000000",grouping:!1,linkedTo:":previous",tooltip:{pointFormat:'\u25cf {series.name}: {point.low} - {point.high}
'}, -whiskerWidth:null});i.errorbar=v(i.boxplot,{type:"errorbar",pointArrayMap:["low","high"],toYData:function(a){return[a.low,a.high]},pointValKey:"high",doQuartiles:!1,drawDataLabels:i.arearange?i.arearange.prototype.drawDataLabels:r,getColumnMetrics:function(){return this.linkedParent&&this.linkedParent.columnMetrics||i.column.prototype.getColumnMetrics.call(this)}});p.waterfall=u(p.column,{lineWidth:1,lineColor:"#333",dashStyle:"dot",borderColor:"#333",dataLabels:{inside:!0},states:{hover:{lineWidthPlus:0}}}); -i.waterfall=v(i.column,{type:"waterfall",upColorProp:"fill",pointValKey:"y",translate:function(){var a=this.options,b=this.yAxis,c,d,g,e,j,l,h,f,k,n=o(a.minPointLength,5),s=a.threshold,m=a.stacking;i.column.prototype.translate.apply(this);this.minPointLengthOffset=0;h=f=s;d=this.points;for(c=0,a=d.length;c0?b.translate(h,0,1)-e.y:b.translate(h,0,1)-b.translate(h-l,0,1);h+=l}e.height<0&&(e.y+=e.height,e.height*=-1);g.plotY=e.y=F(e.y)-this.borderWidth%2/2;e.height=P(F(e.height),0.001); -g.yBottom=e.y+e.height;if(e.height<=n)e.height=n,this.minPointLengthOffset+=n;e.y-=this.minPointLengthOffset;e=g.plotY+(g.negative?e.height:0)-this.minPointLengthOffset;this.chart.inverted?g.tooltipPos[0]=b.len-e:g.tooltipPos[1]=e}},processData:function(a){var b=this.yData,c=this.options.data,d,g=b.length,e,j,l,h,f,k;j=e=l=h=this.options.threshold||0;for(k=0;k -0?(e.pointAttr=g,e.color=d):e.pointAttr=a.pointAttr})},getGraphPath:function(){var a=this.data,b=a.length,c=F(this.options.lineWidth+this.borderWidth)%2/2,d=[],g,e,j;for(j=1;j0?(j-a)/i:0.5,k&&j>=0&&(j=Math.sqrt(j)),j=y.ceil(c+j*(d-c))/2),h.push(j);this.radii=h},animate:function(a){var b=this.options.animation;if(!a)t(this.points, -function(a){var d=a.graphic,a=a.shapeArgs;d&&a&&(d.attr("r",1),d.animate({r:a.r},b))}),this.animate=null},translate:function(){var a,b=this.data,c,d,g=this.radii;i.scatter.prototype.translate.call(this);for(a=b.length;a--;)c=b[a],d=g?g[a]:0,I(d)&&d>=this.minPxSize/2?(c.shapeType="circle",c.shapeArgs={x:c.plotX,y:c.plotY,r:d},c.dlBox={x:c.plotX-d,y:c.plotY-d,width:2*d,height:2*d}):c.shapeArgs=c.plotY=c.dlBox=void 0},drawLegendSymbol:function(a,b){var c=this.chart.renderer,d=c.fontMetrics(a.itemStyle.fontSize).f/ -2;b.legendSymbol=c.circle(d,a.baseline-d,d).attr({zIndex:3}).add(b.legendGroup);b.legendSymbol.isMarker=!0},drawPoints:i.column.prototype.drawPoints,alignDataLabel:i.column.prototype.alignDataLabel,buildKDTree:r,applyZones:r});O.prototype.beforePadding=function(){var a=this,b=this.len,c=this.chart,d=0,g=b,e=this.isXAxis,j=e?"xData":"yData",l=this.min,h={},f=y.min(c.plotWidth,c.plotHeight),k=Number.MAX_VALUE,n=-Number.MAX_VALUE,i=this.max-l,m=b/i,p=[];t(this.series,function(b){var g=b.options;if(b.bubblePadding&& -(b.visible||!c.options.chart.ignoreHiddenSeries))if(a.allowZoomOutside=!0,p.push(b),e)t(["minSize","maxSize"],function(a){var b=g[a],e=/%$/.test(b),b=B(b);h[a]=e?f*b/100:b}),b.minPxSize=h.minSize,b.maxPxSize=h.maxSize,b=b.zData,b.length&&(k=o(g.zMin,y.min(k,y.max(R(b),g.displayNegative===!1?g.zThreshold:-Number.MAX_VALUE))),n=o(g.zMax,y.max(n,S(b))))});t(p,function(b){var c=b[j],f=c.length,h;e&&b.getRadii(k,n,b.minPxSize,b.maxPxSize);if(i>0)for(;f--;)I(c[f])&&a.dataMin<=c[f]&&c[f]<=a.dataMax&&(h= -b.radii[f],d=Math.min((c[f]-l)*m-h,d),g=Math.max((c[f]-l)*m+h,g))});p.length&&i>0&&!this.isLog&&(g-=b,m*=(b+d-g)/b,t([["min","userMin",d],["max","userMax",g]],function(b){o(a.options[b[0]],a[b[1]])===void 0&&(a[b[0]]+=b[2]/m)}))};(function(){function a(a,b){var c=this.chart,d=this.options.animation,h=this.group,f=this.markerGroup,k=this.xAxis.center,i=c.plotLeft,m=c.plotTop;if(c.polar){if(c.renderer.isSVG)d===!0&&(d={}),b?(c={translateX:k[0]+i,translateY:k[1]+m,scaleX:0.001,scaleY:0.001},h.attr(c), -f&&f.attr(c)):(c={translateX:i,translateY:m,scaleX:1,scaleY:1},h.animate(c,d),f&&f.animate(c,d),this.animate=null)}else a.call(this,b)}var b=x.prototype,c=U.prototype,d;b.searchPointByAngle=function(a){var b=this.chart,c=this.xAxis.pane.center;return this.searchKDTree({clientX:180+Math.atan2(a.chartX-c[0]-b.plotLeft,a.chartY-c[1]-b.plotTop)*(-180/Math.PI)})};w(b,"buildKDTree",function(a){if(this.chart.polar)this.kdByAngle?this.searchPoint=this.searchPointByAngle:this.kdDimensions=2;a.apply(this)}); -b.toXY=function(a){var b,c=this.chart,d=a.plotX;b=a.plotY;a.rectPlotX=d;a.rectPlotY=b;b=this.xAxis.postTranslate(a.plotX,this.yAxis.len-b);a.plotX=a.polarPlotX=b.x-c.plotLeft;a.plotY=a.polarPlotY=b.y-c.plotTop;this.kdByAngle?(c=(d/Math.PI*180+this.xAxis.pane.options.startAngle)%360,c<0&&(c+=360),a.clientX=c):a.clientX=a.plotX};i.spline&&w(i.spline.prototype,"getPointSpline",function(a,b,c,d){var h,f,k,i,m,p,o;if(this.chart.polar){h=c.plotX;f=c.plotY;a=b[d-1];k=b[d+1];this.connectEnds&&(a||(a=b[b.length- -2]),k||(k=b[1]));if(a&&k)i=a.plotX,m=a.plotY,b=k.plotX,p=k.plotY,i=(1.5*h+i)/2.5,m=(1.5*f+m)/2.5,k=(1.5*h+b)/2.5,o=(1.5*f+p)/2.5,b=Math.sqrt(Math.pow(i-h,2)+Math.pow(m-f,2)),p=Math.sqrt(Math.pow(k-h,2)+Math.pow(o-f,2)),i=Math.atan2(m-f,i-h),m=Math.atan2(o-f,k-h),o=Math.PI/2+(i+m)/2,Math.abs(i-o)>Math.PI/2&&(o-=Math.PI),i=h+Math.cos(o)*b,m=f+Math.sin(o)*b,k=h+Math.cos(Math.PI+o)*p,o=f+Math.sin(Math.PI+o)*p,c.rightContX=k,c.rightContY=o;d?(c=["C",a.rightContX||a.plotX,a.rightContY||a.plotY,i||h,m|| -f,h,f],a.rightContX=a.rightContY=null):c=["M",h,f]}else c=a.call(this,b,c,d);return c});w(b,"translate",function(a){var b=this.chart;a.call(this);if(b.polar&&(this.kdByAngle=b.tooltip&&b.tooltip.shared,!this.preventPostTranslate)){a=this.points;for(b=a.length;b--;)this.toXY(a[b])}});w(b,"getGraphPath",function(a,b){var c=this;if(this.chart.polar){b=b||this.points;if(this.options.connectEnds!==!1&&b[0]&&b[0].y!==null)this.connectEnds=!0,b.splice(b.length,0,b[0]);t(b,function(a){a.polarPlotY===void 0&& -c.toXY(a)})}return a.apply(this,[].slice.call(arguments,1))});w(b,"animate",a);if(i.column)d=i.column.prototype,d.polarArc=function(a,b,c,d){var h=this.xAxis.center,f=this.yAxis.len;return this.chart.renderer.symbols.arc(h[0],h[1],f-b,null,{start:c,end:d,innerR:f-o(a,f)})},w(d,"animate",a),w(d,"translate",function(a){var b=this.xAxis,c=b.startAngleRad,d,h,f;this.preventPostTranslate=!0;a.call(this);if(b.isRadial){d=this.points;for(f=d.length;f--;)h=d[f],a=h.barX+c,h.shapeType="path",h.shapeArgs={d:this.polarArc(h.yBottom, -h.plotY,a,a+h.pointWidth)},this.toXY(h),h.tooltipPos=[h.plotX,h.plotY],h.ttBelow=h.plotY>b.center[1]}}),w(d,"alignDataLabel",function(a,c,d,i,h,f){if(this.chart.polar){a=c.rectPlotX/Math.PI*180;if(i.align===null)i.align=a>20&&a<160?"left":a>200&&a<340?"right":"center";if(i.verticalAlign===null)i.verticalAlign=a<45||a>315?"bottom":a>135&&a<225?"top":"middle";b.alignDataLabel.call(this,c,d,i,h,f)}else a.call(this,c,d,i,h,f)});w(c,"getCoordinates",function(a,b){var c=this.chart,d={xAxis:[],yAxis:[]}; -c.polar?t(c.axes,function(a){var f=a.isXAxis,g=a.center,i=b.chartX-g[0]-c.plotLeft,g=b.chartY-g[1]-c.plotTop;d[f?"xAxis":"yAxis"].push({axis:a,value:a.translate(f?Math.PI-Math.atan2(i,g):Math.sqrt(Math.pow(i,2)+Math.pow(g,2)),!0)})}):d=a.call(this,b);return d})})()}); diff --git a/public/vendor/highcharts-4.2.5/highcharts-more.src.js b/public/vendor/highcharts-4.2.5/highcharts-more.src.js deleted file mode 100644 index ddda4d43af..0000000000 --- a/public/vendor/highcharts-4.2.5/highcharts-more.src.js +++ /dev/null @@ -1,2692 +0,0 @@ -// ==ClosureCompiler== -// @compilation_level SIMPLE_OPTIMIZATIONS - -/** - * @license Highcharts JS v4.2.5 (2016-05-06) - * - * (c) 2009-2016 Torstein Honsi - * - * License: www.highcharts.com/license - */ - -(function (factory) { - if (typeof module === 'object' && module.exports) { - module.exports = factory; - } else { - factory(Highcharts); - } -}(function (Highcharts) { -var arrayMin = Highcharts.arrayMin, - arrayMax = Highcharts.arrayMax, - each = Highcharts.each, - extend = Highcharts.extend, - isNumber = Highcharts.isNumber, - merge = Highcharts.merge, - map = Highcharts.map, - pick = Highcharts.pick, - pInt = Highcharts.pInt, - correctFloat = Highcharts.correctFloat, - defaultPlotOptions = Highcharts.getOptions().plotOptions, - seriesTypes = Highcharts.seriesTypes, - extendClass = Highcharts.extendClass, - splat = Highcharts.splat, - wrap = Highcharts.wrap, - Axis = Highcharts.Axis, - Tick = Highcharts.Tick, - Point = Highcharts.Point, - Pointer = Highcharts.Pointer, - CenteredSeriesMixin = Highcharts.CenteredSeriesMixin, - TrackerMixin = Highcharts.TrackerMixin, - Series = Highcharts.Series, - math = Math, - mathRound = math.round, - mathFloor = math.floor, - mathMax = math.max, - Color = Highcharts.Color, - noop = function () {}, - UNDEFINED;/** - * The Pane object allows options that are common to a set of X and Y axes. - * - * In the future, this can be extended to basic Highcharts and Highstock. - */ - function Pane(options, chart, firstAxis) { - this.init(options, chart, firstAxis); - } - - // Extend the Pane prototype - extend(Pane.prototype, { - - /** - * Initiate the Pane object - */ - init: function (options, chart, firstAxis) { - var pane = this, - backgroundOption, - defaultOptions = pane.defaultOptions; - - pane.chart = chart; - - // Set options. Angular charts have a default background (#3318) - pane.options = options = merge(defaultOptions, chart.angular ? { background: {} } : undefined, options); - - backgroundOption = options.background; - - // To avoid having weighty logic to place, update and remove the backgrounds, - // push them to the first axis' plot bands and borrow the existing logic there. - if (backgroundOption) { - each([].concat(splat(backgroundOption)).reverse(), function (config) { - var backgroundColor = config.backgroundColor, // if defined, replace the old one (specific for gradients) - axisUserOptions = firstAxis.userOptions; - config = merge(pane.defaultBackgroundOptions, config); - if (backgroundColor) { - config.backgroundColor = backgroundColor; - } - config.color = config.backgroundColor; // due to naming in plotBands - firstAxis.options.plotBands.unshift(config); - axisUserOptions.plotBands = axisUserOptions.plotBands || []; // #3176 - if (axisUserOptions.plotBands !== firstAxis.options.plotBands) { - axisUserOptions.plotBands.unshift(config); - } - }); - } - }, - - /** - * The default options object - */ - defaultOptions: { - // background: {conditional}, - center: ['50%', '50%'], - size: '85%', - startAngle: 0 - //endAngle: startAngle + 360 - }, - - /** - * The default background options - */ - defaultBackgroundOptions: { - shape: 'circle', - borderWidth: 1, - borderColor: 'silver', - backgroundColor: { - linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, - stops: [ - [0, '#FFF'], - [1, '#DDD'] - ] - }, - from: -Number.MAX_VALUE, // corrected to axis min - innerRadius: 0, - to: Number.MAX_VALUE, // corrected to axis max - outerRadius: '105%' - } - }); - - var axisProto = Axis.prototype, - tickProto = Tick.prototype; - - /** - * Augmented methods for the x axis in order to hide it completely, used for the X axis in gauges - */ - var hiddenAxisMixin = { - getOffset: noop, - redraw: function () { - this.isDirty = false; // prevent setting Y axis dirty - }, - render: function () { - this.isDirty = false; // prevent setting Y axis dirty - }, - setScale: noop, - setCategories: noop, - setTitle: noop - }; - - /** - * Augmented methods for the value axis - */ - var radialAxisMixin = { - isRadial: true, - - /** - * The default options extend defaultYAxisOptions - */ - defaultRadialGaugeOptions: { - labels: { - align: 'center', - x: 0, - y: null // auto - }, - minorGridLineWidth: 0, - minorTickInterval: 'auto', - minorTickLength: 10, - minorTickPosition: 'inside', - minorTickWidth: 1, - tickLength: 10, - tickPosition: 'inside', - tickWidth: 2, - title: { - rotation: 0 - }, - zIndex: 2 // behind dials, points in the series group - }, - - // Circular axis around the perimeter of a polar chart - defaultRadialXOptions: { - gridLineWidth: 1, // spokes - labels: { - align: null, // auto - distance: 15, - x: 0, - y: null // auto - }, - maxPadding: 0, - minPadding: 0, - showLastLabel: false, - tickLength: 0 - }, - - // Radial axis, like a spoke in a polar chart - defaultRadialYOptions: { - gridLineInterpolation: 'circle', - labels: { - align: 'right', - x: -3, - y: -2 - }, - showLastLabel: false, - title: { - x: 4, - text: null, - rotation: 90 - } - }, - - /** - * Merge and set options - */ - setOptions: function (userOptions) { - - var options = this.options = merge( - this.defaultOptions, - this.defaultRadialOptions, - userOptions - ); - - // Make sure the plotBands array is instanciated for each Axis (#2649) - if (!options.plotBands) { - options.plotBands = []; - } - - }, - - /** - * Wrap the getOffset method to return zero offset for title or labels in a radial - * axis - */ - getOffset: function () { - // Call the Axis prototype method (the method we're in now is on the instance) - axisProto.getOffset.call(this); - - // Title or label offsets are not counted - this.chart.axisOffset[this.side] = 0; - - // Set the center array - this.center = this.pane.center = CenteredSeriesMixin.getCenter.call(this.pane); - }, - - - /** - * Get the path for the axis line. This method is also referenced in the getPlotLinePath - * method. - */ - getLinePath: function (lineWidth, radius) { - var center = this.center; - radius = pick(radius, center[2] / 2 - this.offset); - - return this.chart.renderer.symbols.arc( - this.left + center[0], - this.top + center[1], - radius, - radius, - { - start: this.startAngleRad, - end: this.endAngleRad, - open: true, - innerR: 0 - } - ); - }, - - /** - * Override setAxisTranslation by setting the translation to the difference - * in rotation. This allows the translate method to return angle for - * any given value. - */ - setAxisTranslation: function () { - - // Call uber method - axisProto.setAxisTranslation.call(this); - - // Set transA and minPixelPadding - if (this.center) { // it's not defined the first time - if (this.isCircular) { - - this.transA = (this.endAngleRad - this.startAngleRad) / - ((this.max - this.min) || 1); - - - } else { - this.transA = (this.center[2] / 2) / ((this.max - this.min) || 1); - } - - if (this.isXAxis) { - this.minPixelPadding = this.transA * this.minPointOffset; - } else { - // This is a workaround for regression #2593, but categories still don't position correctly. - this.minPixelPadding = 0; - } - } - }, - - /** - * In case of auto connect, add one closestPointRange to the max value right before - * tickPositions are computed, so that ticks will extend passed the real max. - */ - beforeSetTickPositions: function () { - if (this.autoConnect) { - this.max += (this.categories && 1) || this.pointRange || this.closestPointRange || 0; // #1197, #2260 - } - }, - - /** - * Override the setAxisSize method to use the arc's circumference as length. This - * allows tickPixelInterval to apply to pixel lengths along the perimeter - */ - setAxisSize: function () { - - axisProto.setAxisSize.call(this); - - if (this.isRadial) { - - // Set the center array - this.center = this.pane.center = Highcharts.CenteredSeriesMixin.getCenter.call(this.pane); - - // The sector is used in Axis.translate to compute the translation of reversed axis points (#2570) - if (this.isCircular) { - this.sector = this.endAngleRad - this.startAngleRad; - } - - // Axis len is used to lay out the ticks - this.len = this.width = this.height = this.center[2] * pick(this.sector, 1) / 2; - - - } - }, - - /** - * Returns the x, y coordinate of a point given by a value and a pixel distance - * from center - */ - getPosition: function (value, length) { - return this.postTranslate( - this.isCircular ? this.translate(value) : 0, // #2848 - pick(this.isCircular ? length : this.translate(value), this.center[2] / 2) - this.offset - ); - }, - - /** - * Translate from intermediate plotX (angle), plotY (axis.len - radius) to final chart coordinates. - */ - postTranslate: function (angle, radius) { - - var chart = this.chart, - center = this.center; - - angle = this.startAngleRad + angle; - - return { - x: chart.plotLeft + center[0] + Math.cos(angle) * radius, - y: chart.plotTop + center[1] + Math.sin(angle) * radius - }; - - }, - - /** - * Find the path for plot bands along the radial axis - */ - getPlotBandPath: function (from, to, options) { - var center = this.center, - startAngleRad = this.startAngleRad, - fullRadius = center[2] / 2, - radii = [ - pick(options.outerRadius, '100%'), - options.innerRadius, - pick(options.thickness, 10) - ], - percentRegex = /%$/, - start, - end, - open, - isCircular = this.isCircular, // X axis in a polar chart - ret; - - // Polygonal plot bands - if (this.options.gridLineInterpolation === 'polygon') { - ret = this.getPlotLinePath(from).concat(this.getPlotLinePath(to, true)); - - // Circular grid bands - } else { - - // Keep within bounds - from = Math.max(from, this.min); - to = Math.min(to, this.max); - - // Plot bands on Y axis (radial axis) - inner and outer radius depend on to and from - if (!isCircular) { - radii[0] = this.translate(from); - radii[1] = this.translate(to); - } - - // Convert percentages to pixel values - radii = map(radii, function (radius) { - if (percentRegex.test(radius)) { - radius = (pInt(radius, 10) * fullRadius) / 100; - } - return radius; - }); - - // Handle full circle - if (options.shape === 'circle' || !isCircular) { - start = -Math.PI / 2; - end = Math.PI * 1.5; - open = true; - } else { - start = startAngleRad + this.translate(from); - end = startAngleRad + this.translate(to); - } - - - ret = this.chart.renderer.symbols.arc( - this.left + center[0], - this.top + center[1], - radii[0], - radii[0], - { - start: Math.min(start, end), // Math is for reversed yAxis (#3606) - end: Math.max(start, end), - innerR: pick(radii[1], radii[0] - radii[2]), - open: open - } - ); - } - - return ret; - }, - - /** - * Find the path for plot lines perpendicular to the radial axis. - */ - getPlotLinePath: function (value, reverse) { - var axis = this, - center = axis.center, - chart = axis.chart, - end = axis.getPosition(value), - xAxis, - xy, - tickPositions, - ret; - - // Spokes - if (axis.isCircular) { - ret = ['M', center[0] + chart.plotLeft, center[1] + chart.plotTop, 'L', end.x, end.y]; - - // Concentric circles - } else if (axis.options.gridLineInterpolation === 'circle') { - value = axis.translate(value); - if (value) { // a value of 0 is in the center - ret = axis.getLinePath(0, value); - } - // Concentric polygons - } else { - // Find the X axis in the same pane - each(chart.xAxis, function (a) { - if (a.pane === axis.pane) { - xAxis = a; - } - }); - ret = []; - value = axis.translate(value); - tickPositions = xAxis.tickPositions; - if (xAxis.autoConnect) { - tickPositions = tickPositions.concat([tickPositions[0]]); - } - // Reverse the positions for concatenation of polygonal plot bands - if (reverse) { - tickPositions = [].concat(tickPositions).reverse(); - } - - each(tickPositions, function (pos, i) { - xy = xAxis.getPosition(pos, value); - ret.push(i ? 'L' : 'M', xy.x, xy.y); - }); - - } - return ret; - }, - - /** - * Find the position for the axis title, by default inside the gauge - */ - getTitlePosition: function () { - var center = this.center, - chart = this.chart, - titleOptions = this.options.title; - - return { - x: chart.plotLeft + center[0] + (titleOptions.x || 0), - y: chart.plotTop + center[1] - ({ high: 0.5, middle: 0.25, low: 0 }[titleOptions.align] * - center[2]) + (titleOptions.y || 0) - }; - } - - }; - - /** - * Override axisProto.init to mix in special axis instance functions and function overrides - */ - wrap(axisProto, 'init', function (proceed, chart, userOptions) { - var axis = this, - angular = chart.angular, - polar = chart.polar, - isX = userOptions.isX, - isHidden = angular && isX, - isCircular, - startAngleRad, - endAngleRad, - options, - chartOptions = chart.options, - paneIndex = userOptions.pane || 0, - pane, - paneOptions; - - // Before prototype.init - if (angular) { - extend(this, isHidden ? hiddenAxisMixin : radialAxisMixin); - isCircular = !isX; - if (isCircular) { - this.defaultRadialOptions = this.defaultRadialGaugeOptions; - } - - } else if (polar) { - //extend(this, userOptions.isX ? radialAxisMixin : radialAxisMixin); - extend(this, radialAxisMixin); - isCircular = isX; - this.defaultRadialOptions = isX ? this.defaultRadialXOptions : merge(this.defaultYAxisOptions, this.defaultRadialYOptions); - - } - - // Disable certain features on angular and polar axes - if (angular || polar) { - chart.inverted = false; - chartOptions.chart.zoomType = null; - } - - // Run prototype.init - proceed.call(this, chart, userOptions); - - if (!isHidden && (angular || polar)) { - options = this.options; - - // Create the pane and set the pane options. - if (!chart.panes) { - chart.panes = []; - } - this.pane = pane = chart.panes[paneIndex] = chart.panes[paneIndex] || new Pane( - splat(chartOptions.pane)[paneIndex], - chart, - axis - ); - paneOptions = pane.options; - - // Start and end angle options are - // given in degrees relative to top, while internal computations are - // in radians relative to right (like SVG). - this.startAngleRad = startAngleRad = (paneOptions.startAngle - 90) * Math.PI / 180; - this.endAngleRad = endAngleRad = (pick(paneOptions.endAngle, paneOptions.startAngle + 360) - 90) * Math.PI / 180; - this.offset = options.offset || 0; - - this.isCircular = isCircular; - - // Automatically connect grid lines? - if (isCircular && userOptions.max === UNDEFINED && endAngleRad - startAngleRad === 2 * Math.PI) { - this.autoConnect = true; - } - } - - }); - - /** - * Wrap auto label align to avoid setting axis-wide rotation on radial axes (#4920) - * @param {Function} proceed - * @returns {String} Alignment - */ - wrap(axisProto, 'autoLabelAlign', function (proceed) { - if (!this.isRadial) { - return proceed.apply(this, [].slice.call(arguments, 1)); - } // else return undefined - }); - - /** - * Add special cases within the Tick class' methods for radial axes. - */ - wrap(tickProto, 'getPosition', function (proceed, horiz, pos, tickmarkOffset, old) { - var axis = this.axis; - - return axis.getPosition ? - axis.getPosition(pos) : - proceed.call(this, horiz, pos, tickmarkOffset, old); - }); - - /** - * Wrap the getLabelPosition function to find the center position of the label - * based on the distance option - */ - wrap(tickProto, 'getLabelPosition', function (proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) { - var axis = this.axis, - optionsY = labelOptions.y, - ret, - centerSlot = 20, // 20 degrees to each side at the top and bottom - align = labelOptions.align, - angle = ((axis.translate(this.pos) + axis.startAngleRad + Math.PI / 2) / Math.PI * 180) % 360; - - if (axis.isRadial) { - ret = axis.getPosition(this.pos, (axis.center[2] / 2) + pick(labelOptions.distance, -25)); - - // Automatically rotated - if (labelOptions.rotation === 'auto') { - label.attr({ - rotation: angle - }); - - // Vertically centered - } else if (optionsY === null) { - optionsY = axis.chart.renderer.fontMetrics(label.styles.fontSize).b - label.getBBox().height / 2; - } - - // Automatic alignment - if (align === null) { - if (axis.isCircular) { - if (this.label.getBBox().width > axis.len * axis.tickInterval / (axis.max - axis.min)) { // #3506 - centerSlot = 0; - } - if (angle > centerSlot && angle < 180 - centerSlot) { - align = 'left'; // right hemisphere - } else if (angle > 180 + centerSlot && angle < 360 - centerSlot) { - align = 'right'; // left hemisphere - } else { - align = 'center'; // top or bottom - } - } else { - align = 'center'; - } - label.attr({ - align: align - }); - } - - ret.x += labelOptions.x; - ret.y += optionsY; - - } else { - ret = proceed.call(this, x, y, label, horiz, labelOptions, tickmarkOffset, index, step); - } - return ret; - }); - - /** - * Wrap the getMarkPath function to return the path of the radial marker - */ - wrap(tickProto, 'getMarkPath', function (proceed, x, y, tickLength, tickWidth, horiz, renderer) { - var axis = this.axis, - endPoint, - ret; - - if (axis.isRadial) { - endPoint = axis.getPosition(this.pos, axis.center[2] / 2 + tickLength); - ret = [ - 'M', - x, - y, - 'L', - endPoint.x, - endPoint.y - ]; - } else { - ret = proceed.call(this, x, y, tickLength, tickWidth, horiz, renderer); - } - return ret; - });/* - * The AreaRangeSeries class - * - */ - - /** - * Extend the default options with map options - */ - defaultPlotOptions.arearange = merge(defaultPlotOptions.area, { - lineWidth: 1, - marker: null, - threshold: null, - tooltip: { - pointFormat: '\u25CF {series.name}: {point.low} - {point.high}
' - }, - trackByArea: true, - dataLabels: { - align: null, - verticalAlign: null, - xLow: 0, - xHigh: 0, - yLow: 0, - yHigh: 0 - }, - states: { - hover: { - halo: false - } - } - }); - - /** - * Add the series type - */ - seriesTypes.arearange = extendClass(seriesTypes.area, { - type: 'arearange', - pointArrayMap: ['low', 'high'], - dataLabelCollections: ['dataLabel', 'dataLabelUpper'], - toYData: function (point) { - return [point.low, point.high]; - }, - pointValKey: 'low', - deferTranslatePolar: true, - - /** - * Translate a point's plotHigh from the internal angle and radius measures to - * true plotHigh coordinates. This is an addition of the toXY method found in - * Polar.js, because it runs too early for arearanges to be considered (#3419). - */ - highToXY: function (point) { - // Find the polar plotX and plotY - var chart = this.chart, - xy = this.xAxis.postTranslate(point.rectPlotX, this.yAxis.len - point.plotHigh); - point.plotHighX = xy.x - chart.plotLeft; - point.plotHigh = xy.y - chart.plotTop; - }, - - /** - * Translate data points from raw values x and y to plotX and plotY - */ - translate: function () { - var series = this, - yAxis = series.yAxis; - - seriesTypes.area.prototype.translate.apply(series); - - // Set plotLow and plotHigh - each(series.points, function (point) { - - var low = point.low, - high = point.high, - plotY = point.plotY; - - if (high === null || low === null) { - point.isNull = true; - } else { - point.plotLow = plotY; - point.plotHigh = yAxis.translate(high, 0, 1, 0, 1); - } - }); - - // Postprocess plotHigh - if (this.chart.polar) { - each(this.points, function (point) { - series.highToXY(point); - }); - } - }, - - /** - * Extend the line series' getSegmentPath method by applying the segment - * path to both lower and higher values of the range - */ - getGraphPath: function () { - - var points = this.points, - highPoints = [], - highAreaPoints = [], - i = points.length, - getGraphPath = Series.prototype.getGraphPath, - point, - pointShim, - linePath, - lowerPath, - options = this.options, - step = options.step, - higherPath, - higherAreaPath; - - // Create the top line and the top part of the area fill. The area fill compensates for - // null points by drawing down to the lower graph, moving across the null gap and - // starting again at the lower graph. - i = points.length; - while (i--) { - point = points[i]; - - if (!point.isNull && (!points[i + 1] || points[i + 1].isNull)) { - highAreaPoints.push({ - plotX: point.plotX, - plotY: point.plotLow - }); - } - pointShim = { - plotX: point.plotX, - plotY: point.plotHigh, - isNull: point.isNull - }; - highAreaPoints.push(pointShim); - highPoints.push(pointShim); - if (!point.isNull && (!points[i - 1] || points[i - 1].isNull)) { - highAreaPoints.push({ - plotX: point.plotX, - plotY: point.plotLow - }); - } - } - - // Get the paths - lowerPath = getGraphPath.call(this, points); - if (step) { - if (step === true) { - step = 'left'; - } - options.step = { left: 'right', center: 'center', right: 'left' }[step]; // swap for reading in getGraphPath - } - higherPath = getGraphPath.call(this, highPoints); - higherAreaPath = getGraphPath.call(this, highAreaPoints); - options.step = step; - - // Create a line on both top and bottom of the range - linePath = [].concat(lowerPath, higherPath); - - // For the area path, we need to change the 'move' statement into 'lineTo' or 'curveTo' - if (!this.chart.polar && higherAreaPath[0] === 'M') { - higherAreaPath[0] = 'L'; // this probably doesn't work for spline - } - this.areaPath = this.areaPath.concat(lowerPath, higherAreaPath); - return linePath; - }, - - /** - * Extend the basic drawDataLabels method by running it for both lower and higher - * values. - */ - drawDataLabels: function () { - - var data = this.data, - length = data.length, - i, - originalDataLabels = [], - seriesProto = Series.prototype, - dataLabelOptions = this.options.dataLabels, - align = dataLabelOptions.align, - verticalAlign = dataLabelOptions.verticalAlign, - inside = dataLabelOptions.inside, - point, - up, - inverted = this.chart.inverted; - - if (dataLabelOptions.enabled || this._hasPointLabels) { - - // Step 1: set preliminary values for plotY and dataLabel and draw the upper labels - i = length; - while (i--) { - point = data[i]; - if (point) { - up = inside ? point.plotHigh < point.plotLow : point.plotHigh > point.plotLow; - - // Set preliminary values - point.y = point.high; - point._plotY = point.plotY; - point.plotY = point.plotHigh; - - // Store original data labels and set preliminary label objects to be picked up - // in the uber method - originalDataLabels[i] = point.dataLabel; - point.dataLabel = point.dataLabelUpper; - - // Set the default offset - point.below = up; - if (inverted) { - if (!align) { - dataLabelOptions.align = up ? 'right' : 'left'; - } - } else { - if (!verticalAlign) { - dataLabelOptions.verticalAlign = up ? 'top' : 'bottom'; - } - } - - dataLabelOptions.x = dataLabelOptions.xHigh; - dataLabelOptions.y = dataLabelOptions.yHigh; - } - } - - if (seriesProto.drawDataLabels) { - seriesProto.drawDataLabels.apply(this, arguments); // #1209 - } - - // Step 2: reorganize and handle data labels for the lower values - i = length; - while (i--) { - point = data[i]; - if (point) { - up = inside ? point.plotHigh < point.plotLow : point.plotHigh > point.plotLow; - - // Move the generated labels from step 1, and reassign the original data labels - point.dataLabelUpper = point.dataLabel; - point.dataLabel = originalDataLabels[i]; - - // Reset values - point.y = point.low; - point.plotY = point._plotY; - - // Set the default offset - point.below = !up; - if (inverted) { - if (!align) { - dataLabelOptions.align = up ? 'left' : 'right'; - } - } else { - if (!verticalAlign) { - dataLabelOptions.verticalAlign = up ? 'bottom' : 'top'; - } - - } - - dataLabelOptions.x = dataLabelOptions.xLow; - dataLabelOptions.y = dataLabelOptions.yLow; - } - } - if (seriesProto.drawDataLabels) { - seriesProto.drawDataLabels.apply(this, arguments); - } - } - - dataLabelOptions.align = align; - dataLabelOptions.verticalAlign = verticalAlign; - }, - - alignDataLabel: function () { - seriesTypes.column.prototype.alignDataLabel.apply(this, arguments); - }, - - setStackedPoints: noop, - - getSymbol: noop, - - drawPoints: noop - }); - /** - * The AreaSplineRangeSeries class - */ - - defaultPlotOptions.areasplinerange = merge(defaultPlotOptions.arearange); - - /** - * AreaSplineRangeSeries object - */ - seriesTypes.areasplinerange = extendClass(seriesTypes.arearange, { - type: 'areasplinerange', - getPointSpline: seriesTypes.spline.prototype.getPointSpline - }); - - (function () { - - var colProto = seriesTypes.column.prototype; - - /** - * The ColumnRangeSeries class - */ - defaultPlotOptions.columnrange = merge(defaultPlotOptions.column, defaultPlotOptions.arearange, { - lineWidth: 1, - pointRange: null - }); - - /** - * ColumnRangeSeries object - */ - seriesTypes.columnrange = extendClass(seriesTypes.arearange, { - type: 'columnrange', - /** - * Translate data points from raw values x and y to plotX and plotY - */ - translate: function () { - var series = this, - yAxis = series.yAxis, - xAxis = series.xAxis, - startAngleRad = xAxis.startAngleRad, - start, - chart = series.chart, - isRadial = series.xAxis.isRadial, - plotHigh; - - colProto.translate.apply(series); - - // Set plotLow and plotHigh - each(series.points, function (point) { - var shapeArgs = point.shapeArgs, - minPointLength = series.options.minPointLength, - heightDifference, - height, - y; - - point.plotHigh = plotHigh = yAxis.translate(point.high, 0, 1, 0, 1); - point.plotLow = point.plotY; - - // adjust shape - y = plotHigh; - height = pick(point.rectPlotY, point.plotY) - plotHigh; - - // Adjust for minPointLength - if (Math.abs(height) < minPointLength) { - heightDifference = (minPointLength - height); - height += heightDifference; - y -= heightDifference / 2; - - // Adjust for negative ranges or reversed Y axis (#1457) - } else if (height < 0) { - height *= -1; - y -= height; - } - - if (isRadial) { - - start = point.barX + startAngleRad; - point.shapeType = 'path'; - point.shapeArgs = { - d: series.polarArc(y + height, y, start, start + point.pointWidth) - }; - } else { - shapeArgs.height = height; - shapeArgs.y = y; - - point.tooltipPos = chart.inverted ? - [ - yAxis.len + yAxis.pos - chart.plotLeft - y - height / 2, - xAxis.len + xAxis.pos - chart.plotTop - shapeArgs.x - shapeArgs.width / 2, - height - ] : [ - xAxis.left - chart.plotLeft + shapeArgs.x + shapeArgs.width / 2, - yAxis.pos - chart.plotTop + y + height / 2, - height - ]; // don't inherit from column tooltip position - #3372 - } - }); - }, - directTouch: true, - trackerGroups: ['group', 'dataLabelsGroup'], - drawGraph: noop, - crispCol: colProto.crispCol, - pointAttrToOptions: colProto.pointAttrToOptions, - drawPoints: colProto.drawPoints, - drawTracker: colProto.drawTracker, - getColumnMetrics: colProto.getColumnMetrics, - animate: function () { - return colProto.animate.apply(this, arguments); - }, - polarArc: function () { - return colProto.polarArc.apply(this, arguments); - } - }); - }()); - - /* - * The GaugeSeries class - */ - - - - /** - * Extend the default options - */ - defaultPlotOptions.gauge = merge(defaultPlotOptions.line, { - dataLabels: { - enabled: true, - defer: false, - y: 15, - borderWidth: 1, - borderColor: 'silver', - borderRadius: 3, - crop: false, - verticalAlign: 'top', - zIndex: 2 - }, - dial: { - // radius: '80%', - // backgroundColor: 'black', - // borderColor: 'silver', - // borderWidth: 0, - // baseWidth: 3, - // topWidth: 1, - // baseLength: '70%' // of radius - // rearLength: '10%' - }, - pivot: { - //radius: 5, - //borderWidth: 0 - //borderColor: 'silver', - //backgroundColor: 'black' - }, - tooltip: { - headerFormat: '' - }, - showInLegend: false - }); - - /** - * Extend the point object - */ - var GaugePoint = extendClass(Point, { - /** - * Don't do any hover colors or anything - */ - setState: function (state) { - this.state = state; - } - }); - - - /** - * Add the series type - */ - var GaugeSeries = { - type: 'gauge', - pointClass: GaugePoint, - - // chart.angular will be set to true when a gauge series is present, and this will - // be used on the axes - angular: true, - directTouch: true, // #5063 - drawGraph: noop, - fixedBox: true, - forceDL: true, - trackerGroups: ['group', 'dataLabelsGroup'], - - /** - * Calculate paths etc - */ - translate: function () { - - var series = this, - yAxis = series.yAxis, - options = series.options, - center = yAxis.center; - - series.generatePoints(); - - each(series.points, function (point) { - - var dialOptions = merge(options.dial, point.dial), - radius = (pInt(pick(dialOptions.radius, 80)) * center[2]) / 200, - baseLength = (pInt(pick(dialOptions.baseLength, 70)) * radius) / 100, - rearLength = (pInt(pick(dialOptions.rearLength, 10)) * radius) / 100, - baseWidth = dialOptions.baseWidth || 3, - topWidth = dialOptions.topWidth || 1, - overshoot = options.overshoot, - rotation = yAxis.startAngleRad + yAxis.translate(point.y, null, null, null, true); - - // Handle the wrap and overshoot options - if (isNumber(overshoot)) { - overshoot = overshoot / 180 * Math.PI; - rotation = Math.max(yAxis.startAngleRad - overshoot, Math.min(yAxis.endAngleRad + overshoot, rotation)); - - } else if (options.wrap === false) { - rotation = Math.max(yAxis.startAngleRad, Math.min(yAxis.endAngleRad, rotation)); - } - - rotation = rotation * 180 / Math.PI; - - point.shapeType = 'path'; - point.shapeArgs = { - d: dialOptions.path || [ - 'M', - -rearLength, -baseWidth / 2, - 'L', - baseLength, -baseWidth / 2, - radius, -topWidth / 2, - radius, topWidth / 2, - baseLength, baseWidth / 2, - -rearLength, baseWidth / 2, - 'z' - ], - translateX: center[0], - translateY: center[1], - rotation: rotation - }; - - // Positions for data label - point.plotX = center[0]; - point.plotY = center[1]; - }); - }, - - /** - * Draw the points where each point is one needle - */ - drawPoints: function () { - - var series = this, - center = series.yAxis.center, - pivot = series.pivot, - options = series.options, - pivotOptions = options.pivot, - renderer = series.chart.renderer; - - each(series.points, function (point) { - - var graphic = point.graphic, - shapeArgs = point.shapeArgs, - d = shapeArgs.d, - dialOptions = merge(options.dial, point.dial); // #1233 - - if (graphic) { - graphic.animate(shapeArgs); - shapeArgs.d = d; // animate alters it - } else { - point.graphic = renderer[point.shapeType](shapeArgs) - .attr({ - stroke: dialOptions.borderColor || 'none', - 'stroke-width': dialOptions.borderWidth || 0, - fill: dialOptions.backgroundColor || 'black', - rotation: shapeArgs.rotation, // required by VML when animation is false - zIndex: 1 - }) - .add(series.group); - } - }); - - // Add or move the pivot - if (pivot) { - pivot.animate({ // #1235 - translateX: center[0], - translateY: center[1] - }); - } else { - series.pivot = renderer.circle(0, 0, pick(pivotOptions.radius, 5)) - .attr({ - 'stroke-width': pivotOptions.borderWidth || 0, - stroke: pivotOptions.borderColor || 'silver', - fill: pivotOptions.backgroundColor || 'black', - zIndex: 2 - }) - .translate(center[0], center[1]) - .add(series.group); - } - }, - - /** - * Animate the arrow up from startAngle - */ - animate: function (init) { - var series = this; - - if (!init) { - each(series.points, function (point) { - var graphic = point.graphic; - - if (graphic) { - // start value - graphic.attr({ - rotation: series.yAxis.startAngleRad * 180 / Math.PI - }); - - // animate - graphic.animate({ - rotation: point.shapeArgs.rotation - }, series.options.animation); - } - }); - - // delete this function to allow it only once - series.animate = null; - } - }, - - render: function () { - this.group = this.plotGroup( - 'group', - 'series', - this.visible ? 'visible' : 'hidden', - this.options.zIndex, - this.chart.seriesGroup - ); - Series.prototype.render.call(this); - this.group.clip(this.chart.clipRect); - }, - - /** - * Extend the basic setData method by running processData and generatePoints immediately, - * in order to access the points from the legend. - */ - setData: function (data, redraw) { - Series.prototype.setData.call(this, data, false); - this.processData(); - this.generatePoints(); - if (pick(redraw, true)) { - this.chart.redraw(); - } - }, - - /** - * If the tracking module is loaded, add the point tracker - */ - drawTracker: TrackerMixin && TrackerMixin.drawTrackerPoint - }; - seriesTypes.gauge = extendClass(seriesTypes.line, GaugeSeries); - - /* **************************************************************************** - * Start Box plot series code * - *****************************************************************************/ - - // Set default options - defaultPlotOptions.boxplot = merge(defaultPlotOptions.column, { - fillColor: '#FFFFFF', - lineWidth: 1, - //medianColor: null, - medianWidth: 2, - states: { - hover: { - brightness: -0.3 - } - }, - //stemColor: null, - //stemDashStyle: 'solid' - //stemWidth: null, - threshold: null, - tooltip: { - pointFormat: '\u25CF {series.name}
' + - 'Maximum: {point.high}
' + - 'Upper quartile: {point.q3}
' + - 'Median: {point.median}
' + - 'Lower quartile: {point.q1}
' + - 'Minimum: {point.low}
' - - }, - //whiskerColor: null, - whiskerLength: '50%', - whiskerWidth: 2 - }); - - // Create the series object - seriesTypes.boxplot = extendClass(seriesTypes.column, { - type: 'boxplot', - pointArrayMap: ['low', 'q1', 'median', 'q3', 'high'], // array point configs are mapped to this - toYData: function (point) { // return a plain array for speedy calculation - return [point.low, point.q1, point.median, point.q3, point.high]; - }, - pointValKey: 'high', // defines the top of the tracker - - /** - * One-to-one mapping from options to SVG attributes - */ - pointAttrToOptions: { // mapping between SVG attributes and the corresponding options - fill: 'fillColor', - stroke: 'color', - 'stroke-width': 'lineWidth' - }, - - /** - * Disable data labels for box plot - */ - drawDataLabels: noop, - - /** - * Translate data points from raw values x and y to plotX and plotY - */ - translate: function () { - var series = this, - yAxis = series.yAxis, - pointArrayMap = series.pointArrayMap; - - seriesTypes.column.prototype.translate.apply(series); - - // do the translation on each point dimension - each(series.points, function (point) { - each(pointArrayMap, function (key) { - if (point[key] !== null) { - point[key + 'Plot'] = yAxis.translate(point[key], 0, 1, 0, 1); - } - }); - }); - }, - - /** - * Draw the data points - */ - drawPoints: function () { - var series = this, //state = series.state, - points = series.points, - options = series.options, - chart = series.chart, - renderer = chart.renderer, - pointAttr, - q1Plot, - q3Plot, - highPlot, - lowPlot, - medianPlot, - crispCorr, - crispX, - graphic, - stemPath, - stemAttr, - boxPath, - whiskersPath, - whiskersAttr, - medianPath, - medianAttr, - width, - left, - right, - halfWidth, - shapeArgs, - color, - doQuartiles = series.doQuartiles !== false, // error bar inherits this series type but doesn't do quartiles - pointWiskerLength, - whiskerLength = series.options.whiskerLength; - - - each(points, function (point) { - - graphic = point.graphic; - shapeArgs = point.shapeArgs; // the box - stemAttr = {}; - whiskersAttr = {}; - medianAttr = {}; - color = point.color || series.color; - - if (point.plotY !== UNDEFINED) { - - pointAttr = point.pointAttr[point.selected ? 'selected' : '']; - - // crisp vector coordinates - width = shapeArgs.width; - left = mathFloor(shapeArgs.x); - right = left + width; - halfWidth = mathRound(width / 2); - //crispX = mathRound(left + halfWidth) + crispCorr; - q1Plot = mathFloor(doQuartiles ? point.q1Plot : point.lowPlot);// + crispCorr; - q3Plot = mathFloor(doQuartiles ? point.q3Plot : point.lowPlot);// + crispCorr; - highPlot = mathFloor(point.highPlot);// + crispCorr; - lowPlot = mathFloor(point.lowPlot);// + crispCorr; - - // Stem attributes - stemAttr.stroke = point.stemColor || options.stemColor || color; - stemAttr['stroke-width'] = pick(point.stemWidth, options.stemWidth, options.lineWidth); - stemAttr.dashstyle = point.stemDashStyle || options.stemDashStyle; - - // Whiskers attributes - whiskersAttr.stroke = point.whiskerColor || options.whiskerColor || color; - whiskersAttr['stroke-width'] = pick(point.whiskerWidth, options.whiskerWidth, options.lineWidth); - - // Median attributes - medianAttr.stroke = point.medianColor || options.medianColor || color; - medianAttr['stroke-width'] = pick(point.medianWidth, options.medianWidth, options.lineWidth); - - // The stem - crispCorr = (stemAttr['stroke-width'] % 2) / 2; - crispX = left + halfWidth + crispCorr; - stemPath = [ - // stem up - 'M', - crispX, q3Plot, - 'L', - crispX, highPlot, - - // stem down - 'M', - crispX, q1Plot, - 'L', - crispX, lowPlot - ]; - - // The box - if (doQuartiles) { - crispCorr = (pointAttr['stroke-width'] % 2) / 2; - crispX = mathFloor(crispX) + crispCorr; - q1Plot = mathFloor(q1Plot) + crispCorr; - q3Plot = mathFloor(q3Plot) + crispCorr; - left += crispCorr; - right += crispCorr; - boxPath = [ - 'M', - left, q3Plot, - 'L', - left, q1Plot, - 'L', - right, q1Plot, - 'L', - right, q3Plot, - 'L', - left, q3Plot, - 'z' - ]; - } - - // The whiskers - if (whiskerLength) { - crispCorr = (whiskersAttr['stroke-width'] % 2) / 2; - highPlot = highPlot + crispCorr; - lowPlot = lowPlot + crispCorr; - pointWiskerLength = (/%$/).test(whiskerLength) ? halfWidth * parseFloat(whiskerLength) / 100 : whiskerLength / 2; - whiskersPath = [ - // High whisker - 'M', - crispX - pointWiskerLength, - highPlot, - 'L', - crispX + pointWiskerLength, - highPlot, - - // Low whisker - 'M', - crispX - pointWiskerLength, - lowPlot, - 'L', - crispX + pointWiskerLength, - lowPlot - ]; - } - - // The median - crispCorr = (medianAttr['stroke-width'] % 2) / 2; - medianPlot = mathRound(point.medianPlot) + crispCorr; - medianPath = [ - 'M', - left, - medianPlot, - 'L', - right, - medianPlot - ]; - - // Create or update the graphics - if (graphic) { // update - - point.stem.animate({ d: stemPath }); - if (whiskerLength) { - point.whiskers.animate({ d: whiskersPath }); - } - if (doQuartiles) { - point.box.animate({ d: boxPath }); - } - point.medianShape.animate({ d: medianPath }); - - } else { // create new - point.graphic = graphic = renderer.g() - .add(series.group); - - point.stem = renderer.path(stemPath) - .attr(stemAttr) - .add(graphic); - - if (whiskerLength) { - point.whiskers = renderer.path(whiskersPath) - .attr(whiskersAttr) - .add(graphic); - } - if (doQuartiles) { - point.box = renderer.path(boxPath) - .attr(pointAttr) - .add(graphic); - } - point.medianShape = renderer.path(medianPath) - .attr(medianAttr) - .add(graphic); - } - } - }); - - }, - setStackedPoints: noop // #3890 - - - }); - - /* **************************************************************************** - * End Box plot series code * - *****************************************************************************/ - /* **************************************************************************** - * Start error bar series code * - *****************************************************************************/ - - // 1 - set default options - defaultPlotOptions.errorbar = merge(defaultPlotOptions.boxplot, { - color: '#000000', - grouping: false, - linkedTo: ':previous', - tooltip: { - pointFormat: '\u25CF {series.name}: {point.low} - {point.high}
' - }, - whiskerWidth: null - }); - - // 2 - Create the series object - seriesTypes.errorbar = extendClass(seriesTypes.boxplot, { - type: 'errorbar', - pointArrayMap: ['low', 'high'], // array point configs are mapped to this - toYData: function (point) { // return a plain array for speedy calculation - return [point.low, point.high]; - }, - pointValKey: 'high', // defines the top of the tracker - doQuartiles: false, - drawDataLabels: seriesTypes.arearange ? seriesTypes.arearange.prototype.drawDataLabels : noop, - - /** - * Get the width and X offset, either on top of the linked series column - * or standalone - */ - getColumnMetrics: function () { - return (this.linkedParent && this.linkedParent.columnMetrics) || - seriesTypes.column.prototype.getColumnMetrics.call(this); - } - }); - - /* **************************************************************************** - * End error bar series code * - *****************************************************************************/ - /* **************************************************************************** - * Start Waterfall series code * - *****************************************************************************/ - - // 1 - set default options - defaultPlotOptions.waterfall = merge(defaultPlotOptions.column, { - lineWidth: 1, - lineColor: '#333', - dashStyle: 'dot', - borderColor: '#333', - dataLabels: { - inside: true - }, - states: { - hover: { - lineWidthPlus: 0 // #3126 - } - } - }); - - - // 2 - Create the series object - seriesTypes.waterfall = extendClass(seriesTypes.column, { - type: 'waterfall', - - upColorProp: 'fill', - - pointValKey: 'y', - - /** - * Translate data points from raw values - */ - translate: function () { - var series = this, - options = series.options, - yAxis = series.yAxis, - len, - i, - points, - point, - shapeArgs, - stack, - y, - yValue, - previousY, - previousIntermediate, - range, - minPointLength = pick(options.minPointLength, 5), - threshold = options.threshold, - stacking = options.stacking, - tooltipY; - - // run column series translate - seriesTypes.column.prototype.translate.apply(this); - series.minPointLengthOffset = 0; - - previousY = previousIntermediate = threshold; - points = series.points; - - for (i = 0, len = points.length; i < len; i++) { - // cache current point object - point = points[i]; - yValue = this.processedYData[i]; - shapeArgs = point.shapeArgs; - - // get current stack - stack = stacking && yAxis.stacks[(series.negStacks && yValue < threshold ? '-' : '') + series.stackKey]; - range = stack ? - stack[point.x].points[series.index + ',' + i] : - [0, yValue]; - - // override point value for sums - // #3710 Update point does not propagate to sum - if (point.isSum) { - point.y = correctFloat(yValue); - } else if (point.isIntermediateSum) { - point.y = correctFloat(yValue - previousIntermediate); // #3840 - } - // up points - y = mathMax(previousY, previousY + point.y) + range[0]; - shapeArgs.y = yAxis.translate(y, 0, 1); - - - // sum points - if (point.isSum) { - shapeArgs.y = yAxis.translate(range[1], 0, 1); - shapeArgs.height = Math.min(yAxis.translate(range[0], 0, 1), yAxis.len) - shapeArgs.y + series.minPointLengthOffset; // #4256 - - } else if (point.isIntermediateSum) { - shapeArgs.y = yAxis.translate(range[1], 0, 1); - shapeArgs.height = Math.min(yAxis.translate(previousIntermediate, 0, 1), yAxis.len) - shapeArgs.y + series.minPointLengthOffset; - previousIntermediate = range[1]; - - // If it's not the sum point, update previous stack end position and get - // shape height (#3886) - } else { - if (previousY !== 0) { // Not the first point - shapeArgs.height = yValue > 0 ? - yAxis.translate(previousY, 0, 1) - shapeArgs.y : - yAxis.translate(previousY, 0, 1) - yAxis.translate(previousY - yValue, 0, 1); - } - previousY += yValue; - } - // #3952 Negative sum or intermediate sum not rendered correctly - if (shapeArgs.height < 0) { - shapeArgs.y += shapeArgs.height; - shapeArgs.height *= -1; - } - - point.plotY = shapeArgs.y = mathRound(shapeArgs.y) - (series.borderWidth % 2) / 2; - shapeArgs.height = mathMax(mathRound(shapeArgs.height), 0.001); // #3151 - point.yBottom = shapeArgs.y + shapeArgs.height; - - if (shapeArgs.height <= minPointLength) { - shapeArgs.height = minPointLength; - series.minPointLengthOffset += minPointLength; - } - - shapeArgs.y -= series.minPointLengthOffset; - - // Correct tooltip placement (#3014) - tooltipY = point.plotY + (point.negative ? shapeArgs.height : 0) - series.minPointLengthOffset; - if (series.chart.inverted) { - point.tooltipPos[0] = yAxis.len - tooltipY; - } else { - point.tooltipPos[1] = tooltipY; - } - - } - }, - - /** - * Call default processData then override yData to reflect waterfall's extremes on yAxis - */ - processData: function (force) { - var series = this, - options = series.options, - yData = series.yData, - points = series.options.data, // #3710 Update point does not propagate to sum - point, - dataLength = yData.length, - threshold = options.threshold || 0, - subSum, - sum, - dataMin, - dataMax, - y, - i; - - sum = subSum = dataMin = dataMax = threshold; - - for (i = 0; i < dataLength; i++) { - y = yData[i]; - point = points && points[i] ? points[i] : {}; - - if (y === 'sum' || point.isSum) { - yData[i] = correctFloat(sum); - } else if (y === 'intermediateSum' || point.isIntermediateSum) { - yData[i] = correctFloat(subSum); - } else { - sum += y; - subSum += y; - } - dataMin = Math.min(sum, dataMin); - dataMax = Math.max(sum, dataMax); - } - - Series.prototype.processData.call(this, force); - - // Record extremes - series.dataMin = dataMin; - series.dataMax = dataMax; - }, - - /** - * Return y value or string if point is sum - */ - toYData: function (pt) { - if (pt.isSum) { - return (pt.x === 0 ? null : 'sum'); //#3245 Error when first element is Sum or Intermediate Sum - } - if (pt.isIntermediateSum) { - return (pt.x === 0 ? null : 'intermediateSum'); //#3245 - } - return pt.y; - }, - - /** - * Postprocess mapping between options and SVG attributes - */ - getAttribs: function () { - seriesTypes.column.prototype.getAttribs.apply(this, arguments); - - var series = this, - options = series.options, - stateOptions = options.states, - upColor = options.upColor || series.color, - hoverColor = Highcharts.Color(upColor).brighten(0.1).get(), - seriesDownPointAttr = merge(series.pointAttr), - upColorProp = series.upColorProp; - - seriesDownPointAttr[''][upColorProp] = upColor; - seriesDownPointAttr.hover[upColorProp] = stateOptions.hover.upColor || hoverColor; - seriesDownPointAttr.select[upColorProp] = stateOptions.select.upColor || upColor; - - each(series.points, function (point) { - if (!point.options.color) { - // Up color - if (point.y > 0) { - point.pointAttr = seriesDownPointAttr; - point.color = upColor; - - // Down color (#3710, update to negative) - } else { - point.pointAttr = series.pointAttr; - } - } - }); - }, - - /** - * Draw columns' connector lines - */ - getGraphPath: function () { - - var data = this.data, - length = data.length, - lineWidth = this.options.lineWidth + this.borderWidth, - normalizer = mathRound(lineWidth) % 2 / 2, - path = [], - M = 'M', - L = 'L', - prevArgs, - pointArgs, - i, - d; - - for (i = 1; i < length; i++) { - pointArgs = data[i].shapeArgs; - prevArgs = data[i - 1].shapeArgs; - - d = [ - M, - prevArgs.x + prevArgs.width, prevArgs.y + normalizer, - L, - pointArgs.x, prevArgs.y + normalizer - ]; - - if (data[i - 1].y < 0) { - d[2] += prevArgs.height; - d[5] += prevArgs.height; - } - - path = path.concat(d); - } - - return path; - }, - - /** - * Extremes are recorded in processData - */ - getExtremes: noop, - - drawGraph: Series.prototype.drawGraph - }); - - /* **************************************************************************** - * End Waterfall series code * - *****************************************************************************/ - /** - * Set the default options for polygon - */ - defaultPlotOptions.polygon = merge(defaultPlotOptions.scatter, { - marker: { - enabled: false - } - }); - - /** - * The polygon series class - */ - seriesTypes.polygon = extendClass(seriesTypes.scatter, { - type: 'polygon', - fillGraph: true, - // Close all segments - getSegmentPath: function (segment) { - return Series.prototype.getSegmentPath.call(this, segment).concat('z'); - }, - drawGraph: Series.prototype.drawGraph, - drawLegendSymbol: Highcharts.LegendSymbolMixin.drawRectangle - }); - /* **************************************************************************** - * Start Bubble series code * - *****************************************************************************/ - - // 1 - set default options - defaultPlotOptions.bubble = merge(defaultPlotOptions.scatter, { - dataLabels: { - formatter: function () { // #2945 - return this.point.z; - }, - inside: true, - verticalAlign: 'middle' - }, - // displayNegative: true, - marker: { - // fillOpacity: 0.5, - lineColor: null, // inherit from series.color - lineWidth: 1 - }, - minSize: 8, - maxSize: '20%', - // negativeColor: null, - // sizeBy: 'area' - softThreshold: false, - states: { - hover: { - halo: { - size: 5 - } - } - }, - tooltip: { - pointFormat: '({point.x}, {point.y}), Size: {point.z}' - }, - turboThreshold: 0, - zThreshold: 0, - zoneAxis: 'z' - }); - - var BubblePoint = extendClass(Point, { - haloPath: function () { - return Point.prototype.haloPath.call(this, this.shapeArgs.r + this.series.options.states.hover.halo.size); - }, - ttBelow: false - }); - - // 2 - Create the series object - seriesTypes.bubble = extendClass(seriesTypes.scatter, { - type: 'bubble', - pointClass: BubblePoint, - pointArrayMap: ['y', 'z'], - parallelArrays: ['x', 'y', 'z'], - trackerGroups: ['group', 'dataLabelsGroup'], - bubblePadding: true, - zoneAxis: 'z', - - /** - * Mapping between SVG attributes and the corresponding options - */ - pointAttrToOptions: { - stroke: 'lineColor', - 'stroke-width': 'lineWidth', - fill: 'fillColor' - }, - - /** - * Apply the fillOpacity to all fill positions - */ - applyOpacity: function (fill) { - var markerOptions = this.options.marker, - fillOpacity = pick(markerOptions.fillOpacity, 0.5); - - // When called from Legend.colorizeItem, the fill isn't predefined - fill = fill || markerOptions.fillColor || this.color; - - if (fillOpacity !== 1) { - fill = Color(fill).setOpacity(fillOpacity).get('rgba'); - } - return fill; - }, - - /** - * Extend the convertAttribs method by applying opacity to the fill - */ - convertAttribs: function () { - var obj = Series.prototype.convertAttribs.apply(this, arguments); - - obj.fill = this.applyOpacity(obj.fill); - - return obj; - }, - - /** - * Get the radius for each point based on the minSize, maxSize and each point's Z value. This - * must be done prior to Series.translate because the axis needs to add padding in - * accordance with the point sizes. - */ - getRadii: function (zMin, zMax, minSize, maxSize) { - var len, - i, - pos, - zData = this.zData, - radii = [], - options = this.options, - sizeByArea = options.sizeBy !== 'width', - zThreshold = options.zThreshold, - zRange = zMax - zMin, - value, - radius; - - // Set the shape type and arguments to be picked up in drawPoints - for (i = 0, len = zData.length; i < len; i++) { - - value = zData[i]; - - // When sizing by threshold, the absolute value of z determines the size - // of the bubble. - if (options.sizeByAbsoluteValue && value !== null) { - value = Math.abs(value - zThreshold); - zMax = Math.max(zMax - zThreshold, Math.abs(zMin - zThreshold)); - zMin = 0; - } - - if (value === null) { - radius = null; - // Issue #4419 - if value is less than zMin, push a radius that's always smaller than the minimum size - } else if (value < zMin) { - radius = minSize / 2 - 1; - } else { - // Relative size, a number between 0 and 1 - pos = zRange > 0 ? (value - zMin) / zRange : 0.5; - - if (sizeByArea && pos >= 0) { - pos = Math.sqrt(pos); - } - radius = math.ceil(minSize + pos * (maxSize - minSize)) / 2; - } - radii.push(radius); - } - this.radii = radii; - }, - - /** - * Perform animation on the bubbles - */ - animate: function (init) { - var animation = this.options.animation; - - if (!init) { // run the animation - each(this.points, function (point) { - var graphic = point.graphic, - shapeArgs = point.shapeArgs; - - if (graphic && shapeArgs) { - // start values - graphic.attr('r', 1); - - // animate - graphic.animate({ - r: shapeArgs.r - }, animation); - } - }); - - // delete this function to allow it only once - this.animate = null; - } - }, - - /** - * Extend the base translate method to handle bubble size - */ - translate: function () { - - var i, - data = this.data, - point, - radius, - radii = this.radii; - - // Run the parent method - seriesTypes.scatter.prototype.translate.call(this); - - // Set the shape type and arguments to be picked up in drawPoints - i = data.length; - - while (i--) { - point = data[i]; - radius = radii ? radii[i] : 0; // #1737 - - if (isNumber(radius) && radius >= this.minPxSize / 2) { - // Shape arguments - point.shapeType = 'circle'; - point.shapeArgs = { - x: point.plotX, - y: point.plotY, - r: radius - }; - - // Alignment box for the data label - point.dlBox = { - x: point.plotX - radius, - y: point.plotY - radius, - width: 2 * radius, - height: 2 * radius - }; - } else { // below zThreshold or z = null - point.shapeArgs = point.plotY = point.dlBox = UNDEFINED; // #1691 - } - } - }, - - /** - * Get the series' symbol in the legend - * - * @param {Object} legend The legend object - * @param {Object} item The series (this) or point - */ - drawLegendSymbol: function (legend, item) { - var renderer = this.chart.renderer, - radius = renderer.fontMetrics(legend.itemStyle.fontSize).f / 2; - - item.legendSymbol = renderer.circle( - radius, - legend.baseline - radius, - radius - ).attr({ - zIndex: 3 - }).add(item.legendGroup); - item.legendSymbol.isMarker = true; - - }, - - drawPoints: seriesTypes.column.prototype.drawPoints, - alignDataLabel: seriesTypes.column.prototype.alignDataLabel, - buildKDTree: noop, - applyZones: noop - }); - - /** - * Add logic to pad each axis with the amount of pixels - * necessary to avoid the bubbles to overflow. - */ - Axis.prototype.beforePadding = function () { - var axis = this, - axisLength = this.len, - chart = this.chart, - pxMin = 0, - pxMax = axisLength, - isXAxis = this.isXAxis, - dataKey = isXAxis ? 'xData' : 'yData', - min = this.min, - extremes = {}, - smallestSize = math.min(chart.plotWidth, chart.plotHeight), - zMin = Number.MAX_VALUE, - zMax = -Number.MAX_VALUE, - range = this.max - min, - transA = axisLength / range, - activeSeries = []; - - // Handle padding on the second pass, or on redraw - each(this.series, function (series) { - - var seriesOptions = series.options, - zData; - - if (series.bubblePadding && (series.visible || !chart.options.chart.ignoreHiddenSeries)) { - - // Correction for #1673 - axis.allowZoomOutside = true; - - // Cache it - activeSeries.push(series); - - if (isXAxis) { // because X axis is evaluated first - - // For each series, translate the size extremes to pixel values - each(['minSize', 'maxSize'], function (prop) { - var length = seriesOptions[prop], - isPercent = /%$/.test(length); - - length = pInt(length); - extremes[prop] = isPercent ? - smallestSize * length / 100 : - length; - - }); - series.minPxSize = extremes.minSize; - series.maxPxSize = extremes.maxSize; - - // Find the min and max Z - zData = series.zData; - if (zData.length) { // #1735 - zMin = pick(seriesOptions.zMin, math.min( - zMin, - math.max( - arrayMin(zData), - seriesOptions.displayNegative === false ? seriesOptions.zThreshold : -Number.MAX_VALUE - ) - )); - zMax = pick(seriesOptions.zMax, math.max(zMax, arrayMax(zData))); - } - } - } - }); - - each(activeSeries, function (series) { - - var data = series[dataKey], - i = data.length, - radius; - - if (isXAxis) { - series.getRadii(zMin, zMax, series.minPxSize, series.maxPxSize); - } - - if (range > 0) { - while (i--) { - if (isNumber(data[i]) && axis.dataMin <= data[i] && data[i] <= axis.dataMax) { - radius = series.radii[i]; - pxMin = Math.min(((data[i] - min) * transA) - radius, pxMin); - pxMax = Math.max(((data[i] - min) * transA) + radius, pxMax); - } - } - } - }); - - - if (activeSeries.length && range > 0 && !this.isLog) { - pxMax -= axisLength; - transA *= (axisLength + pxMin - pxMax) / axisLength; - each([['min', 'userMin', pxMin], ['max', 'userMax', pxMax]], function (keys) { - if (pick(axis.options[keys[0]], axis[keys[1]]) === UNDEFINED) { - axis[keys[0]] += keys[2] / transA; - } - }); - } - }; - - /* **************************************************************************** - * End Bubble series code * - *****************************************************************************/ - - (function () { - - /** - * Extensions for polar charts. Additionally, much of the geometry required for polar charts is - * gathered in RadialAxes.js. - * - */ - - var seriesProto = Series.prototype, - pointerProto = Pointer.prototype, - colProto; - - /** - * Search a k-d tree by the point angle, used for shared tooltips in polar charts - */ - seriesProto.searchPointByAngle = function (e) { - var series = this, - chart = series.chart, - xAxis = series.xAxis, - center = xAxis.pane.center, - plotX = e.chartX - center[0] - chart.plotLeft, - plotY = e.chartY - center[1] - chart.plotTop; - - return this.searchKDTree({ - clientX: 180 + (Math.atan2(plotX, plotY) * (-180 / Math.PI)) - }); - - }; - - /** - * Wrap the buildKDTree function so that it searches by angle (clientX) in case of shared tooltip, - * and by two dimensional distance in case of non-shared. - */ - wrap(seriesProto, 'buildKDTree', function (proceed) { - if (this.chart.polar) { - if (this.kdByAngle) { - this.searchPoint = this.searchPointByAngle; - } else { - this.kdDimensions = 2; - } - } - proceed.apply(this); - }); - - /** - * Translate a point's plotX and plotY from the internal angle and radius measures to - * true plotX, plotY coordinates - */ - seriesProto.toXY = function (point) { - var xy, - chart = this.chart, - plotX = point.plotX, - plotY = point.plotY, - clientX; - - // Save rectangular plotX, plotY for later computation - point.rectPlotX = plotX; - point.rectPlotY = plotY; - - // Find the polar plotX and plotY - xy = this.xAxis.postTranslate(point.plotX, this.yAxis.len - plotY); - point.plotX = point.polarPlotX = xy.x - chart.plotLeft; - point.plotY = point.polarPlotY = xy.y - chart.plotTop; - - // If shared tooltip, record the angle in degrees in order to align X points. Otherwise, - // use a standard k-d tree to get the nearest point in two dimensions. - if (this.kdByAngle) { - clientX = ((plotX / Math.PI * 180) + this.xAxis.pane.options.startAngle) % 360; - if (clientX < 0) { // #2665 - clientX += 360; - } - point.clientX = clientX; - } else { - point.clientX = point.plotX; - } - }; - - if (seriesTypes.spline) { - /** - * Overridden method for calculating a spline from one point to the next - */ - wrap(seriesTypes.spline.prototype, 'getPointSpline', function (proceed, segment, point, i) { - - var ret, - smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc; - denom = smoothing + 1, - plotX, - plotY, - lastPoint, - nextPoint, - lastX, - lastY, - nextX, - nextY, - leftContX, - leftContY, - rightContX, - rightContY, - distanceLeftControlPoint, - distanceRightControlPoint, - leftContAngle, - rightContAngle, - jointAngle; - - - if (this.chart.polar) { - - plotX = point.plotX; - plotY = point.plotY; - lastPoint = segment[i - 1]; - nextPoint = segment[i + 1]; - - // Connect ends - if (this.connectEnds) { - if (!lastPoint) { - lastPoint = segment[segment.length - 2]; // not the last but the second last, because the segment is already connected - } - if (!nextPoint) { - nextPoint = segment[1]; - } - } - - // find control points - if (lastPoint && nextPoint) { - - lastX = lastPoint.plotX; - lastY = lastPoint.plotY; - nextX = nextPoint.plotX; - nextY = nextPoint.plotY; - leftContX = (smoothing * plotX + lastX) / denom; - leftContY = (smoothing * plotY + lastY) / denom; - rightContX = (smoothing * plotX + nextX) / denom; - rightContY = (smoothing * plotY + nextY) / denom; - distanceLeftControlPoint = Math.sqrt(Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2)); - distanceRightControlPoint = Math.sqrt(Math.pow(rightContX - plotX, 2) + Math.pow(rightContY - plotY, 2)); - leftContAngle = Math.atan2(leftContY - plotY, leftContX - plotX); - rightContAngle = Math.atan2(rightContY - plotY, rightContX - plotX); - jointAngle = (Math.PI / 2) + ((leftContAngle + rightContAngle) / 2); - - - // Ensure the right direction, jointAngle should be in the same quadrant as leftContAngle - if (Math.abs(leftContAngle - jointAngle) > Math.PI / 2) { - jointAngle -= Math.PI; - } - - // Find the corrected control points for a spline straight through the point - leftContX = plotX + Math.cos(jointAngle) * distanceLeftControlPoint; - leftContY = plotY + Math.sin(jointAngle) * distanceLeftControlPoint; - rightContX = plotX + Math.cos(Math.PI + jointAngle) * distanceRightControlPoint; - rightContY = plotY + Math.sin(Math.PI + jointAngle) * distanceRightControlPoint; - - // Record for drawing in next point - point.rightContX = rightContX; - point.rightContY = rightContY; - - } - - - // moveTo or lineTo - if (!i) { - ret = ['M', plotX, plotY]; - } else { // curve from last point to this - ret = [ - 'C', - lastPoint.rightContX || lastPoint.plotX, - lastPoint.rightContY || lastPoint.plotY, - leftContX || plotX, - leftContY || plotY, - plotX, - plotY - ]; - lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later - } - - - } else { - ret = proceed.call(this, segment, point, i); - } - return ret; - }); - } - - /** - * Extend translate. The plotX and plotY values are computed as if the polar chart were a - * cartesian plane, where plotX denotes the angle in radians and (yAxis.len - plotY) is the pixel distance from - * center. - */ - wrap(seriesProto, 'translate', function (proceed) { - var chart = this.chart, - points, - i; - - // Run uber method - proceed.call(this); - - // Postprocess plot coordinates - if (chart.polar) { - this.kdByAngle = chart.tooltip && chart.tooltip.shared; - - if (!this.preventPostTranslate) { - points = this.points; - i = points.length; - - while (i--) { - // Translate plotX, plotY from angle and radius to true plot coordinates - this.toXY(points[i]); - } - } - } - }); - - /** - * Extend getSegmentPath to allow connecting ends across 0 to provide a closed circle in - * line-like series. - */ - wrap(seriesProto, 'getGraphPath', function (proceed, points) { - var series = this; - - // Connect the path - if (this.chart.polar) { - points = points || this.points; - - if (this.options.connectEnds !== false && points[0] && points[0].y !== null) { - this.connectEnds = true; // re-used in splines - points.splice(points.length, 0, points[0]); - } - - // For area charts, pseudo points are added to the graph, now we need to translate these - each(points, function (point) { - if (point.polarPlotY === undefined) { - series.toXY(point); - } - }); - } - - // Run uber method - return proceed.apply(this, [].slice.call(arguments, 1)); - - }); - - - function polarAnimate(proceed, init) { - var chart = this.chart, - animation = this.options.animation, - group = this.group, - markerGroup = this.markerGroup, - center = this.xAxis.center, - plotLeft = chart.plotLeft, - plotTop = chart.plotTop, - attribs; - - // Specific animation for polar charts - if (chart.polar) { - - // Enable animation on polar charts only in SVG. In VML, the scaling is different, plus animation - // would be so slow it would't matter. - if (chart.renderer.isSVG) { - - if (animation === true) { - animation = {}; - } - - // Initialize the animation - if (init) { - - // Scale down the group and place it in the center - attribs = { - translateX: center[0] + plotLeft, - translateY: center[1] + plotTop, - scaleX: 0.001, // #1499 - scaleY: 0.001 - }; - - group.attr(attribs); - if (markerGroup) { - //markerGroup.attrSetters = group.attrSetters; - markerGroup.attr(attribs); - } - - // Run the animation - } else { - attribs = { - translateX: plotLeft, - translateY: plotTop, - scaleX: 1, - scaleY: 1 - }; - group.animate(attribs, animation); - if (markerGroup) { - markerGroup.animate(attribs, animation); - } - - // Delete this function to allow it only once - this.animate = null; - } - } - - // For non-polar charts, revert to the basic animation - } else { - proceed.call(this, init); - } - } - - // Define the animate method for regular series - wrap(seriesProto, 'animate', polarAnimate); - - - if (seriesTypes.column) { - - colProto = seriesTypes.column.prototype; - - colProto.polarArc = function (low, high, start, end) { - var center = this.xAxis.center, - len = this.yAxis.len; - - return this.chart.renderer.symbols.arc( - center[0], - center[1], - len - high, - null, - { - start: start, - end: end, - innerR: len - pick(low, len) - } - ); - }; - - /** - * Define the animate method for columnseries - */ - wrap(colProto, 'animate', polarAnimate); - - - /** - * Extend the column prototype's translate method - */ - wrap(colProto, 'translate', function (proceed) { - - var xAxis = this.xAxis, - startAngleRad = xAxis.startAngleRad, - start, - points, - point, - i; - - this.preventPostTranslate = true; - - // Run uber method - proceed.call(this); - - // Postprocess plot coordinates - if (xAxis.isRadial) { - points = this.points; - i = points.length; - while (i--) { - point = points[i]; - start = point.barX + startAngleRad; - point.shapeType = 'path'; - point.shapeArgs = { - d: this.polarArc(point.yBottom, point.plotY, start, start + point.pointWidth) - }; - // Provide correct plotX, plotY for tooltip - this.toXY(point); - point.tooltipPos = [point.plotX, point.plotY]; - point.ttBelow = point.plotY > xAxis.center[1]; - } - } - }); - - - /** - * Align column data labels outside the columns. #1199. - */ - wrap(colProto, 'alignDataLabel', function (proceed, point, dataLabel, options, alignTo, isNew) { - - if (this.chart.polar) { - var angle = point.rectPlotX / Math.PI * 180, - align, - verticalAlign; - - // Align nicely outside the perimeter of the columns - if (options.align === null) { - if (angle > 20 && angle < 160) { - align = 'left'; // right hemisphere - } else if (angle > 200 && angle < 340) { - align = 'right'; // left hemisphere - } else { - align = 'center'; // top or bottom - } - options.align = align; - } - if (options.verticalAlign === null) { - if (angle < 45 || angle > 315) { - verticalAlign = 'bottom'; // top part - } else if (angle > 135 && angle < 225) { - verticalAlign = 'top'; // bottom part - } else { - verticalAlign = 'middle'; // left or right - } - options.verticalAlign = verticalAlign; - } - - seriesProto.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew); - } else { - proceed.call(this, point, dataLabel, options, alignTo, isNew); - } - - }); - } - - /** - * Extend getCoordinates to prepare for polar axis values - */ - wrap(pointerProto, 'getCoordinates', function (proceed, e) { - var chart = this.chart, - ret = { - xAxis: [], - yAxis: [] - }; - - if (chart.polar) { - - each(chart.axes, function (axis) { - var isXAxis = axis.isXAxis, - center = axis.center, - x = e.chartX - center[0] - chart.plotLeft, - y = e.chartY - center[1] - chart.plotTop; - - ret[isXAxis ? 'xAxis' : 'yAxis'].push({ - axis: axis, - value: axis.translate( - isXAxis ? - Math.PI - Math.atan2(x, y) : // angle - Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), // distance from center - true - ) - }); - }); - - } else { - ret = proceed.call(this, e); - } - - return ret; - }); - - }()); - -})); diff --git a/public/vendor/highcharts-4.2.5/highcharts.js b/public/vendor/highcharts-4.2.5/highcharts.js deleted file mode 100644 index 01498be8e1..0000000000 --- a/public/vendor/highcharts-4.2.5/highcharts.js +++ /dev/null @@ -1,343 +0,0 @@ -/* - Highcharts JS v4.2.5 (2016-05-06) - - (c) 2009-2016 Torstein Honsi - - License: www.highcharts.com/license -*/ -(function(D,aa){typeof module==="object"&&module.exports?module.exports=D.document?aa(D):aa:D.Highcharts=aa(D)})(typeof window!=="undefined"?window:this,function(D){function aa(a,b){var c="Highcharts error #"+a+": www.highcharts.com/errors/"+a;if(b)throw Error(c);D.console&&console.log(c)}function pb(a,b,c){this.options=b;this.elem=a;this.prop=c}function E(){var a,b=arguments,c,d={},e=function(a,b){var c,d;typeof a!=="object"&&(a={});for(d in b)b.hasOwnProperty(d)&&(c=b[d],a[d]=c&&typeof c==="object"&& -Object.prototype.toString.call(c)!=="[object Array]"&&d!=="renderTo"&&typeof c.nodeType!=="number"?e(a[d]||{},c):b[d]);return a};b[0]===!0&&(d=b[1],b=Array.prototype.slice.call(b,2));c=b.length;for(a=0;a-1?h.thousandsSep:""))):e=Qa(f,e)}k.push(e);a=a.slice(c+1);c=(d=!d)?"}":"{"}k.push(a);return k.join("")}function rb(a){return Y.pow(10,V(Y.log(a)/Y.LN10))}function sb(a,b,c,d,e){var f,g=a,c=o(c,1);f=a/c;b||(b=[1,2,2.5,5,10],d===!1&&(c===1?b=[1,2,5,10]:c<=0.1&&(b=[1/c])));for(d=0;d=a||!e&&f<=(b[d]+(b[d+1]||b[d]))/2)break;g*=c;return g}function hb(a,b){var c=a.length,d,e;for(e=0;ec&&(c=a[b]);return c}function Ra(a,b){for(var c in a)a[c]&&a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Sa(a){ib||(ib=ba(Ma));a&&ib.appendChild(a);ib.innerHTML=""}function ca(a,b){return parseFloat(a.toPrecision(b||14))}function Ta(a,b){b.renderer.globalAnimation=o(a,b.animation)}function $a(a){return Z(a)? -E(a):{duration:a?500:0}}function Eb(){var a=U.global,b=a.useUTC,c=b?"getUTC":"get",d=b?"setUTC":"set";la=a.Date||D.Date;qb=b&&a.timezoneOffset;Za=b&&a.getTimezoneOffset;jb=function(a,c,d,h,i,k){var j;b?(j=la.UTC.apply(0,arguments),j+=Ya(j)):j=(new la(a,c,o(d,1),o(h,0),o(i,0),o(k,0))).getTime();return j};tb=c+"Minutes";ub=c+"Hours";vb=c+"Day";Ua=c+"Date";ab=c+"Month";bb=c+"FullYear";Fb=d+"Milliseconds";Gb=d+"Seconds";Hb=d+"Minutes";Ib=d+"Hours";kb=d+"Date";wb=d+"Month";xb=d+"FullYear"}function ma(a){if(!(this instanceof -ma))return new ma(a);this.init(a)}function O(){}function Va(a,b,c,d){this.axis=a;this.pos=b;this.type=c||"";this.isNew=!0;!c&&!d&&this.addLabel()}function Jb(a,b,c,d,e){var f=a.chart.inverted;this.axis=a;this.isNegative=c;this.options=b;this.x=d;this.total=null;this.points={};this.stack=e;this.rightCliff=this.leftCliff=0;this.alignOptions={align:b.align||(f?c?"left":"right":"center"),verticalAlign:b.verticalAlign||(f?"middle":c?"bottom":"top"),y:o(b.y,f?4:c?14:-6),x:o(b.x,f?c?-6:6:0)};this.textAlign= -b.textAlign||(f?c?"right":"left":"center")}var y,A=D.document,Y=Math,B=Y.round,V=Y.floor,ua=Y.ceil,t=Y.max,F=Y.min,Q=Y.abs,W=Y.cos,da=Y.sin,ra=Y.PI,ja=ra*2/360,za=D.navigator&&D.navigator.userAgent||"",Kb=D.opera,ya=/(msie|trident|edge)/i.test(za)&&!Kb,lb=A&&A.documentMode===8,mb=!ya&&/AppleWebKit/.test(za),Na=/Firefox/.test(za),Lb=/(Mobile|Android|Windows Phone)/.test(za),Ha="http://www.w3.org/2000/svg",fa=A&&A.createElementNS&&!!A.createElementNS(Ha,"svg").createSVGRect,Pb=Na&&parseInt(za.split("Firefox/")[1], -10)<4,ka=A&&!fa&&!ya&&!!A.createElement("canvas").getContext,cb,db,Mb={},yb=0,ib,U,Qa,H,Aa=function(){},T=[],eb=0,Ma="div",Qb=/^[0-9]+$/,nb=["plotTop","marginRight","marginBottom","plotLeft"],la,jb,qb,Za,tb,ub,vb,Ua,ab,bb,Fb,Gb,Hb,Ib,kb,wb,xb,L={},x;x=D.Highcharts?aa(16,!0):{win:D};x.seriesTypes=L;var Ia=[],na,sa,p,Ba,zb,Ca,N,X,I,Wa,Oa;pb.prototype={dSetter:function(){var a=this.paths[0],b=this.paths[1],c=[],d=this.now,e=a.length,f;if(d===1)c=this.toD;else if(e===b.length&&d<1)for(;e--;)f=parseFloat(a[e]), -c[e]=isNaN(f)?a[e]:d*parseFloat(b[e]-f)+f;else c=b;this.elem.attr("d",c)},update:function(){var a=this.elem,b=this.prop,c=this.now,d=this.options.step;if(this[b+"Setter"])this[b+"Setter"]();else a.attr?a.element&&a.attr(b,c):a.style[b]=c+this.unit;d&&d.call(a,c,this)},run:function(a,b,c){var d=this,e=function(a){return e.stopped?!1:d.step(a)},f;this.startTime=+new la;this.start=a;this.end=b;this.unit=c;this.now=this.start;this.pos=0;e.elem=this.elem;if(e()&&Ia.push(e)===1)e.timerId=setInterval(function(){for(f= -0;f=f+this.startTime){this.now=this.end;this.pos=1;this.update();a=g[this.prop]=!0;for(h in g)g[h]!==!0&&(a=!1);a&&e&&e.call(c);c=!1}else this.pos=d.easing((b-this.startTime)/f),this.now=this.start+(this.end-this.start)*this.pos,this.update(),c=!0;return c},initPath:function(a, -b,c){var b=b||"",d=a.shift,e=b.indexOf("C")>-1,f=e?7:3,g,b=b.split(" "),c=[].concat(c),h=a.isArea,i=h?2:1,k=function(a){for(g=a.length;g--;)(a[g]==="M"||a[g]==="L")&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&(k(b),k(c));if(d<=c.length/f&&b.length===c.length)for(;d--;)c=c.slice(0,f).concat(c),h&&(c=c.concat(c.slice(c.length-f)));a.shift=0;if(b.length)for(a=c.length;b.length3?g.length%3:0;c=o(c,e.decimalPoint);d=o(d,e.thousandsSep);a=a<0?"-":"";a+=h?g.substr(0,h)+d:"";a+=g.substr(h).replace(/(\d{3})(?=\d)/g,"$1"+d);b&&(d=Math.abs(i-g+Math.pow(10,-Math.max(b,f)-1)),a+=c+d.toFixed(b).slice(2)); -return a};Math.easeInOutSine=function(a){return-0.5*(Math.cos(Math.PI*a)-1)};na=function(a,b){var c;if(b==="width")return Math.min(a.offsetWidth,a.scrollWidth)-na(a,"padding-left")-na(a,"padding-right");else if(b==="height")return Math.min(a.offsetHeight,a.scrollHeight)-na(a,"padding-top")-na(a,"padding-bottom");return(c=D.getComputedStyle(a,void 0))&&C(c.getPropertyValue(b))};sa=function(a,b){return b.indexOf?b.indexOf(a):[].indexOf.call(b,a)};Ba=function(a,b){return[].filter.call(a,b)};Ca=function(a, -b){for(var c=[],d=0,e=a.length;d-1&&(f.splice(h,1),g[b]=f),d(b,c)):(e(),g[b]=[])):(e(),a.hcEvents={})};I=function(a,b,c,d){var e;e=a.hcEvents;var f,g,c=c||{};if(A.createEvent&&(a.dispatchEvent||a.fireEvent))e=A.createEvent("Events"),e.initEvent(b,!0,!0),e.target=a,u(e,c),a.dispatchEvent?a.dispatchEvent(e):a.fireEvent(b,e);else if(e){e=e[b]||[];f=e.length;if(!c.preventDefault)c.preventDefault=function(){c.defaultPrevented=!0};c.target=a;if(!c.type)c.type=b;for(b=0;b{point.key}
',pointFormat:'\u25cf {series.name}: {point.y}
', -shadow:!0,snap:Lb?25:10,style:{color:"#333333",cursor:"default",fontSize:"12px",padding:"8px",pointerEvents:"none",whiteSpace:"nowrap"}},credits:{enabled:!0,text:"Highcharts.com",href:"http://www.highcharts.com",position:{align:"right",x:-10,verticalAlign:"bottom",y:-5},style:{cursor:"pointer",color:"#909090",fontSize:"9px"}}};var ea=U.plotOptions,ga=ea.line;Eb();ma.prototype={parsers:[{regex:/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,parse:function(a){return[C(a[1]), -C(a[2]),C(a[3]),parseFloat(a[4],10)]}},{regex:/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,parse:function(a){return[C(a[1],16),C(a[2],16),C(a[3],16),1]}},{regex:/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,parse:function(a){return[C(a[1]),C(a[2]),C(a[3]),1]}}],init:function(a){var b,c,d,e;if((this.input=a)&&a.stops)this.stops=Ca(a.stops,function(a){return new ma(a[1])});else for(d=this.parsers.length;d--&&!c;)e=this.parsers[d],(b=e.regex.exec(a))&&(c=e.parse(b));this.rgba= -c||[]},get:function(a){var b=this.input,c=this.rgba,d;this.stops?(d=E(b),d.stops=[].concat(d.stops),p(this.stops,function(b,c){d.stops[c]=[d.stops[c][0],b.get(a)]})):d=c&&J(c[0])?a==="rgb"||!a&&c[3]===1?"rgb("+c[0]+","+c[1]+","+c[2]+")":a==="a"?c[3]:"rgba("+c.join(",")+")":b;return d},brighten:function(a){var b,c=this.rgba;if(this.stops)p(this.stops,function(b){b.brighten(a)});else if(J(a)&&a!==0)for(b=0;b<3;b++)c[b]+=C(a*255),c[b]<0&&(c[b]=0),c[b]>255&&(c[b]=255);return this},setOpacity:function(a){this.rgba[3]= -a;return this}};O.prototype={opacity:1,textProps:"direction,fontSize,fontWeight,fontFamily,fontStyle,color,lineHeight,width,textDecoration,textOverflow,textShadow".split(","),init:function(a,b){this.element=b==="span"?ba(b):A.createElementNS(Ha,b);this.renderer=a},animate:function(a,b,c){b=o(b,this.renderer.globalAnimation,!0);Oa(this);if(b){if(c)b.complete=c;Wa(this,a,b)}else this.attr(a,null,c);return this},colorGradient:function(a,b,c){var d=this.renderer,e,f,g,h,i,k,j,l,m,n,q,z=[],s;a.linearGradient? -f="linearGradient":a.radialGradient&&(f="radialGradient");if(f){g=a[f];i=d.gradients;j=a.stops;n=c.radialReference;Ea(g)&&(a[f]=g={x1:g[0],y1:g[1],x2:g[2],y2:g[3],gradientUnits:"userSpaceOnUse"});f==="radialGradient"&&n&&!r(g.gradientUnits)&&(h=g,g=E(g,d.getRadialAttr(n,h),{gradientUnits:"userSpaceOnUse"}));for(q in g)q!=="id"&&z.push(q,g[q]);for(q in j)z.push(j[q]);z=z.join(",");i[z]?n=i[z].attr("id"):(g.id=n="highcharts-"+yb++,i[z]=k=d.createElement(f).attr(g).add(d.defs),k.radAttr=h,k.stops=[], -p(j,function(a){a[1].indexOf("rgba")===0?(e=ma(a[1]),l=e.get("rgb"),m=e.get("a")):(l=a[1],m=1);a=d.createElement("stop").attr({offset:a[0],"stop-color":l,"stop-opacity":m}).add(k);k.stops.push(a)}));s="url("+d.url+"#"+n+")";c.setAttribute(b,s);c.gradient=z;a.toString=function(){return s}}},applyTextShadow:function(a){var b=this.element,c,d=a.indexOf("contrast")!==-1,e={},f=this.renderer.forExport,g=f||b.style.textShadow!==y&&!ya;if(d)e.textShadow=a=a.replace(/contrast/g,this.renderer.getContrast(b.style.fill)); -if(mb||f)e.textRendering="geometricPrecision";g?this.css(e):(this.fakeTS=!0,this.ySetter=this.xSetter,c=[].slice.call(b.getElementsByTagName("tspan")),p(a.split(/\s?,\s?/g),function(a){var d=b.firstChild,e,f,a=a.split(" ");e=a[a.length-1];(f=a[a.length-2])&&p(c,function(a,c){var g;c===0&&(a.setAttribute("x",b.getAttribute("x")),c=b.getAttribute("y"),a.setAttribute("y",c||0),c===null&&b.setAttribute("y",0));g=a.cloneNode(1);P(g,{"class":"highcharts-text-shadow",fill:e,stroke:e,"stroke-opacity":1/t(C(f), -3),"stroke-width":f,"stroke-linejoin":"round"});b.insertBefore(g,d)})}))},attr:function(a,b,c){var d,e=this.element,f,g=this,h;typeof a==="string"&&b!==y&&(d=a,a={},a[d]=b);if(typeof a==="string")g=(this[a+"Getter"]||this._defaultGetter).call(this,a,e);else{for(d in a){b=a[d];h=!1;this.symbolName&&/^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(d)&&(f||(this.symbolAttr(a),f=!0),h=!0);if(this.rotation&&(d==="x"||d==="y"))this.doTransform=!0;h||(h=this[d+"Setter"]||this._defaultSetter, -h.call(this,b,d,e),this.shadows&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(d)&&this.updateShadows(d,b,h))}if(this.doTransform)this.updateTransform(),this.doTransform=!1}c&&c();return g},updateShadows:function(a,b,c){for(var d=this.shadows,e=d.length;e--;)c.call(d[e],a==="height"?Math.max(b-(d[e].cutHeight||0),0):a==="d"?this.d:b,a,d[e])},addClass:function(a){var b=this.element,c=P(b,"class")||"";c.indexOf(a)===-1&&P(b,"class",c+" "+a);return this},symbolAttr:function(a){var b=this; -p("x,y,r,start,end,width,height,innerR,anchorX,anchorY".split(","),function(c){b[c]=o(a[c],b[c])});b.attr({d:b.renderer.symbols[b.symbolName](b.x,b.y,b.width,b.height,b)})},clip:function(a){return this.attr("clip-path",a?"url("+this.renderer.url+"#"+a.id+")":"none")},crisp:function(a){var b,c={},d,e=this.strokeWidth||0;d=B(e)%2/2;a.x=V(a.x||this.x||0)+d;a.y=V(a.y||this.y||0)+d;a.width=V((a.width||this.width||0)-2*d);a.height=V((a.height||this.height||0)-2*d);a.strokeWidth=e;for(b in a)this[b]!==a[b]&& -(this[b]=c[b]=a[b]);return c},css:function(a){var b=this.styles,c={},d=this.element,e,f,g="";e=!b;if(a&&a.color)a.fill=a.color;if(b)for(f in a)a[f]!==b[f]&&(c[f]=a[f],e=!0);if(e){e=this.textWidth=a&&a.width&&d.nodeName.toLowerCase()==="text"&&C(a.width)||this.textWidth;b&&(a=u(b,c));this.styles=a;e&&(ka||!fa&&this.renderer.forExport)&&delete a.width;if(ya&&!fa)M(this.element,a);else{b=function(a,b){return"-"+b.toLowerCase()};for(f in a)g+=f.replace(/([A-Z])/g,b)+":"+a[f]+";";P(d,"style",g)}e&&this.added&& -this.renderer.buildText(this)}return this},on:function(a,b){var c=this,d=c.element;db&&a==="click"?(d.ontouchstart=function(a){c.touchEventFired=la.now();a.preventDefault();b.call(d,a)},d.onclick=function(a){(za.indexOf("Android")===-1||la.now()-(c.touchEventFired||0)>1100)&&b.call(d,a)}):d["on"+a]=b;return this},setRadialReference:function(a){var b=this.renderer.gradients[this.element.gradient];this.element.radialReference=a;b&&b.radAttr&&b.animate(this.renderer.getRadialAttr(a,b.radAttr));return this}, -translate:function(a,b){return this.attr({translateX:a,translateY:b})},invert:function(){this.inverted=!0;this.updateTransform();return this},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=this.scaleX,d=this.scaleY,e=this.inverted,f=this.rotation,g=this.element;e&&(a+=this.attr("width"),b+=this.attr("height"));a=["translate("+a+","+b+")"];e?a.push("rotate(90) scale(-1,1)"):f&&a.push("rotate("+f+" "+(g.getAttribute("x")||0)+" "+(g.getAttribute("y")||0)+")");(r(c)||r(d))&& -a.push("scale("+o(c,1)+" "+o(d,1)+")");a.length&&g.setAttribute("transform",a.join(" "))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);return this},align:function(a,b,c){var d,e,f,g,h={};e=this.renderer;f=e.alignedObjects;if(a){if(this.alignOptions=a,this.alignByTranslate=b,!c||xa(c))this.alignTo=d=c||"renderer",pa(f,this),f.push(this),c=null}else a=this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=o(c,e[d],e);d=a.align;e=a.verticalAlign;f=(c.x||0)+(a.x||0);g=(c.y||0)+ -(a.y||0);if(d==="right"||d==="center")f+=(c.width-(a.width||0))/{right:1,center:2}[d];h[b?"translateX":"x"]=B(f);if(e==="bottom"||e==="middle")g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]||1);h[b?"translateY":"y"]=B(g);this[this.placed?"animate":"attr"](h);this.placed=!0;this.alignAttr=h;return this},getBBox:function(a,b){var c,d=this.renderer,e,f,g,h=this.element,i=this.styles;e=this.textStr;var k,j=h.style,l,m=d.cache,n=d.cacheKeys,q;f=o(b,this.rotation);g=f*ja;e!==y&&(q=["",f||0,i&&i.fontSize, -h.style.width].join(","),q=e===""||Qb.test(e)?"num:"+e.toString().length+q:e+q);q&&!a&&(c=m[q]);if(!c){if(h.namespaceURI===Ha||d.forExport){try{l=this.fakeTS&&function(a){p(h.querySelectorAll(".highcharts-text-shadow"),function(b){b.style.display=a})},Na&&j.textShadow?(k=j.textShadow,j.textShadow=""):l&&l("none"),c=h.getBBox?u({},h.getBBox()):{width:h.offsetWidth,height:h.offsetHeight},k?j.textShadow=k:l&&l("")}catch(z){}if(!c||c.width<0)c={width:0,height:0}}else c=this.htmlGetBBox();if(d.isSVG){d= -c.width;e=c.height;if(ya&&i&&i.fontSize==="11px"&&e.toPrecision(3)==="16.9")c.height=e=14;if(f)c.width=Q(e*da(g))+Q(d*W(g)),c.height=Q(e*W(g))+Q(d*da(g))}if(q){for(;n.length>250;)delete m[n.shift()];m[q]||n.push(q);m[q]=c}}return c},show:function(a){return this.attr({visibility:a?"inherit":"visible"})},hide:function(){return this.attr({visibility:"hidden"})},fadeOut:function(a){var b=this;b.animate({opacity:0},{duration:a||150,complete:function(){b.attr({y:-9999})}})},add:function(a){var b=this.renderer, -c=this.element,d;if(a)this.parentGroup=a;this.parentInverted=a&&a.inverted;this.textStr!==void 0&&b.buildText(this);this.added=!0;if(!a||a.handleZ||this.zIndex)d=this.zIndexSetter();d||(a?a.element:b.box).appendChild(c);if(this.onAdd)this.onAdd();return this},safeRemoveChild:function(a){var b=a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||{},c=a.shadows,d=a.renderer.isSVG&&b.nodeName==="SPAN"&&a.parentGroup,e,f;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=b.point= -null;Oa(a);if(a.clipPath)a.clipPath=a.clipPath.destroy();if(a.stops){for(f=0;f]*>/g,"")))},textSetter:function(a){if(a!==this.textStr)delete this.bBox,this.textStr=a,this.added&&this.renderer.buildText(this)},fillSetter:function(a,b,c){typeof a==="string"?c.setAttribute(b,a):a&&this.colorGradient(a,b,c)},visibilitySetter:function(a,b,c){a==="inherit"?c.removeAttribute(b):c.setAttribute(b,a)},zIndexSetter:function(a,b){var c=this.renderer,d=this.parentGroup,c=(d||c).element||c.box,e,f,g= -this.element,h;e=this.added;var i;if(r(a))g.zIndex=a,a=+a,this[b]===a&&(e=!1),this[b]=a;if(e){if((a=this.zIndex)&&d)d.handleZ=!0;d=c.childNodes;for(i=0;ia||!r(a)&&r(f)))c.insertBefore(g,e),h=!0;h||c.appendChild(g)}return h},_defaultSetter:function(a,b,c){c.setAttribute(b,a)}};O.prototype.yGetter=O.prototype.xGetter;O.prototype.translateXSetter=O.prototype.translateYSetter=O.prototype.rotationSetter=O.prototype.verticalAlignSetter=O.prototype.scaleXSetter= -O.prototype.scaleYSetter=function(a,b){this[b]=a;this.doTransform=!0};O.prototype["stroke-widthSetter"]=O.prototype.strokeSetter=function(a,b,c){this[b]=a;if(this.stroke&&this["stroke-width"])this.strokeWidth=this["stroke-width"],O.prototype.fillSetter.call(this,this.stroke,"stroke",c),c.setAttribute("stroke-width",this["stroke-width"]),this.hasStroke=!0;else if(b==="stroke-width"&&a===0&&this.hasStroke)c.removeAttribute("stroke"),this.hasStroke=!1};var Da=function(){this.init.apply(this,arguments)}; -Da.prototype={Element:O,init:function(a,b,c,d,e,f){var g,d=this.createElement("svg").attr({version:"1.1"}).css(this.getStyle(d));g=d.element;a.appendChild(g);a.innerHTML.indexOf("xmlns")===-1&&P(g,"xmlns",Ha);this.isSVG=!0;this.box=g;this.boxWrapper=d;this.alignedObjects=[];this.url=(Na||mb)&&A.getElementsByTagName("base").length?D.location.href.replace(/#.*?$/,"").replace(/([\('\)])/g,"\\$1").replace(/ /g,"%20"):"";this.createElement("desc").add().element.appendChild(A.createTextNode("Created with Highcharts 4.2.5")); -this.defs=this.createElement("defs").add();this.allowHTML=f;this.forExport=e;this.gradients={};this.cache={};this.cacheKeys=[];this.imgCount=0;this.setSize(b,c,!1);var h;if(Na&&a.getBoundingClientRect)this.subPixelFix=b=function(){M(a,{left:0,top:0});h=a.getBoundingClientRect();M(a,{left:ua(h.left)-h.left+"px",top:ua(h.top)-h.top+"px"})},b(),N(D,"resize",b)},getStyle:function(a){return this.style=u({fontFamily:'"Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif',fontSize:"12px"}, -a)},isHidden:function(){return!this.boxWrapper.getBBox().width},destroy:function(){var a=this.defs;this.box=null;this.boxWrapper=this.boxWrapper.destroy();Ra(this.gradients||{});this.gradients=null;if(a)this.defs=a.destroy();this.subPixelFix&&X(D,"resize",this.subPixelFix);return this.alignedObjects=null},createElement:function(a){var b=new this.Element;b.init(this,a);return b},draw:function(){},getRadialAttr:function(a,b){return{cx:a[0]-a[2]/2+b.cx*a[2],cy:a[1]-a[2]/2+b.cy*a[2],r:b.r*a[2]}},buildText:function(a){for(var b= -a.element,c=this,d=c.forExport,e=o(a.textStr,"").toString(),f=e.indexOf("<")!==-1,g=b.childNodes,h,i,k,j=P(b,"x"),l=a.styles,m=a.textWidth,n=l&&l.lineHeight,q=l&&l.textShadow,z=l&&l.textOverflow==="ellipsis",s=g.length,G=m&&!a.added&&this.box,w=function(a){return n?C(n):c.fontMetrics(/(px|em)$/.test(a&&a.style.fontSize)?a.style.fontSize:l&&l.fontSize||c.style.fontSize||12,a).h},v=function(a){return a.replace(/</g,"<").replace(/>/g,">")};s--;)b.removeChild(g[s]);!f&&!q&&!z&&e.indexOf(" ")=== --1?b.appendChild(A.createTextNode(v(e))):(h=/<.*style="([^"]+)".*>/,i=/<.*href="(http[^"]+)".*>/,G&&G.appendChild(b),e=f?e.replace(/<(b|strong)>/g,'').replace(/<(i|em)>/g,'').replace(//g,"").split(//g):[e],e=Ba(e,function(a){return a!==""}),p(e,function(e,f){var g,n=0,e=e.replace(/^\s+|\s+$/g,"").replace(//g,"|||");g=e.split("|||");p(g, -function(e){if(e!==""||g.length===1){var q={},s=A.createElementNS(Ha,"tspan"),o;h.test(e)&&(o=e.match(h)[1].replace(/(;| |^)color([ :])/,"$1fill$2"),P(s,"style",o));i.test(e)&&!d&&(P(s,"onclick",'location.href="'+e.match(i)[1]+'"'),M(s,{cursor:"pointer"}));e=v(e.replace(/<(.|\n)*?>/g,"")||" ");if(e!==" "){s.appendChild(A.createTextNode(e));if(n)q.dx=0;else if(f&&j!==null)q.x=j;P(s,q);b.appendChild(s);!n&&f&&(!fa&&d&&M(s,{display:"block"}),P(s,"dy",w(s)));if(m){for(var q=e.replace(/([^\^])-/g,"$1- ").split(" "), -p=g.length>1||f||q.length>1&&l.whiteSpace!=="nowrap",G,S,r=[],t=w(s),u=1,B=a.rotation,y=e,x=y.length;(p||z)&&(q.length||r.length);)a.rotation=0,G=a.getBBox(!0),S=G.width,!fa&&c.forExport&&(S=c.measureSpanWidth(s.firstChild.data,a.styles)),G=S>m,k===void 0&&(k=G),z&&k?(x/=2,y===""||!G&&x<0.5?q=[]:(y=e.substring(0,y.length+(G?-1:1)*ua(x)),q=[y+(m>3?"\u2026":"")],s.removeChild(s.firstChild))):!G||q.length===1?(q=r,r=[],q.length&&(u++,s=A.createElementNS(Ha,"tspan"),P(s,{dy:t,x:j}),o&&P(s,"style",o), -b.appendChild(s)),S>m&&(m=S)):(s.removeChild(s.firstChild),r.unshift(q.pop())),q.length&&s.appendChild(A.createTextNode(q.join(" ").replace(/- /g,"-")));a.rotation=B}n++}}})}),k&&a.attr("title",a.textStr),G&&G.removeChild(b),q&&a.applyTextShadow&&a.applyTextShadow(q))},getContrast:function(a){a=ma(a).rgba;return a[0]+a[1]+a[2]>384?"#000000":"#FFFFFF"},button:function(a,b,c,d,e,f,g,h,i){var k=this.label(a,b,c,i,null,null,null,null,"button"),j=0,l,m,n,q,z,s,a={x1:0,y1:0,x2:0,y2:1},e=E({"stroke-width":1, -stroke:"#CCCCCC",fill:{linearGradient:a,stops:[[0,"#FEFEFE"],[1,"#F6F6F6"]]},r:2,padding:5,style:{color:"black"}},e);n=e.style;delete e.style;f=E(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#FFF"],[1,"#ACF"]]}},f);q=f.style;delete f.style;g=E(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#9BD"],[1,"#CDF"]]}},g);z=g.style;delete g.style;h=E(e,{style:{color:"#CCC"}},h);s=h.style;delete h.style;N(k.element,ya?"mouseover":"mouseenter",function(){j!==3&&k.attr(f).css(q)});N(k.element,ya?"mouseout": -"mouseleave",function(){j!==3&&(l=[e,f,g][j],m=[n,q,z][j],k.attr(l).css(m))});k.setState=function(a){(k.state=j=a)?a===2?k.attr(g).css(z):a===3&&k.attr(h).css(s):k.attr(e).css(n)};return k.on("click",function(a){j!==3&&d.call(k,a)}).attr(e).css(u({cursor:"default"},n))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]=B(a[1])-b%2/2);a[2]===a[5]&&(a[2]=a[5]=B(a[2])+b%2/2);return a},path:function(a){var b={fill:"none"};Ea(a)?b.d=a:Z(a)&&u(b,a);return this.createElement("path").attr(b)},circle:function(a, -b,c){a=Z(a)?a:{x:a,y:b,r:c};b=this.createElement("circle");b.xSetter=b.ySetter=function(a,b,c){c.setAttribute("c"+b,a)};return b.attr(a)},arc:function(a,b,c,d,e,f){if(Z(a))b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x;a=this.symbol("arc",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||0});a.r=c;return a},rect:function(a,b,c,d,e,f){var e=Z(a)?a.r:e,g=this.createElement("rect"),a=Z(a)?a:a===y?{}:{x:a,y:b,width:t(c,0),height:t(d,0)};if(f!==y)g.strokeWidth=f,a=g.crisp(a);if(e)a.r=e;g.rSetter=function(a, -b,c){P(c,{rx:a,ry:a})};return g.attr(a)},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length;this.width=a;this.height=b;for(this.boxWrapper[o(c,!0)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return r(a)?b.attr({"class":"highcharts-"+a}):b},image:function(a,b,c,d,e){var f={preserveAspectRatio:"none"};arguments.length>1&&u(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f);f.element.setAttributeNS?f.element.setAttributeNS("http://www.w3.org/1999/xlink", -"href",a):f.element.setAttribute("hc-svg-href",a);return f},symbol:function(a,b,c,d,e,f){var g=this,h,i=this.symbols[a],i=i&&i(B(b),B(c),d,e,f),k=/^url\((.*?)\)$/,j,l;if(i)h=this.path(i),u(h,{symbolName:a,x:b,y:c,width:d,height:e}),f&&u(h,f);else if(k.test(a))l=function(a,b){a.element&&(a.attr({width:b[0],height:b[1]}),a.alignByTranslate||a.translate(B((d-b[0])/2),B((e-b[1])/2)))},j=a.match(k)[1],a=Mb[j]||f&&f.width&&f.height&&[f.width,f.height],h=this.image(j).attr({x:b,y:c}),h.isImg=!0,a?l(h,a): -(h.attr({width:0,height:0}),ba("img",{onload:function(){this.width===0&&(M(this,{position:"absolute",top:"-999em"}),A.body.appendChild(this));l(h,Mb[j]=[this.width,this.height]);this.parentNode&&this.parentNode.removeChild(this);g.imgCount--;if(!g.imgCount&&T[g.chartIndex].onload)T[g.chartIndex].onload()},src:j}),this.imgCount++);return h},symbols:{circle:function(a,b,c,d){var e=0.166*c;return["M",a+c/2,b,"C",a+c+e,b,a+c+e,b+d,a+c/2,b+d,"C",a-e,b+d,a-e,b,a+c/2,b,"Z"]},square:function(a,b,c,d){return["M", -a,b,"L",a+c,b,a+c,b+d,a,b+d,"Z"]},triangle:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d,a,b+d,"Z"]},"triangle-down":function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c/2,b+d,"Z"]},diamond:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d/2,a+c/2,b+d,a,b+d/2,"Z"]},arc:function(a,b,c,d,e){var f=e.start,c=e.r||c||d,g=e.end-0.001,d=e.innerR,h=e.open,i=W(f),k=da(f),j=W(g),g=da(g),e=e.end-fc&&e>b+g&&eb+g&&ed&&h>a+g&&ha+g&&hj&&/[ \-]/.test(b.textContent||b.innerText))M(b,{width:j+"px",display:"block",whiteSpace:l||"normal"}),this.hasTextWidth=!0;else if(this.hasTextWidth)M(b,{width:"",display:"",whiteSpace:l||"nowrap"}),this.hasTextWidth=!1;this.getSpanCorrection(this.hasTextWidth?j:b.offsetWidth,k,h,i,g)}M(b,{left:e+(this.xCorr||0)+"px",top:f+(this.yCorr||0)+"px"});if(mb)k=b.offsetHeight; -this.cTT=m}}else this.alignOnAdd=!0},setSpanRotation:function(a,b,c){var d={},e=ya?"-ms-transform":mb?"-webkit-transform":Na?"MozTransform":Kb?"-o-transform":"";d[e]=d.transform="rotate("+a+"deg)";d[e+(Na?"Origin":"-origin")]=d.transformOrigin=b*100+"% "+c+"px";M(this.element,d)},getSpanCorrection:function(a,b,c){this.xCorr=-a*c;this.yCorr=-b}});u(Da.prototype,{html:function(a,b,c){var d=this.createElement("span"),e=d.element,f=d.renderer,g=f.isSVG,h=function(a,b){p(["opacity","visibility"],function(c){fb(a, -c+"Setter",function(a,c,d,e){a.call(this,c,d,e);b[d]=c})})};d.textSetter=function(a){a!==e.innerHTML&&delete this.bBox;e.innerHTML=this.textStr=a;d.htmlUpdateTransform()};g&&h(d,d.element.style);d.xSetter=d.ySetter=d.alignSetter=d.rotationSetter=function(a,b){b==="align"&&(b="textAlign");d[b]=a;d.htmlUpdateTransform()};d.attr({text:a,x:B(b),y:B(c)}).css({position:"absolute",fontFamily:this.style.fontFamily,fontSize:this.style.fontSize});e.style.whiteSpace="nowrap";d.css=d.htmlCss;if(g)d.add=function(a){var b, -c=f.box.parentNode,g=[];if(this.parentGroup=a){if(b=a.div,!b){for(;a;)g.push(a),a=a.parentGroup;p(g.reverse(),function(a){var d,e=P(a.element,"class");e&&(e={className:e});b=a.div=a.div||ba(Ma,e,{position:"absolute",left:(a.translateX||0)+"px",top:(a.translateY||0)+"px",opacity:a.opacity},b||c);d=b.style;u(a,{translateXSetter:function(b,c){d.left=b+"px";a[c]=b;a.doTransform=!0},translateYSetter:function(b,c){d.top=b+"px";a[c]=b;a.doTransform=!0}});h(a,d)})}}else b=c;b.appendChild(e);d.added=!0;d.alignOnAdd&& -d.htmlUpdateTransform();return d};return d}});var K;if(!fa&&!ka){K={init:function(a,b){var c=["<",b,' filled="f" stroked="f"'],d=["position: ","absolute",";"],e=b===Ma;(b==="shape"||e)&&d.push("left:0;top:0;width:1px;height:1px;");d.push("visibility: ",e?"hidden":"visible");c.push(' style="',d.join(""),'"/>');if(b)c=e||b==="span"||b==="img"?c.join(""):a.prepVML(c),this.element=ba(c);this.renderer=a},add:function(a){var b=this.renderer,c=this.element,d=b.box,e=a&&a.inverted,d=a?a.element||a:d;if(a)this.parentGroup= -a;e&&b.invertChild(c,d);d.appendChild(c);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform();if(this.onAdd)this.onAdd();return this},updateTransform:O.prototype.htmlUpdateTransform,setSpanRotation:function(){var a=this.rotation,b=W(a*ja),c=da(a*ja);M(this.element,{filter:a?["progid:DXImageTransform.Microsoft.Matrix(M11=",b,", M12=",-c,", M21=",c,", M22=",b,", sizingMethod='auto expand')"].join(""):"none"})},getSpanCorrection:function(a,b,c,d,e){var f=d?W(d*ja):1,g=d? -da(d*ja):0,h=o(this.elemHeight,this.element.offsetHeight),i;this.xCorr=f<0&&-a;this.yCorr=g<0&&-h;i=f*g<0;this.xCorr+=g*b*(i?1-c:c);this.yCorr-=f*b*(d?i?c:1-c:1);e&&e!=="left"&&(this.xCorr-=a*c*(f<0?-1:1),d&&(this.yCorr-=h*c*(g<0?-1:1)),M(this.element,{textAlign:e}))},pathToVML:function(a){for(var b=a.length,c=[];b--;)if(J(a[b]))c[b]=B(a[b]*10)-5;else if(a[b]==="Z")c[b]="x";else if(c[b]=a[b],a.isArc&&(a[b]==="wa"||a[b]==="at"))c[b+5]===c[b+7]&&(c[b+7]+=a[b+7]>a[b+5]?1:-1),c[b+6]===c[b+8]&&(c[b+8]+= -a[b+8]>a[b+6]?1:-1);return c.join(" ")||"x"},clip:function(a){var b=this,c;a?(c=a.members,pa(c,b),c.push(b),b.destroyClip=function(){pa(c,b)},a=a.getCSS(b)):(b.destroyClip&&b.destroyClip(),a={clip:lb?"inherit":"rect(auto)"});return b.css(a)},css:O.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&&Sa(a)},destroy:function(){this.destroyClip&&this.destroyClip();return O.prototype.destroy.apply(this)},on:function(a,b){this.element["on"+a]=function(){var a=D.event;a.target=a.srcElement;b(a)}; -return this},cutOffPath:function(a,b){var c,a=a.split(/[ ,]/);c=a.length;if(c===9||c===11)a[c-4]=a[c-2]=C(a[c-2])-10*b;return a.join(" ")},shadow:function(a,b,c){var d=[],e,f=this.element,g=this.renderer,h,i=f.style,k,j=f.path,l,m,n,q;j&&typeof j.value!=="string"&&(j="x");m=j;if(a){n=o(a.width,3);q=(a.opacity||0.15)/n;for(e=1;e<=3;e++){l=n*2+1-2*e;c&&(m=this.cutOffPath(j.value,l+0.5));k=[''];h=ba(g.prepVML(k),null,{left:C(i.left)+o(a.offsetX,1),top:C(i.top)+o(a.offsetY,1)});if(c)h.cutOff=l+1;k=[''];ba(g.prepVML(k),null,null,h);b?b.element.appendChild(h):f.parentNode.insertBefore(h,f);d.push(h)}this.shadows=d}return this},updateShadows:Aa,setAttr:function(a,b){lb?this.element[a]=b:this.element.setAttribute(a,b)},classSetter:function(a){this.element.className=a},dashstyleSetter:function(a,b,c){(c.getElementsByTagName("stroke")[0]|| -ba(this.renderer.prepVML([""]),null,null,c))[b]=a||"solid";this[b]=a},dSetter:function(a,b,c){var d=this.shadows,a=a||[];this.d=a.join&&a.join(" ");c.path=a=this.pathToVML(a);if(d)for(c=d.length;c--;)d[c].path=d[c].cutOff?this.cutOffPath(a,d[c].cutOff):a;this.setAttr(b,a)},fillSetter:function(a,b,c){var d=c.nodeName;if(d==="SPAN")c.style.color=a;else if(d!=="IMG")c.filled=a!=="none",this.setAttr("fillcolor",this.renderer.color(a,c,b,this))},"fill-opacitySetter":function(a,b,c){ba(this.renderer.prepVML(["<", -b.split("-")[0],' opacity="',a,'"/>']),null,null,c)},opacitySetter:Aa,rotationSetter:function(a,b,c){c=c.style;this[b]=c[b]=a;c.left=-B(da(a*ja)+1)+"px";c.top=B(W(a*ja))+"px"},strokeSetter:function(a,b,c){this.setAttr("strokecolor",this.renderer.color(a,c,b,this))},"stroke-widthSetter":function(a,b,c){c.stroked=!!a;this[b]=a;J(a)&&(a+="px");this.setAttr("strokeweight",a)},titleSetter:function(a,b){this.setAttr(b,a)},visibilitySetter:function(a,b,c){a==="inherit"&&(a="visible");this.shadows&&p(this.shadows, -function(c){c.style[b]=a});c.nodeName==="DIV"&&(a=a==="hidden"?"-999em":0,lb||(c.style[b]=a?"visible":"hidden"),b="top");c.style[b]=a},xSetter:function(a,b,c){this[b]=a;b==="x"?b="left":b==="y"&&(b="top");this.updateClipping?(this[b]=a,this.updateClipping()):c.style[b]=a},zIndexSetter:function(a,b,c){c.style[b]=a}};K["stroke-opacitySetter"]=K["fill-opacitySetter"];x.VMLElement=K=qa(O,K);K.prototype.ySetter=K.prototype.widthSetter=K.prototype.heightSetter=K.prototype.xSetter;var Bb={Element:K,isIE8:za.indexOf("MSIE 8.0")> --1,init:function(a,b,c,d){var e;this.alignedObjects=[];d=this.createElement(Ma).css(u(this.getStyle(d),{position:"relative"}));e=d.element;a.appendChild(d.element);this.isVML=!0;this.box=e;this.boxWrapper=d;this.gradients={};this.cache={};this.cacheKeys=[];this.imgCount=0;this.setSize(b,c,!1);if(!A.namespaces.hcv){A.namespaces.add("hcv","urn:schemas-microsoft-com:vml");try{A.createStyleSheet().cssText="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}catch(f){A.styleSheets[0].cssText+= -"hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}}},isHidden:function(){return!this.box.offsetWidth},clipRect:function(a,b,c,d){var e=this.createElement(),f=Z(a);return u(e,{members:[],count:0,left:(f?a.x:a)+1,top:(f?a.y:b)+1,width:(f?a.width:c)-1,height:(f?a.height:d)-1,getCSS:function(a){var b=a.element,c=b.nodeName,a=a.inverted,d=this.top-(c==="shape"?b.offsetTop:0),e=this.left,b=e+this.width,f=d+this.height,d={clip:"rect("+B(a?e:d)+"px,"+ -B(a?f:b)+"px,"+B(a?b:f)+"px,"+B(a?d:e)+"px)"};!a&&lb&&c==="DIV"&&u(d,{width:b+"px",height:f+"px"});return d},updateClipping:function(){p(e.members,function(a){a.element&&a.css(e.getCSS(a))})}})},color:function(a,b,c,d){var e=this,f,g=/^rgba/,h,i,k="none";a&&a.linearGradient?i="gradient":a&&a.radialGradient&&(i="pattern");if(i){var j,l,m=a.linearGradient||a.radialGradient,n,q,z,s,o,w="",a=a.stops,v,S=[],r=function(){h=[''];ba(e.prepVML(h),null,null,b)};n=a[0];v=a[a.length-1];n[0]>0&&a.unshift([0,n[1]]);v[0]<1&&a.push([1,v[1]]);p(a,function(a,b){g.test(a[1])?(f=ma(a[1]),j=f.get("rgb"),l=f.get("a")):(j=a[1],l=1);S.push(a[0]*100+"% "+j);b?(z=l,s=j):(q=l,o=j)});if(c==="fill")if(i==="gradient")c=m.x1||m[0]||0,a=m.y1||m[1]||0,n=m.x2||m[2]||0,m=m.y2||m[3]||0,w='angle="'+(90-Y.atan((m-a)/(n-c))*180/ra)+'"',r();else{var k=m.r,$=k*2,t=k*2,u=m.cx,y=m.cy,B=b.radialReference,x,k=function(){B&&(x= -d.getBBox(),u+=(B[0]-x.x)/x.width-0.5,y+=(B[1]-x.y)/x.height-0.5,$*=B[2]/x.width,t*=B[2]/x.height);w='src="'+U.global.VMLRadialGradientURL+'" size="'+$+","+t+'" origin="0.5,0.5" position="'+u+","+y+'" color2="'+o+'" ';r()};d.added?k():d.onAdd=k;k=s}else k=j}else if(g.test(a)&&b.tagName!=="IMG")f=ma(a),d[c+"-opacitySetter"](f.get("a"),c,b),k=f.get("rgb");else{k=b.getElementsByTagName(c);if(k.length)k[0].opacity=1,k[0].type="solid";k=a}return k},prepVML:function(a){var b=this.isIE8,a=a.join("");b?(a= -a.replace("/>",' xmlns="urn:schemas-microsoft-com:vml" />'),a=a.indexOf('style="')===-1?a.replace("/>",' style="display:inline-block;behavior:url(#default#VML);" />'):a.replace('style="','style="display:inline-block;behavior:url(#default#VML);')):a=a.replace("<","1&&f.attr({x:b,y:c,width:d,height:e});return f},createElement:function(a){return a==="rect"?this.symbol(a):Da.prototype.createElement.call(this,a)},invertChild:function(a,b){var c=this,d=b.style,e=a.tagName==="IMG"&&a.style;M(a,{flip:"x",left:C(d.width)-(e?C(e.top): -1),top:C(d.height)-(e?C(e.left):1),rotation:-90});p(a.childNodes,function(b){c.invertChild(b,a)})},symbols:{arc:function(a,b,c,d,e){var f=e.start,g=e.end,h=e.r||c||d,c=e.innerR,d=W(f),i=da(f),k=W(g),j=da(g);if(g-f===0)return["x"];f=["wa",a-h,b-h,a+h,b+h,a+h*d,b+h*i,a+h*k,b+h*j];e.open&&!c&&f.push("e","M",a,b);f.push("at",a-c,b-c,a+c,b+c,a+c*k,b+c*j,a+c*d,b+c*i,"x","e");f.isArc=!0;return f},circle:function(a,b,c,d,e){e&&(c=d=2*e.r);e&&e.isCircle&&(a-=c/2,b-=d/2);return["wa",a,b,a+c,b+d,a+c,b+d/2,a+ -c,b+d/2,"e"]},rect:function(a,b,c,d,e){return Da.prototype.symbols[!r(e)||!e.r?"square":"callout"].call(0,a,b,c,d,e)}}};x.VMLRenderer=K=function(){this.init.apply(this,arguments)};K.prototype=E(Da.prototype,Bb);cb=K}Da.prototype.measureSpanWidth=function(a,b){var c=A.createElement("span"),d;d=A.createTextNode(a);c.appendChild(d);M(c,b);this.box.appendChild(c);d=c.offsetWidth;Sa(c);return d};var Nb;if(ka)x.CanVGRenderer=K=function(){Ha="http://www.w3.org/1999/xhtml"},K.prototype.symbols={},Nb=function(){function a(){var a= -b.length,d;for(d=0;d0&&c+i*k>e&&(n=B((d-c)/W(h*ja)));else if(d=c+(1-i)*k,c-i*ke&&(l=e-a.x+l*i,m=-1),l=F(j,l),ll||b.autoRotation&&g.styles.width)n=l;if(n){q.width=n;if(!b.options.labels.style.textOverflow)q.textOverflow= -"ellipsis";g.css(q)}},getPosition:function(a,b,c,d){var e=this.axis,f=e.chart,g=d&&f.oldChartHeight||f.chartHeight;return{x:a?e.translate(b+c,null,null,d)+e.transB:e.left+e.offset+(e.opposite?(d&&f.oldChartWidth||f.chartWidth)-e.right-e.left:0),y:a?g-e.bottom+e.offset-(e.opposite?e.height:0):g-e.translate(b+c,null,null,d)-e.transB}},getLabelPosition:function(a,b,c,d,e,f,g,h){var i=this.axis,k=i.transA,j=i.reversed,l=i.staggerLines,m=i.tickRotCorr||{x:0,y:0},n=e.y;r(n)||(n=i.side===0?c.rotation?-8: --c.getBBox().height:i.side===2?m.y+8:W(c.rotation*ja)*(m.y-c.getBBox(!1,0).height/2));a=a+e.x+m.x-(f&&d?f*k*(j?-1:1):0);b=b+n-(f&&!d?f*k*(j?1:-1):0);l&&(c=g/(h||1)%l,i.opposite&&(c=l-c-1),b+=c*(i.labelOffset/l));return{x:a,y:B(b)}},getMarkPath:function(a,b,c,d,e,f){return f.crispLine(["M",a,b,"L",a+(e?0:-c),b+(e?c:0)],d)},render:function(a,b,c){var d=this.axis,e=d.options,f=d.chart.renderer,g=d.horiz,h=this.type,i=this.label,k=this.pos,j=e.labels,l=this.gridLine,m=h?h+"Grid":"grid",n=h?h+"Tick":"tick", -q=e[m+"LineWidth"],z=e[m+"LineColor"],s=e[m+"LineDashStyle"],m=d.tickSize(n),n=e[n+"Color"],p=this.mark,w=j.step,v=!0,S=d.tickmarkOffset,r=this.getPosition(g,k,S,b),$=r.x,r=r.y,t=g&&$===d.pos+d.len||!g&&r===d.pos?-1:1,c=o(c,1);this.isActive=!0;if(q){k=d.getPlotLinePath(k+S,q*t,b,!0);if(l===y){l={stroke:z,"stroke-width":q};if(s)l.dashstyle=s;if(!h)l.zIndex=1;if(b)l.opacity=0;this.gridLine=l=q?f.path(k).attr(l).add(d.gridGroup):null}if(!b&&l&&k)l[this.isNew?"attr":"animate"]({d:k,opacity:c})}if(m)d.opposite&& -(m[0]=-m[0]),h=this.getMarkPath($,r,m[0],m[1]*t,g,f),p?p.animate({d:h,opacity:c}):this.mark=f.path(h).attr({stroke:n,"stroke-width":m[1],opacity:c}).add(d.axisGroup);if(i&&J($))i.xy=r=this.getLabelPosition($,r,i,g,j,S,a,w),this.isFirst&&!this.isLast&&!o(e.showFirstLabel,1)||this.isLast&&!this.isFirst&&!o(e.showLastLabel,1)?v=!1:g&&!d.isRadial&&!j.step&&!j.rotation&&!b&&c!==0&&this.handleOverflow(r),w&&a%w&&(v=!1),v&&J(r.y)?(r.opacity=c,i[this.isNew?"attr":"animate"](r),this.isNew=!1):i.attr("y",-9999)}, -destroy:function(){Ra(this,this.axis)}};x.PlotLineOrBand=function(a,b){this.axis=a;if(b)this.options=b,this.id=b.id};x.PlotLineOrBand.prototype={render:function(){var a=this,b=a.axis,c=b.horiz,d=a.options,e=d.label,f=a.label,g=d.width,h=d.to,i=d.from,k=r(i)&&r(h),j=d.value,l=d.dashStyle,m=a.svgElem,n=[],q,z=d.color,s=o(d.zIndex,0),p=d.events,w={},v=b.chart.renderer,n=b.log2lin;b.isLog&&(i=n(i),h=n(h),j=n(j));if(g){if(n=b.getPlotLinePath(j,g),w={stroke:z,"stroke-width":g},l)w.dashstyle=l}else if(k){n= -b.getPlotBandPath(i,h,d);if(z)w.fill=z;if(d.borderWidth)w.stroke=d.borderColor,w["stroke-width"]=d.borderWidth}else return;w.zIndex=s;if(m)if(n)m.show(),m.animate({d:n});else{if(m.hide(),f)a.label=f=f.destroy()}else if(n&&n.length&&(a.svgElem=m=v.path(n).attr(w).add(),p))for(q in d=function(b){m.on(b,function(c){p[b].apply(a,[c])})},p)d(q);e&&r(e.text)&&n&&n.length&&b.width>0&&b.height>0&&!n.flat?(e=E({align:c&&k&&"center",x:c?!k&&4:10,verticalAlign:!c&&k&&"middle",y:c?k?16:10:k?6:-4,rotation:c&& -!k&&90},e),this.renderLabel(e,n,k,s)):f&&f.hide();return a},renderLabel:function(a,b,c,d){var e=this.label,f=this.axis.chart.renderer;if(!e)e={align:a.textAlign||a.align,rotation:a.rotation},e.zIndex=d,this.label=e=f.text(a.text,0,0,a.useHTML).attr(e).css(a.style).add();d=[b[1],b[4],c?b[6]:b[1]];b=[b[2],b[5],c?b[7]:b[2]];c=La(d);f=La(b);e.align(a,!1,{x:c,y:f,width:Ga(d)-c,height:Ga(b)-f});e.show()},destroy:function(){pa(this.axis.plotLinesAndBands,this);delete this.axis;Ra(this)}};var ha=x.Axis=function(){this.init.apply(this, -arguments)};ha.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:"%H:%M:%S.%L",second:"%H:%M:%S",minute:"%H:%M",hour:"%H:%M",day:"%e. %b",week:"%e. %b",month:"%b '%y",year:"%Y"},endOnTick:!1,gridLineColor:"#D8D8D8",labels:{enabled:!0,style:{color:"#606060",cursor:"default",fontSize:"11px"},x:0},lineColor:"#C0D0E0",lineWidth:1,minPadding:0.01,maxPadding:0.01,minorGridLineColor:"#E0E0E0",minorGridLineWidth:1,minorTickColor:"#A0A0A0",minorTickLength:2,minorTickPosition:"outside",startOfWeek:1, -startOnTick:!1,tickColor:"#C0D0E0",tickLength:10,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",title:{align:"middle",style:{color:"#707070"}},type:"linear"},defaultYAxisOptions:{endOnTick:!0,gridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0,title:{rotation:270,text:"Values"},stackLabels:{enabled:!1,formatter:function(){return x.numberFormat(this.total,-1)},style:E(ea.line.dataLabels.style,{color:"#000000"})}}, -defaultLeftAxisOptions:{labels:{x:-15},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:15},title:{rotation:90}},defaultBottomAxisOptions:{labels:{autoRotation:[-45],x:0},title:{rotation:0}},defaultTopAxisOptions:{labels:{autoRotation:[-45],x:0},title:{rotation:0}},init:function(a,b){var c=b.isX;this.chart=a;this.horiz=a.inverted?!c:c;this.coll=(this.isXAxis=c)?"xAxis":"yAxis";this.opposite=b.opposite;this.side=b.side||(this.horiz?this.opposite?0:2:this.opposite?1:3);this.setOptions(b);var d= -this.options,e=d.type;this.labelFormatter=d.labels.formatter||this.defaultLabelFormatter;this.userOptions=b;this.minPixelPadding=0;this.reversed=d.reversed;this.visible=d.visible!==!1;this.zoomEnabled=d.zoomEnabled!==!1;this.categories=d.categories||e==="category";this.names=this.names||[];this.isLog=e==="logarithmic";this.isDatetimeAxis=e==="datetime";this.isLinked=r(d.linkedTo);this.ticks={};this.labelEdge=[];this.minorTicks={};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange= -this.userMinRange=d.minRange||d.maxZoom;this.range=d.range;this.offset=d.offset||0;this.stacks={};this.oldStacks={};this.stacksTouched=0;this.min=this.max=null;this.crosshair=o(d.crosshair,ta(a.options.tooltip.crosshairs)[c?0:1],!1);var f,d=this.options.events;sa(this,a.axes)===-1&&(c&&!this.isColorAxis?a.axes.splice(a.xAxis.length,0,this):a.axes.push(this),a[this.coll].push(this));this.series=this.series||[];if(a.inverted&&c&&this.reversed===y)this.reversed=!0;this.removePlotLine=this.removePlotBand= -this.removePlotBandOrLine;for(f in d)N(this,f,d[f]);if(this.isLog)this.val2lin=this.log2lin,this.lin2val=this.lin2log},setOptions:function(a){this.options=E(this.defaultOptions,this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],E(U[this.coll],a))},defaultLabelFormatter:function(){var a=this.axis,b=this.value,c=a.categories,d=this.dateTimeLabelFormat,e=U.lang.numericSymbols,f=e&&e.length, -g,h=a.options.labels.format,a=a.isLog?b:a.tickInterval;if(h)g=Ka(h,this);else if(c)g=b;else if(d)g=Qa(d,b);else if(f&&a>=1E3)for(;f--&&g===y;)c=Math.pow(1E3,f+1),a>=c&&b*10%c===0&&e[f]!==null&&(g=x.numberFormat(b/c,-1)+e[f]);g===y&&(g=Q(b)>=1E4?x.numberFormat(b,-1):x.numberFormat(b,-1,y,""));return g},getSeriesExtremes:function(){var a=this,b=a.chart;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=a.threshold=null;a.softThreshold=!a.isXAxis;a.buildStacks&&a.buildStacks();p(a.series,function(c){if(c.visible|| -!b.options.chart.ignoreHiddenSeries){var d=c.options,e=d.threshold,f;a.hasVisibleSeries=!0;a.isLog&&e<=0&&(e=null);if(a.isXAxis){if(d=c.xData,d.length)c=La(d),!J(c)&&!(c instanceof la)&&(d=Ba(d,function(a){return J(a)}),c=La(d)),a.dataMin=F(o(a.dataMin,d[0]),c),a.dataMax=t(o(a.dataMax,d[0]),Ga(d))}else{c.getExtremes();f=c.dataMax;c=c.dataMin;if(r(c)&&r(f))a.dataMin=F(o(a.dataMin,c),c),a.dataMax=t(o(a.dataMax,f),f);if(r(e))a.threshold=e;if(!d.softThreshold||a.isLog)a.softThreshold=!1}}})},translate:function(a, -b,c,d,e,f){var g=this.linkedParent||this,h=1,i=0,k=d?g.oldTransA:g.transA,d=d?g.oldMin:g.min,j=g.minPixelPadding,e=(g.isOrdinal||g.isBroken||g.isLog&&e)&&g.lin2val;if(!k)k=g.transA;if(c)h*=-1,i=g.len;g.reversed&&(h*=-1,i-=h*(g.sector||g.len));b?(a=a*h+i,a-=j,a=a/k+d,e&&(a=g.lin2val(a))):(e&&(a=g.val2lin(a)),f==="between"&&(f=0.5),a=h*(a-d)*k+i+h*j+(J(f)?k*f*g.pointRange:0));return a},toPixels:function(a,b){return this.translate(a,!1,!this.horiz,null,!0)+(b?0:this.pos)},toValue:function(a,b){return this.translate(a- -(b?0:this.pos),!0,!this.horiz,null,!0)},getPlotLinePath:function(a,b,c,d,e){var f=this.chart,g=this.left,h=this.top,i,k,j=c&&f.oldChartHeight||f.chartHeight,l=c&&f.oldChartWidth||f.chartWidth,m;i=this.transB;var n=function(a,b,c){if(ac)d?a=F(t(b,a),c):m=!0;return a},e=o(e,this.translate(a,null,null,c)),a=c=B(e+i);i=k=B(j-e-i);J(e)?this.horiz?(i=h,k=j-this.bottom,a=c=n(a,g,g+this.width)):(a=g,c=l-this.right,i=k=n(i,h,h+this.height)):m=!0;return m&&!d?null:f.renderer.crispLine(["M",a,i,"L",c, -k],b||1)},getLinearTickPositions:function(a,b,c){var d,e=ca(V(b/a)*a),f=ca(ua(c/a)*a),g=[];if(b===c&&J(b))return[b];for(b=e;b<=f;){g.push(b);b=ca(b+a);if(b===d)break;d=b}return g},getMinorTickPositions:function(){var a=this.options,b=this.tickPositions,c=this.minorTickInterval,d=[],e,f=this.pointRangePadding||0;e=this.min-f;var f=this.max+f,g=f-e;if(g&&g/c=this.minRange,f,g,h,i,k,j;if(this.isXAxis&&this.minRange===y&&!this.isLog)r(a.min)||r(a.max)?this.minRange=null:(p(this.series,function(a){i=a.xData;for(g=k=a.xIncrement?1:i.length-1;g>0;g--)if(h=i[g]- -i[g-1],f===y||h=q?(s=q,j=0):b.dataMax<=q&&(G=q,k=0)),b.min=o(w,s,b.dataMin),b.max=o(v,G,b.dataMax));if(e)!a&&F(b.min,o(b.dataMin,b.min))<=0&&aa(10,1),b.min=ca(f(b.min),15),b.max=ca(f(b.max),15);if(b.range&&r(b.max))b.userMin=b.min=w=t(b.min,b.minFromRange()),b.userMax=v=b.max,b.range=null;I(b,"foundExtremes");b.beforePadding&&b.beforePadding();b.adjustForMinRange();if(!n&&!b.axisPointRange&&!b.usePercentage&& -!i&&r(b.min)&&r(b.max)&&(f=b.max-b.min))!r(w)&&j&&(b.min-=f*j),!r(v)&&k&&(b.max+=f*k);if(J(d.floor))b.min=t(b.min,d.floor);if(J(d.ceiling))b.max=F(b.max,d.ceiling);if(z&&r(b.dataMin))if(q=q||0,!r(w)&&b.min=q)b.min=q;else if(!r(v)&&b.max>q&&b.dataMax<=q)b.max=q;b.tickInterval=b.min===b.max||b.min===void 0||b.max===void 0?1:i&&!l&&m===b.linkedParent.options.tickPixelInterval?l=b.linkedParent.tickInterval:o(l,this.tickAmount?(b.max-b.min)/t(this.tickAmount-1,1):void 0,n?1:(b.max-b.min)* -m/t(b.len,m));h&&!a&&p(b.series,function(a){a.processData(b.min!==b.oldMin||b.max!==b.oldMax)});b.setAxisTranslation(!0);b.beforeSetTickPositions&&b.beforeSetTickPositions();if(b.postProcessTickInterval)b.tickInterval=b.postProcessTickInterval(b.tickInterval);if(b.pointRange&&!l)b.tickInterval=t(b.pointRange,b.tickInterval);a=o(d.minTickInterval,b.isDatetimeAxis&&b.closestPointRange);if(!l&&b.tickInterval0.5&&b.tickInterval<5&&b.max>1E3&&b.max<9999)),!!this.tickAmount);if(!this.tickAmount&&this.len)b.tickInterval=b.unsquish();this.setTickPositions()},setTickPositions:function(){var a=this.options,b,c=a.tickPositions,d=a.tickPositioner,e=a.startOnTick,f=a.endOnTick,g;this.tickmarkOffset=this.categories&&a.tickmarkPlacement==="between"&&this.tickInterval===1?0.5:0;this.minorTickInterval=a.minorTickInterval==="auto"&&this.tickInterval?this.tickInterval/5:a.minorTickInterval;this.tickPositions= -b=c&&c.slice();if(!b&&(b=this.isDatetimeAxis?this.getTimeTicks(this.normalizeTimeTickInterval(this.tickInterval,a.units),this.min,this.max,a.startOfWeek,this.ordinalPositions,this.closestPointRange,!0):this.isLog?this.getLogTickPositions(this.tickInterval,this.min,this.max):this.getLinearTickPositions(this.tickInterval,this.min,this.max),b.length>this.len&&(b=[b[0],b.pop()]),this.tickPositions=b,d&&(d=d.apply(this,[this.min,this.max]))))this.tickPositions=b=d;if(!this.isLinked)this.trimTicks(b,e, -f),this.min===this.max&&r(this.min)&&!this.tickAmount&&(g=!0,this.min-=0.5,this.max+=0.5),this.single=g,!c&&!d&&this.adjustTickAmount()},trimTicks:function(a,b,c){var d=a[0],e=a[a.length-1],f=this.minPointOffset||0;if(b)this.min=d;else for(;this.min-f>a[0];)a.shift();if(c)this.max=e;else for(;this.max+fc&&(this.tickInterval*=2,this.setTickPositions());if(r(d)){for(a=c=b.length;a--;)(d===3&&a%2===1||d<=2&&a>0&&a=e&&(b=e));this.displayBtn=a!==y||b!==y;this.setExtremes(a,b,!1,y,{trigger:"zoom"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,d=this.horiz,e=o(b.width,a.plotWidth-c+(b.offsetRight||0)),f=o(b.height, -a.plotHeight),g=o(b.top,a.plotTop),b=o(b.left,a.plotLeft+c),c=/%$/;c.test(f)&&(f=Math.round(parseFloat(f)/100*a.plotHeight));c.test(g)&&(g=Math.round(parseFloat(g)/100*a.plotHeight+a.plotTop));this.left=b;this.top=g;this.width=e;this.height=f;this.bottom=a.chartHeight-f-g;this.right=a.chartWidth-e-b;this.len=t(d?e:f,0);this.pos=d?b:g},getExtremes:function(){var a=this.isLog,b=this.lin2log;return{min:a?ca(b(this.min)):this.min,max:a?ca(b(this.max)):this.max,dataMin:this.dataMin,dataMax:this.dataMax, -userMin:this.userMin,userMax:this.userMax}},getThreshold:function(a){var b=this.isLog,c=this.lin2log,d=b?c(this.min):this.min,b=b?c(this.max):this.max;a===null?a=b<0?b:d:d>a?a=d:b15&&a<165?"right":a>195&&a<345?"left":"center"},tickSize:function(a){var b=this.options,c=b[a+"Length"],d=o(b[a+"Width"],a==="tick"&&this.isXAxis?1:0);if(d&&c)return b[a+"Position"]==="inside"&&(c=-c),[c,d]},labelMetrics:function(){return this.chart.renderer.fontMetrics(this.options.labels.style.fontSize, -this.ticks[0]&&this.ticks[0].label)},unsquish:function(){var a=this.options.labels,b=this.horiz,c=this.tickInterval,d=c,e=this.len/(((this.categories?1:0)+this.max-this.min)/c),f,g=a.rotation,h=this.labelMetrics(),i,k=Number.MAX_VALUE,j,l=function(a){a/=e||1;a=a>1?ua(a):1;return a*c};b?(j=!a.staggerLines&&!a.step&&(r(g)?[g]:e=-90&&a<=90)i=l(Q(h.h/da(ja*a))),b=i+Q(a/360),bm)m=a.labelLength}),m>h&&m>k.h?i.rotation=this.labelRotation:this.labelRotation=0;else if(g&&(l={width:h+"px"},!j)){l.textOverflow="clip";for(n=c.length;!f&&n--;)if(q=c[n],h=d[q].label)if(h.styles.textOverflow==="ellipsis"?h.css({textOverflow:"clip"}):d[q].labelLength>g&&h.css({width:g+"px"}),h.getBBox().height>this.len/c.length-(k.h-k.f))h.specCss={textOverflow:"ellipsis"}}if(i.rotation&& -(l={width:(m>a.chartHeight*0.5?a.chartHeight*0.33:a.chartHeight)+"px"},!j))l.textOverflow="ellipsis";if(this.labelAlign=e.align||this.autoLabelAlign(this.labelRotation))i.align=this.labelAlign;p(c,function(a){var b=(a=d[a])&&a.label;if(b)b.attr(i),l&&b.css(E(l,b.specCss)),delete b.specCss,a.rotation=i.rotation});this.tickRotCorr=b.rotCorr(k.b,this.labelRotation||0,this.side!==0)},hasData:function(){return this.hasVisibleSeries||r(this.min)&&r(this.max)&&!!this.tickPositions},getOffset:function(){var a= -this,b=a.chart,c=b.renderer,d=a.options,e=a.tickPositions,f=a.ticks,g=a.horiz,h=a.side,i=b.inverted?[1,0,3,2][h]:h,k,j,l=0,m,n=0,q=d.title,z=d.labels,s=0,G=a.opposite,w=b.axisOffset,b=b.clipOffset,v=[-1,1,1,-1][h],S,u=a.axisParent,$=this.tickSize("tick");k=a.hasData();a.showAxis=j=k||o(d.showEmpty,!0);a.staggerLines=a.horiz&&z.staggerLines;if(!a.axisGroup)a.gridGroup=c.g("grid").attr({zIndex:d.gridZIndex||1}).add(u),a.axisGroup=c.g("axis").attr({zIndex:d.zIndex||2}).add(u),a.labelGroup=c.g("axis-labels").attr({zIndex:z.zIndex|| -7}).addClass("highcharts-"+a.coll.toLowerCase()+"-labels").add(u);if(k||a.isLinked){if(p(e,function(b){f[b]?f[b].addLabel():f[b]=new Va(a,b)}),a.renderUnsquish(),z.reserveSpace!==!1&&(h===0||h===2||{1:"left",3:"right"}[h]===a.labelAlign||a.labelAlign==="center")&&p(e,function(a){s=t(f[a].getLabelSize(),s)}),a.staggerLines)s*=a.staggerLines,a.labelOffset=s*(a.opposite?-1:1)}else for(S in f)f[S].destroy(),delete f[S];if(q&&q.text&&q.enabled!==!1){if(!a.axisTitle)(S=q.textAlign)||(S=(g?{low:"left",middle:"center", -high:"right"}:{low:G?"right":"left",middle:"center",high:G?"left":"right"})[q.align]),a.axisTitle=c.text(q.text,0,0,q.useHTML).attr({zIndex:7,rotation:q.rotation||0,align:S}).addClass("highcharts-"+this.coll.toLowerCase()+"-title").css(q.style).add(a.axisGroup),a.axisTitle.isNew=!0;if(j)l=a.axisTitle.getBBox()[g?"height":"width"],m=q.offset,n=r(m)?0:o(q.margin,g?5:10);a.axisTitle[j?"show":"hide"](!0)}a.offset=v*o(d.offset,w[h]);a.tickRotCorr=a.tickRotCorr||{x:0,y:0};c=h===0?-a.labelMetrics().h:h=== -2?a.tickRotCorr.y:0;n=Math.abs(s)+n;s&&(n-=c,n+=v*(g?o(z.y,a.tickRotCorr.y+v*8):z.x));a.axisTitleMargin=o(m,n);w[h]=t(w[h],a.axisTitleMargin+l+v*a.offset,n,k&&e.length&&$?$[0]:0);d=d.offset?0:V(d.lineWidth/2)*2;b[i]=t(b[i],d)},getLinePath:function(a){var b=this.chart,c=this.opposite,d=this.offset,e=this.horiz,f=this.left+(c?this.width:0)+d,d=b.chartHeight-this.bottom-(c?this.height:0)+d;c&&(a*=-1);return b.renderer.crispLine(["M",e?this.left:f,e?d:this.top,"L",e?b.chartWidth-this.right:f,e?d:b.chartHeight- -this.bottom],a)},getTitlePosition:function(){var a=this.horiz,b=this.left,c=this.top,d=this.len,e=this.options.title,f=a?b:c,g=this.opposite,h=this.offset,i=e.x||0,k=e.y||0,j=C(e.style.fontSize||12),d={low:f+(a?0:d),middle:f+d/2,high:f+(a?d:0)}[e.align],b=(a?c+this.height:b)+(a?1:-1)*(g?-1:1)*this.axisTitleMargin+(this.side===2?j:0);return{x:a?d+i:b+(g?this.width:0)+h+i,y:a?b+k-(g?this.height:0)+h:d+k}},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.options,e=a.isLog,f=a.lin2log,g=a.isLinked, -h=a.tickPositions,i=a.axisTitle,k=a.ticks,j=a.minorTicks,l=a.alternateBands,m=d.stackLabels,n=d.alternateGridColor,q=a.tickmarkOffset,z=d.lineWidth,s,o=b.hasRendered&&J(a.oldMin),w=a.showAxis,v=$a(c.globalAnimation),r,t;a.labelEdge.length=0;a.overlap=!1;p([k,j,l],function(a){for(var b in a)a[b].isActive=!1});if(a.hasData()||g){a.minorTickInterval&&!a.categories&&p(a.getMinorTickPositions(),function(b){j[b]||(j[b]=new Va(a,b,"minor"));o&&j[b].isNew&&j[b].render(null,!0);j[b].render(null,!1,1)});if(h.length&& -(p(h,function(b,c){if(!g||b>=a.min&&b<=a.max)k[b]||(k[b]=new Va(a,b)),o&&k[b].isNew&&k[b].render(c,!0,0.1),k[b].render(c)}),q&&(a.min===0||a.single)))k[-1]||(k[-1]=new Va(a,-1,null,!0)),k[-1].render(-1);n&&p(h,function(c,d){t=h[d+1]!==y?h[d+1]+q:a.max-q;if(d%2===0&&c=H.second?0:j*V(i.getMilliseconds()/j));if(k>=H.second)i[Gb](k>=H.minute?0:j*V(i.getSeconds()/j));if(k>=H.minute)i[Hb](k>=H.hour?0:j*V(i[tb]()/j));if(k>=H.hour)i[Ib](k>=H.day?0:j*V(i[ub]()/j)); -if(k>=H.day)i[kb](k>=H.month?1:j*V(i[Ua]()/j));k>=H.month&&(i[wb](k>=H.year?0:j*V(i[ab]()/j)),h=i[bb]());k>=H.year&&(h-=h%j,i[xb](h));if(k===H.week)i[kb](i[Ua]()-i[vb]()+o(d,1));b=1;if(qb||Za)i=i.getTime(),i=new la(i+Ya(i));h=i[bb]();for(var d=i.getTime(),l=i[ab](),m=i[Ua](),n=!g||!!Za,q=(H.day+(g?Ya(i):i.getTimezoneOffset()*6E4))%H.day;d=0.5)a=B(a),i=this.getLinearTickPositions(a,b,c);else if(a>=0.08)for(var f=V(b),k,j,l,m,n,e=a>0.3?[1,2,4]:a>0.15?[1,2,4,6,8]:[1,2,3,4,5,6,7,8,9];fb&&(!d||m<=c)&&m!==y&&i.push(m),m> -c&&(n=!0),m=l}else if(b=g(b),c=g(c),a=e[d?"minorTickInterval":"tickInterval"],a=o(a==="auto"?null:a,this._minorAutoInterval,(c-b)*(e.tickPixelInterval/(d?5:1))/((d?f/this.tickPositions.length:f)||1)),a=sb(a,null,rb(a)),i=Ca(this.getLinearTickPositions(a,b,c),h),!d)this._minorAutoInterval=a/5;if(!d)this.tickInterval=a;return i};ha.prototype.log2lin=function(a){return Y.log(a)/Y.LN10};ha.prototype.lin2log=function(a){return Y.pow(10,a)};var Ob=x.Tooltip=function(){this.init.apply(this,arguments)};Ob.prototype= -{init:function(a,b){var c=b.borderWidth,d=b.style,e=C(d.padding);this.chart=a;this.options=b;this.crosshairs=[];this.now={x:0,y:0};this.isHidden=!0;this.label=a.renderer.label("",0,0,b.shape||"callout",null,null,b.useHTML,null,"tooltip").attr({padding:e,fill:b.backgroundColor,"stroke-width":c,r:b.borderRadius,zIndex:8}).css(d).css({padding:0}).add().attr({y:-9999});ka||this.label.shadow(b.shadow);this.shared=b.shared},destroy:function(){if(this.label)this.label=this.label.destroy();clearTimeout(this.hideTimer); -clearTimeout(this.tooltipTimeout)},move:function(a,b,c,d){var e=this,f=e.now,g=e.options.animation!==!1&&!e.isHidden&&(Q(a-f.x)>1||Q(b-f.y)>1),h=e.followPointer||e.len>1;u(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:h?y:g?(2*f.anchorX+c)/3:c,anchorY:h?y:g?(f.anchorY+d)/2:d});e.label.attr(f);if(g)clearTimeout(this.tooltipTimeout),this.tooltipTimeout=setTimeout(function(){e&&e.move(a,b,c,d)},32)},hide:function(a){var b=this;clearTimeout(this.hideTimer);a=o(a,this.options.hideDelay,500);if(!this.isHidden)this.hideTimer= -Pa(function(){b.label[a?"fadeOut":"hide"]();b.isHidden=!0},a)},getAnchor:function(a,b){var c,d=this.chart,e=d.inverted,f=d.plotTop,g=d.plotLeft,h=0,i=0,k,j,a=ta(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===y&&(b=d.pointer.normalize(b)),c=[b.chartX-d.plotLeft,b.chartY-f]);c||(p(a,function(a){k=a.series.yAxis;j=a.series.xAxis;h+=a.plotX+(!e&&j?j.left-g:0);i+=(a.plotLow?(a.plotLow+a.plotHigh)/2:a.plotY)+(!e&&k?k.top-f:0)}),h/=a.length,i/=a.length,c=[e?d.plotWidth-i:h,this.shared&&!e&&a.length> -1&&b?b.chartY-f:e?d.plotHeight-h:i]);return Ca(c,B)},getPosition:function(a,b,c){var d=this.chart,e=this.distance,f={},g=c.h||0,h,i=["y",d.chartHeight,b,c.plotY+d.plotTop,d.plotTop,d.plotTop+d.plotHeight],k=["x",d.chartWidth,a,c.plotX+d.plotLeft,d.plotLeft,d.plotLeft+d.plotWidth],j=!this.followPointer&&o(c.ttBelow,!d.inverted===!!c.negative),l=function(a,b,c,d,h,i){var k=cb?d: -d+g);else return!1},m=function(a,b,c,d){var g;db-e?g=!1:f[a]=db-c/2?b-c-2:d-c/2;return g},n=function(a){var b=i;i=k;k=b;h=a},q=function(){l.apply(0,i)!==!1?m.apply(0,k)===!1&&!h&&(n(!0),q()):h?f.x=f.y=0:(n(!0),q())};(d.inverted||this.len>1)&&n();q();return f},defaultFormatter:function(a){var b=this.points||ta(this),c;c=[a.tooltipFooterHeaderFormatter(b[0])];c=c.concat(a.bodyFormatter(b));c.push(a.tooltipFooterHeaderFormatter(b[0],!0));return c.join("")},refresh:function(a,b){var c= -this.chart,d=this.label,e=this.options,f,g,h,i={},k,j=[];k=e.formatter||this.defaultFormatter;var i=c.hoverPoints,l,m=this.shared;clearTimeout(this.hideTimer);this.followPointer=ta(a)[0].series.tooltipOptions.followPointer;h=this.getAnchor(a,b);f=h[0];g=h[1];m&&(!a.series||!a.series.noSharedTooltip)?(c.hoverPoints=a,i&&p(i,function(a){a.setState()}),p(a,function(a){a.setState("hover");j.push(a.getLabelConfig())}),i={x:a[0].category,y:a[0].y},i.points=j,this.len=j.length,a=a[0]):i=a.getLabelConfig(); -k=k.call(i,this);i=a.series;this.distance=o(i.tooltipOptions.distance,16);k===!1?this.hide():(this.isHidden&&(Oa(d),d.attr("opacity",1).show()),d.attr({text:k}),l=e.borderColor||a.color||i.color||"#606060",d.attr({stroke:l}),this.updatePosition({plotX:f,plotY:g,negative:a.negative,ttBelow:a.ttBelow,h:h[2]||0}),this.isHidden=!1);I(c,"tooltipRefresh",{text:k,x:f+c.plotLeft,y:g+c.plotTop,borderColor:l})},updatePosition:function(a){var b=this.chart,c=this.label,c=(this.options.positioner||this.getPosition).call(this, -c.width,c.height,a);this.move(B(c.x),B(c.y||0),a.plotX+b.plotLeft,a.plotY+b.plotTop)},getXDateFormat:function(a,b,c){var d,b=b.dateTimeLabelFormats,e=c&&c.closestPointRange,f,g={millisecond:15,second:12,minute:9,hour:6,day:3},h,i="millisecond";if(e){h=Qa("%m-%d %H:%M:%S.%L",a.x);for(f in H){if(e===H.week&&+Qa("%w",a.x)===c.options.startOfWeek&&h.substr(6)==="00:00:00.000"){f="week";break}if(H[f]>e){f=i;break}if(g[f]&&h.substr(g[f])!=="01-01 00:00:00.000".substr(g[f]))break;f!=="week"&&(i=f)}f&&(d= -b[f])}else d=b.day;return d||b.year},tooltipFooterHeaderFormatter:function(a,b){var c=b?"footer":"header",d=a.series,e=d.tooltipOptions,f=e.xDateFormat,g=d.xAxis,h=g&&g.options.type==="datetime"&&J(a.key),c=e[c+"Format"];h&&!f&&(f=this.getXDateFormat(a,e,g));h&&f&&(c=c.replace("{point.key}","{point.key:"+f+"}"));return Ka(c,{point:a,series:d})},bodyFormatter:function(a){return Ca(a,function(a){var c=a.series.tooltipOptions;return(c.pointFormatter||a.point.tooltipFormatter).call(a.point,c.pointFormat)})}}; -var ia;db=A&&A.documentElement.ontouchstart!==y;var Xa=x.Pointer=function(a,b){this.init(a,b)};Xa.prototype={init:function(a,b){var c=b.chart,d=c.events,e=ka?"":c.zoomType,c=a.inverted,f;this.options=b;this.chart=a;this.zoomX=f=/x/.test(e);this.zoomY=e=/y/.test(e);this.zoomHor=f&&!c||e&&c;this.zoomVert=e&&!c||f&&c;this.hasZoom=f||e;this.runChartClick=d&&!!d.click;this.pinchDown=[];this.lastValidTouch={};if(x.Tooltip&&b.tooltip.enabled)a.tooltip=new Ob(a,b.tooltip),this.followTouchMove=o(b.tooltip.followTouchMove, -!0);this.setDOMEvents()},normalize:function(a,b){var c,d,a=a||D.event;if(!a.target)a.target=a.srcElement;d=a.touches?a.touches.length?a.touches.item(0):a.changedTouches[0]:a;if(!b)this.chartPosition=b=zb(this.chart.container);d.pageX===y?(c=t(a.x,a.clientX-b.left),d=a.y):(c=d.pageX-b.left,d=d.pageY-b.top);return u(a,{chartX:B(c),chartY:B(d)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};p(this.chart.axes,function(c){b[c.isXAxis?"xAxis":"yAxis"].push({axis:c,value:c.toValue(a[c.horiz?"chartX": -"chartY"])})});return b},runPointActions:function(a){var b=this.chart,c=b.series,d=b.tooltip,e=d?d.shared:!1,f=b.hoverPoint,g=b.hoverSeries,h,i=[Number.MAX_VALUE,Number.MAX_VALUE],k,j,l=[],m=[],n;if(!e&&!g)for(h=0;h=m[c].series.group.zIndex;if(a[b]h+k&&(d=h+k),ei+j&&(e=i+j),this.hasDragged=Math.sqrt(Math.pow(n-d,2)+Math.pow(q-e,2)),this.hasDragged>10){l=b.isInsidePlot(n-h,q-i);if(b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&l&&!o&&!m)this.selectionMarker=m=b.renderer.rect(h,i, -f?1:k,g?1:j,0).attr({fill:c.selectionMarkerFill||"rgba(69,114,167,0.25)",zIndex:7}).add();m&&f&&(d-=n,m.attr({width:Q(d),x:(d>0?0:d)+n}));m&&g&&(d=e-q,m.attr({height:Q(d),y:(d>0?0:d)+q}));l&&!m&&c.panning&&b.pan(a,c.panning)}},drop:function(a){var b=this,c=this.chart,d=this.hasPinched;if(this.selectionMarker){var e={originalEvent:a,xAxis:[],yAxis:[]},f=this.selectionMarker,g=f.attr?f.attr("x"):f.x,h=f.attr?f.attr("y"):f.y,i=f.attr?f.attr("width"):f.width,k=f.attr?f.attr("height"):f.height,j;if(this.hasDragged|| -d)p(c.axes,function(c){if(c.zoomEnabled&&r(c.min)&&(d||b[{xAxis:"zoomX",yAxis:"zoomY"}[c.coll]])){var f=c.horiz,n=a.type==="touchend"?c.minPixelPadding:0,q=c.toValue((f?g:h)+n),f=c.toValue((f?g+i:h+k)-n);e[c.coll].push({axis:c,min:F(q,f),max:t(q,f)});j=!0}}),j&&I(c,"selection",e,function(a){c.zoom(u(a,d?{animation:!1}:null))});this.selectionMarker=this.selectionMarker.destroy();d&&this.scaleGroups()}if(c)M(c.container,{cursor:c._cursor}),c.cancelClick=this.hasDragged>10,c.mouseIsDown=this.hasDragged= -this.hasPinched=!1,this.pinchDown=[]},onContainerMouseDown:function(a){a=this.normalize(a);a.preventDefault&&a.preventDefault();this.dragStart(a)},onDocumentMouseUp:function(a){T[ia]&&T[ia].pointer.drop(a)},onDocumentMouseMove:function(a){var b=this.chart,c=this.chartPosition,a=this.normalize(a,c);c&&!this.inClass(a.target,"highcharts-tracker")&&!b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)&&this.reset()},onContainerMouseLeave:function(a){var b=T[ia];if(b&&(a.relatedTarget||a.toElement))b.pointer.reset(), -b.pointer.chartPosition=null},onContainerMouseMove:function(a){var b=this.chart;if(!r(ia)||!T[ia]||!T[ia].mouseIsDown)ia=b.index;a=this.normalize(a);a.returnValue=!1;b.mouseIsDown==="mousedown"&&this.drag(a);(this.inClass(a.target,"highcharts-tracker")||b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop))&&!b.openMenu&&this.runPointActions(a)},inClass:function(a,b){for(var c;a;){if(c=P(a,"class")){if(c.indexOf(b)!==-1)return!0;if(c.indexOf("highcharts-container")!==-1)return!1}a=a.parentNode}}, -onTrackerMouseOut:function(a){var b=this.chart.hoverSeries,a=a.relatedTarget||a.toElement;if(b&&a&&!b.options.stickyTracking&&!this.inClass(a,"highcharts-tooltip")&&!this.inClass(a,"highcharts-series-"+b.index))b.onMouseOut()},onContainerClick:function(a){var b=this.chart,c=b.hoverPoint,d=b.plotLeft,e=b.plotTop,a=this.normalize(a);b.cancelClick||(c&&this.inClass(a.target,"highcharts-tracker")?(I(c.series,"click",u(a,{point:c})),b.hoverPoint&&c.firePointEvent("click",a)):(u(a,this.getCoordinates(a)), -b.isInsidePlot(a.chartX-d,a.chartY-e)&&I(b,"click",a)))},setDOMEvents:function(){var a=this,b=a.chart.container;b.onmousedown=function(b){a.onContainerMouseDown(b)};b.onmousemove=function(b){a.onContainerMouseMove(b)};b.onclick=function(b){a.onContainerClick(b)};N(b,"mouseleave",a.onContainerMouseLeave);eb===1&&N(A,"mouseup",a.onDocumentMouseUp);if(db)b.ontouchstart=function(b){a.onContainerTouchStart(b)},b.ontouchmove=function(b){a.onContainerTouchMove(b)},eb===1&&N(A,"touchend",a.onDocumentTouchEnd)}, -destroy:function(){var a;X(this.chart.container,"mouseleave",this.onContainerMouseLeave);eb||(X(A,"mouseup",this.onDocumentMouseUp),X(A,"touchend",this.onDocumentTouchEnd));clearInterval(this.tooltipTimeout);for(a in this)this[a]=null}};u(x.Pointer.prototype,{pinchTranslate:function(a,b,c,d,e,f){(this.zoomHor||this.pinchHor)&&this.pinchTranslateDirection(!0,a,b,c,d,e,f);(this.zoomVert||this.pinchVert)&&this.pinchTranslateDirection(!1,a,b,c,d,e,f)},pinchTranslateDirection:function(a,b,c,d,e,f,g,h){var i= -this.chart,k=a?"x":"y",j=a?"X":"Y",l="chart"+j,m=a?"width":"height",n=i["plot"+(a?"Left":"Top")],q,o,s=h||1,p=i.inverted,w=i.bounds[a?"h":"v"],v=b.length===1,r=b[0][l],t=c[0][l],u=!v&&b[1][l],x=!v&&c[1][l],B,c=function(){!v&&Q(r-u)>20&&(s=h||Q(t-x)/Q(r-u));o=(n-t)/s+r;q=i["plot"+(a?"Width":"Height")]/s};c();b=o;bw.max&&(b=w.max-q,B=!0);B?(t-=0.8*(t-g[k][0]),v||(x-=0.8*(x-g[k][1])),c()):g[k]=[t,x];p||(f[k]=o-n,f[m]=q);f=p?1/s:s;e[m]=q;e[k]=b;d[p?a?"scaleY":"scaleX":"scale"+ -j]=s;d["translate"+j]=f*n+(t-f*r)},pinch:function(a){var b=this,c=b.chart,d=b.pinchDown,e=a.touches,f=e.length,g=b.lastValidTouch,h=b.hasZoom,i=b.selectionMarker,k={},j=f===1&&(b.inClass(a.target,"highcharts-tracker")&&c.runTrackerClick||b.runChartClick),l={};if(f>1)b.initiated=!0;h&&b.initiated&&!j&&a.preventDefault();Ca(e,function(a){return b.normalize(a)});if(a.type==="touchstart")p(e,function(a,b){d[b]={chartX:a.chartX,chartY:a.chartY}}),g.x=[d[0].chartX,d[1]&&d[1].chartX],g.y=[d[0].chartY,d[1]&& -d[1].chartY],p(c.axes,function(a){if(a.zoomEnabled){var b=c.bounds[a.horiz?"h":"v"],d=a.minPixelPadding,e=a.toPixels(o(a.options.min,a.dataMin)),f=a.toPixels(o(a.options.max,a.dataMax)),g=F(e,f),e=t(e,f);b.min=F(a.pos,g-d);b.max=t(a.pos+a.len,e+d)}}),b.res=!0;else if(d.length){if(!i)b.selectionMarker=i=u({destroy:Aa,touch:!0},c.plotBox);b.pinchTranslate(d,e,k,i,l,g);b.hasPinched=h;b.scaleGroups(k,l);if(!h&&b.followTouchMove&&f===1)this.runPointActions(b.normalize(a));else if(b.res)b.res=!1,this.reset(!1, -0)}},touch:function(a,b){var c=this.chart,d;ia=c.index;if(a.touches.length===1)if(a=this.normalize(a),c.isInsidePlot(a.chartX-c.plotLeft,a.chartY-c.plotTop)&&!c.openMenu){b&&this.runPointActions(a);if(a.type==="touchmove")c=this.pinchDown,d=c[0]?Math.sqrt(Math.pow(c[0].chartX-a.chartX,2)+Math.pow(c[0].chartY-a.chartY,2))>=4:!1;o(d,!0)&&this.pinch(a)}else b&&this.reset();else a.touches.length===2&&this.pinch(a)},onContainerTouchStart:function(a){this.touch(a,!0)},onContainerTouchMove:function(a){this.touch(a)}, -onDocumentTouchEnd:function(a){T[ia]&&T[ia].pointer.drop(a)}});if(D.PointerEvent||D.MSPointerEvent){var va={},Cb=!!D.PointerEvent,Rb=function(){var a,b=[];b.item=function(a){return this[a]};for(a in va)va.hasOwnProperty(a)&&b.push({pageX:va[a].pageX,pageY:va[a].pageY,target:va[a].target});return b},Db=function(a,b,c,d){if((a.pointerType==="touch"||a.pointerType===a.MSPOINTER_TYPE_TOUCH)&&T[ia])d(a),d=T[ia].pointer,d[b]({type:c,target:a.currentTarget,preventDefault:Aa,touches:Rb()})};u(Xa.prototype, -{onContainerPointerDown:function(a){Db(a,"onContainerTouchStart","touchstart",function(a){va[a.pointerId]={pageX:a.pageX,pageY:a.pageY,target:a.currentTarget}})},onContainerPointerMove:function(a){Db(a,"onContainerTouchMove","touchmove",function(a){va[a.pointerId]={pageX:a.pageX,pageY:a.pageY};if(!va[a.pointerId].target)va[a.pointerId].target=a.currentTarget})},onDocumentPointerUp:function(a){Db(a,"onDocumentTouchEnd","touchend",function(a){delete va[a.pointerId]})},batchMSEvents:function(a){a(this.chart.container, -Cb?"pointerdown":"MSPointerDown",this.onContainerPointerDown);a(this.chart.container,Cb?"pointermove":"MSPointerMove",this.onContainerPointerMove);a(A,Cb?"pointerup":"MSPointerUp",this.onDocumentPointerUp)}});fb(Xa.prototype,"init",function(a,b,c){a.call(this,b,c);this.hasZoom&&M(b.container,{"-ms-touch-action":"none","touch-action":"none"})});fb(Xa.prototype,"setDOMEvents",function(a){a.apply(this);(this.hasZoom||this.followTouchMove)&&this.batchMSEvents(N)});fb(Xa.prototype,"destroy",function(a){this.batchMSEvents(X); -a.call(this)})}var ob=x.Legend=function(a,b){this.init(a,b)};ob.prototype={init:function(a,b){var c=this,d=b.itemStyle,e=b.itemMarginTop||0;this.options=b;if(b.enabled)c.itemStyle=d,c.itemHiddenStyle=E(d,b.itemHiddenStyle),c.itemMarginTop=e,c.padding=d=o(b.padding,8),c.initialItemX=d,c.initialItemY=d-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.symbolWidth=o(b.symbolWidth,16),c.pages=[],c.render(),N(c.chart,"endResize",function(){c.positionCheckboxes()})},colorizeItem:function(a,b){var c=this.options, -d=a.legendItem,e=a.legendLine,f=a.legendSymbol,g=this.itemHiddenStyle.color,c=b?c.itemStyle.color:g,h=b?a.legendColor||a.color||"#CCC":g,g=a.options&&a.options.marker,i={fill:h},k;d&&d.css({fill:c,color:c});e&&e.attr({stroke:h});if(f){if(g&&f.isMarker)for(k in i.stroke=h,g=a.convertAttribs(g),g)d=g[k],d!==y&&(i[k]=d);f.attr(i)}},positionItem:function(a){var b=this.options,c=b.symbolPadding,b=!b.rtl,d=a._legendItemPos,e=d[0],d=d[1],f=a.checkbox;(a=a.legendGroup)&&a.element&&a.translate(b?e:this.legendWidth- -e-2*c-4,d);if(f)f.x=e,f.y=d},destroyItem:function(a){var b=a.checkbox;p(["legendItem","legendLine","legendSymbol","legendGroup"],function(b){a[b]&&(a[b]=a[b].destroy())});b&&Sa(a.checkbox)},destroy:function(){var a=this.group,b=this.box;if(b)this.box=b.destroy();if(a)this.group=a.destroy()},positionCheckboxes:function(a){var b=this.group.alignAttr,c,d=this.clipHeight||this.legendHeight,e=this.titleHeight;if(b)c=b.translateY,p(this.allItems,function(f){var g=f.checkbox,h;g&&(h=c+e+g.y+(a||0)+3,M(g, -{left:b.translateX+f.checkboxOffset+g.x-20+"px",top:h+"px",display:h>c-6&&h(m||b.chartWidth-2*k-z-d.x))this.itemX=z,this.itemY+=q+this.lastLineHeight+n,this.lastLineHeight=0;this.maxItemWidth=t(this.maxItemWidth,f);this.lastItemY=q+this.itemY+n;this.lastLineHeight=t(g,this.lastLineHeight);a._legendItemPos=[this.itemX,this.itemY];e?this.itemX+=f:(this.itemY+=q+g+n,this.lastLineHeight=g);this.offsetWidth=m||t((e?this.itemX-z-j:f)+k,this.offsetWidth)}, -getAllItems:function(){var a=[];p(this.chart.series,function(b){var c=b.options;if(o(c.showInLegend,!r(c.linkedTo)?y:!1,!0))a=a.concat(b.legendItems||(c.legendType==="point"?b.data:b))});return a},adjustMargins:function(a,b){var c=this.chart,d=this.options,e=d.align.charAt(0)+d.verticalAlign.charAt(0)+d.layout.charAt(0);this.display&&!d.floating&&p([/(lth|ct|rth)/,/(rtv|rm|rbv)/,/(rbh|cb|lbh)/,/(lbv|lm|ltv)/],function(f,g){f.test(e)&&!r(a[g])&&(c[nb[g]]=t(c[nb[g]],c.legend[(g+1)%2?"legendHeight": -"legendWidth"]+[1,-1,-1,1][g]*d[g%2?"x":"y"]+o(d.margin,12)+b[g]))})},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,e,f,g,h,i=a.box,k=a.options,j=a.padding,l=k.borderWidth,m=k.backgroundColor;a.itemX=a.initialItemX;a.itemY=a.initialItemY;a.offsetWidth=0;a.lastItemY=0;if(!d)a.group=d=c.g("legend").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),a.scrollGroup=c.g().add(a.contentGroup);a.renderTitle();e=a.getAllItems();hb(e,function(a,b){return(a.options&&a.options.legendIndex|| -0)-(b.options&&b.options.legendIndex||0)});k.reversed&&e.reverse();a.allItems=e;a.display=f=!!e.length;a.lastLineHeight=0;p(e,function(b){a.renderItem(b)});g=(k.width||a.offsetWidth)+j;h=a.lastItemY+a.lastLineHeight+a.titleHeight;h=a.handleOverflow(h);h+=j;if(l||m){if(i){if(g>0&&h>0)i[i.isNew?"attr":"animate"](i.crisp({width:g,height:h})),i.isNew=!1}else a.box=i=c.rect(0,0,g,h,k.borderRadius,l||0).attr({stroke:k.borderColor,"stroke-width":l||0,fill:m||"none"}).add(d).shadow(k.shadow),i.isNew=!0;i[f? -"show":"hide"]()}a.legendWidth=g;a.legendHeight=h;p(e,function(b){a.positionItem(b)});f&&d.align(u({width:g,height:h},k),!0,"spacingBox");b.isResizing||this.positionCheckboxes()},handleOverflow:function(a){var b=this,c=this.chart,d=c.renderer,e=this.options,f=e.y,f=c.spacingBox.height+(e.verticalAlign==="top"?-f:f)-this.padding,g=e.maxHeight,h,i=this.clipRect,k=e.navigation,j=o(k.animation,!0),l=k.arrowSize||12,m=this.nav,n=this.pages,q=this.padding,z,s=this.allItems,r=function(a){i.attr({height:a}); -if(b.contentGroup.div)b.contentGroup.div.style.clip="rect("+q+"px,9999px,"+(q+a)+"px,0)"};e.layout==="horizontal"&&(f/=2);g&&(f=F(f,g));n.length=0;if(a>f&&k.enabled!==!1){this.clipHeight=h=t(f-20-this.titleHeight-q,0);this.currentPage=o(this.currentPage,1);this.fullHeight=a;p(s,function(a,b){var c=a._legendItemPos[1],d=B(a.legendItem.getBBox().height),e=n.length;if(!e||c-n[e-1]>h&&(z||c)!==n[e-1])n.push(z||c),e++;b===s.length-1&&c+d-n[e-1]>h&&n.push(c);c!==z&&(z=c)});if(!i)i=b.clipRect=d.clipRect(0, -q,9999,0),b.contentGroup.clip(i);r(h);if(!m)this.nav=m=d.g().attr({zIndex:1}).add(this.group),this.up=d.symbol("triangle",0,0,l,l).on("click",function(){b.scroll(-1,j)}).add(m),this.pager=d.text("",15,10).css(k.style).add(m),this.down=d.symbol("triangle-down",0,0,l,l).on("click",function(){b.scroll(1,j)}).add(m);b.scroll(0);a=f}else if(m)r(c.chartHeight),m.hide(),this.scrollGroup.attr({translateY:1}),this.clipHeight=0;return a},scroll:function(a,b){var c=this.pages,d=c.length,e=this.currentPage+a, -f=this.clipHeight,g=this.options.navigation,h=g.activeColor,g=g.inactiveColor,i=this.pager,k=this.padding;e>d&&(e=d);if(e>0)b!==y&&Ta(b,this.chart),this.nav.attr({translateX:k,translateY:f+this.padding+7+this.titleHeight,visibility:"visible"}),this.up.attr({fill:e===1?g:h}).css({cursor:e===1?"default":"pointer"}),i.attr({text:e+"/"+d}),this.down.attr({x:18+this.pager.getBBox().width,fill:e===d?g:h}).css({cursor:e===d?"default":"pointer"}),c=-c[e-1]+this.initialItemY,this.scrollGroup.animate({translateY:c}), -this.currentPage=e,this.positionCheckboxes(c)}};K=x.LegendSymbolMixin={drawRectangle:function(a,b){var c=a.options.symbolHeight||a.fontMetrics.f;b.legendSymbol=this.chart.renderer.rect(0,a.baseline-c+1,a.symbolWidth,c,a.options.symbolRadius||0).attr({zIndex:3}).add(b.legendGroup)},drawLineMarker:function(a){var b=this.options,c=b.marker,d=a.symbolWidth,e=this.chart.renderer,f=this.legendGroup,a=a.baseline-B(a.fontMetrics.b*0.3),g;if(b.lineWidth){g={"stroke-width":b.lineWidth};if(b.dashStyle)g.dashstyle= -b.dashStyle;this.legendLine=e.path(["M",0,a,"L",d,a]).attr(g).add(f)}if(c&&c.enabled!==!1)b=c.radius,this.legendSymbol=c=e.symbol(this.symbol,d/2-b,a-b,2*b,2*b,c).add(f),c.isMarker=!0}};(/Trident\/7\.0/.test(za)||Na)&&fb(ob.prototype,"positionItem",function(a,b){var c=this,d=function(){b._legendItemPos&&a.call(c,b)};d();setTimeout(d)});var gb=x.Chart=function(){this.getArgs.apply(this,arguments)};x.chart=function(a,b,c){return new gb(a,b,c)};gb.prototype={callbacks:[],getArgs:function(){var a=[].slice.call(arguments); -if(xa(a[0])||a[0].nodeName)this.renderTo=a.shift();this.init(a[0],a[1])},init:function(a,b){var c,d=a.series;a.series=null;c=E(U,a);c.series=a.series=d;this.userOptions=a;d=c.chart;this.margin=this.splashArray("margin",d);this.spacing=this.splashArray("spacing",d);var e=d.events;this.bounds={h:{},v:{}};this.callback=b;this.isResizing=0;this.options=c;this.axes=[];this.series=[];this.hasCartesianSeries=d.showAxes;var f=this,g;f.index=T.length;T.push(f);eb++;d.reflow!==!1&&N(f,"load",function(){f.initReflow()}); -if(e)for(g in e)N(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=ka?!1:o(d.animation,!0);f.pointCount=f.colorCounter=f.symbolCounter=0;f.firstRender()},initSeries:function(a){var b=this.options.chart;(b=L[a.type||b.type||b.defaultSeriesType])||aa(17,!0);b=new b;b.init(this,a);return b},isInsidePlot:function(a,b,c){var d=c?b:a,a=c?a:b;return d>=0&&d<=this.plotWidth&&a>=0&&a<=this.plotHeight},redraw:function(a){var b=this.axes,c=this.series,d=this.pointer,e=this.legend,f=this.isDirtyLegend,g,h,i=this.hasCartesianSeries, -k=this.isDirtyBox,j=c.length,l=j,m=this.renderer,n=m.isHidden(),q=[];Ta(a,this);n&&this.cloneRenderTo();for(this.layOutTitles();l--;)if(a=c[l],a.options.stacking&&(g=!0,a.isDirty)){h=!0;break}if(h)for(l=j;l--;)if(a=c[l],a.options.stacking)a.isDirty=!0;p(c,function(a){a.isDirty&&a.options.legendType==="point"&&(a.updateTotals&&a.updateTotals(),f=!0);a.isDirtyData&&I(a,"updatedData")});if(f&&e.options.enabled)e.render(),this.isDirtyLegend=!1;g&&this.getStacks();if(i&&!this.isResizing)this.maxTicks= -null,p(b,function(a){a.setScale()});this.getMargins();i&&(p(b,function(a){a.isDirty&&(k=!0)}),p(b,function(a){var b=a.min+","+a.max;if(a.extKey!==b)a.extKey=b,q.push(function(){I(a,"afterSetExtremes",u(a.eventArgs,a.getExtremes()));delete a.eventArgs});(k||g)&&a.redraw()}));k&&this.drawChartBox();p(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});d&&d.reset(!0);m.draw();I(this,"redraw");n&&this.cloneRenderTo(!0);p(q,function(a){a.call()})},get:function(a){var b=this.axes, -c=this.series,d,e;for(d=0;d19?this.containerHeight:400))},cloneRenderTo:function(a){var b= -this.renderToClone,c=this.container;a?b&&(this.renderTo.appendChild(c),Sa(b),delete this.renderToClone):(c&&c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=b=this.renderTo.cloneNode(0),M(b,{position:"absolute",top:"-9999px",display:"block"}),b.style.setProperty&&b.style.setProperty("display","block","important"),A.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options,c=b.chart,d,e;a=this.renderTo;var f="highcharts-"+yb++;if(!a)this.renderTo= -a=c.renderTo;if(xa(a))this.renderTo=a=A.getElementById(a);a||aa(13,!0);d=C(P(a,"data-highcharts-chart"));J(d)&&T[d]&&T[d].hasRendered&&T[d].destroy();P(a,"data-highcharts-chart",this.index);a.innerHTML="";!c.skipClone&&!a.offsetWidth&&this.cloneRenderTo();this.getChartSize();d=this.chartWidth;e=this.chartHeight;this.container=a=ba(Ma,{className:"highcharts-container"+(c.className?" "+c.className:""),id:f},u({position:"relative",overflow:"hidden",width:d+"px",height:e+"px",textAlign:"left",lineHeight:"normal", -zIndex:0,"-webkit-tap-highlight-color":"rgba(0,0,0,0)"},c.style),this.renderToClone||a);this._cursor=a.style.cursor;this.renderer=new (x[c.renderer]||cb)(a,d,e,c.style,c.forExport,b.exporting&&b.exporting.allowHTML);ka&&this.renderer.create(this,a,d,e);this.renderer.chartIndex=this.index},getMargins:function(a){var b=this.spacing,c=this.margin,d=this.titleOffset;this.resetMargins();if(d&&!r(c[0]))this.plotTop=t(this.plotTop,d+this.options.title.margin+b[0]);this.legend.adjustMargins(c,b);this.extraBottomMargin&& -(this.marginBottom+=this.extraBottomMargin);this.extraTopMargin&&(this.plotTop+=this.extraTopMargin);a||this.getAxisMargins()},getAxisMargins:function(){var a=this,b=a.axisOffset=[0,0,0,0],c=a.margin;a.hasCartesianSeries&&p(a.axes,function(a){a.visible&&a.getOffset()});p(nb,function(d,e){r(c[e])||(a[d]+=b[e])});a.setChartSize()},reflow:function(a){var b=this,c=b.options.chart,d=b.renderTo,e=c.width||na(d,"width"),f=c.height||na(d,"height"),c=a?a.target:D;if(!b.hasUserSize&&!b.isPrinting&&e&&f&&(c=== -D||c===A)){if(e!==b.containerWidth||f!==b.containerHeight)clearTimeout(b.reflowTimeout),b.reflowTimeout=Pa(function(){if(b.container)b.setSize(e,f,!1),b.hasUserSize=null},a?100:0);b.containerWidth=e;b.containerHeight=f}},initReflow:function(){var a=this,b=function(b){a.reflow(b)};N(D,"resize",b);N(a,"destroy",function(){X(D,"resize",b)})},setSize:function(a,b,c){var d=this,e,f,g=d.renderer;d.isResizing+=1;Ta(c,d);d.oldChartHeight=d.chartHeight;d.oldChartWidth=d.chartWidth;if(r(a))d.chartWidth=e=t(0, -B(a)),d.hasUserSize=!!e;if(r(b))d.chartHeight=f=t(0,B(b));a=g.globalAnimation;(a?Wa:M)(d.container,{width:e+"px",height:f+"px"},a);d.setChartSize(!0);g.setSize(e,f,c);d.maxTicks=null;p(d.axes,function(a){a.isDirty=!0;a.setScale()});p(d.series,function(a){a.isDirty=!0});d.isDirtyLegend=!0;d.isDirtyBox=!0;d.layOutTitles();d.getMargins();d.redraw(c);d.oldChartHeight=null;I(d,"resize");Pa(function(){d&&I(d,"endResize",null,function(){d.isResizing-=1})},$a(a).duration)},setChartSize:function(a){var b= -this.inverted,c=this.renderer,d=this.chartWidth,e=this.chartHeight,f=this.options.chart,g=this.spacing,h=this.clipOffset,i,k,j,l;this.plotLeft=i=B(this.plotLeft);this.plotTop=k=B(this.plotTop);this.plotWidth=j=t(0,B(d-i-this.marginRight));this.plotHeight=l=t(0,B(e-k-this.marginBottom));this.plotSizeX=b?l:j;this.plotSizeY=b?j:l;this.plotBorderWidth=f.plotBorderWidth||0;this.spacingBox=c.spacingBox={x:g[3],y:g[0],width:d-g[3]-g[1],height:e-g[0]-g[2]};this.plotBox=c.plotBox={x:i,y:k,width:j,height:l}; -d=2*V(this.plotBorderWidth/2);b=ua(t(d,h[3])/2);c=ua(t(d,h[0])/2);this.clipBox={x:b,y:c,width:V(this.plotSizeX-t(d,h[1])/2-b),height:t(0,V(this.plotSizeY-t(d,h[2])/2-c))};a||p(this.axes,function(a){a.setAxisSize();a.setAxisTranslation()})},resetMargins:function(){var a=this;p(nb,function(b,c){a[b]=o(a.margin[c],a.spacing[c])});a.axisOffset=[0,0,0,0];a.clipOffset=[0,0,0,0]},drawChartBox:function(){var a=this.options.chart,b=this.renderer,c=this.chartWidth,d=this.chartHeight,e=this.chartBackground, -f=this.plotBackground,g=this.plotBorder,h=this.plotBGImage,i=a.borderWidth||0,k=a.backgroundColor,j=a.plotBackgroundColor,l=a.plotBackgroundImage,m=a.plotBorderWidth||0,n,q=this.plotLeft,o=this.plotTop,p=this.plotWidth,r=this.plotHeight,w=this.plotBox,v=this.clipRect,t=this.clipBox;n=i+(a.shadow?8:0);if(i||k)if(e)e.animate(e.crisp({width:c-n,height:d-n}));else{e={fill:k||"none"};if(i)e.stroke=a.borderColor,e["stroke-width"]=i;this.chartBackground=b.rect(n/2,n/2,c-n,d-n,a.borderRadius,i).attr(e).addClass("highcharts-background").add().shadow(a.shadow)}if(j)f? -f.animate(w):this.plotBackground=b.rect(q,o,p,r,0).attr({fill:j}).add().shadow(a.plotShadow);if(l)h?h.animate(w):this.plotBGImage=b.image(l,q,o,p,r).add();v?v.animate({width:t.width,height:t.height}):this.clipRect=b.clipRect(t);if(m)g?(g.strokeWidth=-m,g.animate(g.crisp({x:q,y:o,width:p,height:r}))):this.plotBorder=b.rect(q,o,p,r,0,-m).attr({stroke:a.plotBorderColor,"stroke-width":m,fill:"none",zIndex:1}).add();this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,c,d=a.options.series, -e,f;p(["inverted","angular","polar"],function(g){c=L[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&&e--;)(c=L[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},linkSeries:function(){var a=this,b=a.series;p(b,function(a){a.linkedSeries.length=0});p(b,function(b){var d=b.options.linkedTo;if(xa(d)&&(d=d===":previous"?a.series[b.index-1]:a.get(d)))d.linkedSeries.push(b),b.linkedParent=d,b.visible=o(b.options.visible,d.options.visible,b.visible)})},renderSeries:function(){p(this.series, -function(a){a.translate();a.render()})},renderLabels:function(){var a=this,b=a.options.labels;b.items&&p(b.items,function(c){var d=u(b.style,c.style),e=C(d.left)+a.plotLeft,f=C(d.top)+a.plotTop+12;delete d.left;delete d.top;a.renderer.text(c.html,e,f).attr({zIndex:2}).css(d).add()})},render:function(){var a=this.axes,b=this.renderer,c=this.options,d,e,f,g;this.setTitle();this.legend=new ob(this,c.legend);this.getStacks&&this.getStacks();this.getMargins(!0);this.setChartSize();d=this.plotWidth;e=this.plotHeight-= -21;p(a,function(a){a.setScale()});this.getAxisMargins();f=d/this.plotWidth>1.1;g=e/this.plotHeight>1.05;if(f||g)this.maxTicks=null,p(a,function(a){(a.horiz&&f||!a.horiz&&g)&&a.setTickInterval(!0)}),this.getMargins();this.drawChartBox();this.hasCartesianSeries&&p(a,function(a){a.visible&&a.render()});if(!this.seriesGroup)this.seriesGroup=b.g("series-group").attr({zIndex:3}).add();this.renderSeries();this.renderLabels();this.showCredits(c.credits);this.hasRendered=!0},showCredits:function(a){if(a.enabled&& -!this.credits)this.credits=this.renderer.text(a.text,0,0).on("click",function(){if(a.href)D.location.href=a.href}).attr({align:a.position.align,zIndex:8}).css(a.style).add().align(a.position)},destroy:function(){var a=this,b=a.axes,c=a.series,d=a.container,e,f=d&&d.parentNode;I(a,"destroy");T[a.index]=y;eb--;a.renderTo.removeAttribute("data-highcharts-chart");X(a);for(e=b.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=c[e].destroy();p("title,subtitle,chartBackground,plotBackground,plotBGImage,plotBorder,seriesGroup,clipRect,credits,pointer,scroller,rangeSelector,legend,resetZoomButton,tooltip,renderer".split(","), -function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});if(d)d.innerHTML="",X(d),f&&Sa(d);for(e in a)delete a[e]},isReadyToRender:function(){var a=this;return!fa&&D==D.top&&A.readyState!=="complete"||ka&&!D.canvg?(ka?Nb.push(function(){a.firstRender()},a.options.global.canvasToolsURL):A.attachEvent("onreadystatechange",function(){A.detachEvent("onreadystatechange",a.firstRender);A.readyState==="complete"&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options;if(a.isReadyToRender()){a.getContainer(); -I(a,"init");a.resetMargins();a.setChartSize();a.propFromSeries();a.getAxes();p(b.series||[],function(b){a.initSeries(b)});a.linkSeries();I(a,"beforeRender");if(x.Pointer)a.pointer=new Xa(a,b);a.render();a.renderer.draw();if(!a.renderer.imgCount&&a.onload)a.onload();a.cloneRenderTo(!0)}},onload:function(){var a=this;p([this.callback].concat(this.callbacks),function(b){b&&a.index!==void 0&&b.apply(a,[a])});I(a,"load");this.onload=null},splashArray:function(a,b){var c=b[a],c=Z(c)?c:[c,c,c,c];return[o(b[a+ -"Top"],c[0]),o(b[a+"Right"],c[1]),o(b[a+"Bottom"],c[2]),o(b[a+"Left"],c[3])]}};var Bb=x.CenteredSeriesMixin={getCenter:function(){var a=this.options,b=this.chart,c=2*(a.slicedOffset||0),d=b.plotWidth-2*c,b=b.plotHeight-2*c,e=a.center,e=[o(e[0],"50%"),o(e[1],"50%"),a.size||"100%",a.innerSize||0],f=F(d,b),g,h;for(g=0;g<4;++g)h=e[g],a=g<2||g===2&&/%$/.test(h),e[g]=(/%$/.test(h)?[d,b,f,e[2]][g]*parseFloat(h)/100:parseFloat(h))+(a?c:0);e[3]>e[2]&&(e[3]=e[2]);return e}},Ja=function(){};Ja.prototype={init:function(a, -b,c){this.series=a;this.color=a.color;this.applyOptions(b,c);this.pointAttr={};if(a.options.colorByPoint&&(b=a.options.colors||a.chart.options.colors,this.color=this.color||b[a.colorCounter++],a.colorCounter===b.length))a.colorCounter=0;a.chart.pointCount++;return this},applyOptions:function(a,b){var c=this.series,d=c.options.pointValKey||c.pointValKey,a=Ja.prototype.optionsToObject.call(this,a);u(this,a);this.options=this.options?u(this.options,a):a;if(d)this.y=this[d];this.isNull=this.x===null|| -this.y===null;if(this.x===void 0&&c)this.x=b===void 0?c.autoIncrement():b;return this},optionsToObject:function(a){var b={},c=this.series,d=c.options.keys,e=d||c.pointArrayMap||["y"],f=e.length,g=0,h=0;if(J(a)||a===null)b[e[0]]=a;else if(Ea(a)){if(!d&&a.length>f){c=typeof a[0];if(c==="string")b.name=a[0];else if(c==="number")b.x=a[0];g++}for(;hn){for(c=0;j===null&&ci||this.forceCrop))if(b[d-1]q)b=[],c=[];else if(b[0]q)e=this.cropData(this.xData,this.yData,n,q),b=e.xData,c=e.yData,e=e.start,f=!0;for(i=b.length||1;--i;)d=m?k(b[i])-k(b[i-1]):b[i]-b[i-1],d>0&&(g===y||d=c){f=t(0,i-h);break}for(c=i;cd){g=c+h;break}return{xData:a.slice(f,g),yData:b.slice(f,g),start:f,end:g}},generatePoints:function(){var a=this.options.data,b=this.data,c,d=this.processedXData,e=this.processedYData,f=this.pointClass,g=d.length,h=this.cropStart||0,i,k=this.hasGroupedData,j,l=[],m;if(!b&&!k)b=[],b.length=a.length,b=this.data=b;for(m=0;m0),k=this.getExtremesFromAll||this.options.getExtremesFromAll||this.cropped||(c[l+1]||k)>=g&&(c[l-1]||k)<=h,i&&k)if(i=j.length)for(;i--;)j[i]!==null&&(e[f++]=j[i]);else e[f++]=j;this.dataMin=La(e);this.dataMax=Ga(e)},translate:function(){this.processedXData||this.processData();this.generatePoints();for(var a=this.options,b=a.stacking,c=this.xAxis,d=c.categories,e=this.yAxis,f=this.points,g=f.length,h=!!this.modifyValue,i=a.pointPlacement, -k=i==="between"||J(i),j=a.threshold,l=a.startFromThreshold?j:0,m,n,q,p,s=Number.MAX_VALUE,a=0;a=0&&n<=e.len&&m>=0&&m<=c.len;G.clientX=k?c.translate(w,0,0,0,1):m;G.negative=G.y<(j||0);G.category=d&&d[G.x]!==y?d[G.x]:G.x;G.isNull||(q!==void 0&&(s=F(s,Q(m-q))),q=m)}this.closestPointRangePx=s},getValidPoints:function(a, -b){var c=this.chart;return Ba(a||this.points||[],function(a){return b&&!c.isInsidePlot(a.plotX,a.plotY,c.inverted)?!1:!a.isNull})},setClip:function(a){var b=this.chart,c=this.options,d=b.renderer,e=b.inverted,f=this.clipBox,g=f||b.clipBox,h=this.sharedClipKey||["_sharedClip",a&&a.duration,a&&a.easing,g.height,c.xAxis,c.yAxis].join(","),i=b[h],k=b[h+"m"];if(!i){if(a)g.width=0,b[h+"m"]=k=d.clipRect(-99,e?-b.plotLeft:-b.plotTop,99,e?b.chartWidth:b.chartHeight);b[h]=i=d.clipRect(g)}a&&(i.count+=1);if(c.clip!== -!1)this.group.clip(a||f?i:b.clipRect),this.markerGroup.clip(k),this.sharedClipKey=h;a||(i.count-=1,i.count<=0&&h&&b[h]&&(f||(b[h]=b[h].destroy()),b[h+"m"]&&(b[h+"m"]=b[h+"m"].destroy())))},animate:function(a){var b=this.chart,c=this.options.animation,d;if(c&&!Z(c))c=ea[this.type].animation;a?this.setClip(c):(d=this.sharedClipKey,(a=b[d])&&a.animate({width:b.plotSizeX},c),b[d+"m"]&&b[d+"m"].animate({width:b.plotSizeX+99},c),this.animate=null)},afterAnimate:function(){this.setClip();I(this,"afterAnimate")}, -drawPoints:function(){var a,b=this.points,c=this.chart,d,e,f,g,h,i,k,j,l=this.options.marker,m=this.pointAttr[""],n,q,p,s=this.markerGroup,r=o(l.enabled,this.xAxis.isRadial,this.closestPointRangePx>2*l.radius);if(l.enabled!==!1||this._hasPointMarkers)for(f=b.length;f--;)if(g=b[f],d=V(g.plotX),e=g.plotY,j=g.graphic,n=g.marker||{},q=!!g.marker,a=r&&n.enabled===y||n.enabled,p=g.isInside,a&&J(e)&&g.y!==null)if(a=g.pointAttr[g.selected?"select":""]||m,h=a.r,i=o(n.symbol,this.symbol),k=i.indexOf("url")=== -0,j)j[p?"show":"hide"](!0).attr(a).animate(u({x:d-h,y:e-h},j.symbolName?{width:2*h,height:2*h}:{}));else{if(p&&(h>0||k))g.graphic=c.renderer.symbol(i,d-h,e-h,2*h,2*h,q?n:l).attr(a).add(s)}else if(j)g.graphic=j.destroy()},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,h={},a=a||{},b=b||{},c=c||{},d=d||{};for(f in e)g=e[f],h[f]=o(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=a.options,c=ea[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color,h=a.options.negativeColor, -i={stroke:g,fill:g},k=a.points||[],j,l=[],m,n=a.pointAttrToOptions;f=a.hasPointSpecificOptions;var q=c.lineColor,z=c.fillColor;j=b.turboThreshold;var s=a.zones,t=a.zoneAxis||"y",w,v;b.marker?(e.radius=e.radius||c.radius+e.radiusPlus,e.lineWidth=e.lineWidth||c.lineWidth+e.lineWidthPlus):(e.color=e.color||ma(e.color||g).brighten(e.brightness).get(),e.negativeColor=e.negativeColor||ma(e.negativeColor||h).brighten(e.brightness).get());l[""]=a.convertAttribs(c,i);p(["hover","select"],function(b){l[b]= -a.convertAttribs(d[b],l[""])});a.pointAttr=l;g=k.length;if(!j||g=i.value;)i=s[++f];j.color=j.fillColor=i=o(i.color,a.color)}f=b.colorByPoint||j.color;if(j.options)for(v in n)r(c[n[v]])&&(f=!0);if(f){c=c||{};m=[];d=c.states||{};f=d.hover=d.hover||{};if(!b.marker||j.negative&&!f.fillColor&&!e.fillColor)f[a.pointAttrToOptions.fill]=f.color||!j.options.color&&e[j.negative&& -h?"negativeColor":"color"]||ma(j.color).brighten(f.brightness||e.brightness).get();w={color:j.color};if(!z)w.fillColor=j.color;if(!q)w.lineColor=j.color;c.hasOwnProperty("color")&&!c.color&&delete c.color;if(i&&!e.fillColor)f.fillColor=i;m[""]=a.convertAttribs(u(w,c),l[""]);m.hover=a.convertAttribs(d.hover,l.hover,m[""]);m.select=a.convertAttribs(d.select,l.select,m[""])}else m=l;j.pointAttr=m}},destroy:function(){var a=this,b=a.chart,c=/AppleWebKit\/533/.test(za),d,e=a.data||[],f,g,h;I(a,"destroy"); -X(a);p(a.axisTypes||[],function(b){if(h=a[b])pa(h.series,a),h.isDirty=h.forceRedraw=!0});a.legendItem&&a.chart.legend.destroyItem(a);for(d=e.length;d--;)(f=e[d])&&f.destroy&&f.destroy();a.points=null;clearTimeout(a.animationTimeout);for(g in a)a[g]instanceof O&&!a[g].survive&&(d=c&&g==="group"?"hide":"destroy",a[g][d]());if(b.hoverSeries===a)b.hoverSeries=null;pa(b.series,a);for(g in a)delete a[g]},getGraphPath:function(a,b,c){var d=this,e=d.options,f=e.step,g,h=[],i,a=a||d.points;(g=a.reversed)&& -a.reverse();(f={right:1,center:2}[f]||f&&3)&&g&&(f=4-f);e.connectNulls&&!b&&!c&&(a=this.getValidPoints(a));p(a,function(g,j){var l=g.plotX,m=g.plotY,n=a[j-1];if((g.leftCliff||n&&n.rightCliff)&&!c)i=!0;g.isNull&&!r(b)&&j>0?i=!e.connectNulls:g.isNull&&!b?i=!0:(j===0||i?n=["M",g.plotX,g.plotY]:d.getPointSpline?n=d.getPointSpline(a,g,j):f?(n=f===1?["L",n.plotX,m]:f===2?["L",(n.plotX+l)/2,n.plotY,"L",(n.plotX+l)/2,m]:["L",l,n.plotY],n.push("L",l,m)):n=["L",l,m],h.push.apply(h,n),i=!1)});return d.graphPath= -h},drawGraph:function(){var a=this,b=this.options,c=[["graph",b.lineColor||this.color,b.dashStyle]],d=b.lineWidth,e=b.linecap!=="square",f=(this.gappedPath||this.getGraphPath).call(this),g=this.fillGraph&&this.color||"none";p(this.zones,function(d,e){c.push(["zoneGraph"+e,d.color||a.color,d.dashStyle||b.dashStyle])});p(c,function(c,i){var k=c[0],j=a[k];if(j)j.animate({d:f});else if((d||g)&&f.length)j={stroke:c[1],"stroke-width":d,fill:g,zIndex:1},c[2]?j.dashstyle=c[2]:e&&(j["stroke-linecap"]=j["stroke-linejoin"]= -"round"),a[k]=a.chart.renderer.path(f).attr(j).add(a.group).shadow(i<2&&b.shadow)})},applyZones:function(){var a=this,b=this.chart,c=b.renderer,d=this.zones,e,f,g=this.clips||[],h,i=this.graph,k=this.area,j=t(b.chartWidth,b.chartHeight),l=this[(this.zoneAxis||"y")+"Axis"],m,n=l.reversed,q=b.inverted,z=l.horiz,s,r,w,v=!1;if(d.length&&(i||k)&&l.min!==y)i&&i.hide(),k&&k.hide(),m=l.getExtremes(),p(d,function(d,p){e=n?z?b.plotWidth:0:z?0:l.toPixels(m.min);e=F(t(o(f,e),0),j);f=F(t(B(l.toPixels(o(d.value, -m.max),!0)),0),j);v&&(e=f=l.toPixels(m.max));s=Math.abs(e-f);r=F(e,f);w=t(e,f);if(l.isXAxis){if(h={x:q?w:r,y:0,width:s,height:j},!z)h.x=b.plotHeight-h.x}else if(h={x:0,y:q?w:r,width:j,height:s},z)h.y=b.plotWidth-h.y;b.inverted&&c.isVML&&(h=l.isXAxis?{x:0,y:n?r:w,height:h.width,width:b.chartWidth}:{x:h.y-b.plotLeft-b.spacingBox.x,y:0,width:h.height,height:b.chartHeight});g[p]?g[p].animate(h):(g[p]=c.clipRect(h),i&&a["zoneGraph"+p].clip(g[p]),k&&a["zoneArea"+p].clip(g[p]));v=d.value>m.max}),this.clips= -g},invertGroups:function(){function a(){var a={width:b.yAxis.len,height:b.xAxis.len};p(["group","markerGroup"],function(c){b[c]&&b[c].attr(a).invert()})}var b=this,c=b.chart;if(b.xAxis)N(c,"resize",a),N(b,"destroy",function(){X(c,"resize",a)}),a(),b.invertGroups=a},plotGroup:function(a,b,c,d,e){var f=this[a],g=!f;g&&(this[a]=f=this.chart.renderer.g(b).attr({zIndex:d||0.1}).add(e),f.addClass("highcharts-series-"+this.index));f.attr({visibility:c})[g?"attr":"animate"](this.getPlotBox());return f},getPlotBox:function(){var a= -this.chart,b=this.xAxis,c=this.yAxis;if(a.inverted)b=c,c=this.xAxis;return{translateX:b?b.left:a.plotLeft,translateY:c?c.top:a.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this,b=a.chart,c,d=a.options,e=!!a.animate&&b.renderer.isSVG&&$a(d.animation).duration,f=a.visible?"inherit":"hidden",g=d.zIndex,h=a.hasRendered,i=b.seriesGroup;c=a.plotGroup("group","series",f,g,i);a.markerGroup=a.plotGroup("markerGroup","markers",f,g,i);e&&a.animate(!0);a.getAttribs();c.inverted=a.isCartesian?b.inverted: -!1;a.drawGraph&&(a.drawGraph(),a.applyZones());p(a.points,function(a){a.redraw&&a.redraw()});a.drawDataLabels&&a.drawDataLabels();a.visible&&a.drawPoints();a.drawTracker&&a.options.enableMouseTracking!==!1&&a.drawTracker();b.inverted&&a.invertGroups();d.clip!==!1&&!a.sharedClipKey&&!h&&c.clip(b.clipRect);e&&a.animate();if(!h)a.animationTimeout=Pa(function(){a.afterAnimate()},e);a.isDirty=a.isDirtyData=!1;a.hasRendered=!0},redraw:function(){var a=this.chart,b=this.isDirty||this.isDirtyData,c=this.group, -d=this.xAxis,e=this.yAxis;c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:o(d&&d.left,a.plotLeft),translateY:o(e&&e.top,a.plotTop)}));this.translate();this.render();b&&delete this.kdTree},kdDimensions:1,kdAxisArray:["clientX","plotY"],searchPoint:function(a,b){var c=this.xAxis,d=this.yAxis,e=this.chart.inverted;return this.searchKDTree({clientX:e?c.len-a.chartY+c.pos:a.chartX-c.pos,plotY:e?d.len-a.chartX+d.pos:a.chartY-d.pos},b)},buildKDTree:function(){function a(c, -e,f){var g,h;if(h=c&&c.length)return g=b.kdAxisArray[e%f],c.sort(function(a,b){return a[g]-b[g]}),h=Math.floor(h/2),{point:c[h],left:a(c.slice(0,h),e+1,f),right:a(c.slice(h+1),e+1,f)}}var b=this,c=b.kdDimensions;delete b.kdTree;Pa(function(){b.kdTree=a(b.getValidPoints(null,!b.directTouch),c,c)},b.options.kdNow?0:1)},searchKDTree:function(a,b){function c(a,b,k,j){var l=b.point,m=d.kdAxisArray[k%j],n,q,o=l;q=r(a[e])&&r(l[e])?Math.pow(a[e]-l[e],2):null;n=r(a[f])&&r(l[f])?Math.pow(a[f]-l[f],2):null; -n=(q||0)+(n||0);l.dist=r(n)?Math.sqrt(n):Number.MAX_VALUE;l.distX=r(q)?Math.sqrt(q):Number.MAX_VALUE;m=a[m]-l[m];n=m<0?"left":"right";q=m<0?"right":"left";b[n]&&(n=c(a,b[n],k+1,j),o=n[g]0&&this.singleStacks===!1&&(s.points[r][0]=s.points[this.index+","+v+",0"][0]);e==="percent"?(p=p?i:k,j&&m[p]&&m[p][v]?(p=m[p][v],s.total=p.total=t(p.total,s.total)+Q(u)||0):s.total=ca(s.total+(Q(u)||0))):s.total=ca(s.total+(u||0));s.cum=o(s.cum,g)+(u||0);if(u!==null)s.points[r].push(s.cum),c[w]=s.cum}if(e==="percent")l.usePercentage=!0;this.stackedYData=c;l.oldStacks= -{}}};R.prototype.setPercentStacks=function(){var a=this,b=a.stackKey,c=a.yAxis.stacks,d=a.processedXData,e;p([b,"-"+b],function(b){var f;for(var g=d.length,h,i;g--;)if(h=d[g],e=a.getStackIndicator(e,h,a.index),f=(i=c[b]&&c[b][h])&&i.points[e.key],h=f)i=i.total?100/i.total:0,h[0]=ca(h[0]*i),h[1]=ca(h[1]*i),a.stackedYData[g]=h[1]})};R.prototype.getStackIndicator=function(a,b,c){!r(a)||a.x!==b?a={x:b,index:0}:a.index++;a.key=[c,b,a.index].join(",");return a};u(gb.prototype,{addSeries:function(a,b,c){var d, -e=this;a&&(b=o(b,!0),I(e,"addSeries",{options:a},function(){d=e.initSeries(a);e.isDirtyLegend=!0;e.linkSeries();b&&e.redraw(c)}));return d},addAxis:function(a,b,c,d){var e=b?"xAxis":"yAxis",f=this.options,a=E(a,{index:this[e].length,isX:b});new ha(this,a);f[e]=ta(f[e]||{});f[e].push(a);o(c,!0)&&this.redraw(d)},showLoading:function(a){var b=this,c=b.options,d=b.loadingDiv,e=c.loading,f=function(){d&&M(d,{left:b.plotLeft+"px",top:b.plotTop+"px",width:b.plotWidth+"px",height:b.plotHeight+"px"})};if(!d)b.loadingDiv= -d=ba(Ma,{className:"highcharts-loading"},u(e.style,{zIndex:10,display:"none"}),b.container),b.loadingSpan=ba("span",null,e.labelStyle,d),N(b,"redraw",f);b.loadingSpan.innerHTML=a||c.lang.loading;if(!b.loadingShown)M(d,{opacity:0,display:""}),Wa(d,{opacity:e.style.opacity},{duration:e.showDuration||0}),b.loadingShown=!0;f()},hideLoading:function(){var a=this.options,b=this.loadingDiv;b&&Wa(b,{opacity:0},{duration:a.loading.hideDuration||100,complete:function(){M(b,{display:"none"})}});this.loadingShown= -!1}});u(Ja.prototype,{update:function(a,b,c,d){function e(){f.applyOptions(a);if(f.y===null&&h)f.graphic=h.destroy();if(Z(a)&&!Ea(a))f.redraw=function(){if(h&&h.element&&a&&a.marker&&a.marker.symbol)f.graphic=h.destroy();if(a&&a.dataLabels&&f.dataLabel)f.dataLabel=f.dataLabel.destroy();f.redraw=null};i=f.index;g.updateParallelArrays(f,i);if(l&&f.name)l[f.x]=f.name;j.data[i]=Z(j.data[i])&&!Ea(j.data[i])?f.options:a;g.isDirty=g.isDirtyData=!0;if(!g.fixedBox&&g.hasCartesianSeries)k.isDirtyBox=!0;if(j.legendType=== -"point")k.isDirtyLegend=!0;b&&k.redraw(c)}var f=this,g=f.series,h=f.graphic,i,k=g.chart,j=g.options,l=g.xAxis&&g.xAxis.names,b=o(b,!0);d===!1?e():f.firePointEvent("update",{options:a},e)},remove:function(a,b){this.series.removePoint(sa(this,this.series.data),a,b)}});u(R.prototype,{addPoint:function(a,b,c,d){var e=this,f=e.options,g=e.data,h=e.graph,i=e.area,k=e.chart,j=e.xAxis&&e.xAxis.names,l=h&&h.shift||0,m=["graph","area"],h=f.data,n,q=e.xData;Ta(d,k);if(c){for(d=e.zones.length;d--;)m.push("zoneGraph"+ -d,"zoneArea"+d);p(m,function(a){if(e[a])e[a].shift=l+(f.step?2:1)})}if(i)i.isArea=!0;b=o(b,!0);i={series:e};e.pointClass.prototype.applyOptions.apply(i,[a]);m=i.x;d=q.length;if(e.requireSorting&&mm;)d--;e.updateParallelArrays(i,"splice",d,0,0);e.updateParallelArrays(i,d);if(j&&i.name)j[m]=i.name;h.splice(d,0,a);n&&(e.data.splice(d,0,null),e.processData());f.legendType==="point"&&e.generatePoints();c&&(g[0]&&g[0].remove?g[0].remove(!1):(g.shift(),e.updateParallelArrays(i, -"shift"),h.shift()));e.isDirty=!0;e.isDirtyData=!0;b&&(e.getAttribs(),k.redraw())},removePoint:function(a,b,c){var d=this,e=d.data,f=e[a],g=d.points,h=d.chart,i=function(){g&&g.length===e.length&&g.splice(a,1);e.splice(a,1);d.options.data.splice(a,1);d.updateParallelArrays(f||{series:d},"splice",a,1);f&&f.destroy();d.isDirty=!0;d.isDirtyData=!0;b&&h.redraw()};Ta(c,h);b=o(b,!0);f?f.firePointEvent("remove",null,i):i()},remove:function(a,b){var c=this,d=c.chart;I(c,"remove",null,function(){c.destroy(); -d.isDirtyLegend=d.isDirtyBox=!0;d.linkSeries();o(a,!0)&&d.redraw(b)})},update:function(a,b){var c=this,d=this.chart,e=this.userOptions,f=this.type,g=L[f].prototype,h=["group","markerGroup","dataLabelsGroup"],i;if(a.type&&a.type!==f||a.zIndex!==void 0)h.length=0;p(h,function(a){h[a]=c[a];delete c[a]});a=E(e,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data},a);this.remove(!1);for(i in g)this[i]=y;u(this,L[a.type||f].prototype);p(h,function(a){c[a]=h[a]});this.init(d, -a);d.linkSeries();o(b,!0)&&d.redraw(!1)}});u(ha.prototype,{update:function(a,b){var c=this.chart,a=c.options[this.coll][this.options.index]=E(this.userOptions,a);this.destroy(!0);this._addedPlotLB=this.chart._labelPanes=y;this.init(c,u(a,{events:y}));c.isDirtyBox=!0;o(b,!0)&&c.redraw()},remove:function(a){for(var b=this.chart,c=this.coll,d=this.series,e=d.length;e--;)d[e]&&d[e].remove(!1);pa(b.axes,this);pa(b[c],this);b.options[c].splice(this.options.index,1);p(b[c],function(a,b){a.options.index= -b});this.destroy();b.isDirtyBox=!0;o(a,!0)&&b.redraw()},setTitle:function(a,b){this.update({title:a},b)},setCategories:function(a,b){this.update({categories:a},b)}});var wa=qa(R);L.line=wa;ea.area=E(ga,{softThreshold:!1,threshold:0});var oa=qa(R,{type:"area",singleStacks:!1,getStackPoints:function(){var a=[],b=[],c=this.xAxis,d=this.yAxis,e=d.stacks[this.stackKey],f={},g=this.points,h=this.index,i=d.series,k=i.length,j,l=o(d.options.reversedStacks,!0)?1:-1,m,n;if(this.options.stacking){for(m=0;m< -g.length;m++)f[g[m].x]=g[m];for(n in e)e[n].total!==null&&b.push(n);b.sort(function(a,b){return a-b});j=Ca(i,function(){return this.visible});p(b,function(g,i){var n=0,o,r;if(f[g]&&!f[g].isNull)a.push(f[g]),p([-1,1],function(a){var c=a===1?"rightNull":"leftNull",d=0,n=e[b[i+a]];if(n)for(m=h;m>=0&&m=0&&ma&&h>e?(h=t(a,e),k=2*e-h):hc&&k>e?(k=t(c,e),h=2*e-k):k0.5;b=Math.round(b)+f;d-=b;g&&d&&(b-=1,d+=1);return{x:a,y:b,width:c,height:d}},translate:function(){var a=this,b=a.chart,c=a.options,d=a.borderWidth=o(c.borderWidth,a.closestPointRange*a.xAxis.transA<2?0:1),e=a.yAxis,f= -a.translatedThreshold=e.getThreshold(c.threshold),g=o(c.minPointLength,5),h=a.getColumnMetrics(),i=h.width,k=a.barW=t(i,1+2*d),j=a.pointXOffset=h.offset;b.inverted&&(f-=0.5);c.pointPadding&&(k=ua(k));R.prototype.translate.apply(a);p(a.points,function(c){var d=F(o(c.yBottom,f),9E4),h=999+Q(d),h=F(t(-h,c.plotY),e.len+h),q=c.plotX+j,p=k,s=F(h,d),r,w=t(h,d)-s;Q(w)g?d-g:f-(r?g:0));c.barX=q;c.pointWidth=i;c.tooltipPos=b.inverted?[e.len+ -e.pos-b.plotLeft-h,a.xAxis.len-q-p/2,w]:[q+p/2,h+e.pos-b.plotTop,w];c.shapeType="rect";c.shapeArgs=a.crispCol(q,s,p,w)})},getSymbol:Aa,drawLegendSymbol:K.drawRectangle,drawGraph:Aa,drawPoints:function(){var a=this,b=this.chart,c=a.options,d=b.renderer,e=c.animationLimit||250,f,g;p(a.points,function(h){var i=h.graphic,k;if(J(h.plotY)&&h.y!==null)f=h.shapeArgs,k=r(a.borderWidth)?{"stroke-width":a.borderWidth}:{},g=h.pointAttr[h.selected?"select":""]||a.pointAttr[""],i?(Oa(i),i.attr(k).attr(g)[b.pointCount< -e?"animate":"attr"](E(f))):h.graphic=d[h.shapeType](f).attr(k).attr(g).add(h.group||a.group).shadow(c.shadow,null,c.stacking&&!c.borderRadius);else if(i)h.graphic=i.destroy()})},animate:function(a){var b=this,c=this.yAxis,d=b.options,e=this.chart.inverted,f={};if(fa)a?(f.scaleY=0.001,a=F(c.pos+c.len,t(c.pos,c.toPixels(d.threshold))),e?f.translateX=a-c.len:f.translateY=a,b.group.attr(f)):(f[e?"translateX":"translateY"]=c.pos,b.group.animate(f,u($a(b.options.animation),{step:function(a,c){b.group.attr({scaleY:t(0.001, -c.pos)})}})),b.animate=null)},remove:function(){var a=this,b=a.chart;b.hasRendered&&p(b.series,function(b){if(b.type===a.type)b.isDirty=!0});R.prototype.remove.apply(a,arguments)}});L.column=wa;ea.bar=E(ea.column);oa=qa(wa,{type:"bar",inverted:!0});L.bar=oa;ea.scatter=E(ga,{lineWidth:0,marker:{enabled:!0},tooltip:{headerFormat:'\u25cf {series.name}
',pointFormat:"x: {point.x}
y: {point.y}
"}}); -oa=qa(R,{type:"scatter",sorted:!1,requireSorting:!1,noSharedTooltip:!0,trackerGroups:["group","markerGroup","dataLabelsGroup"],takeOrdinalPosition:!1,kdDimensions:2,drawGraph:function(){this.options.lineWidth&&R.prototype.drawGraph.call(this)}});L.scatter=oa;ea.pie=E(ga,{borderColor:"#FFFFFF",borderWidth:1,center:[null,null],clip:!1,colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return this.y===null?void 0:this.point.name},x:0},ignoreHiddenPoint:!0,legendType:"point",marker:null, -size:null,showInLegend:!1,slicedOffset:10,states:{hover:{brightness:0.1,shadow:!1}},stickyTracking:!1,tooltip:{followPointer:!0}});ga={type:"pie",isCartesian:!1,pointClass:qa(Ja,{init:function(){Ja.prototype.init.apply(this,arguments);var a=this,b;a.name=o(a.name,"Slice");b=function(b){a.slice(b.type==="select")};N(a,"select",b);N(a,"unselect",b);return a},setVisible:function(a,b){var c=this,d=c.series,e=d.chart,f=d.options.ignoreHiddenPoint,b=o(b,f);if(a!==c.visible){c.visible=c.options.visible= -a=a===y?!c.visible:a;d.options.data[sa(c,d.data)]=c.options;p(["graphic","dataLabel","connector","shadowGroup"],function(b){if(c[b])c[b][a?"show":"hide"](!0)});c.legendItem&&e.legend.colorizeItem(c,a);!a&&c.state==="hover"&&c.setState("");if(f)d.isDirty=!0;b&&e.redraw()}},slice:function(a,b,c){var d=this.series;Ta(c,d.chart);o(b,!0);this.sliced=this.options.sliced=a=r(a)?a:!this.sliced;d.options.data[sa(this,d.data)]=this.options;a=a?this.slicedTranslation:{translateX:0,translateY:0};this.graphic.animate(a); -this.shadowGroup&&this.shadowGroup.animate(a)},haloPath:function(a){var b=this.shapeArgs,c=this.series.chart;return this.sliced||!this.visible?[]:this.series.chart.renderer.symbols.arc(c.plotLeft+b.x,c.plotTop+b.y,b.r+a,b.r+a,{innerR:this.shapeArgs.r,start:b.start,end:b.end})}}),requireSorting:!1,directTouch:!0,noSharedTooltip:!0,trackerGroups:["group","dataLabelsGroup"],axisTypes:[],pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color"},animate:function(a){var b=this, -c=b.points,d=b.startAngleRad;if(!a)p(c,function(a){var c=a.graphic,g=a.shapeArgs;c&&(c.attr({r:a.startR||b.center[3]/2,start:d,end:d}),c.animate({r:g.r,start:g.start,end:g.end},b.options.animation))}),b.animate=null},updateTotals:function(){var a,b=0,c=this.points,d=c.length,e,f=this.options.ignoreHiddenPoint;for(a=0;a0&&(e.visible||!f)?e.y/b*100:0,e.total=b},generatePoints:function(){R.prototype.generatePoints.call(this); -this.updateTotals()},translate:function(a){this.generatePoints();var b=0,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f,g,h,i=c.startAngle||0,k=this.startAngleRad=ra/180*(i-90),i=(this.endAngleRad=ra/180*(o(c.endAngle,i+360)-90))-k,j=this.points,l=c.dataLabels.distance,c=c.ignoreHiddenPoint,m,n=j.length,q;if(!a)this.center=a=this.getCenter();this.getX=function(b,c){h=Y.asin(F((b-a[1])/(a[2]/2+l),1));return a[0]+(c?-1:1)*W(h)*(a[2]/2+l)};for(m=0;m1.5*ra?h-=2*ra:h<-ra/2&&(h+=2*ra);q.slicedTranslation={translateX:B(W(h)*d),translateY:B(da(h)*d)};f=W(h)*a[2]/2;g=da(h)*a[2]/2;q.tooltipPos=[a[0]+f*0.7,a[1]+g*0.7];q.half=h<-ra/2||h>ra/2?1:0;q.angle=h;e=F(e,l/2);q.labelPos=[a[0]+f+W(h)*l,a[1]+g+da(h)*l,a[0]+f+W(h)*e,a[1]+g+da(h)*e,a[0]+f,a[1]+g,l<0?"center":q.half?"right":"left",h]}},drawGraph:null,drawPoints:function(){var a= -this,b=a.chart.renderer,c,d,e=a.options.shadow,f,g,h,i;if(e&&!a.shadowGroup)a.shadowGroup=b.g("shadow").add(a.group);p(a.points,function(k){if(k.y!==null){d=k.graphic;h=k.shapeArgs;f=k.shadowGroup;g=k.pointAttr[k.selected?"select":""];if(!g.stroke)g.stroke=g.fill;if(e&&!f)f=k.shadowGroup=b.g("shadow").add(a.shadowGroup);c=k.sliced?k.slicedTranslation:{translateX:0,translateY:0};f&&f.attr(c);if(d)d.setRadialReference(a.center).attr(g).animate(u(h,c));else{i={"stroke-linejoin":"round"};if(!k.visible)i.visibility= -"hidden";k.graphic=d=b[k.shapeType](h).setRadialReference(a.center).attr(g).attr(i).attr(c).add(a.group).shadow(e,f)}}})},searchPoint:Aa,sortByAngle:function(a,b){a.sort(function(a,d){return a.angle!==void 0&&(d.angle-a.angle)*b})},drawLegendSymbol:K.drawRectangle,getCenter:Bb.getCenter,getSymbol:Aa};ga=qa(R,ga);L.pie=ga;R.prototype.drawDataLabels=function(){var a=this,b=a.options,c=b.cursor,d=b.dataLabels,e=a.points,f,g,h=a.hasRendered||0,i,k,j=o(d.defer,!0),l=a.chart.renderer;if(d.enabled||a._hasPointLabels)a.dlProcessOptions&& -a.dlProcessOptions(d),k=a.plotGroup("dataLabelsGroup","data-labels",j&&!h?"hidden":"visible",d.zIndex||6),j&&(k.attr({opacity:+h}),h||N(a,"afterAnimate",function(){a.visible&&k.show();k[b.animation?"animate":"attr"]({opacity:1},{duration:200})})),g=d,p(e,function(e){var h,j=e.dataLabel,p,s,t=e.connector,w=!0,v,x={};f=e.dlOptions||e.options&&e.options.dataLabels;h=o(f&&f.enabled,g.enabled)&&e.y!==null;if(j&&!h)e.dataLabel=j.destroy();else if(h){d=E(g,f);v=d.style;h=d.rotation;p=e.getLabelConfig(); -i=d.format?Ka(d.format,p):d.formatter.call(p,d);v.color=o(d.color,v.color,a.color,"black");if(j)if(r(i))j.attr({text:i}),w=!1;else{if(e.dataLabel=j=j.destroy(),t)e.connector=t.destroy()}else if(r(i)){j={fill:d.backgroundColor,stroke:d.borderColor,"stroke-width":d.borderWidth,r:d.borderRadius||0,rotation:h,padding:d.padding,zIndex:1};if(v.color==="contrast")x.color=d.inside||d.distance<0||b.stacking?l.getContrast(e.color||a.color):"#000000";if(c)x.cursor=c;for(s in j)j[s]===y&&delete j[s];j=e.dataLabel= -l[h?"text":"label"](i,0,-9999,d.shape,null,null,d.useHTML).attr(j).css(u(v,x)).add(k).shadow(d.shadow)}j&&a.alignDataLabel(e,j,d,null,w)}})};R.prototype.alignDataLabel=function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=o(a.plotX,-9999),i=o(a.plotY,-9999),k=b.getBBox(),j=f.renderer.fontMetrics(c.style.fontSize).b,l=c.rotation,m=c.align,n=this.visible&&(a.series.forceDL||f.isInsidePlot(h,B(i),g)||d&&f.isInsidePlot(h,g?d.x+1:d.y+d.height-1,g)),q=o(c.overflow,"justify")==="justify";if(n)d=u({x:g?f.plotWidth- -i:h,y:B(g?f.plotHeight-h:i),width:0,height:0},d),u(c,{width:k.width,height:k.height}),l?(q=!1,g=f.renderer.rotCorr(j,l),g={x:d.x+c.x+d.width/2+g.x,y:d.y+c.y+{top:0,middle:0.5,bottom:1}[c.verticalAlign]*d.height},b[e?"attr":"animate"](g).attr({align:m}),h=(l+720)%360,h=h>180&&h<360,m==="left"?g.y-=h?k.height:0:m==="center"?(g.x-=k.width/2,g.y-=k.height/2):m==="right"&&(g.x-=k.width,g.y-=h?0:k.height)):(b.align(c,null,d),g=b.alignAttr),q?this.justifyDataLabel(b,c,g,k,d,e):o(c.crop,!0)&&(n=f.isInsidePlot(g.x, -g.y)&&f.isInsidePlot(g.x+k.width,g.y+k.height)),c.shape&&!l&&b.attr({anchorX:a.plotX,anchorY:a.plotY});if(!n)Oa(b),b.attr({y:-9999}),b.placed=!1};R.prototype.justifyDataLabel=function(a,b,c,d,e,f){var g=this.chart,h=b.align,i=b.verticalAlign,k,j,l=a.box?0:a.padding||0;k=c.x+l;if(k<0)h==="right"?b.align="left":b.x=-k,j=!0;k=c.x+d.width-l;if(k>g.plotWidth)h==="left"?b.align="right":b.x=g.plotWidth-k,j=!0;k=c.y+l;if(k<0)i==="bottom"?b.verticalAlign="top":b.y=-k,j=!0;k=c.y+d.height-l;if(k>g.plotHeight)i=== -"top"?b.verticalAlign="bottom":b.y=g.plotHeight-k,j=!0;if(j)a.placed=!f,a.align(b,null,e)};if(L.pie)L.pie.prototype.drawDataLabels=function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=o(e.connectorPadding,10),g=o(e.connectorWidth,1),h=d.plotWidth,i=d.plotHeight,k,j,l=o(e.softConnector,!0),m=e.distance,n=a.center,q=n[2]/2,r=n[1],s=m>0,u,w,v,x=[[],[]],y,A,D,E,C,H=[0,0,0,0],M=function(a,b){return b.y-a.y};if(a.visible&&(e.enabled||a._hasPointLabels)){R.prototype.drawDataLabels.apply(a); -p(b,function(a){if(a.dataLabel&&a.visible)x[a.half].push(a),a.dataLabel._pos=null});for(E=2;E--;){var I=[],N=[],J=x[E],L=J.length,K;if(L){a.sortByAngle(J,E-0.5);for(C=b=0;!b&&J[C];)b=J[C]&&J[C].dataLabel&&(J[C].dataLabel.getBBox().height||21),C++;if(m>0){w=F(r+q+m,d.plotHeight);for(C=t(0,r-q-m);C<=w;C+=b)I.push(C);w=I.length;if(L>w){c=[].concat(J);c.sort(M);for(C=L;C--;)c[C].rank=C;for(C=L;C--;)J[C].rank>=w&&J.splice(C,1);L=J.length}for(C=0;C0){if(w=N.pop(),K=w.i,A=w.y,c>A&&I[K+1]!==null||ch-f&&(H[1]=t(B(y+w-h+f),H[1])),A-b/2<0?H[0]=t(B(-A+b/2),H[0]):A+b/2>i&&(H[2]=t(B(A+b/2-i),H[2]))}}}if(Ga(H)===0||this.verifyDataLabelOverflow(H))this.placeDataLabels(),s&&g&&p(this.points,function(b){k=b.connector;v=b.labelPos;if((u=b.dataLabel)&&u._pos&&b.visible)D=u._attr.visibility,y=u.connX,A=u.connY,j=l?["M",y+(v[6]==="left"?5:-5),A,"C",y,A,2*v[2]-v[4],2*v[3]-v[5],v[2],v[3],"L",v[4],v[5]]:["M",y+ -(v[6]==="left"?5:-5),A,"L",v[2],v[3],"L",v[4],v[5]],k?(k.animate({d:j}),k.attr("visibility",D)):b.connector=k=a.chart.renderer.path(j).attr({"stroke-width":g,stroke:e.connectorColor||b.color||"#606060",visibility:D}).add(a.dataLabelsGroup);else if(k)b.connector=k.destroy()})}},L.pie.prototype.placeDataLabels=function(){p(this.points,function(a){var b=a.dataLabel;if(b&&a.visible)(a=b._pos)?(b.attr(b._attr),b[b.moved?"animate":"attr"](a),b.moved=!0):b&&b.attr({y:-9999})})},L.pie.prototype.alignDataLabel= -Aa,L.pie.prototype.verifyDataLabelOverflow=function(a){var b=this.center,c=this.options,d=c.center,e=c.minSize||80,f=e,g;d[0]!==null?f=t(b[2]-t(a[1],a[3]),e):(f=t(b[2]-a[1]-a[3],e),b[0]+=(a[3]-a[1])/2);d[1]!==null?f=t(F(f,b[2]-t(a[0],a[2])),e):(f=t(F(f,b[2]-a[0]-a[2]),e),b[1]+=(a[0]-a[2])/2);fo(this.translatedThreshold,g.yAxis.len)),k=o(c.inside,!!this.options.stacking);if(h){d=E(h);if(d.y<0)d.height+=d.y,d.y=0;h=d.y+d.height-g.yAxis.len;h>0&&(d.height-=h);f&&(d={x:g.yAxis.len-d.y-d.height,y:g.xAxis.len-d.x-d.width,width:d.height,height:d.width});if(!k)f?(d.x+=i?0:d.width,d.width=0):(d.y+=i?d.height:0,d.height=0)}c.align=o(c.align,!f||k?"center":i?"right":"left");c.verticalAlign=o(c.verticalAlign, -f||k?"middle":i?"top":"bottom");R.prototype.alignDataLabel.call(this,a,b,c,d,e)};(function(a){var b=a.Chart,c=a.each,d=a.pick,e=a.addEvent;b.prototype.callbacks.push(function(a){function b(){var e=[];c(a.series,function(a){var b=a.options.dataLabels,f=a.dataLabelCollections||["dataLabel"];(b.enabled||a._hasPointLabels)&&!b.allowOverlap&&a.visible&&c(f,function(b){c(a.points,function(a){if(a[b])a[b].labelrank=d(a.labelrank,a.shapeArgs&&a.shapeArgs.height),e.push(a[b])})})});a.hideOverlappingLabels(e)} -b();e(a,"redraw",b)});b.prototype.hideOverlappingLabels=function(a){var b=a.length,d,e,k,j,l,m,n,q,o;for(e=0;el.x+n.translateX+(k.width-o)||m.x+q.translateX+(j.width- -o)l.y+n.translateY+(k.height-o)||m.y+q.translateY+(j.height-o)h;if(b.series.length&&(i||l>F(j.dataMin,j.min))&&(!i||k
/g, '
') - .split(//g); - - } else { - lines = [textStr]; - } - - - // Trim empty lines (#5261) - lines = grep(lines, function (line) { - return line !== ''; - }); - - - // build the lines - each(lines, function buildTextLines(line, lineNo) { - var spans, - spanNo = 0; - line = line - .replace(/^\s+|\s+$/g, '') // Trim to prevent useless/costly process on the spaces (#5258) - .replace(//g, '|||'); - spans = line.split('|||'); - - each(spans, function buildTextSpans(span) { - if (span !== '' || spans.length === 1) { - var attributes = {}, - tspan = doc.createElementNS(SVG_NS, 'tspan'), - spanStyle; // #390 - if (styleRegex.test(span)) { - spanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2'); - attr(tspan, 'style', spanStyle); - } - if (hrefRegex.test(span) && !forExport) { // Not for export - #1529 - attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"'); - css(tspan, { cursor: 'pointer' }); - } - - span = unescapeAngleBrackets(span.replace(/<(.|\n)*?>/g, '') || ' '); - - // Nested tags aren't supported, and cause crash in Safari (#1596) - if (span !== ' ') { - - // add the text node - tspan.appendChild(doc.createTextNode(span)); - - if (!spanNo) { // first span in a line, align it to the left - if (lineNo && parentX !== null) { - attributes.x = parentX; - } - } else { - attributes.dx = 0; // #16 - } - - // add attributes - attr(tspan, attributes); - - // Append it - textNode.appendChild(tspan); - - // first span on subsequent line, add the line height - if (!spanNo && lineNo) { - - // allow getting the right offset height in exporting in IE - if (!hasSVG && forExport) { - css(tspan, { display: 'block' }); - } - - // Set the line height based on the font size of either - // the text element or the tspan element - attr( - tspan, - 'dy', - getLineHeight(tspan) - ); - } - - /*if (width) { - renderer.breakText(wrapper, width); - }*/ - - // Check width and apply soft breaks or ellipsis - if (width) { - var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273 - hasWhiteSpace = spans.length > 1 || lineNo || (words.length > 1 && textStyles.whiteSpace !== 'nowrap'), - tooLong, - actualWidth, - rest = [], - dy = getLineHeight(tspan), - softLineNo = 1, - rotation = wrapper.rotation, - wordStr = span, // for ellipsis - cursor = wordStr.length, // binary search cursor - bBox; - - while ((hasWhiteSpace || ellipsis) && (words.length || rest.length)) { - wrapper.rotation = 0; // discard rotation when computing box - bBox = wrapper.getBBox(true); - actualWidth = bBox.width; - - // Old IE cannot measure the actualWidth for SVG elements (#2314) - if (!hasSVG && renderer.forExport) { - actualWidth = renderer.measureSpanWidth(tspan.firstChild.data, wrapper.styles); - } - - tooLong = actualWidth > width; - - // For ellipsis, do a binary search for the correct string length - if (wasTooLong === undefined) { - wasTooLong = tooLong; // First time - } - if (ellipsis && wasTooLong) { - cursor /= 2; - - if (wordStr === '' || (!tooLong && cursor < 0.5)) { - words = []; // All ok, break out - } else { - wordStr = span.substring(0, wordStr.length + (tooLong ? -1 : 1) * mathCeil(cursor)); - words = [wordStr + (width > 3 ? '\u2026' : '')]; - tspan.removeChild(tspan.firstChild); - } - - // Looping down, this is the first word sequence that is not too long, - // so we can move on to build the next line. - } else if (!tooLong || words.length === 1) { - words = rest; - rest = []; - - if (words.length) { - softLineNo++; - - tspan = doc.createElementNS(SVG_NS, 'tspan'); - attr(tspan, { - dy: dy, - x: parentX - }); - if (spanStyle) { // #390 - attr(tspan, 'style', spanStyle); - } - textNode.appendChild(tspan); - } - if (actualWidth > width) { // a single word is pressing it out - width = actualWidth; - } - } else { // append to existing line tspan - tspan.removeChild(tspan.firstChild); - rest.unshift(words.pop()); - } - if (words.length) { - tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-'))); - } - } - wrapper.rotation = rotation; - } - - spanNo++; - } - } - }); - }); - - if (wasTooLong) { - wrapper.attr('title', wrapper.textStr); - } - if (tempParent) { - tempParent.removeChild(textNode); // attach it to the DOM to read offset width - } - - // Apply the text shadow - if (textShadow && wrapper.applyTextShadow) { - wrapper.applyTextShadow(textShadow); - } - } - }, - - - - /* - breakText: function (wrapper, width) { - var bBox = wrapper.getBBox(), - node = wrapper.element, - textLength = node.textContent.length, - pos = mathRound(width * textLength / bBox.width), // try this position first, based on average character width - increment = 0, - finalPos; - - if (bBox.width > width) { - while (finalPos === undefined) { - textLength = node.getSubStringLength(0, pos); - - if (textLength <= width) { - if (increment === -1) { - finalPos = pos; - } else { - increment = 1; - } - } else { - if (increment === 1) { - finalPos = pos - 1; - } else { - increment = -1; - } - } - pos += increment; - } - } - console.log('width', width, 'stringWidth', node.getSubStringLength(0, finalPos)) - }, - */ - - /** - * Returns white for dark colors and black for bright colors - */ - getContrast: function (color) { - color = Color(color).rgba; - return color[0] + color[1] + color[2] > 384 ? '#000000' : '#FFFFFF'; - }, - - /** - * Create a button with preset states - * @param {String} text - * @param {Number} x - * @param {Number} y - * @param {Function} callback - * @param {Object} normalState - * @param {Object} hoverState - * @param {Object} pressedState - */ - button: function (text, x, y, callback, normalState, hoverState, pressedState, disabledState, shape) { - var label = this.label(text, x, y, shape, null, null, null, null, 'button'), - curState = 0, - stateOptions, - stateStyle, - normalStyle, - hoverStyle, - pressedStyle, - disabledStyle, - verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 }; - - // Normal state - prepare the attributes - normalState = merge({ - 'stroke-width': 1, - stroke: '#CCCCCC', - fill: { - linearGradient: verticalGradient, - stops: [ - [0, '#FEFEFE'], - [1, '#F6F6F6'] - ] - }, - r: 2, - padding: 5, - style: { - color: 'black' - } - }, normalState); - normalStyle = normalState.style; - delete normalState.style; - - // Hover state - hoverState = merge(normalState, { - stroke: '#68A', - fill: { - linearGradient: verticalGradient, - stops: [ - [0, '#FFF'], - [1, '#ACF'] - ] - } - }, hoverState); - hoverStyle = hoverState.style; - delete hoverState.style; - - // Pressed state - pressedState = merge(normalState, { - stroke: '#68A', - fill: { - linearGradient: verticalGradient, - stops: [ - [0, '#9BD'], - [1, '#CDF'] - ] - } - }, pressedState); - pressedStyle = pressedState.style; - delete pressedState.style; - - // Disabled state - disabledState = merge(normalState, { - style: { - color: '#CCC' - } - }, disabledState); - disabledStyle = disabledState.style; - delete disabledState.style; - - // Add the events. IE9 and IE10 need mouseover and mouseout to funciton (#667). - addEvent(label.element, isMS ? 'mouseover' : 'mouseenter', function () { - if (curState !== 3) { - label.attr(hoverState) - .css(hoverStyle); - } - }); - addEvent(label.element, isMS ? 'mouseout' : 'mouseleave', function () { - if (curState !== 3) { - stateOptions = [normalState, hoverState, pressedState][curState]; - stateStyle = [normalStyle, hoverStyle, pressedStyle][curState]; - label.attr(stateOptions) - .css(stateStyle); - } - }); - - label.setState = function (state) { - label.state = curState = state; - if (!state) { - label.attr(normalState) - .css(normalStyle); - } else if (state === 2) { - label.attr(pressedState) - .css(pressedStyle); - } else if (state === 3) { - label.attr(disabledState) - .css(disabledStyle); - } - }; - - return label - .on('click', function (e) { - if (curState !== 3) { - callback.call(label, e); - } - }) - .attr(normalState) - .css(extend({ cursor: 'default' }, normalStyle)); - }, - - /** - * Make a straight line crisper by not spilling out to neighbour pixels - * @param {Array} points - * @param {Number} width - */ - crispLine: function (points, width) { - // points format: [M, 0, 0, L, 100, 0] - // normalize to a crisp line - if (points[1] === points[4]) { - // Substract due to #1129. Now bottom and left axis gridlines behave the same. - points[1] = points[4] = mathRound(points[1]) - (width % 2 / 2); - } - if (points[2] === points[5]) { - points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2); - } - return points; - }, - - - /** - * Draw a path - * @param {Array} path An SVG path in array form - */ - path: function (path) { - var attr = { - fill: NONE - }; - if (isArray(path)) { - attr.d = path; - } else if (isObject(path)) { // attributes - extend(attr, path); - } - return this.createElement('path').attr(attr); - }, - - /** - * Draw and return an SVG circle - * @param {Number} x The x position - * @param {Number} y The y position - * @param {Number} r The radius - */ - circle: function (x, y, r) { - var attr = isObject(x) ? x : { x: x, y: y, r: r }, - wrapper = this.createElement('circle'); - - // Setting x or y translates to cx and cy - wrapper.xSetter = wrapper.ySetter = function (value, key, element) { - element.setAttribute('c' + key, value); - }; - - return wrapper.attr(attr); - }, - - /** - * Draw and return an arc - * @param {Number} x X position - * @param {Number} y Y position - * @param {Number} r Radius - * @param {Number} innerR Inner radius like used in donut charts - * @param {Number} start Starting angle - * @param {Number} end Ending angle - */ - arc: function (x, y, r, innerR, start, end) { - var arc; - - if (isObject(x)) { - y = x.y; - r = x.r; - innerR = x.innerR; - start = x.start; - end = x.end; - x = x.x; - } - - // Arcs are defined as symbols for the ability to set - // attributes in attr and animate - arc = this.symbol('arc', x || 0, y || 0, r || 0, r || 0, { - innerR: innerR || 0, - start: start || 0, - end: end || 0 - }); - arc.r = r; // #959 - return arc; - }, - - /** - * Draw and return a rectangle - * @param {Number} x Left position - * @param {Number} y Top position - * @param {Number} width - * @param {Number} height - * @param {Number} r Border corner radius - * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing - */ - rect: function (x, y, width, height, r, strokeWidth) { - - r = isObject(x) ? x.r : r; - - var wrapper = this.createElement('rect'), - attribs = isObject(x) ? x : x === UNDEFINED ? {} : { - x: x, - y: y, - width: mathMax(width, 0), - height: mathMax(height, 0) - }; - - if (strokeWidth !== UNDEFINED) { - wrapper.strokeWidth = strokeWidth; - attribs = wrapper.crisp(attribs); - } - - if (r) { - attribs.r = r; - } - - wrapper.rSetter = function (value, key, element) { - attr(element, { - rx: value, - ry: value - }); - }; - - return wrapper.attr(attribs); - }, - - /** - * Resize the box and re-align all aligned elements - * @param {Object} width - * @param {Object} height - * @param {Boolean} animate - * - */ - setSize: function (width, height, animate) { - var renderer = this, - alignedObjects = renderer.alignedObjects, - i = alignedObjects.length; - - renderer.width = width; - renderer.height = height; - - renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({ - width: width, - height: height - }); - - while (i--) { - alignedObjects[i].align(); - } - }, - - /** - * Create a group - * @param {String} name The group will be given a class name of 'highcharts-{name}'. - * This can be used for styling and scripting. - */ - g: function (name) { - var elem = this.createElement('g'); - return defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem; - }, - - /** - * Display an image - * @param {String} src - * @param {Number} x - * @param {Number} y - * @param {Number} width - * @param {Number} height - */ - image: function (src, x, y, width, height) { - var attribs = { - preserveAspectRatio: NONE - }, - elemWrapper; - - // optional properties - if (arguments.length > 1) { - extend(attribs, { - x: x, - y: y, - width: width, - height: height - }); - } - - elemWrapper = this.createElement('image').attr(attribs); - - // set the href in the xlink namespace - if (elemWrapper.element.setAttributeNS) { - elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink', - 'href', src); - } else { - // could be exporting in IE - // using href throws "not supported" in ie7 and under, requries regex shim to fix later - elemWrapper.element.setAttribute('hc-svg-href', src); - } - return elemWrapper; - }, - - /** - * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object. - * - * @param {Object} symbol - * @param {Object} x - * @param {Object} y - * @param {Object} radius - * @param {Object} options - */ - symbol: function (symbol, x, y, width, height, options) { - - var ren = this, - obj, - - // get the symbol definition function - symbolFn = this.symbols[symbol], - - // check if there's a path defined for this symbol - path = symbolFn && symbolFn( - mathRound(x), - mathRound(y), - width, - height, - options - ), - - imageRegex = /^url\((.*?)\)$/, - imageSrc, - imageSize, - centerImage; - - if (path) { - - obj = this.path(path); - // expando properties for use in animate and attr - extend(obj, { - symbolName: symbol, - x: x, - y: y, - width: width, - height: height - }); - if (options) { - extend(obj, options); - } - - - // image symbols - } else if (imageRegex.test(symbol)) { - - // On image load, set the size and position - centerImage = function (img, size) { - if (img.element) { // it may be destroyed in the meantime (#1390) - img.attr({ - width: size[0], - height: size[1] - }); - - if (!img.alignByTranslate) { // #185 - img.translate( - mathRound((width - size[0]) / 2), // #1378 - mathRound((height - size[1]) / 2) - ); - } - } - }; - - imageSrc = symbol.match(imageRegex)[1]; - imageSize = symbolSizes[imageSrc] || (options && options.width && options.height && [options.width, options.height]); - - // Ireate the image synchronously, add attribs async - obj = this.image(imageSrc) - .attr({ - x: x, - y: y - }); - obj.isImg = true; - - if (imageSize) { - centerImage(obj, imageSize); - } else { - // Initialize image to be 0 size so export will still function if there's no cached sizes. - obj.attr({ width: 0, height: 0 }); - - // Create a dummy JavaScript image to get the width and height. Due to a bug in IE < 8, - // the created element must be assigned to a variable in order to load (#292). - createElement('img', { - onload: function () { - - // Special case for SVGs on IE11, the width is not accessible until the image is - // part of the DOM (#2854). - if (this.width === 0) { - css(this, { - position: ABSOLUTE, - top: '-999em' - }); - doc.body.appendChild(this); - } - - // Center the image - centerImage(obj, symbolSizes[imageSrc] = [this.width, this.height]); - - // Clean up after #2854 workaround. - if (this.parentNode) { - this.parentNode.removeChild(this); - } - - // Fire the load event when all external images are loaded - ren.imgCount--; - if (!ren.imgCount && charts[ren.chartIndex].onload) { - charts[ren.chartIndex].onload(); - } - }, - src: imageSrc - }); - this.imgCount++; - } - } - - return obj; - }, - - /** - * An extendable collection of functions for defining symbol paths. - */ - symbols: { - 'circle': function (x, y, w, h) { - var cpw = 0.166 * w; - return [ - M, x + w / 2, y, - 'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h, - 'C', x - cpw, y + h, x - cpw, y, x + w / 2, y, - 'Z' - ]; - }, - - 'square': function (x, y, w, h) { - return [ - M, x, y, - L, x + w, y, - x + w, y + h, - x, y + h, - 'Z' - ]; - }, - - 'triangle': function (x, y, w, h) { - return [ - M, x + w / 2, y, - L, x + w, y + h, - x, y + h, - 'Z' - ]; - }, - - 'triangle-down': function (x, y, w, h) { - return [ - M, x, y, - L, x + w, y, - x + w / 2, y + h, - 'Z' - ]; - }, - 'diamond': function (x, y, w, h) { - return [ - M, x + w / 2, y, - L, x + w, y + h / 2, - x + w / 2, y + h, - x, y + h / 2, - 'Z' - ]; - }, - 'arc': function (x, y, w, h, options) { - var start = options.start, - radius = options.r || w || h, - end = options.end - 0.001, // to prevent cos and sin of start and end from becoming equal on 360 arcs (related: #1561) - innerRadius = options.innerR, - open = options.open, - cosStart = mathCos(start), - sinStart = mathSin(start), - cosEnd = mathCos(end), - sinEnd = mathSin(end), - longArc = options.end - start < mathPI ? 0 : 1; - - return [ - M, - x + radius * cosStart, - y + radius * sinStart, - 'A', // arcTo - radius, // x radius - radius, // y radius - 0, // slanting - longArc, // long or short arc - 1, // clockwise - x + radius * cosEnd, - y + radius * sinEnd, - open ? M : L, - x + innerRadius * cosEnd, - y + innerRadius * sinEnd, - 'A', // arcTo - innerRadius, // x radius - innerRadius, // y radius - 0, // slanting - longArc, // long or short arc - 0, // clockwise - x + innerRadius * cosStart, - y + innerRadius * sinStart, - - open ? '' : 'Z' // close - ]; - }, - - /** - * Callout shape used for default tooltips, also used for rounded rectangles in VML - */ - callout: function (x, y, w, h, options) { - var arrowLength = 6, - halfDistance = 6, - r = mathMin((options && options.r) || 0, w, h), - safeDistance = r + halfDistance, - anchorX = options && options.anchorX, - anchorY = options && options.anchorY, - path; - - path = [ - 'M', x + r, y, - 'L', x + w - r, y, // top side - 'C', x + w, y, x + w, y, x + w, y + r, // top-right corner - 'L', x + w, y + h - r, // right side - 'C', x + w, y + h, x + w, y + h, x + w - r, y + h, // bottom-right corner - 'L', x + r, y + h, // bottom side - 'C', x, y + h, x, y + h, x, y + h - r, // bottom-left corner - 'L', x, y + r, // left side - 'C', x, y, x, y, x + r, y // top-right corner - ]; - - if (anchorX && anchorX > w && anchorY > y + safeDistance && anchorY < y + h - safeDistance) { // replace right side - path.splice(13, 3, - 'L', x + w, anchorY - halfDistance, - x + w + arrowLength, anchorY, - x + w, anchorY + halfDistance, - x + w, y + h - r - ); - } else if (anchorX && anchorX < 0 && anchorY > y + safeDistance && anchorY < y + h - safeDistance) { // replace left side - path.splice(33, 3, - 'L', x, anchorY + halfDistance, - x - arrowLength, anchorY, - x, anchorY - halfDistance, - x, y + r - ); - } else if (anchorY && anchorY > h && anchorX > x + safeDistance && anchorX < x + w - safeDistance) { // replace bottom - path.splice(23, 3, - 'L', anchorX + halfDistance, y + h, - anchorX, y + h + arrowLength, - anchorX - halfDistance, y + h, - x + r, y + h - ); - } else if (anchorY && anchorY < 0 && anchorX > x + safeDistance && anchorX < x + w - safeDistance) { // replace top - path.splice(3, 3, - 'L', anchorX - halfDistance, y, - anchorX, y - arrowLength, - anchorX + halfDistance, y, - w - r, y - ); - } - return path; - } - }, - - /** - * Define a clipping rectangle - * @param {String} id - * @param {Number} x - * @param {Number} y - * @param {Number} width - * @param {Number} height - */ - clipRect: function (x, y, width, height) { - var wrapper, - id = PREFIX + idCounter++, - - clipPath = this.createElement('clipPath').attr({ - id: id - }).add(this.defs); - - wrapper = this.rect(x, y, width, height, 0).add(clipPath); - wrapper.id = id; - wrapper.clipPath = clipPath; - wrapper.count = 0; - - return wrapper; - }, - - - - - - /** - * Add text to the SVG object - * @param {String} str - * @param {Number} x Left position - * @param {Number} y Top position - * @param {Boolean} useHTML Use HTML to render the text - */ - text: function (str, x, y, useHTML) { - - // declare variables - var renderer = this, - fakeSVG = useCanVG || (!hasSVG && renderer.forExport), - wrapper, - attr = {}; - - if (useHTML && (renderer.allowHTML || !renderer.forExport)) { - return renderer.html(str, x, y); - } - - attr.x = Math.round(x || 0); // X is always needed for line-wrap logic - if (y) { - attr.y = Math.round(y); - } - if (str || str === 0) { - attr.text = str; - } - - wrapper = renderer.createElement('text') - .attr(attr); - - // Prevent wrapping from creating false offsetWidths in export in legacy IE (#1079, #1063) - if (fakeSVG) { - wrapper.css({ - position: ABSOLUTE - }); - } - - if (!useHTML) { - wrapper.xSetter = function (value, key, element) { - var tspans = element.getElementsByTagName('tspan'), - tspan, - parentVal = element.getAttribute(key), - i; - for (i = 0; i < tspans.length; i++) { - tspan = tspans[i]; - // If the x values are equal, the tspan represents a linebreak - if (tspan.getAttribute(key) === parentVal) { - tspan.setAttribute(key, value); - } - } - element.setAttribute(key, value); - }; - } - - return wrapper; - }, - - /** - * Utility to return the baseline offset and total line height from the font size - */ - fontMetrics: function (fontSize, elem) { - var lineHeight, - baseline, - style; - - fontSize = fontSize || this.style.fontSize; - if (!fontSize && elem && win.getComputedStyle) { - elem = elem.element || elem; // SVGElement - style = win.getComputedStyle(elem, ''); - fontSize = style && style.fontSize; // #4309, the style doesn't exist inside a hidden iframe in Firefox - } - fontSize = /px/.test(fontSize) ? pInt(fontSize) : /em/.test(fontSize) ? parseFloat(fontSize) * 12 : 12; - - // Empirical values found by comparing font size and bounding box height. - // Applies to the default font family. http://jsfiddle.net/highcharts/7xvn7/ - lineHeight = fontSize < 24 ? fontSize + 3 : mathRound(fontSize * 1.2); - baseline = mathRound(lineHeight * 0.8); - - return { - h: lineHeight, - b: baseline, - f: fontSize - }; - }, - - /** - * Correct X and Y positioning of a label for rotation (#1764) - */ - rotCorr: function (baseline, rotation, alterY) { - var y = baseline; - if (rotation && alterY) { - y = mathMax(y * mathCos(rotation * deg2rad), 4); - } - return { - x: (-baseline / 3) * mathSin(rotation * deg2rad), - y: y - }; - }, - - /** - * Add a label, a text item that can hold a colored or gradient background - * as well as a border and shadow. - * @param {string} str - * @param {Number} x - * @param {Number} y - * @param {String} shape - * @param {Number} anchorX In case the shape has a pointer, like a flag, this is the - * coordinates it should be pinned to - * @param {Number} anchorY - * @param {Boolean} baseline Whether to position the label relative to the text baseline, - * like renderer.text, or to the upper border of the rectangle. - * @param {String} className Class name for the group - */ - label: function (str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) { - - var renderer = this, - wrapper = renderer.g(className), - text = renderer.text('', 0, 0, useHTML) - .attr({ - zIndex: 1 - }), - //.add(wrapper), - box, - bBox, - alignFactor = 0, - padding = 3, - paddingLeft = 0, - width, - height, - wrapperX, - wrapperY, - crispAdjust = 0, - deferredAttr = {}, - baselineOffset, - needsBox, - updateBoxSize, - updateTextPadding, - boxAttr; - - /** - * This function runs after the label is added to the DOM (when the bounding box is - * available), and after the text of the label is updated to detect the new bounding - * box and reflect it in the border box. - */ - updateBoxSize = function () { - var boxX, - boxY, - style = text.element.style; - - bBox = (width === undefined || height === undefined || wrapper.styles.textAlign) && defined(text.textStr) && - text.getBBox(); //#3295 && 3514 box failure when string equals 0 - wrapper.width = (width || bBox.width || 0) + 2 * padding + paddingLeft; - wrapper.height = (height || bBox.height || 0) + 2 * padding; - - // update the label-scoped y offset - baselineOffset = padding + renderer.fontMetrics(style && style.fontSize, text).b; - - - if (needsBox) { - - if (!box) { - // create the border box if it is not already present - boxX = crispAdjust; - boxY = (baseline ? -baselineOffset : 0) + crispAdjust; - - wrapper.box = box = shape ? - renderer.symbol(shape, boxX, boxY, wrapper.width, wrapper.height, deferredAttr) : - renderer.rect(boxX, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]); - - if (!box.isImg) { // #4324, fill "none" causes it to be ignored by mouse events in IE - box.attr('fill', NONE); - } - box.add(wrapper); - } - - // apply the box attributes - if (!box.isImg) { // #1630 - box.attr(extend({ - width: mathRound(wrapper.width), - height: mathRound(wrapper.height) - }, deferredAttr)); - } - deferredAttr = null; - } - }; - - /** - * This function runs after setting text or padding, but only if padding is changed - */ - updateTextPadding = function () { - var styles = wrapper.styles, - textAlign = styles && styles.textAlign, - x = paddingLeft + padding, - y; - - // determin y based on the baseline - y = baseline ? 0 : baselineOffset; - - // compensate for alignment - if (defined(width) && bBox && (textAlign === 'center' || textAlign === 'right')) { - x += { center: 0.5, right: 1 }[textAlign] * (width - bBox.width); - } - - // update if anything changed - if (x !== text.x || y !== text.y) { - text.attr('x', x); - if (y !== UNDEFINED) { - text.attr('y', y); - } - } - - // record current values - text.x = x; - text.y = y; - }; - - /** - * Set a box attribute, or defer it if the box is not yet created - * @param {Object} key - * @param {Object} value - */ - boxAttr = function (key, value) { - if (box) { - box.attr(key, value); - } else { - deferredAttr[key] = value; - } - }; - - /** - * After the text element is added, get the desired size of the border box - * and add it before the text in the DOM. - */ - wrapper.onAdd = function () { - text.add(wrapper); - wrapper.attr({ - text: (str || str === 0) ? str : '', // alignment is available now // #3295: 0 not rendered if given as a value - x: x, - y: y - }); - - if (box && defined(anchorX)) { - wrapper.attr({ - anchorX: anchorX, - anchorY: anchorY - }); - } - }; - - /* - * Add specific attribute setters. - */ - - // only change local variables - wrapper.widthSetter = function (value) { - width = value; - }; - wrapper.heightSetter = function (value) { - height = value; - }; - wrapper.paddingSetter = function (value) { - if (defined(value) && value !== padding) { - padding = wrapper.padding = value; - updateTextPadding(); - } - }; - wrapper.paddingLeftSetter = function (value) { - if (defined(value) && value !== paddingLeft) { - paddingLeft = value; - updateTextPadding(); - } - }; - - - // change local variable and prevent setting attribute on the group - wrapper.alignSetter = function (value) { - value = { left: 0, center: 0.5, right: 1 }[value]; - if (value !== alignFactor) { - alignFactor = value; - if (bBox) { // Bounding box exists, means we're dynamically changing - wrapper.attr({ x: wrapperX }); // #5134 - } - } - }; - - // apply these to the box and the text alike - wrapper.textSetter = function (value) { - if (value !== UNDEFINED) { - text.textSetter(value); - } - updateBoxSize(); - updateTextPadding(); - }; - - // apply these to the box but not to the text - wrapper['stroke-widthSetter'] = function (value, key) { - if (value) { - needsBox = true; - } - crispAdjust = value % 2 / 2; - boxAttr(key, value); - }; - wrapper.strokeSetter = wrapper.fillSetter = wrapper.rSetter = function (value, key) { - if (key === 'fill' && value) { - needsBox = true; - } - boxAttr(key, value); - }; - wrapper.anchorXSetter = function (value, key) { - anchorX = value; - boxAttr(key, mathRound(value) - crispAdjust - wrapperX); - }; - wrapper.anchorYSetter = function (value, key) { - anchorY = value; - boxAttr(key, value - wrapperY); - }; - - // rename attributes - wrapper.xSetter = function (value) { - wrapper.x = value; // for animation getter - if (alignFactor) { - value -= alignFactor * ((width || bBox.width) + 2 * padding); - } - wrapperX = mathRound(value); - wrapper.attr('translateX', wrapperX); - }; - wrapper.ySetter = function (value) { - wrapperY = wrapper.y = mathRound(value); - wrapper.attr('translateY', wrapperY); - }; - - // Redirect certain methods to either the box or the text - var baseCss = wrapper.css; - return extend(wrapper, { - /** - * Pick up some properties and apply them to the text instead of the wrapper - */ - css: function (styles) { - if (styles) { - var textStyles = {}; - styles = merge(styles); // create a copy to avoid altering the original object (#537) - each(wrapper.textProps, function (prop) { - if (styles[prop] !== UNDEFINED) { - textStyles[prop] = styles[prop]; - delete styles[prop]; - } - }); - text.css(textStyles); - } - return baseCss.call(wrapper, styles); - }, - /** - * Return the bounding box of the box, not the group - */ - getBBox: function () { - return { - width: bBox.width + 2 * padding, - height: bBox.height + 2 * padding, - x: bBox.x - padding, - y: bBox.y - padding - }; - }, - /** - * Apply the shadow to the box - */ - shadow: function (b) { - if (box) { - box.shadow(b); - } - return wrapper; - }, - /** - * Destroy and release memory. - */ - destroy: function () { - - // Added by button implementation - removeEvent(wrapper.element, 'mouseenter'); - removeEvent(wrapper.element, 'mouseleave'); - - if (text) { - text = text.destroy(); - } - if (box) { - box = box.destroy(); - } - // Call base implementation to destroy the rest - SVGElement.prototype.destroy.call(wrapper); - - // Release local pointers (#1298) - wrapper = renderer = updateBoxSize = updateTextPadding = boxAttr = null; - } - }); - } - }; // end SVGRenderer - - - // general renderer - Renderer = SVGRenderer; - // extend SvgElement for useHTML option - extend(SVGElement.prototype, { - /** - * Apply CSS to HTML elements. This is used in text within SVG rendering and - * by the VML renderer - */ - htmlCss: function (styles) { - var wrapper = this, - element = wrapper.element, - textWidth = styles && element.tagName === 'SPAN' && styles.width; - - if (textWidth) { - delete styles.width; - wrapper.textWidth = textWidth; - wrapper.updateTransform(); - } - if (styles && styles.textOverflow === 'ellipsis') { - styles.whiteSpace = 'nowrap'; - styles.overflow = 'hidden'; - } - wrapper.styles = extend(wrapper.styles, styles); - css(wrapper.element, styles); - - return wrapper; - }, - - /** - * VML and useHTML method for calculating the bounding box based on offsets - * @param {Boolean} refresh Whether to force a fresh value from the DOM or to - * use the cached value - * - * @return {Object} A hash containing values for x, y, width and height - */ - - htmlGetBBox: function () { - var wrapper = this, - element = wrapper.element; - - // faking getBBox in exported SVG in legacy IE - // faking getBBox in exported SVG in legacy IE (is this a duplicate of the fix for #1079?) - if (element.nodeName === 'text') { - element.style.position = ABSOLUTE; - } - - return { - x: element.offsetLeft, - y: element.offsetTop, - width: element.offsetWidth, - height: element.offsetHeight - }; - }, - - /** - * VML override private method to update elements based on internal - * properties based on SVG transform - */ - htmlUpdateTransform: function () { - // aligning non added elements is expensive - if (!this.added) { - this.alignOnAdd = true; - return; - } - - var wrapper = this, - renderer = wrapper.renderer, - elem = wrapper.element, - translateX = wrapper.translateX || 0, - translateY = wrapper.translateY || 0, - x = wrapper.x || 0, - y = wrapper.y || 0, - align = wrapper.textAlign || 'left', - alignCorrection = { left: 0, center: 0.5, right: 1 }[align], - shadows = wrapper.shadows, - styles = wrapper.styles; - - // apply translate - css(elem, { - marginLeft: translateX, - marginTop: translateY - }); - if (shadows) { // used in labels/tooltip - each(shadows, function (shadow) { - css(shadow, { - marginLeft: translateX + 1, - marginTop: translateY + 1 - }); - }); - } - - // apply inversion - if (wrapper.inverted) { // wrapper is a group - each(elem.childNodes, function (child) { - renderer.invertChild(child, elem); - }); - } - - if (elem.tagName === 'SPAN') { - - var rotation = wrapper.rotation, - baseline, - textWidth = pInt(wrapper.textWidth), - whiteSpace = styles && styles.whiteSpace, - currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth, wrapper.textAlign].join(','); - - if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed - - - baseline = renderer.fontMetrics(elem.style.fontSize).b; - - // Renderer specific handling of span rotation - if (defined(rotation)) { - wrapper.setSpanRotation(rotation, alignCorrection, baseline); - } - - // Update textWidth - if (elem.offsetWidth > textWidth && /[ \-]/.test(elem.textContent || elem.innerText)) { // #983, #1254 - css(elem, { - width: textWidth + PX, - display: 'block', - whiteSpace: whiteSpace || 'normal' // #3331 - }); - wrapper.hasTextWidth = true; - } else if (wrapper.hasTextWidth) { // #4928 - css(elem, { - width: '', - display: '', - whiteSpace: whiteSpace || 'nowrap' - }); - wrapper.hasTextWidth = false; - } - - wrapper.getSpanCorrection(wrapper.hasTextWidth ? textWidth : elem.offsetWidth, baseline, alignCorrection, rotation, align); - } - - // apply position with correction - css(elem, { - left: (x + (wrapper.xCorr || 0)) + PX, - top: (y + (wrapper.yCorr || 0)) + PX - }); - - // force reflow in webkit to apply the left and top on useHTML element (#1249) - if (isWebKit) { - baseline = elem.offsetHeight; // assigned to baseline for lint purpose - } - - // record current text transform - wrapper.cTT = currentTextTransform; - } - }, - - /** - * Set the rotation of an individual HTML span - */ - setSpanRotation: function (rotation, alignCorrection, baseline) { - var rotationStyle = {}, - cssTransformKey = isMS ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : isOpera ? '-o-transform' : ''; - - rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)'; - rotationStyle[cssTransformKey + (isFirefox ? 'Origin' : '-origin')] = rotationStyle.transformOrigin = (alignCorrection * 100) + '% ' + baseline + 'px'; - css(this.element, rotationStyle); - }, - - /** - * Get the correction in X and Y positioning as the element is rotated. - */ - getSpanCorrection: function (width, baseline, alignCorrection) { - this.xCorr = -width * alignCorrection; - this.yCorr = -baseline; - } - }); - - // Extend SvgRenderer for useHTML option. - extend(SVGRenderer.prototype, { - /** - * Create HTML text node. This is used by the VML renderer as well as the SVG - * renderer through the useHTML option. - * - * @param {String} str - * @param {Number} x - * @param {Number} y - */ - html: function (str, x, y) { - var wrapper = this.createElement('span'), - element = wrapper.element, - renderer = wrapper.renderer, - isSVG = renderer.isSVG, - addSetters = function (element, style) { - // These properties are set as attributes on the SVG group, and as - // identical CSS properties on the div. (#3542) - each(['opacity', 'visibility'], function (prop) { - wrap(element, prop + 'Setter', function (proceed, value, key, elem) { - proceed.call(this, value, key, elem); - style[key] = value; - }); - }); - }; - - // Text setter - wrapper.textSetter = function (value) { - if (value !== element.innerHTML) { - delete this.bBox; - } - element.innerHTML = this.textStr = value; - wrapper.htmlUpdateTransform(); - }; - - // Add setters for the element itself (#4938) - if (isSVG) { // #4938, only for HTML within SVG - addSetters(wrapper, wrapper.element.style); - } - - // Various setters which rely on update transform - wrapper.xSetter = wrapper.ySetter = wrapper.alignSetter = wrapper.rotationSetter = function (value, key) { - if (key === 'align') { - key = 'textAlign'; // Do not overwrite the SVGElement.align method. Same as VML. - } - wrapper[key] = value; - wrapper.htmlUpdateTransform(); - }; - - // Set the default attributes - wrapper - .attr({ - text: str, - x: mathRound(x), - y: mathRound(y) - }) - .css({ - position: ABSOLUTE, - fontFamily: this.style.fontFamily, - fontSize: this.style.fontSize - }); - - // Keep the whiteSpace style outside the wrapper.styles collection - element.style.whiteSpace = 'nowrap'; - - // Use the HTML specific .css method - wrapper.css = wrapper.htmlCss; - - // This is specific for HTML within SVG - if (isSVG) { - wrapper.add = function (svgGroupWrapper) { - - var htmlGroup, - container = renderer.box.parentNode, - parentGroup, - parents = []; - - this.parentGroup = svgGroupWrapper; - - // Create a mock group to hold the HTML elements - if (svgGroupWrapper) { - htmlGroup = svgGroupWrapper.div; - if (!htmlGroup) { - - // Read the parent chain into an array and read from top down - parentGroup = svgGroupWrapper; - while (parentGroup) { - - parents.push(parentGroup); - - // Move up to the next parent group - parentGroup = parentGroup.parentGroup; - } - - // Ensure dynamically updating position when any parent is translated - each(parents.reverse(), function (parentGroup) { - var htmlGroupStyle, - cls = attr(parentGroup.element, 'class'); - - if (cls) { - cls = { className: cls }; - } // else null - - // Create a HTML div and append it to the parent div to emulate - // the SVG group structure - htmlGroup = parentGroup.div = parentGroup.div || createElement(DIV, cls, { - position: ABSOLUTE, - left: (parentGroup.translateX || 0) + PX, - top: (parentGroup.translateY || 0) + PX, - opacity: parentGroup.opacity // #5075 - }, htmlGroup || container); // the top group is appended to container - - // Shortcut - htmlGroupStyle = htmlGroup.style; - - // Set listeners to update the HTML div's position whenever the SVG group - // position is changed - extend(parentGroup, { - translateXSetter: function (value, key) { - htmlGroupStyle.left = value + PX; - parentGroup[key] = value; - parentGroup.doTransform = true; - }, - translateYSetter: function (value, key) { - htmlGroupStyle.top = value + PX; - parentGroup[key] = value; - parentGroup.doTransform = true; - } - }); - addSetters(parentGroup, htmlGroupStyle); - }); - - } - } else { - htmlGroup = container; - } - - htmlGroup.appendChild(element); - - // Shared with VML: - wrapper.added = true; - if (wrapper.alignOnAdd) { - wrapper.htmlUpdateTransform(); - } - - return wrapper; - }; - } - return wrapper; - } - }); - - - /* **************************************************************************** - * * - * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE * - * * - * For applications and websites that don't need IE support, like platform * - * targeted mobile apps and web apps, this code can be removed. * - * * - *****************************************************************************/ - - /** - * @constructor - */ - var VMLRenderer, VMLElement; - if (!hasSVG && !useCanVG) { - - /** - * The VML element wrapper. - */ - VMLElement = { - - /** - * Initialize a new VML element wrapper. It builds the markup as a string - * to minimize DOM traffic. - * @param {Object} renderer - * @param {Object} nodeName - */ - init: function (renderer, nodeName) { - var wrapper = this, - markup = ['<', nodeName, ' filled="f" stroked="f"'], - style = ['position: ', ABSOLUTE, ';'], - isDiv = nodeName === DIV; - - // divs and shapes need size - if (nodeName === 'shape' || isDiv) { - style.push('left:0;top:0;width:1px;height:1px;'); - } - style.push('visibility: ', isDiv ? HIDDEN : VISIBLE); - - markup.push(' style="', style.join(''), '"/>'); - - // create element with default attributes and style - if (nodeName) { - markup = isDiv || nodeName === 'span' || nodeName === 'img' ? - markup.join('') : - renderer.prepVML(markup); - wrapper.element = createElement(markup); - } - - wrapper.renderer = renderer; - }, - - /** - * Add the node to the given parent - * @param {Object} parent - */ - add: function (parent) { - var wrapper = this, - renderer = wrapper.renderer, - element = wrapper.element, - box = renderer.box, - inverted = parent && parent.inverted, - - // get the parent node - parentNode = parent ? - parent.element || parent : - box; - - if (parent) { - this.parentGroup = parent; - } - - // if the parent group is inverted, apply inversion on all children - if (inverted) { // only on groups - renderer.invertChild(element, parentNode); - } - - // append it - parentNode.appendChild(element); - - // align text after adding to be able to read offset - wrapper.added = true; - if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) { - wrapper.updateTransform(); - } - - // fire an event for internal hooks - if (wrapper.onAdd) { - wrapper.onAdd(); - } - - return wrapper; - }, - - /** - * VML always uses htmlUpdateTransform - */ - updateTransform: SVGElement.prototype.htmlUpdateTransform, - - /** - * Set the rotation of a span with oldIE's filter - */ - setSpanRotation: function () { - // Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented - // but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+ - // has support for CSS3 transform. The getBBox method also needs to be updated - // to compensate for the rotation, like it currently does for SVG. - // Test case: http://jsfiddle.net/highcharts/Ybt44/ - - var rotation = this.rotation, - costheta = mathCos(rotation * deg2rad), - sintheta = mathSin(rotation * deg2rad); - - css(this.element, { - filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta, - ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta, - ', sizingMethod=\'auto expand\')'].join('') : NONE - }); - }, - - /** - * Get the positioning correction for the span after rotating. - */ - getSpanCorrection: function (width, baseline, alignCorrection, rotation, align) { - - var costheta = rotation ? mathCos(rotation * deg2rad) : 1, - sintheta = rotation ? mathSin(rotation * deg2rad) : 0, - height = pick(this.elemHeight, this.element.offsetHeight), - quad, - nonLeft = align && align !== 'left'; - - // correct x and y - this.xCorr = costheta < 0 && -width; - this.yCorr = sintheta < 0 && -height; - - // correct for baseline and corners spilling out after rotation - quad = costheta * sintheta < 0; - this.xCorr += sintheta * baseline * (quad ? 1 - alignCorrection : alignCorrection); - this.yCorr -= costheta * baseline * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1); - // correct for the length/height of the text - if (nonLeft) { - this.xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1); - if (rotation) { - this.yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1); - } - css(this.element, { - textAlign: align - }); - } - }, - - /** - * Converts a subset of an SVG path definition to its VML counterpart. Takes an array - * as the parameter and returns a string. - */ - pathToVML: function (value) { - // convert paths - var i = value.length, - path = []; - - while (i--) { - - // Multiply by 10 to allow subpixel precision. - // Substracting half a pixel seems to make the coordinates - // align with SVG, but this hasn't been tested thoroughly - if (isNumber(value[i])) { - path[i] = mathRound(value[i] * 10) - 5; - } else if (value[i] === 'Z') { // close the path - path[i] = 'x'; - } else { - path[i] = value[i]; - - // When the start X and end X coordinates of an arc are too close, - // they are rounded to the same value above. In this case, substract or - // add 1 from the end X and Y positions. #186, #760, #1371, #1410. - if (value.isArc && (value[i] === 'wa' || value[i] === 'at')) { - // Start and end X - if (path[i + 5] === path[i + 7]) { - path[i + 7] += value[i + 7] > value[i + 5] ? 1 : -1; - } - // Start and end Y - if (path[i + 6] === path[i + 8]) { - path[i + 8] += value[i + 8] > value[i + 6] ? 1 : -1; - } - } - } - } - - - // Loop up again to handle path shortcuts (#2132) - /*while (i++ < path.length) { - if (path[i] === 'H') { // horizontal line to - path[i] = 'L'; - path.splice(i + 2, 0, path[i - 1]); - } else if (path[i] === 'V') { // vertical line to - path[i] = 'L'; - path.splice(i + 1, 0, path[i - 2]); - } - }*/ - return path.join(' ') || 'x'; - }, - - /** - * Set the element's clipping to a predefined rectangle - * - * @param {String} id The id of the clip rectangle - */ - clip: function (clipRect) { - var wrapper = this, - clipMembers, - cssRet; - - if (clipRect) { - clipMembers = clipRect.members; - erase(clipMembers, wrapper); // Ensure unique list of elements (#1258) - clipMembers.push(wrapper); - wrapper.destroyClip = function () { - erase(clipMembers, wrapper); - }; - cssRet = clipRect.getCSS(wrapper); - - } else { - if (wrapper.destroyClip) { - wrapper.destroyClip(); - } - cssRet = { clip: docMode8 ? 'inherit' : 'rect(auto)' }; // #1214 - } - - return wrapper.css(cssRet); - - }, - - /** - * Set styles for the element - * @param {Object} styles - */ - css: SVGElement.prototype.htmlCss, - - /** - * Removes a child either by removeChild or move to garbageBin. - * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not. - */ - safeRemoveChild: function (element) { - // discardElement will detach the node from its parent before attaching it - // to the garbage bin. Therefore it is important that the node is attached and have parent. - if (element.parentNode) { - discardElement(element); - } - }, - - /** - * Extend element.destroy by removing it from the clip members array - */ - destroy: function () { - if (this.destroyClip) { - this.destroyClip(); - } - - return SVGElement.prototype.destroy.apply(this); - }, - - /** - * Add an event listener. VML override for normalizing event parameters. - * @param {String} eventType - * @param {Function} handler - */ - on: function (eventType, handler) { - // simplest possible event model for internal use - this.element['on' + eventType] = function () { - var evt = win.event; - evt.target = evt.srcElement; - handler(evt); - }; - return this; - }, - - /** - * In stacked columns, cut off the shadows so that they don't overlap - */ - cutOffPath: function (path, length) { - - var len; - - path = path.split(/[ ,]/); - len = path.length; - - if (len === 9 || len === 11) { - path[len - 4] = path[len - 2] = pInt(path[len - 2]) - 10 * length; - } - return path.join(' '); - }, - - /** - * Apply a drop shadow by copying elements and giving them different strokes - * @param {Boolean|Object} shadowOptions - */ - shadow: function (shadowOptions, group, cutOff) { - var shadows = [], - i, - element = this.element, - renderer = this.renderer, - shadow, - elemStyle = element.style, - markup, - path = element.path, - strokeWidth, - modifiedPath, - shadowWidth, - shadowElementOpacity; - - // some times empty paths are not strings - if (path && typeof path.value !== 'string') { - path = 'x'; - } - modifiedPath = path; - - if (shadowOptions) { - shadowWidth = pick(shadowOptions.width, 3); - shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth; - for (i = 1; i <= 3; i++) { - - strokeWidth = (shadowWidth * 2) + 1 - (2 * i); - - // Cut off shadows for stacked column items - if (cutOff) { - modifiedPath = this.cutOffPath(path.value, strokeWidth + 0.5); - } - - markup = ['']; - - shadow = createElement(renderer.prepVML(markup), - null, { - left: pInt(elemStyle.left) + pick(shadowOptions.offsetX, 1), - top: pInt(elemStyle.top) + pick(shadowOptions.offsetY, 1) - } - ); - if (cutOff) { - shadow.cutOff = strokeWidth + 1; - } - - // apply the opacity - markup = ['']; - createElement(renderer.prepVML(markup), null, null, shadow); - - - // insert it - if (group) { - group.element.appendChild(shadow); - } else { - element.parentNode.insertBefore(shadow, element); - } - - // record it - shadows.push(shadow); - - } - - this.shadows = shadows; - } - return this; - }, - updateShadows: noop, // Used in SVG only - - setAttr: function (key, value) { - if (docMode8) { // IE8 setAttribute bug - this.element[key] = value; - } else { - this.element.setAttribute(key, value); - } - }, - classSetter: function (value) { - // IE8 Standards mode has problems retrieving the className unless set like this - this.element.className = value; - }, - dashstyleSetter: function (value, key, element) { - var strokeElem = element.getElementsByTagName('stroke')[0] || - createElement(this.renderer.prepVML(['']), null, null, element); - strokeElem[key] = value || 'solid'; - this[key] = value; /* because changing stroke-width will change the dash length - and cause an epileptic effect */ - }, - dSetter: function (value, key, element) { - var i, - shadows = this.shadows; - value = value || []; - this.d = value.join && value.join(' '); // used in getter for animation - - element.path = value = this.pathToVML(value); - - // update shadows - if (shadows) { - i = shadows.length; - while (i--) { - shadows[i].path = shadows[i].cutOff ? this.cutOffPath(value, shadows[i].cutOff) : value; - } - } - this.setAttr(key, value); - }, - fillSetter: function (value, key, element) { - var nodeName = element.nodeName; - if (nodeName === 'SPAN') { // text color - element.style.color = value; - } else if (nodeName !== 'IMG') { // #1336 - element.filled = value !== NONE; - this.setAttr('fillcolor', this.renderer.color(value, element, key, this)); - } - }, - 'fill-opacitySetter': function (value, key, element) { - createElement( - this.renderer.prepVML(['<', key.split('-')[0], ' opacity="', value, '"/>']), - null, - null, - element - ); - }, - opacitySetter: noop, // Don't bother - animation is too slow and filters introduce artifacts - rotationSetter: function (value, key, element) { - var style = element.style; - this[key] = style[key] = value; // style is for #1873 - - // Correction for the 1x1 size of the shape container. Used in gauge needles. - style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX; - style.top = mathRound(mathCos(value * deg2rad)) + PX; - }, - strokeSetter: function (value, key, element) { - this.setAttr('strokecolor', this.renderer.color(value, element, key, this)); - }, - 'stroke-widthSetter': function (value, key, element) { - element.stroked = !!value; // VML "stroked" attribute - this[key] = value; // used in getter, issue #113 - if (isNumber(value)) { - value += PX; - } - this.setAttr('strokeweight', value); - }, - titleSetter: function (value, key) { - this.setAttr(key, value); - }, - visibilitySetter: function (value, key, element) { - - // Handle inherited visibility - if (value === 'inherit') { - value = VISIBLE; - } - - // Let the shadow follow the main element - if (this.shadows) { - each(this.shadows, function (shadow) { - shadow.style[key] = value; - }); - } - - // Instead of toggling the visibility CSS property, move the div out of the viewport. - // This works around #61 and #586 - if (element.nodeName === 'DIV') { - value = value === HIDDEN ? '-999em' : 0; - - // In order to redraw, IE7 needs the div to be visible when tucked away - // outside the viewport. So the visibility is actually opposite of - // the expected value. This applies to the tooltip only. - if (!docMode8) { - element.style[key] = value ? VISIBLE : HIDDEN; - } - key = 'top'; - } - element.style[key] = value; - }, - xSetter: function (value, key, element) { - this[key] = value; // used in getter - - if (key === 'x') { - key = 'left'; - } else if (key === 'y') { - key = 'top'; - }/* else { - value = mathMax(0, value); // don't set width or height below zero (#311) - }*/ - - // clipping rectangle special - if (this.updateClipping) { - this[key] = value; // the key is now 'left' or 'top' for 'x' and 'y' - this.updateClipping(); - } else { - // normal - element.style[key] = value; - } - }, - zIndexSetter: function (value, key, element) { - element.style[key] = value; - } - }; - VMLElement['stroke-opacitySetter'] = VMLElement['fill-opacitySetter']; - - Highcharts.VMLElement = VMLElement = extendClass(SVGElement, VMLElement); - - // Some shared setters - VMLElement.prototype.ySetter = - VMLElement.prototype.widthSetter = - VMLElement.prototype.heightSetter = - VMLElement.prototype.xSetter; - - - /** - * The VML renderer - */ - var VMLRendererExtension = { // inherit SVGRenderer - - Element: VMLElement, - isIE8: userAgent.indexOf('MSIE 8.0') > -1, - - - /** - * Initialize the VMLRenderer - * @param {Object} container - * @param {Number} width - * @param {Number} height - */ - init: function (container, width, height, style) { - var renderer = this, - boxWrapper, - box, - css; - - renderer.alignedObjects = []; - - boxWrapper = renderer.createElement(DIV) - .css(extend(this.getStyle(style), { position: 'relative' })); - box = boxWrapper.element; - container.appendChild(boxWrapper.element); - - - // generate the containing box - renderer.isVML = true; - renderer.box = box; - renderer.boxWrapper = boxWrapper; - renderer.gradients = {}; - renderer.cache = {}; // Cache for numerical bounding boxes - renderer.cacheKeys = []; - renderer.imgCount = 0; - - - renderer.setSize(width, height, false); - - // The only way to make IE6 and IE7 print is to use a global namespace. However, - // with IE8 the only way to make the dynamic shapes visible in screen and print mode - // seems to be to add the xmlns attribute and the behaviour style inline. - if (!doc.namespaces.hcv) { - - doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml'); - - // Setup default CSS (#2153, #2368, #2384) - css = 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' + - '{ behavior:url(#default#VML); display: inline-block; } '; - try { - doc.createStyleSheet().cssText = css; - } catch (e) { - doc.styleSheets[0].cssText += css; - } - - } - }, - - - /** - * Detect whether the renderer is hidden. This happens when one of the parent elements - * has display: none - */ - isHidden: function () { - return !this.box.offsetWidth; - }, - - /** - * Define a clipping rectangle. In VML it is accomplished by storing the values - * for setting the CSS style to all associated members. - * - * @param {Number} x - * @param {Number} y - * @param {Number} width - * @param {Number} height - */ - clipRect: function (x, y, width, height) { - - // create a dummy element - var clipRect = this.createElement(), - isObj = isObject(x); - - // mimic a rectangle with its style object for automatic updating in attr - return extend(clipRect, { - members: [], - count: 0, - left: (isObj ? x.x : x) + 1, - top: (isObj ? x.y : y) + 1, - width: (isObj ? x.width : width) - 1, - height: (isObj ? x.height : height) - 1, - getCSS: function (wrapper) { - var element = wrapper.element, - nodeName = element.nodeName, - isShape = nodeName === 'shape', - inverted = wrapper.inverted, - rect = this, - top = rect.top - (isShape ? element.offsetTop : 0), - left = rect.left, - right = left + rect.width, - bottom = top + rect.height, - ret = { - clip: 'rect(' + - mathRound(inverted ? left : top) + 'px,' + - mathRound(inverted ? bottom : right) + 'px,' + - mathRound(inverted ? right : bottom) + 'px,' + - mathRound(inverted ? top : left) + 'px)' - }; - - // issue 74 workaround - if (!inverted && docMode8 && nodeName === 'DIV') { - extend(ret, { - width: right + PX, - height: bottom + PX - }); - } - return ret; - }, - - // used in attr and animation to update the clipping of all members - updateClipping: function () { - each(clipRect.members, function (member) { - if (member.element) { // Deleted series, like in stock/members/series-remove demo. Should be removed from members, but this will do. - member.css(clipRect.getCSS(member)); - } - }); - } - }); - - }, - - - /** - * Take a color and return it if it's a string, make it a gradient if it's a - * gradient configuration object, and apply opacity. - * - * @param {Object} color The color or config object - */ - color: function (color, elem, prop, wrapper) { - var renderer = this, - colorObject, - regexRgba = /^rgba/, - markup, - fillType, - ret = NONE; - - // Check for linear or radial gradient - if (color && color.linearGradient) { - fillType = 'gradient'; - } else if (color && color.radialGradient) { - fillType = 'pattern'; - } - - - if (fillType) { - - var stopColor, - stopOpacity, - gradient = color.linearGradient || color.radialGradient, - x1, - y1, - x2, - y2, - opacity1, - opacity2, - color1, - color2, - fillAttr = '', - stops = color.stops, - firstStop, - lastStop, - colors = [], - addFillNode = function () { - // Add the fill subnode. When colors attribute is used, the meanings of opacity and o:opacity2 - // are reversed. - markup = ['']; - createElement(renderer.prepVML(markup), null, null, elem); - }; - - // Extend from 0 to 1 - firstStop = stops[0]; - lastStop = stops[stops.length - 1]; - if (firstStop[0] > 0) { - stops.unshift([ - 0, - firstStop[1] - ]); - } - if (lastStop[0] < 1) { - stops.push([ - 1, - lastStop[1] - ]); - } - - // Compute the stops - each(stops, function (stop, i) { - if (regexRgba.test(stop[1])) { - colorObject = Color(stop[1]); - stopColor = colorObject.get('rgb'); - stopOpacity = colorObject.get('a'); - } else { - stopColor = stop[1]; - stopOpacity = 1; - } - - // Build the color attribute - colors.push((stop[0] * 100) + '% ' + stopColor); - - // Only start and end opacities are allowed, so we use the first and the last - if (!i) { - opacity1 = stopOpacity; - color2 = stopColor; - } else { - opacity2 = stopOpacity; - color1 = stopColor; - } - }); - - // Apply the gradient to fills only. - if (prop === 'fill') { - - // Handle linear gradient angle - if (fillType === 'gradient') { - x1 = gradient.x1 || gradient[0] || 0; - y1 = gradient.y1 || gradient[1] || 0; - x2 = gradient.x2 || gradient[2] || 0; - y2 = gradient.y2 || gradient[3] || 0; - fillAttr = 'angle="' + (90 - math.atan( - (y2 - y1) / // y vector - (x2 - x1) // x vector - ) * 180 / mathPI) + '"'; - - addFillNode(); - - // Radial (circular) gradient - } else { - - var r = gradient.r, - sizex = r * 2, - sizey = r * 2, - cx = gradient.cx, - cy = gradient.cy, - radialReference = elem.radialReference, - bBox, - applyRadialGradient = function () { - if (radialReference) { - bBox = wrapper.getBBox(); - cx += (radialReference[0] - bBox.x) / bBox.width - 0.5; - cy += (radialReference[1] - bBox.y) / bBox.height - 0.5; - sizex *= radialReference[2] / bBox.width; - sizey *= radialReference[2] / bBox.height; - } - fillAttr = 'src="' + defaultOptions.global.VMLRadialGradientURL + '" ' + - 'size="' + sizex + ',' + sizey + '" ' + - 'origin="0.5,0.5" ' + - 'position="' + cx + ',' + cy + '" ' + - 'color2="' + color2 + '" '; - - addFillNode(); - }; - - // Apply radial gradient - if (wrapper.added) { - applyRadialGradient(); - } else { - // We need to know the bounding box to get the size and position right - wrapper.onAdd = applyRadialGradient; - } - - // The fill element's color attribute is broken in IE8 standards mode, so we - // need to set the parent shape's fillcolor attribute instead. - ret = color1; - } - - // Gradients are not supported for VML stroke, return the first color. #722. - } else { - ret = stopColor; - } - - // If the color is an rgba color, split it and add a fill node - // to hold the opacity component - } else if (regexRgba.test(color) && elem.tagName !== 'IMG') { - - colorObject = Color(color); - - wrapper[prop + '-opacitySetter'](colorObject.get('a'), prop, elem); - - ret = colorObject.get('rgb'); - - - } else { - var propNodes = elem.getElementsByTagName(prop); // 'stroke' or 'fill' node - if (propNodes.length) { - propNodes[0].opacity = 1; - propNodes[0].type = 'solid'; - } - ret = color; - } - - return ret; - }, - - /** - * Take a VML string and prepare it for either IE8 or IE6/IE7. - * @param {Array} markup A string array of the VML markup to prepare - */ - prepVML: function (markup) { - var vmlStyle = 'display:inline-block;behavior:url(#default#VML);', - isIE8 = this.isIE8; - - markup = markup.join(''); - - if (isIE8) { // add xmlns and style inline - markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />'); - if (markup.indexOf('style="') === -1) { - markup = markup.replace('/>', ' style="' + vmlStyle + '" />'); - } else { - markup = markup.replace('style="', 'style="' + vmlStyle); - } - - } else { // add namespace - markup = markup.replace('<', ' 1) { - obj.attr({ - x: x, - y: y, - width: width, - height: height - }); - } - return obj; - }, - - /** - * For rectangles, VML uses a shape for rect to overcome bugs and rotation problems - */ - createElement: function (nodeName) { - return nodeName === 'rect' ? this.symbol(nodeName) : SVGRenderer.prototype.createElement.call(this, nodeName); - }, - - /** - * In the VML renderer, each child of an inverted div (group) is inverted - * @param {Object} element - * @param {Object} parentNode - */ - invertChild: function (element, parentNode) { - var ren = this, - parentStyle = parentNode.style, - imgStyle = element.tagName === 'IMG' && element.style; // #1111 - - css(element, { - flip: 'x', - left: pInt(parentStyle.width) - (imgStyle ? pInt(imgStyle.top) : 1), - top: pInt(parentStyle.height) - (imgStyle ? pInt(imgStyle.left) : 1), - rotation: -90 - }); - - // Recursively invert child elements, needed for nested composite shapes like box plots and error bars. #1680, #1806. - each(element.childNodes, function (child) { - ren.invertChild(child, element); - }); - }, - - /** - * Symbol definitions that override the parent SVG renderer's symbols - * - */ - symbols: { - // VML specific arc function - arc: function (x, y, w, h, options) { - var start = options.start, - end = options.end, - radius = options.r || w || h, - innerRadius = options.innerR, - cosStart = mathCos(start), - sinStart = mathSin(start), - cosEnd = mathCos(end), - sinEnd = mathSin(end), - ret; - - if (end - start === 0) { // no angle, don't show it. - return ['x']; - } - - ret = [ - 'wa', // clockwise arc to - x - radius, // left - y - radius, // top - x + radius, // right - y + radius, // bottom - x + radius * cosStart, // start x - y + radius * sinStart, // start y - x + radius * cosEnd, // end x - y + radius * sinEnd // end y - ]; - - if (options.open && !innerRadius) { - ret.push( - 'e', - M, - x, // - innerRadius, - y// - innerRadius - ); - } - - ret.push( - 'at', // anti clockwise arc to - x - innerRadius, // left - y - innerRadius, // top - x + innerRadius, // right - y + innerRadius, // bottom - x + innerRadius * cosEnd, // start x - y + innerRadius * sinEnd, // start y - x + innerRadius * cosStart, // end x - y + innerRadius * sinStart, // end y - 'x', // finish path - 'e' // close - ); - - ret.isArc = true; - return ret; - - }, - // Add circle symbol path. This performs significantly faster than v:oval. - circle: function (x, y, w, h, wrapper) { - - if (wrapper) { - w = h = 2 * wrapper.r; - } - - // Center correction, #1682 - if (wrapper && wrapper.isCircle) { - x -= w / 2; - y -= h / 2; - } - - // Return the path - return [ - 'wa', // clockwisearcto - x, // left - y, // top - x + w, // right - y + h, // bottom - x + w, // start x - y + h / 2, // start y - x + w, // end x - y + h / 2, // end y - //'x', // finish path - 'e' // close - ]; - }, - /** - * Add rectangle symbol path which eases rotation and omits arcsize problems - * compared to the built-in VML roundrect shape. When borders are not rounded, - * use the simpler square path, else use the callout path without the arrow. - */ - rect: function (x, y, w, h, options) { - return SVGRenderer.prototype.symbols[ - !defined(options) || !options.r ? 'square' : 'callout' - ].call(0, x, y, w, h, options); - } - } - }; - Highcharts.VMLRenderer = VMLRenderer = function () { - this.init.apply(this, arguments); - }; - VMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension); - - // general renderer - Renderer = VMLRenderer; - } - - // This method is used with exporting in old IE, when emulating SVG (see #2314) - SVGRenderer.prototype.measureSpanWidth = function (text, styles) { - var measuringSpan = doc.createElement('span'), - offsetWidth, - textNode = doc.createTextNode(text); - - measuringSpan.appendChild(textNode); - css(measuringSpan, styles); - this.box.appendChild(measuringSpan); - offsetWidth = measuringSpan.offsetWidth; - discardElement(measuringSpan); // #2463 - return offsetWidth; - }; - - - /* **************************************************************************** - * * - * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE * - * * - *****************************************************************************/ - /* **************************************************************************** - * * - * START OF ANDROID < 3 SPECIFIC CODE. THIS CAN BE REMOVED IF YOU'RE NOT * - * TARGETING THAT SYSTEM. * - * * - *****************************************************************************/ - var CanVGRenderer, - CanVGController; - - /** - * Downloads a script and executes a callback when done. - * @param {String} scriptLocation - * @param {Function} callback - */ - function getScript(scriptLocation, callback) { - var head = doc.getElementsByTagName('head')[0], - script = doc.createElement('script'); - - script.type = 'text/javascript'; - script.src = scriptLocation; - script.onload = callback; - - head.appendChild(script); - } - - if (useCanVG) { - /** - * The CanVGRenderer is empty from start to keep the source footprint small. - * When requested, the CanVGController downloads the rest of the source packaged - * together with the canvg library. - */ - Highcharts.CanVGRenderer = CanVGRenderer = function () { - // Override the global SVG namespace to fake SVG/HTML that accepts CSS - SVG_NS = 'http://www.w3.org/1999/xhtml'; - }; - - /** - * Start with an empty symbols object. This is needed when exporting is used (exporting.src.js will add a few symbols), but - * the implementation from SvgRenderer will not be merged in until first render. - */ - CanVGRenderer.prototype.symbols = {}; - - /** - * Handles on demand download of canvg rendering support. - */ - CanVGController = (function () { - // List of renderering calls - var deferredRenderCalls = []; - - /** - * When downloaded, we are ready to draw deferred charts. - */ - function drawDeferred() { - var callLength = deferredRenderCalls.length, - callIndex; - - // Draw all pending render calls - for (callIndex = 0; callIndex < callLength; callIndex++) { - deferredRenderCalls[callIndex](); - } - // Clear the list - deferredRenderCalls = []; - } - - return { - push: function (func, scriptLocation) { - // Only get the script once - if (deferredRenderCalls.length === 0) { - getScript(scriptLocation, drawDeferred); - } - // Register render call - deferredRenderCalls.push(func); - } - }; - }()); - - Renderer = CanVGRenderer; - } // end CanVGRenderer - - /* **************************************************************************** - * * - * END OF ANDROID < 3 SPECIFIC CODE * - * * - *****************************************************************************/ - - /** - * The Tick class - */ - function Tick(axis, pos, type, noLabel) { - this.axis = axis; - this.pos = pos; - this.type = type || ''; - this.isNew = true; - - if (!type && !noLabel) { - this.addLabel(); - } - } - - Tick.prototype = { - /** - * Write the tick label - */ - addLabel: function () { - var tick = this, - axis = tick.axis, - options = axis.options, - chart = axis.chart, - categories = axis.categories, - names = axis.names, - pos = tick.pos, - labelOptions = options.labels, - str, - tickPositions = axis.tickPositions, - isFirst = pos === tickPositions[0], - isLast = pos === tickPositions[tickPositions.length - 1], - value = categories ? - pick(categories[pos], names[pos], pos) : - pos, - label = tick.label, - tickPositionInfo = tickPositions.info, - dateTimeLabelFormat; - - // Set the datetime label format. If a higher rank is set for this position, use that. If not, - // use the general format. - if (axis.isDatetimeAxis && tickPositionInfo) { - dateTimeLabelFormat = options.dateTimeLabelFormats[tickPositionInfo.higherRanks[pos] || tickPositionInfo.unitName]; - } - // set properties for access in render method - tick.isFirst = isFirst; - tick.isLast = isLast; - - // get the string - str = axis.labelFormatter.call({ - axis: axis, - chart: chart, - isFirst: isFirst, - isLast: isLast, - dateTimeLabelFormat: dateTimeLabelFormat, - value: axis.isLog ? correctFloat(axis.lin2log(value)) : value - }); - - // prepare CSS - //css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX }; - - // first call - if (!defined(label)) { - - tick.label = label = - defined(str) && labelOptions.enabled ? - chart.renderer.text( - str, - 0, - 0, - labelOptions.useHTML - ) - //.attr(attr) - // without position absolute, IE export sometimes is wrong - .css(merge(labelOptions.style)) - .add(axis.labelGroup) : - null; - tick.labelLength = label && label.getBBox().width; // Un-rotated length - tick.rotation = 0; // Base value to detect change for new calls to getBBox - - // update - } else if (label) { - label.attr({ text: str }); - } - }, - - /** - * Get the offset height or width of the label - */ - getLabelSize: function () { - return this.label ? - this.label.getBBox()[this.axis.horiz ? 'height' : 'width'] : - 0; - }, - - /** - * Handle the label overflow by adjusting the labels to the left and right edge, or - * hide them if they collide into the neighbour label. - */ - handleOverflow: function (xy) { - var axis = this.axis, - pxPos = xy.x, - chartWidth = axis.chart.chartWidth, - spacing = axis.chart.spacing, - leftBound = pick(axis.labelLeft, mathMin(axis.pos, spacing[3])), - rightBound = pick(axis.labelRight, mathMax(axis.pos + axis.len, chartWidth - spacing[1])), - label = this.label, - rotation = this.rotation, - factor = { left: 0, center: 0.5, right: 1 }[axis.labelAlign], - labelWidth = label.getBBox().width, - slotWidth = axis.getSlotWidth(), - modifiedSlotWidth = slotWidth, - xCorrection = factor, - goRight = 1, - leftPos, - rightPos, - textWidth, - css = {}; - - // Check if the label overshoots the chart spacing box. If it does, move it. - // If it now overshoots the slotWidth, add ellipsis. - if (!rotation) { - leftPos = pxPos - factor * labelWidth; - rightPos = pxPos + (1 - factor) * labelWidth; - - if (leftPos < leftBound) { - modifiedSlotWidth = xy.x + modifiedSlotWidth * (1 - factor) - leftBound; - } else if (rightPos > rightBound) { - modifiedSlotWidth = rightBound - xy.x + modifiedSlotWidth * factor; - goRight = -1; - } - - modifiedSlotWidth = mathMin(slotWidth, modifiedSlotWidth); // #4177 - if (modifiedSlotWidth < slotWidth && axis.labelAlign === 'center') { - xy.x += goRight * (slotWidth - modifiedSlotWidth - xCorrection * (slotWidth - mathMin(labelWidth, modifiedSlotWidth))); - } - // If the label width exceeds the available space, set a text width to be - // picked up below. Also, if a width has been set before, we need to set a new - // one because the reported labelWidth will be limited by the box (#3938). - if (labelWidth > modifiedSlotWidth || (axis.autoRotation && label.styles.width)) { - textWidth = modifiedSlotWidth; - } - - // Add ellipsis to prevent rotated labels to be clipped against the edge of the chart - } else if (rotation < 0 && pxPos - factor * labelWidth < leftBound) { - textWidth = mathRound(pxPos / mathCos(rotation * deg2rad) - leftBound); - } else if (rotation > 0 && pxPos + factor * labelWidth > rightBound) { - textWidth = mathRound((chartWidth - pxPos) / mathCos(rotation * deg2rad)); - } - - if (textWidth) { - css.width = textWidth; - if (!axis.options.labels.style.textOverflow) { - css.textOverflow = 'ellipsis'; - } - label.css(css); - } - }, - - /** - * Get the x and y position for ticks and labels - */ - getPosition: function (horiz, pos, tickmarkOffset, old) { - var axis = this.axis, - chart = axis.chart, - cHeight = (old && chart.oldChartHeight) || chart.chartHeight; - - return { - x: horiz ? - axis.translate(pos + tickmarkOffset, null, null, old) + axis.transB : - axis.left + axis.offset + (axis.opposite ? ((old && chart.oldChartWidth) || chart.chartWidth) - axis.right - axis.left : 0), - - y: horiz ? - cHeight - axis.bottom + axis.offset - (axis.opposite ? axis.height : 0) : - cHeight - axis.translate(pos + tickmarkOffset, null, null, old) - axis.transB - }; - - }, - - /** - * Get the x, y position of the tick label - */ - getLabelPosition: function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) { - var axis = this.axis, - transA = axis.transA, - reversed = axis.reversed, - staggerLines = axis.staggerLines, - rotCorr = axis.tickRotCorr || { x: 0, y: 0 }, - yOffset = labelOptions.y, - line; - - if (!defined(yOffset)) { - if (axis.side === 0) { - yOffset = label.rotation ? -8 : -label.getBBox().height; - } else if (axis.side === 2) { - yOffset = rotCorr.y + 8; - } else { - // #3140, #3140 - yOffset = mathCos(label.rotation * deg2rad) * (rotCorr.y - label.getBBox(false, 0).height / 2); - } - } - - x = x + labelOptions.x + rotCorr.x - (tickmarkOffset && horiz ? - tickmarkOffset * transA * (reversed ? -1 : 1) : 0); - y = y + yOffset - (tickmarkOffset && !horiz ? - tickmarkOffset * transA * (reversed ? 1 : -1) : 0); - - // Correct for staggered labels - if (staggerLines) { - line = (index / (step || 1) % staggerLines); - if (axis.opposite) { - line = staggerLines - line - 1; - } - y += line * (axis.labelOffset / staggerLines); - } - - return { - x: x, - y: mathRound(y) - }; - }, - - /** - * Extendible method to return the path of the marker - */ - getMarkPath: function (x, y, tickLength, tickWidth, horiz, renderer) { - return renderer.crispLine([ - M, - x, - y, - L, - x + (horiz ? 0 : -tickLength), - y + (horiz ? tickLength : 0) - ], tickWidth); - }, - - /** - * Put everything in place - * - * @param index {Number} - * @param old {Boolean} Use old coordinates to prepare an animation into new position - */ - render: function (index, old, opacity) { - var tick = this, - axis = tick.axis, - options = axis.options, - chart = axis.chart, - renderer = chart.renderer, - horiz = axis.horiz, - type = tick.type, - label = tick.label, - pos = tick.pos, - labelOptions = options.labels, - gridLine = tick.gridLine, - gridPrefix = type ? type + 'Grid' : 'grid', - tickPrefix = type ? type + 'Tick' : 'tick', - gridLineWidth = options[gridPrefix + 'LineWidth'], - gridLineColor = options[gridPrefix + 'LineColor'], - dashStyle = options[gridPrefix + 'LineDashStyle'], - tickSize = axis.tickSize(tickPrefix), - tickColor = options[tickPrefix + 'Color'], - gridLinePath, - mark = tick.mark, - markPath, - step = /*axis.labelStep || */labelOptions.step, - attribs, - show = true, - tickmarkOffset = axis.tickmarkOffset, - xy = tick.getPosition(horiz, pos, tickmarkOffset, old), - x = xy.x, - y = xy.y, - reverseCrisp = ((horiz && x === axis.pos + axis.len) || (!horiz && y === axis.pos)) ? -1 : 1; // #1480, #1687 - - opacity = pick(opacity, 1); - this.isActive = true; - - // create the grid line - if (gridLineWidth) { - gridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLineWidth * reverseCrisp, old, true); - - if (gridLine === UNDEFINED) { - attribs = { - stroke: gridLineColor, - 'stroke-width': gridLineWidth - }; - if (dashStyle) { - attribs.dashstyle = dashStyle; - } - if (!type) { - attribs.zIndex = 1; - } - if (old) { - attribs.opacity = 0; - } - tick.gridLine = gridLine = - gridLineWidth ? - renderer.path(gridLinePath) - .attr(attribs).add(axis.gridGroup) : - null; - } - - // If the parameter 'old' is set, the current call will be followed - // by another call, therefore do not do any animations this time - if (!old && gridLine && gridLinePath) { - gridLine[tick.isNew ? 'attr' : 'animate']({ - d: gridLinePath, - opacity: opacity - }); - } - } - - // create the tick mark - if (tickSize) { - if (axis.opposite) { - tickSize[0] = -tickSize[0]; - } - markPath = tick.getMarkPath(x, y, tickSize[0], tickSize[1] * reverseCrisp, horiz, renderer); - if (mark) { // updating - mark.animate({ - d: markPath, - opacity: opacity - }); - } else { // first time - tick.mark = renderer.path( - markPath - ).attr({ - stroke: tickColor, - 'stroke-width': tickSize[1], - opacity: opacity - }).add(axis.axisGroup); - } - } - - // the label is created on init - now move it into place - if (label && isNumber(x)) { - label.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step); - - // Apply show first and show last. If the tick is both first and last, it is - // a single centered tick, in which case we show the label anyway (#2100). - if ((tick.isFirst && !tick.isLast && !pick(options.showFirstLabel, 1)) || - (tick.isLast && !tick.isFirst && !pick(options.showLastLabel, 1))) { - show = false; - - // Handle label overflow and show or hide accordingly - } else if (horiz && !axis.isRadial && !labelOptions.step && !labelOptions.rotation && !old && opacity !== 0) { - tick.handleOverflow(xy); - } - - // apply step - if (step && index % step) { - // show those indices dividable by step - show = false; - } - - // Set the new position, and show or hide - if (show && isNumber(xy.y)) { - xy.opacity = opacity; - label[tick.isNew ? 'attr' : 'animate'](xy); - tick.isNew = false; - } else { - label.attr('y', -9999); // #1338 - } - } - }, - - /** - * Destructor for the tick prototype - */ - destroy: function () { - destroyObjectProperties(this, this.axis); - } - }; - - /** - * The object wrapper for plot lines and plot bands - * @param {Object} options - */ - Highcharts.PlotLineOrBand = function (axis, options) { - this.axis = axis; - - if (options) { - this.options = options; - this.id = options.id; - } - }; - - Highcharts.PlotLineOrBand.prototype = { - - /** - * Render the plot line or plot band. If it is already existing, - * move it. - */ - render: function () { - var plotLine = this, - axis = plotLine.axis, - horiz = axis.horiz, - options = plotLine.options, - optionsLabel = options.label, - label = plotLine.label, - width = options.width, - to = options.to, - from = options.from, - isBand = defined(from) && defined(to), - value = options.value, - dashStyle = options.dashStyle, - svgElem = plotLine.svgElem, - path = [], - addEvent, - eventType, - color = options.color, - zIndex = pick(options.zIndex, 0), - events = options.events, - attribs = {}, - renderer = axis.chart.renderer, - log2lin = axis.log2lin; - - // logarithmic conversion - if (axis.isLog) { - from = log2lin(from); - to = log2lin(to); - value = log2lin(value); - } - - // plot line - if (width) { - path = axis.getPlotLinePath(value, width); - attribs = { - stroke: color, - 'stroke-width': width - }; - if (dashStyle) { - attribs.dashstyle = dashStyle; - } - } else if (isBand) { // plot band - - path = axis.getPlotBandPath(from, to, options); - if (color) { - attribs.fill = color; - } - if (options.borderWidth) { - attribs.stroke = options.borderColor; - attribs['stroke-width'] = options.borderWidth; - } - } else { - return; - } - // zIndex - attribs.zIndex = zIndex; - - // common for lines and bands - if (svgElem) { - if (path) { - svgElem.show(); - svgElem.animate({ d: path }); - } else { - svgElem.hide(); - if (label) { - plotLine.label = label = label.destroy(); - } - } - } else if (path && path.length) { - plotLine.svgElem = svgElem = renderer.path(path) - .attr(attribs).add(); - - // events - if (events) { - addEvent = function (eventType) { - svgElem.on(eventType, function (e) { - events[eventType].apply(plotLine, [e]); - }); - }; - for (eventType in events) { - addEvent(eventType); - } - } - } - - // the plot band/line label - if (optionsLabel && defined(optionsLabel.text) && path && path.length && - axis.width > 0 && axis.height > 0 && !path.flat) { - // apply defaults - optionsLabel = merge({ - align: horiz && isBand && 'center', - x: horiz ? !isBand && 4 : 10, - verticalAlign: !horiz && isBand && 'middle', - y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4, - rotation: horiz && !isBand && 90 - }, optionsLabel); - - this.renderLabel(optionsLabel, path, isBand, zIndex); - - } else if (label) { // move out of sight - label.hide(); - } - - // chainable - return plotLine; - }, - - /** - * Render and align label for plot line or band. - */ - renderLabel: function (optionsLabel, path, isBand, zIndex) { - var plotLine = this, - label = plotLine.label, - renderer = plotLine.axis.chart.renderer, - attribs, - xs, - ys, - x, - y; - - // add the SVG element - if (!label) { - attribs = { - align: optionsLabel.textAlign || optionsLabel.align, - rotation: optionsLabel.rotation - }; - - attribs.zIndex = zIndex; - - plotLine.label = label = renderer.text( - optionsLabel.text, - 0, - 0, - optionsLabel.useHTML - ) - .attr(attribs) - .css(optionsLabel.style) - .add(); - } - - // get the bounding box and align the label - // #3000 changed to better handle choice between plotband or plotline - xs = [path[1], path[4], (isBand ? path[6] : path[1])]; - ys = [path[2], path[5], (isBand ? path[7] : path[2])]; - x = arrayMin(xs); - y = arrayMin(ys); - - label.align(optionsLabel, false, { - x: x, - y: y, - width: arrayMax(xs) - x, - height: arrayMax(ys) - y - }); - label.show(); - }, - - /** - * Remove the plot line or band - */ - destroy: function () { - // remove it from the lookup - erase(this.axis.plotLinesAndBands, this); - - delete this.axis; - destroyObjectProperties(this); - } - }; - - /** - * Object with members for extending the Axis prototype - */ - - AxisPlotLineOrBandExtension = { - - /** - * Create the path for a plot band - */ - getPlotBandPath: function (from, to) { - var toPath = this.getPlotLinePath(to, null, null, true), - path = this.getPlotLinePath(from, null, null, true); - - if (path && toPath) { - - // Flat paths don't need labels (#3836) - path.flat = path.toString() === toPath.toString(); - - path.push( - toPath[4], - toPath[5], - toPath[1], - toPath[2] - ); - } else { // outside the axis area - path = null; - } - - return path; - }, - - addPlotBand: function (options) { - return this.addPlotBandOrLine(options, 'plotBands'); - }, - - addPlotLine: function (options) { - return this.addPlotBandOrLine(options, 'plotLines'); - }, - - /** - * Add a plot band or plot line after render time - * - * @param options {Object} The plotBand or plotLine configuration object - */ - addPlotBandOrLine: function (options, coll) { - var obj = new Highcharts.PlotLineOrBand(this, options).render(), - userOptions = this.userOptions; - - if (obj) { // #2189 - // Add it to the user options for exporting and Axis.update - if (coll) { - userOptions[coll] = userOptions[coll] || []; - userOptions[coll].push(options); - } - this.plotLinesAndBands.push(obj); - } - - return obj; - }, - - /** - * Remove a plot band or plot line from the chart by id - * @param {Object} id - */ - removePlotBandOrLine: function (id) { - var plotLinesAndBands = this.plotLinesAndBands, - options = this.options, - userOptions = this.userOptions, - i = plotLinesAndBands.length; - while (i--) { - if (plotLinesAndBands[i].id === id) { - plotLinesAndBands[i].destroy(); - } - } - each([options.plotLines || [], userOptions.plotLines || [], options.plotBands || [], userOptions.plotBands || []], function (arr) { - i = arr.length; - while (i--) { - if (arr[i].id === id) { - erase(arr, arr[i]); - } - } - }); - } - }; - - /** - * Create a new axis object - * @param {Object} chart - * @param {Object} options - */ - var Axis = Highcharts.Axis = function () { - this.init.apply(this, arguments); - }; - - Axis.prototype = { - - /** - * Default options for the X axis - the Y axis has extended defaults - */ - defaultOptions: { - // allowDecimals: null, - // alternateGridColor: null, - // categories: [], - dateTimeLabelFormats: { - millisecond: '%H:%M:%S.%L', - second: '%H:%M:%S', - minute: '%H:%M', - hour: '%H:%M', - day: '%e. %b', - week: '%e. %b', - month: '%b \'%y', - year: '%Y' - }, - endOnTick: false, - gridLineColor: '#D8D8D8', - // gridLineDashStyle: 'solid', - // gridLineWidth: 0, - // reversed: false, - - labels: { - enabled: true, - // rotation: 0, - // align: 'center', - // step: null, - style: { - color: '#606060', - cursor: 'default', - fontSize: '11px' - }, - x: 0 - //y: undefined - /*formatter: function () { - return this.value; - },*/ - }, - lineColor: '#C0D0E0', - lineWidth: 1, - //linkedTo: null, - //max: undefined, - //min: undefined, - minPadding: 0.01, - maxPadding: 0.01, - //minRange: null, - minorGridLineColor: '#E0E0E0', - // minorGridLineDashStyle: null, - minorGridLineWidth: 1, - minorTickColor: '#A0A0A0', - //minorTickInterval: null, - minorTickLength: 2, - minorTickPosition: 'outside', // inside or outside - //minorTickWidth: 0, - //opposite: false, - //offset: 0, - //plotBands: [{ - // events: {}, - // zIndex: 1, - // labels: { align, x, verticalAlign, y, style, rotation, textAlign } - //}], - //plotLines: [{ - // events: {} - // dashStyle: {} - // zIndex: - // labels: { align, x, verticalAlign, y, style, rotation, textAlign } - //}], - //reversed: false, - // showFirstLabel: true, - // showLastLabel: true, - startOfWeek: 1, - startOnTick: false, - tickColor: '#C0D0E0', - //tickInterval: null, - tickLength: 10, - tickmarkPlacement: 'between', // on or between - tickPixelInterval: 100, - tickPosition: 'outside', - //tickWidth: 1, - title: { - //text: null, - align: 'middle', // low, middle or high - //margin: 0 for horizontal, 10 for vertical axes, - //rotation: 0, - //side: 'outside', - style: { - color: '#707070' - } - //x: 0, - //y: 0 - }, - type: 'linear' // linear, logarithmic or datetime - //visible: true - }, - - /** - * This options set extends the defaultOptions for Y axes - */ - defaultYAxisOptions: { - endOnTick: true, - gridLineWidth: 1, - tickPixelInterval: 72, - showLastLabel: true, - labels: { - x: -8 - }, - lineWidth: 0, - maxPadding: 0.05, - minPadding: 0.05, - startOnTick: true, - //tickWidth: 0, - title: { - rotation: 270, - text: 'Values' - }, - stackLabels: { - enabled: false, - //align: dynamic, - //y: dynamic, - //x: dynamic, - //verticalAlign: dynamic, - //textAlign: dynamic, - //rotation: 0, - formatter: function () { - return Highcharts.numberFormat(this.total, -1); - }, - style: merge(defaultPlotOptions.line.dataLabels.style, { color: '#000000' }) - } - }, - - /** - * These options extend the defaultOptions for left axes - */ - defaultLeftAxisOptions: { - labels: { - x: -15 - }, - title: { - rotation: 270 - } - }, - - /** - * These options extend the defaultOptions for right axes - */ - defaultRightAxisOptions: { - labels: { - x: 15 - }, - title: { - rotation: 90 - } - }, - - /** - * These options extend the defaultOptions for bottom axes - */ - defaultBottomAxisOptions: { - labels: { - autoRotation: [-45], - x: 0 - // overflow: undefined, - // staggerLines: null - }, - title: { - rotation: 0 - } - }, - /** - * These options extend the defaultOptions for top axes - */ - defaultTopAxisOptions: { - labels: { - autoRotation: [-45], - x: 0 - // overflow: undefined - // staggerLines: null - }, - title: { - rotation: 0 - } - }, - - /** - * Initialize the axis - */ - init: function (chart, userOptions) { - - - var isXAxis = userOptions.isX, - axis = this; - - axis.chart = chart; - - // Flag, is the axis horizontal - axis.horiz = chart.inverted ? !isXAxis : isXAxis; - - // Flag, isXAxis - axis.isXAxis = isXAxis; - axis.coll = isXAxis ? 'xAxis' : 'yAxis'; - - axis.opposite = userOptions.opposite; // needed in setOptions - axis.side = userOptions.side || (axis.horiz ? - (axis.opposite ? 0 : 2) : // top : bottom - (axis.opposite ? 1 : 3)); // right : left - - axis.setOptions(userOptions); - - - var options = this.options, - type = options.type, - isDatetimeAxis = type === 'datetime'; - - axis.labelFormatter = options.labels.formatter || axis.defaultLabelFormatter; // can be overwritten by dynamic format - - - // Flag, stagger lines or not - axis.userOptions = userOptions; - - //axis.axisTitleMargin = UNDEFINED,// = options.title.margin, - axis.minPixelPadding = 0; - - axis.reversed = options.reversed; - axis.visible = options.visible !== false; - axis.zoomEnabled = options.zoomEnabled !== false; - - // Initial categories - axis.categories = options.categories || type === 'category'; - axis.names = axis.names || []; // Preserve on update (#3830) - - // Elements - //axis.axisGroup = UNDEFINED; - //axis.gridGroup = UNDEFINED; - //axis.axisTitle = UNDEFINED; - //axis.axisLine = UNDEFINED; - - // Shorthand types - axis.isLog = type === 'logarithmic'; - axis.isDatetimeAxis = isDatetimeAxis; - - // Flag, if axis is linked to another axis - axis.isLinked = defined(options.linkedTo); - // Linked axis. - //axis.linkedParent = UNDEFINED; - - // Tick positions - //axis.tickPositions = UNDEFINED; // array containing predefined positions - // Tick intervals - //axis.tickInterval = UNDEFINED; - //axis.minorTickInterval = UNDEFINED; - - - // Major ticks - axis.ticks = {}; - axis.labelEdge = []; - // Minor ticks - axis.minorTicks = {}; - - // List of plotLines/Bands - axis.plotLinesAndBands = []; - - // Alternate bands - axis.alternateBands = {}; - - // Axis metrics - //axis.left = UNDEFINED; - //axis.top = UNDEFINED; - //axis.width = UNDEFINED; - //axis.height = UNDEFINED; - //axis.bottom = UNDEFINED; - //axis.right = UNDEFINED; - //axis.transA = UNDEFINED; - //axis.transB = UNDEFINED; - //axis.oldTransA = UNDEFINED; - axis.len = 0; - //axis.oldMin = UNDEFINED; - //axis.oldMax = UNDEFINED; - //axis.oldUserMin = UNDEFINED; - //axis.oldUserMax = UNDEFINED; - //axis.oldAxisLength = UNDEFINED; - axis.minRange = axis.userMinRange = options.minRange || options.maxZoom; - axis.range = options.range; - axis.offset = options.offset || 0; - - - // Dictionary for stacks - axis.stacks = {}; - axis.oldStacks = {}; - axis.stacksTouched = 0; - - // Min and max in the data - //axis.dataMin = UNDEFINED, - //axis.dataMax = UNDEFINED, - - // The axis range - axis.max = null; - axis.min = null; - - // User set min and max - //axis.userMin = UNDEFINED, - //axis.userMax = UNDEFINED, - - // Crosshair options - axis.crosshair = pick(options.crosshair, splat(chart.options.tooltip.crosshairs)[isXAxis ? 0 : 1], false); - // Run Axis - - var eventType, - events = axis.options.events; - - // Register - if (inArray(axis, chart.axes) === -1) { // don't add it again on Axis.update() - if (isXAxis && !this.isColorAxis) { // #2713 - chart.axes.splice(chart.xAxis.length, 0, axis); - } else { - chart.axes.push(axis); - } - - chart[axis.coll].push(axis); - } - - axis.series = axis.series || []; // populated by Series - - // inverted charts have reversed xAxes as default - if (chart.inverted && isXAxis && axis.reversed === UNDEFINED) { - axis.reversed = true; - } - - axis.removePlotBand = axis.removePlotBandOrLine; - axis.removePlotLine = axis.removePlotBandOrLine; - - - // register event listeners - for (eventType in events) { - addEvent(axis, eventType, events[eventType]); - } - - // extend logarithmic axis - if (axis.isLog) { - axis.val2lin = axis.log2lin; - axis.lin2val = axis.lin2log; - } - }, - - /** - * Merge and set options - */ - setOptions: function (userOptions) { - this.options = merge( - this.defaultOptions, - this.isXAxis ? {} : this.defaultYAxisOptions, - [this.defaultTopAxisOptions, this.defaultRightAxisOptions, - this.defaultBottomAxisOptions, this.defaultLeftAxisOptions][this.side], - merge( - defaultOptions[this.coll], // if set in setOptions (#1053) - userOptions - ) - ); - }, - - /** - * The default label formatter. The context is a special config object for the label. - */ - defaultLabelFormatter: function () { - var axis = this.axis, - value = this.value, - categories = axis.categories, - dateTimeLabelFormat = this.dateTimeLabelFormat, - numericSymbols = defaultOptions.lang.numericSymbols, - i = numericSymbols && numericSymbols.length, - multi, - ret, - formatOption = axis.options.labels.format, - - // make sure the same symbol is added for all labels on a linear axis - numericSymbolDetector = axis.isLog ? value : axis.tickInterval; - - if (formatOption) { - ret = format(formatOption, this); - - } else if (categories) { - ret = value; - - } else if (dateTimeLabelFormat) { // datetime axis - ret = dateFormat(dateTimeLabelFormat, value); - - } else if (i && numericSymbolDetector >= 1000) { - // Decide whether we should add a numeric symbol like k (thousands) or M (millions). - // If we are to enable this in tooltip or other places as well, we can move this - // logic to the numberFormatter and enable it by a parameter. - while (i-- && ret === UNDEFINED) { - multi = Math.pow(1000, i + 1); - if (numericSymbolDetector >= multi && (value * 10) % multi === 0 && numericSymbols[i] !== null) { - ret = Highcharts.numberFormat(value / multi, -1) + numericSymbols[i]; - } - } - } - - if (ret === UNDEFINED) { - if (mathAbs(value) >= 10000) { // add thousands separators - ret = Highcharts.numberFormat(value, -1); - - } else { // small numbers - ret = Highcharts.numberFormat(value, -1, UNDEFINED, ''); // #2466 - } - } - - return ret; - }, - - /** - * Get the minimum and maximum for the series of each axis - */ - getSeriesExtremes: function () { - var axis = this, - chart = axis.chart; - - axis.hasVisibleSeries = false; - - // Reset properties in case we're redrawing (#3353) - axis.dataMin = axis.dataMax = axis.threshold = null; - axis.softThreshold = !axis.isXAxis; - - if (axis.buildStacks) { - axis.buildStacks(); - } - - // loop through this axis' series - each(axis.series, function (series) { - - if (series.visible || !chart.options.chart.ignoreHiddenSeries) { - - var seriesOptions = series.options, - xData, - threshold = seriesOptions.threshold, - seriesDataMin, - seriesDataMax; - - axis.hasVisibleSeries = true; - - // Validate threshold in logarithmic axes - if (axis.isLog && threshold <= 0) { - threshold = null; - } - - // Get dataMin and dataMax for X axes - if (axis.isXAxis) { - xData = series.xData; - if (xData.length) { - // If xData contains values which is not numbers, then filter them out. - // To prevent performance hit, we only do this after we have already - // found seriesDataMin because in most cases all data is valid. #5234. - seriesDataMin = arrayMin(xData); - if (!isNumber(seriesDataMin) && !(seriesDataMin instanceof Date)) { // Date for #5010 - xData = grep(xData, function (x) { - return isNumber(x); - }); - seriesDataMin = arrayMin(xData); // Do it again with valid data - } - - axis.dataMin = mathMin(pick(axis.dataMin, xData[0]), seriesDataMin); - axis.dataMax = mathMax(pick(axis.dataMax, xData[0]), arrayMax(xData)); - - } - - // Get dataMin and dataMax for Y axes, as well as handle stacking and processed data - } else { - - // Get this particular series extremes - series.getExtremes(); - seriesDataMax = series.dataMax; - seriesDataMin = series.dataMin; - - // Get the dataMin and dataMax so far. If percentage is used, the min and max are - // always 0 and 100. If seriesDataMin and seriesDataMax is null, then series - // doesn't have active y data, we continue with nulls - if (defined(seriesDataMin) && defined(seriesDataMax)) { - axis.dataMin = mathMin(pick(axis.dataMin, seriesDataMin), seriesDataMin); - axis.dataMax = mathMax(pick(axis.dataMax, seriesDataMax), seriesDataMax); - } - - // Adjust to threshold - if (defined(threshold)) { - axis.threshold = threshold; - } - // If any series has a hard threshold, it takes precedence - if (!seriesOptions.softThreshold || axis.isLog) { - axis.softThreshold = false; - } - } - } - }); - }, - - /** - * Translate from axis value to pixel position on the chart, or back - * - */ - translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacement) { - var axis = this.linkedParent || this, // #1417 - sign = 1, - cvsOffset = 0, - localA = old ? axis.oldTransA : axis.transA, - localMin = old ? axis.oldMin : axis.min, - returnValue, - minPixelPadding = axis.minPixelPadding, - doPostTranslate = (axis.isOrdinal || axis.isBroken || (axis.isLog && handleLog)) && axis.lin2val; - - if (!localA) { - localA = axis.transA; - } - - // In vertical axes, the canvas coordinates start from 0 at the top like in - // SVG. - if (cvsCoord) { - sign *= -1; // canvas coordinates inverts the value - cvsOffset = axis.len; - } - - // Handle reversed axis - if (axis.reversed) { - sign *= -1; - cvsOffset -= sign * (axis.sector || axis.len); - } - - // From pixels to value - if (backwards) { // reverse translation - - val = val * sign + cvsOffset; - val -= minPixelPadding; - returnValue = val / localA + localMin; // from chart pixel to value - if (doPostTranslate) { // log and ordinal axes - returnValue = axis.lin2val(returnValue); - } - - // From value to pixels - } else { - if (doPostTranslate) { // log and ordinal axes - val = axis.val2lin(val); - } - if (pointPlacement === 'between') { - pointPlacement = 0.5; - } - returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding) + - (isNumber(pointPlacement) ? localA * pointPlacement * axis.pointRange : 0); - } - - return returnValue; - }, - - /** - * Utility method to translate an axis value to pixel position. - * @param {Number} value A value in terms of axis units - * @param {Boolean} paneCoordinates Whether to return the pixel coordinate relative to the chart - * or just the axis/pane itself. - */ - toPixels: function (value, paneCoordinates) { - return this.translate(value, false, !this.horiz, null, true) + (paneCoordinates ? 0 : this.pos); - }, - - /* - * Utility method to translate a pixel position in to an axis value - * @param {Number} pixel The pixel value coordinate - * @param {Boolean} paneCoordiantes Whether the input pixel is relative to the chart or just the - * axis/pane itself. - */ - toValue: function (pixel, paneCoordinates) { - return this.translate(pixel - (paneCoordinates ? 0 : this.pos), true, !this.horiz, null, true); - }, - - /** - * Create the path for a plot line that goes from the given value on - * this axis, across the plot to the opposite side - * @param {Number} value - * @param {Number} lineWidth Used for calculation crisp line - * @param {Number] old Use old coordinates (for resizing and rescaling) - */ - getPlotLinePath: function (value, lineWidth, old, force, translatedValue) { - var axis = this, - chart = axis.chart, - axisLeft = axis.left, - axisTop = axis.top, - x1, - y1, - x2, - y2, - cHeight = (old && chart.oldChartHeight) || chart.chartHeight, - cWidth = (old && chart.oldChartWidth) || chart.chartWidth, - skip, - transB = axis.transB, - /** - * Check if x is between a and b. If not, either move to a/b or skip, - * depending on the force parameter. - */ - between = function (x, a, b) { - if (x < a || x > b) { - if (force) { - x = mathMin(mathMax(a, x), b); - } else { - skip = true; - } - } - return x; - }; - - translatedValue = pick(translatedValue, axis.translate(value, null, null, old)); - x1 = x2 = mathRound(translatedValue + transB); - y1 = y2 = mathRound(cHeight - translatedValue - transB); - if (!isNumber(translatedValue)) { // no min or max - skip = true; - - } else if (axis.horiz) { - y1 = axisTop; - y2 = cHeight - axis.bottom; - x1 = x2 = between(x1, axisLeft, axisLeft + axis.width); - } else { - x1 = axisLeft; - x2 = cWidth - axis.right; - y1 = y2 = between(y1, axisTop, axisTop + axis.height); - } - return skip && !force ? - null : - chart.renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 1); - }, - - /** - * Set the tick positions of a linear axis to round values like whole tens or every five. - */ - getLinearTickPositions: function (tickInterval, min, max) { - var pos, - lastPos, - roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval), - roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval), - tickPositions = []; - - // For single points, add a tick regardless of the relative position (#2662) - if (min === max && isNumber(min)) { - return [min]; - } - - // Populate the intermediate values - pos = roundedMin; - while (pos <= roundedMax) { - - // Place the tick on the rounded value - tickPositions.push(pos); - - // Always add the raw tickInterval, not the corrected one. - pos = correctFloat(pos + tickInterval); - - // If the interval is not big enough in the current min - max range to actually increase - // the loop variable, we need to break out to prevent endless loop. Issue #619 - if (pos === lastPos) { - break; - } - - // Record the last value - lastPos = pos; - } - return tickPositions; - }, - - /** - * Return the minor tick positions. For logarithmic axes, reuse the same logic - * as for major ticks. - */ - getMinorTickPositions: function () { - var axis = this, - options = axis.options, - tickPositions = axis.tickPositions, - minorTickInterval = axis.minorTickInterval, - minorTickPositions = [], - pos, - i, - pointRangePadding = axis.pointRangePadding || 0, - min = axis.min - pointRangePadding, // #1498 - max = axis.max + pointRangePadding, // #1498 - range = max - min, - len; - - // If minor ticks get too dense, they are hard to read, and may cause long running script. So we don't draw them. - if (range && range / minorTickInterval < axis.len / 3) { // #3875 - - if (axis.isLog) { - len = tickPositions.length; - for (i = 1; i < len; i++) { - minorTickPositions = minorTickPositions.concat( - axis.getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true) - ); - } - } else if (axis.isDatetimeAxis && options.minorTickInterval === 'auto') { // #1314 - minorTickPositions = minorTickPositions.concat( - axis.getTimeTicks( - axis.normalizeTimeTickInterval(minorTickInterval), - min, - max, - options.startOfWeek - ) - ); - } else { - for (pos = min + (tickPositions[0] - min) % minorTickInterval; pos <= max; pos += minorTickInterval) { - minorTickPositions.push(pos); - } - } - } - - if (minorTickPositions.length !== 0) { // don't change the extremes, when there is no minor ticks - axis.trimTicks(minorTickPositions, options.startOnTick, options.endOnTick); // #3652 #3743 #1498 - } - return minorTickPositions; - }, - - /** - * Adjust the min and max for the minimum range. Keep in mind that the series data is - * not yet processed, so we don't have information on data cropping and grouping, or - * updated axis.pointRange or series.pointRange. The data can't be processed until - * we have finally established min and max. - */ - adjustForMinRange: function () { - var axis = this, - options = axis.options, - min = axis.min, - max = axis.max, - zoomOffset, - spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange, - closestDataRange, - i, - distance, - xData, - loopLength, - minArgs, - maxArgs, - minRange; - - // Set the automatic minimum range based on the closest point distance - if (axis.isXAxis && axis.minRange === UNDEFINED && !axis.isLog) { - - if (defined(options.min) || defined(options.max)) { - axis.minRange = null; // don't do this again - - } else { - - // Find the closest distance between raw data points, as opposed to - // closestPointRange that applies to processed points (cropped and grouped) - each(axis.series, function (series) { - xData = series.xData; - loopLength = series.xIncrement ? 1 : xData.length - 1; - for (i = loopLength; i > 0; i--) { - distance = xData[i] - xData[i - 1]; - if (closestDataRange === UNDEFINED || distance < closestDataRange) { - closestDataRange = distance; - } - } - }); - axis.minRange = mathMin(closestDataRange * 5, axis.dataMax - axis.dataMin); - } - } - - // if minRange is exceeded, adjust - if (max - min < axis.minRange) { - minRange = axis.minRange; - zoomOffset = (minRange - max + min) / 2; - - // if min and max options have been set, don't go beyond it - minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)]; - if (spaceAvailable) { // if space is available, stay within the data range - minArgs[2] = axis.dataMin; - } - min = arrayMax(minArgs); - - maxArgs = [min + minRange, pick(options.max, min + minRange)]; - if (spaceAvailable) { // if space is availabe, stay within the data range - maxArgs[2] = axis.dataMax; - } - - max = arrayMin(maxArgs); - - // now if the max is adjusted, adjust the min back - if (max - min < minRange) { - minArgs[0] = max - minRange; - minArgs[1] = pick(options.min, max - minRange); - min = arrayMax(minArgs); - } - } - - // Record modified extremes - axis.min = min; - axis.max = max; - }, - - /** - * Find the closestPointRange across all series - */ - getClosest: function () { - var ret; - each(this.series, function (series) { - var seriesClosest = series.closestPointRange; - if (!series.noSharedTooltip && defined(seriesClosest)) { - ret = defined(ret) ? - mathMin(ret, seriesClosest) : - seriesClosest; - } - }); - return ret; - }, - - /** - * Update translation information - */ - setAxisTranslation: function (saveOld) { - var axis = this, - range = axis.max - axis.min, - pointRange = axis.axisPointRange || 0, - closestPointRange, - minPointOffset = 0, - pointRangePadding = 0, - linkedParent = axis.linkedParent, - ordinalCorrection, - hasCategories = !!axis.categories, - transA = axis.transA, - isXAxis = axis.isXAxis; - - // Adjust translation for padding. Y axis with categories need to go through the same (#1784). - if (isXAxis || hasCategories || pointRange) { - if (linkedParent) { - minPointOffset = linkedParent.minPointOffset; - pointRangePadding = linkedParent.pointRangePadding; - - } else { - - // Get the closest points - closestPointRange = axis.getClosest(); - - each(axis.series, function (series) { - var seriesPointRange = hasCategories ? - 1 : - (isXAxis ? - pick(series.options.pointRange, closestPointRange, 0) : - (axis.axisPointRange || 0)), // #2806 - pointPlacement = series.options.pointPlacement; - - pointRange = mathMax(pointRange, seriesPointRange); - - if (!axis.single) { - // minPointOffset is the value padding to the left of the axis in order to make - // room for points with a pointRange, typically columns. When the pointPlacement option - // is 'between' or 'on', this padding does not apply. - minPointOffset = mathMax( - minPointOffset, - isString(pointPlacement) ? 0 : seriesPointRange / 2 - ); - - // Determine the total padding needed to the length of the axis to make room for the - // pointRange. If the series' pointPlacement is 'on', no padding is added. - pointRangePadding = mathMax( - pointRangePadding, - pointPlacement === 'on' ? 0 : seriesPointRange - ); - } - }); - } - - // Record minPointOffset and pointRangePadding - ordinalCorrection = axis.ordinalSlope && closestPointRange ? axis.ordinalSlope / closestPointRange : 1; // #988, #1853 - axis.minPointOffset = minPointOffset = minPointOffset * ordinalCorrection; - axis.pointRangePadding = pointRangePadding = pointRangePadding * ordinalCorrection; - - // pointRange means the width reserved for each point, like in a column chart - axis.pointRange = mathMin(pointRange, range); - - // closestPointRange means the closest distance between points. In columns - // it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange - // is some other value - if (isXAxis) { - axis.closestPointRange = closestPointRange; - } - } - - // Secondary values - if (saveOld) { - axis.oldTransA = transA; - } - axis.translationSlope = axis.transA = transA = axis.len / ((range + pointRangePadding) || 1); - axis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend - axis.minPixelPadding = transA * minPointOffset; - }, - - minFromRange: function () { - return this.max - this.range; - }, - - /** - * Set the tick positions to round values and optionally extend the extremes - * to the nearest tick - */ - setTickInterval: function (secondPass) { - var axis = this, - chart = axis.chart, - options = axis.options, - isLog = axis.isLog, - log2lin = axis.log2lin, - isDatetimeAxis = axis.isDatetimeAxis, - isXAxis = axis.isXAxis, - isLinked = axis.isLinked, - maxPadding = options.maxPadding, - minPadding = options.minPadding, - length, - linkedParentExtremes, - tickIntervalOption = options.tickInterval, - minTickInterval, - tickPixelIntervalOption = options.tickPixelInterval, - categories = axis.categories, - threshold = axis.threshold, - softThreshold = axis.softThreshold, - thresholdMin, - thresholdMax, - hardMin, - hardMax; - - if (!isDatetimeAxis && !categories && !isLinked) { - this.getTickAmount(); - } - - // Min or max set either by zooming/setExtremes or initial options - hardMin = pick(axis.userMin, options.min); - hardMax = pick(axis.userMax, options.max); - - // Linked axis gets the extremes from the parent axis - if (isLinked) { - axis.linkedParent = chart[axis.coll][options.linkedTo]; - linkedParentExtremes = axis.linkedParent.getExtremes(); - axis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin); - axis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax); - if (options.type !== axis.linkedParent.options.type) { - error(11, 1); // Can't link axes of different type - } - - // Initial min and max from the extreme data values - } else { - - // Adjust to hard threshold - if (!softThreshold && defined(threshold)) { - if (axis.dataMin >= threshold) { - thresholdMin = threshold; - minPadding = 0; - } else if (axis.dataMax <= threshold) { - thresholdMax = threshold; - maxPadding = 0; - } - } - - axis.min = pick(hardMin, thresholdMin, axis.dataMin); - axis.max = pick(hardMax, thresholdMax, axis.dataMax); - - } - - if (isLog) { - if (!secondPass && mathMin(axis.min, pick(axis.dataMin, axis.min)) <= 0) { // #978 - error(10, 1); // Can't plot negative values on log axis - } - // The correctFloat cures #934, float errors on full tens. But it - // was too aggressive for #4360 because of conversion back to lin, - // therefore use precision 15. - axis.min = correctFloat(log2lin(axis.min), 15); - axis.max = correctFloat(log2lin(axis.max), 15); - } - - // handle zoomed range - if (axis.range && defined(axis.max)) { - axis.userMin = axis.min = hardMin = mathMax(axis.min, axis.minFromRange()); // #618 - axis.userMax = hardMax = axis.max; - - axis.range = null; // don't use it when running setExtremes - } - - // Hook for Highstock Scroller. Consider combining with beforePadding. - fireEvent(axis, 'foundExtremes'); - - // Hook for adjusting this.min and this.max. Used by bubble series. - if (axis.beforePadding) { - axis.beforePadding(); - } - - // adjust min and max for the minimum range - axis.adjustForMinRange(); - - // Pad the values to get clear of the chart's edges. To avoid tickInterval taking the padding - // into account, we do this after computing tick interval (#1337). - if (!categories && !axis.axisPointRange && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) { - length = axis.max - axis.min; - if (length) { - if (!defined(hardMin) && minPadding) { - axis.min -= length * minPadding; - } - if (!defined(hardMax) && maxPadding) { - axis.max += length * maxPadding; - } - } - } - - // Stay within floor and ceiling - if (isNumber(options.floor)) { - axis.min = mathMax(axis.min, options.floor); - } - if (isNumber(options.ceiling)) { - axis.max = mathMin(axis.max, options.ceiling); - } - - // When the threshold is soft, adjust the extreme value only if - // the data extreme and the padded extreme land on either side of the threshold. For example, - // a series of [0, 1, 2, 3] would make the yAxis add a tick for -1 because of the - // default minPadding and startOnTick options. This is prevented by the softThreshold - // option. - if (softThreshold && defined(axis.dataMin)) { - threshold = threshold || 0; - if (!defined(hardMin) && axis.min < threshold && axis.dataMin >= threshold) { - axis.min = threshold; - } else if (!defined(hardMax) && axis.max > threshold && axis.dataMax <= threshold) { - axis.max = threshold; - } - } - - - // get tickInterval - if (axis.min === axis.max || axis.min === undefined || axis.max === undefined) { - axis.tickInterval = 1; - } else if (isLinked && !tickIntervalOption && - tickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) { - axis.tickInterval = tickIntervalOption = axis.linkedParent.tickInterval; - } else { - axis.tickInterval = pick( - tickIntervalOption, - this.tickAmount ? ((axis.max - axis.min) / mathMax(this.tickAmount - 1, 1)) : undefined, - categories ? // for categoried axis, 1 is default, for linear axis use tickPix - 1 : - // don't let it be more than the data range - (axis.max - axis.min) * tickPixelIntervalOption / mathMax(axis.len, tickPixelIntervalOption) - ); - } - - // Now we're finished detecting min and max, crop and group series data. This - // is in turn needed in order to find tick positions in ordinal axes. - if (isXAxis && !secondPass) { - each(axis.series, function (series) { - series.processData(axis.min !== axis.oldMin || axis.max !== axis.oldMax); - }); - } - - // set the translation factor used in translate function - axis.setAxisTranslation(true); - - // hook for ordinal axes and radial axes - if (axis.beforeSetTickPositions) { - axis.beforeSetTickPositions(); - } - - // hook for extensions, used in Highstock ordinal axes - if (axis.postProcessTickInterval) { - axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval); - } - - // In column-like charts, don't cramp in more ticks than there are points (#1943, #4184) - if (axis.pointRange && !tickIntervalOption) { - axis.tickInterval = mathMax(axis.pointRange, axis.tickInterval); - } - - // Before normalizing the tick interval, handle minimum tick interval. This applies only if tickInterval is not defined. - minTickInterval = pick(options.minTickInterval, axis.isDatetimeAxis && axis.closestPointRange); - if (!tickIntervalOption && axis.tickInterval < minTickInterval) { - axis.tickInterval = minTickInterval; - } - - // for linear axes, get magnitude and normalize the interval - if (!isDatetimeAxis && !isLog && !tickIntervalOption) { - axis.tickInterval = normalizeTickInterval( - axis.tickInterval, - null, - getMagnitude(axis.tickInterval), - // If the tick interval is between 0.5 and 5 and the axis max is in the order of - // thousands, chances are we are dealing with years. Don't allow decimals. #3363. - pick(options.allowDecimals, !(axis.tickInterval > 0.5 && axis.tickInterval < 5 && axis.max > 1000 && axis.max < 9999)), - !!this.tickAmount - ); - } - - // Prevent ticks from getting so close that we can't draw the labels - if (!this.tickAmount && this.len) { // Color axis with disabled legend has no length - axis.tickInterval = axis.unsquish(); - } - - this.setTickPositions(); - }, - - /** - * Now we have computed the normalized tickInterval, get the tick positions - */ - setTickPositions: function () { - - var options = this.options, - tickPositions, - tickPositionsOption = options.tickPositions, - tickPositioner = options.tickPositioner, - startOnTick = options.startOnTick, - endOnTick = options.endOnTick, - single; - - // Set the tickmarkOffset - this.tickmarkOffset = (this.categories && options.tickmarkPlacement === 'between' && - this.tickInterval === 1) ? 0.5 : 0; // #3202 - - - // get minorTickInterval - this.minorTickInterval = options.minorTickInterval === 'auto' && this.tickInterval ? - this.tickInterval / 5 : options.minorTickInterval; - - // Find the tick positions - this.tickPositions = tickPositions = tickPositionsOption && tickPositionsOption.slice(); // Work on a copy (#1565) - if (!tickPositions) { - - if (this.isDatetimeAxis) { - tickPositions = this.getTimeTicks( - this.normalizeTimeTickInterval(this.tickInterval, options.units), - this.min, - this.max, - options.startOfWeek, - this.ordinalPositions, - this.closestPointRange, - true - ); - } else if (this.isLog) { - tickPositions = this.getLogTickPositions(this.tickInterval, this.min, this.max); - } else { - tickPositions = this.getLinearTickPositions(this.tickInterval, this.min, this.max); - } - - // Too dense ticks, keep only the first and last (#4477) - if (tickPositions.length > this.len) { - tickPositions = [tickPositions[0], tickPositions.pop()]; - } - - this.tickPositions = tickPositions; - - // Run the tick positioner callback, that allows modifying auto tick positions. - if (tickPositioner) { - tickPositioner = tickPositioner.apply(this, [this.min, this.max]); - if (tickPositioner) { - this.tickPositions = tickPositions = tickPositioner; - } - } - - } - - if (!this.isLinked) { - - // reset min/max or remove extremes based on start/end on tick - this.trimTicks(tickPositions, startOnTick, endOnTick); - - // When there is only one point, or all points have the same value on this axis, then min - // and max are equal and tickPositions.length is 0 or 1. In this case, add some padding - // in order to center the point, but leave it with one tick. #1337. - if (this.min === this.max && defined(this.min) && !this.tickAmount) { - // Substract half a unit (#2619, #2846, #2515, #3390) - single = true; - this.min -= 0.5; - this.max += 0.5; - } - this.single = single; - - if (!tickPositionsOption && !tickPositioner) { - this.adjustTickAmount(); - } - } - }, - - /** - * Handle startOnTick and endOnTick by either adapting to padding min/max or rounded min/max - */ - trimTicks: function (tickPositions, startOnTick, endOnTick) { - var roundedMin = tickPositions[0], - roundedMax = tickPositions[tickPositions.length - 1], - minPointOffset = this.minPointOffset || 0; - - if (startOnTick) { - this.min = roundedMin; - } else { - while (this.min - minPointOffset > tickPositions[0]) { - tickPositions.shift(); - } - } - - if (endOnTick) { - this.max = roundedMax; - } else { - while (this.max + minPointOffset < tickPositions[tickPositions.length - 1]) { - tickPositions.pop(); - } - } - - // If no tick are left, set one tick in the middle (#3195) - if (tickPositions.length === 0 && defined(roundedMin)) { - tickPositions.push((roundedMax + roundedMin) / 2); - } - }, - - /** - * Check if there are multiple axes in the same pane - * @returns {Boolean} There are other axes - */ - alignToOthers: function () { - var others = {}, // Whether there is another axis to pair with this one - hasOther, - options = this.options; - - if (this.chart.options.chart.alignTicks !== false && options.alignTicks !== false) { - each(this.chart[this.coll], function (axis) { - var otherOptions = axis.options, - horiz = axis.horiz, - key = [ - horiz ? otherOptions.left : otherOptions.top, - otherOptions.width, - otherOptions.height, - otherOptions.pane - ].join(','); - - - if (axis.series.length) { // #4442 - if (others[key]) { - hasOther = true; // #4201 - } else { - others[key] = 1; - } - } - }); - } - return hasOther; - }, - - /** - * Set the max ticks of either the x and y axis collection - */ - getTickAmount: function () { - var options = this.options, - tickAmount = options.tickAmount, - tickPixelInterval = options.tickPixelInterval; - - if (!defined(options.tickInterval) && this.len < tickPixelInterval && !this.isRadial && - !this.isLog && options.startOnTick && options.endOnTick) { - tickAmount = 2; - } - - if (!tickAmount && this.alignToOthers()) { - // Add 1 because 4 tick intervals require 5 ticks (including first and last) - tickAmount = mathCeil(this.len / tickPixelInterval) + 1; - } - - // For tick amounts of 2 and 3, compute five ticks and remove the intermediate ones. This - // prevents the axis from adding ticks that are too far away from the data extremes. - if (tickAmount < 4) { - this.finalTickAmt = tickAmount; - tickAmount = 5; - } - - this.tickAmount = tickAmount; - }, - - /** - * When using multiple axes, adjust the number of ticks to match the highest - * number of ticks in that group - */ - adjustTickAmount: function () { - var tickInterval = this.tickInterval, - tickPositions = this.tickPositions, - tickAmount = this.tickAmount, - finalTickAmt = this.finalTickAmt, - currentTickAmount = tickPositions && tickPositions.length, - i, - len; - - if (currentTickAmount < tickAmount) { - while (tickPositions.length < tickAmount) { - tickPositions.push(correctFloat( - tickPositions[tickPositions.length - 1] + tickInterval - )); - } - this.transA *= (currentTickAmount - 1) / (tickAmount - 1); - this.max = tickPositions[tickPositions.length - 1]; - - // We have too many ticks, run second pass to try to reduce ticks - } else if (currentTickAmount > tickAmount) { - this.tickInterval *= 2; - this.setTickPositions(); - } - - // The finalTickAmt property is set in getTickAmount - if (defined(finalTickAmt)) { - i = len = tickPositions.length; - while (i--) { - if ( - (finalTickAmt === 3 && i % 2 === 1) || // Remove every other tick - (finalTickAmt <= 2 && i > 0 && i < len - 1) // Remove all but first and last - ) { - tickPositions.splice(i, 1); - } - } - this.finalTickAmt = UNDEFINED; - } - }, - - /** - * Set the scale based on data min and max, user set min and max or options - * - */ - setScale: function () { - var axis = this, - isDirtyData, - isDirtyAxisLength; - - axis.oldMin = axis.min; - axis.oldMax = axis.max; - axis.oldAxisLength = axis.len; - - // set the new axisLength - axis.setAxisSize(); - //axisLength = horiz ? axisWidth : axisHeight; - isDirtyAxisLength = axis.len !== axis.oldAxisLength; - - // is there new data? - each(axis.series, function (series) { - if (series.isDirtyData || series.isDirty || - series.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well - isDirtyData = true; - } - }); - - // do we really need to go through all this? - if (isDirtyAxisLength || isDirtyData || axis.isLinked || axis.forceRedraw || - axis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax || axis.alignToOthers()) { - - if (axis.resetStacks) { - axis.resetStacks(); - } - - axis.forceRedraw = false; - - // get data extremes if needed - axis.getSeriesExtremes(); - - // get fixed positions based on tickInterval - axis.setTickInterval(); - - // record old values to decide whether a rescale is necessary later on (#540) - axis.oldUserMin = axis.userMin; - axis.oldUserMax = axis.userMax; - - // Mark as dirty if it is not already set to dirty and extremes have changed. #595. - if (!axis.isDirty) { - axis.isDirty = isDirtyAxisLength || axis.min !== axis.oldMin || axis.max !== axis.oldMax; - } - } else if (axis.cleanStacks) { - axis.cleanStacks(); - } - }, - - /** - * Set the extremes and optionally redraw - * @param {Number} newMin - * @param {Number} newMax - * @param {Boolean} redraw - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - * @param {Object} eventArguments - * - */ - setExtremes: function (newMin, newMax, redraw, animation, eventArguments) { - var axis = this, - chart = axis.chart; - - redraw = pick(redraw, true); // defaults to true - - each(axis.series, function (serie) { - delete serie.kdTree; - }); - - // Extend the arguments with min and max - eventArguments = extend(eventArguments, { - min: newMin, - max: newMax - }); - - // Fire the event - fireEvent(axis, 'setExtremes', eventArguments, function () { // the default event handler - - axis.userMin = newMin; - axis.userMax = newMax; - axis.eventArgs = eventArguments; - - if (redraw) { - chart.redraw(animation); - } - }); - }, - - /** - * Overridable method for zooming chart. Pulled out in a separate method to allow overriding - * in stock charts. - */ - zoom: function (newMin, newMax) { - var dataMin = this.dataMin, - dataMax = this.dataMax, - options = this.options, - min = mathMin(dataMin, pick(options.min, dataMin)), - max = mathMax(dataMax, pick(options.max, dataMax)); - - // Prevent pinch zooming out of range. Check for defined is for #1946. #1734. - if (!this.allowZoomOutside) { - if (defined(dataMin) && newMin <= min) { - newMin = min; - } - if (defined(dataMax) && newMax >= max) { - newMax = max; - } - } - - // In full view, displaying the reset zoom button is not required - this.displayBtn = newMin !== UNDEFINED || newMax !== UNDEFINED; - - // Do it - this.setExtremes( - newMin, - newMax, - false, - UNDEFINED, - { trigger: 'zoom' } - ); - return true; - }, - - /** - * Update the axis metrics - */ - setAxisSize: function () { - var chart = this.chart, - options = this.options, - offsetLeft = options.offsetLeft || 0, - offsetRight = options.offsetRight || 0, - horiz = this.horiz, - width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight), - height = pick(options.height, chart.plotHeight), - top = pick(options.top, chart.plotTop), - left = pick(options.left, chart.plotLeft + offsetLeft), - percentRegex = /%$/; - - // Check for percentage based input values. Rounding fixes problems with - // column overflow and plot line filtering (#4898, #4899) - if (percentRegex.test(height)) { - height = Math.round(parseFloat(height) / 100 * chart.plotHeight); - } - if (percentRegex.test(top)) { - top = Math.round(parseFloat(top) / 100 * chart.plotHeight + chart.plotTop); - } - - // Expose basic values to use in Series object and navigator - this.left = left; - this.top = top; - this.width = width; - this.height = height; - this.bottom = chart.chartHeight - height - top; - this.right = chart.chartWidth - width - left; - - // Direction agnostic properties - this.len = mathMax(horiz ? width : height, 0); // mathMax fixes #905 - this.pos = horiz ? left : top; // distance from SVG origin - }, - - /** - * Get the actual axis extremes - */ - getExtremes: function () { - var axis = this, - isLog = axis.isLog, - lin2log = axis.lin2log; - - return { - min: isLog ? correctFloat(lin2log(axis.min)) : axis.min, - max: isLog ? correctFloat(lin2log(axis.max)) : axis.max, - dataMin: axis.dataMin, - dataMax: axis.dataMax, - userMin: axis.userMin, - userMax: axis.userMax - }; - }, - - /** - * Get the zero plane either based on zero or on the min or max value. - * Used in bar and area plots - */ - getThreshold: function (threshold) { - var axis = this, - isLog = axis.isLog, - lin2log = axis.lin2log, - realMin = isLog ? lin2log(axis.min) : axis.min, - realMax = isLog ? lin2log(axis.max) : axis.max; - - // With a threshold of null, make the columns/areas rise from the top or bottom - // depending on the value, assuming an actual threshold of 0 (#4233). - if (threshold === null) { - threshold = realMax < 0 ? realMax : realMin; - } else if (realMin > threshold) { - threshold = realMin; - } else if (realMax < threshold) { - threshold = realMax; - } - - return axis.translate(threshold, 0, 1, 0, 1); - }, - - /** - * Compute auto alignment for the axis label based on which side the axis is on - * and the given rotation for the label - */ - autoLabelAlign: function (rotation) { - var ret, - angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360; - - if (angle > 15 && angle < 165) { - ret = 'right'; - } else if (angle > 195 && angle < 345) { - ret = 'left'; - } else { - ret = 'center'; - } - return ret; - }, - - /** - * Get the tick length and width for the axis. - * @param {String} prefix 'tick' or 'minorTick' - * @returns {Array} An array of tickLength and tickWidth - */ - tickSize: function (prefix) { - var options = this.options, - tickLength = options[prefix + 'Length'], - tickWidth = pick(options[prefix + 'Width'], prefix === 'tick' && this.isXAxis ? 1 : 0); // X axis defaults to 1 - - if (tickWidth && tickLength) { - // Negate the length - if (options[prefix + 'Position'] === 'inside') { - tickLength = -tickLength; - } - return [tickLength, tickWidth]; - } - - }, - - /** - * Return the size of the labels - */ - labelMetrics: function () { - return this.chart.renderer.fontMetrics( - this.options.labels.style.fontSize, - this.ticks[0] && this.ticks[0].label - ); - }, - - /** - * Prevent the ticks from getting so close we can't draw the labels. On a horizontal - * axis, this is handled by rotating the labels, removing ticks and adding ellipsis. - * On a vertical axis remove ticks and add ellipsis. - */ - unsquish: function () { - var labelOptions = this.options.labels, - horiz = this.horiz, - tickInterval = this.tickInterval, - newTickInterval = tickInterval, - slotSize = this.len / (((this.categories ? 1 : 0) + this.max - this.min) / tickInterval), - rotation, - rotationOption = labelOptions.rotation, - labelMetrics = this.labelMetrics(), - step, - bestScore = Number.MAX_VALUE, - autoRotation, - // Return the multiple of tickInterval that is needed to avoid collision - getStep = function (spaceNeeded) { - var step = spaceNeeded / (slotSize || 1); - step = step > 1 ? mathCeil(step) : 1; - return step * tickInterval; - }; - - if (horiz) { - autoRotation = !labelOptions.staggerLines && !labelOptions.step && ( // #3971 - defined(rotationOption) ? - [rotationOption] : - slotSize < pick(labelOptions.autoRotationLimit, 80) && labelOptions.autoRotation - ); - - if (autoRotation) { - - // Loop over the given autoRotation options, and determine which gives the best score. The - // best score is that with the lowest number of steps and a rotation closest to horizontal. - each(autoRotation, function (rot) { - var score; - - if (rot === rotationOption || (rot && rot >= -90 && rot <= 90)) { // #3891 - - step = getStep(mathAbs(labelMetrics.h / mathSin(deg2rad * rot))); - - score = step + mathAbs(rot / 360); - - if (score < bestScore) { - bestScore = score; - rotation = rot; - newTickInterval = step; - } - } - }); - } - - } else if (!labelOptions.step) { // #4411 - newTickInterval = getStep(labelMetrics.h); - } - - this.autoRotation = autoRotation; - this.labelRotation = pick(rotation, rotationOption); - - return newTickInterval; - }, - - /** - * Get the general slot width for this axis. This may change between the pre-render (from Axis.getOffset) - * and the final tick rendering and placement (#5086). - */ - getSlotWidth: function () { - var chart = this.chart, - horiz = this.horiz, - labelOptions = this.options.labels, - slotCount = Math.max(this.tickPositions.length - (this.categories ? 0 : 1), 1), - marginLeft = chart.margin[3]; - - return (horiz && (labelOptions.step || 0) < 2 && !labelOptions.rotation && // #4415 - ((this.staggerLines || 1) * chart.plotWidth) / slotCount) || - (!horiz && ((marginLeft && (marginLeft - chart.spacing[3])) || chart.chartWidth * 0.33)); // #1580, #1931 - - }, - - /** - * Render the axis labels and determine whether ellipsis or rotation need to be applied - */ - renderUnsquish: function () { - var chart = this.chart, - renderer = chart.renderer, - tickPositions = this.tickPositions, - ticks = this.ticks, - labelOptions = this.options.labels, - horiz = this.horiz, - slotWidth = this.getSlotWidth(), - innerWidth = mathMax(1, mathRound(slotWidth - 2 * (labelOptions.padding || 5))), - attr = {}, - labelMetrics = this.labelMetrics(), - textOverflowOption = labelOptions.style.textOverflow, - css, - labelLength = 0, - label, - i, - pos; - - // Set rotation option unless it is "auto", like in gauges - if (!isString(labelOptions.rotation)) { - attr.rotation = labelOptions.rotation || 0; // #4443 - } - - // Handle auto rotation on horizontal axis - if (this.autoRotation) { - - // Get the longest label length - each(tickPositions, function (tick) { - tick = ticks[tick]; - if (tick && tick.labelLength > labelLength) { - labelLength = tick.labelLength; - } - }); - - // Apply rotation only if the label is too wide for the slot, and - // the label is wider than its height. - if (labelLength > innerWidth && labelLength > labelMetrics.h) { - attr.rotation = this.labelRotation; - } else { - this.labelRotation = 0; - } - - // Handle word-wrap or ellipsis on vertical axis - } else if (slotWidth) { - // For word-wrap or ellipsis - css = { width: innerWidth + PX }; - - if (!textOverflowOption) { - css.textOverflow = 'clip'; - - // On vertical axis, only allow word wrap if there is room for more lines. - i = tickPositions.length; - while (!horiz && i--) { - pos = tickPositions[i]; - label = ticks[pos].label; - if (label) { - // Reset ellipsis in order to get the correct bounding box (#4070) - if (label.styles.textOverflow === 'ellipsis') { - label.css({ textOverflow: 'clip' }); - - // Set the correct width in order to read the bounding box height (#4678, #5034) - } else if (ticks[pos].labelLength > slotWidth) { - label.css({ width: slotWidth + 'px' }); - } - - if (label.getBBox().height > this.len / tickPositions.length - (labelMetrics.h - labelMetrics.f)) { - label.specCss = { textOverflow: 'ellipsis' }; - } - } - } - } - } - - - // Add ellipsis if the label length is significantly longer than ideal - if (attr.rotation) { - css = { - width: (labelLength > chart.chartHeight * 0.5 ? chart.chartHeight * 0.33 : chart.chartHeight) + PX - }; - if (!textOverflowOption) { - css.textOverflow = 'ellipsis'; - } - } - - // Set the explicit or automatic label alignment - this.labelAlign = labelOptions.align || this.autoLabelAlign(this.labelRotation); - if (this.labelAlign) { - attr.align = this.labelAlign; - } - - // Apply general and specific CSS - each(tickPositions, function (pos) { - var tick = ticks[pos], - label = tick && tick.label; - if (label) { - label.attr(attr); // This needs to go before the CSS in old IE (#4502) - if (css) { - label.css(merge(css, label.specCss)); - } - delete label.specCss; - tick.rotation = attr.rotation; - } - }); - - // Note: Why is this not part of getLabelPosition? - this.tickRotCorr = renderer.rotCorr(labelMetrics.b, this.labelRotation || 0, this.side !== 0); - }, - - /** - * Return true if the axis has associated data - */ - hasData: function () { - return this.hasVisibleSeries || (defined(this.min) && defined(this.max) && !!this.tickPositions); - }, - - /** - * Render the tick labels to a preliminary position to get their sizes - */ - getOffset: function () { - var axis = this, - chart = axis.chart, - renderer = chart.renderer, - options = axis.options, - tickPositions = axis.tickPositions, - ticks = axis.ticks, - horiz = axis.horiz, - side = axis.side, - invertedSide = chart.inverted ? [1, 0, 3, 2][side] : side, - hasData, - showAxis, - titleOffset = 0, - titleOffsetOption, - titleMargin = 0, - axisTitleOptions = options.title, - labelOptions = options.labels, - labelOffset = 0, // reset - labelOffsetPadded, - opposite = axis.opposite, - axisOffset = chart.axisOffset, - clipOffset = chart.clipOffset, - clip, - directionFactor = [-1, 1, 1, -1][side], - n, - textAlign, - axisParent = axis.axisParent, // Used in color axis - lineHeightCorrection, - tickSize = this.tickSize('tick'); - - // For reuse in Axis.render - hasData = axis.hasData(); - axis.showAxis = showAxis = hasData || pick(options.showEmpty, true); - - // Set/reset staggerLines - axis.staggerLines = axis.horiz && labelOptions.staggerLines; - - // Create the axisGroup and gridGroup elements on first iteration - if (!axis.axisGroup) { - axis.gridGroup = renderer.g('grid') - .attr({ zIndex: options.gridZIndex || 1 }) - .add(axisParent); - axis.axisGroup = renderer.g('axis') - .attr({ zIndex: options.zIndex || 2 }) - .add(axisParent); - axis.labelGroup = renderer.g('axis-labels') - .attr({ zIndex: labelOptions.zIndex || 7 }) - .addClass(PREFIX + axis.coll.toLowerCase() + '-labels') - .add(axisParent); - } - - if (hasData || axis.isLinked) { - - // Generate ticks - each(tickPositions, function (pos) { - if (!ticks[pos]) { - ticks[pos] = new Tick(axis, pos); - } else { - ticks[pos].addLabel(); // update labels depending on tick interval - } - }); - - axis.renderUnsquish(); - - - // Left side must be align: right and right side must have align: left for labels - if (labelOptions.reserveSpace !== false && (side === 0 || side === 2 || - { 1: 'left', 3: 'right' }[side] === axis.labelAlign || axis.labelAlign === 'center')) { - each(tickPositions, function (pos) { - - // get the highest offset - labelOffset = mathMax( - ticks[pos].getLabelSize(), - labelOffset - ); - }); - } - - if (axis.staggerLines) { - labelOffset *= axis.staggerLines; - axis.labelOffset = labelOffset * (axis.opposite ? -1 : 1); - } - - - } else { // doesn't have data - for (n in ticks) { - ticks[n].destroy(); - delete ticks[n]; - } - } - - if (axisTitleOptions && axisTitleOptions.text && axisTitleOptions.enabled !== false) { - if (!axis.axisTitle) { - textAlign = axisTitleOptions.textAlign; - if (!textAlign) { - textAlign = (horiz ? { - low: 'left', - middle: 'center', - high: 'right' - } : { - low: opposite ? 'right' : 'left', - middle: 'center', - high: opposite ? 'left' : 'right' - })[axisTitleOptions.align]; - } - axis.axisTitle = renderer.text( - axisTitleOptions.text, - 0, - 0, - axisTitleOptions.useHTML - ) - .attr({ - zIndex: 7, - rotation: axisTitleOptions.rotation || 0, - align: textAlign - }) - .addClass(PREFIX + this.coll.toLowerCase() + '-title') - .css(axisTitleOptions.style) - .add(axis.axisGroup); - axis.axisTitle.isNew = true; - } - - if (showAxis) { - titleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width']; - titleOffsetOption = axisTitleOptions.offset; - titleMargin = defined(titleOffsetOption) ? 0 : pick(axisTitleOptions.margin, horiz ? 5 : 10); - } - - // hide or show the title depending on whether showEmpty is set - axis.axisTitle[showAxis ? 'show' : 'hide'](true); - } - - // handle automatic or user set offset - axis.offset = directionFactor * pick(options.offset, axisOffset[side]); - - axis.tickRotCorr = axis.tickRotCorr || { x: 0, y: 0 }; // polar - if (side === 0) { - lineHeightCorrection = -axis.labelMetrics().h; - } else if (side === 2) { - lineHeightCorrection = axis.tickRotCorr.y; - } else { - lineHeightCorrection = 0; - } - - // Find the padded label offset - labelOffsetPadded = Math.abs(labelOffset) + titleMargin; - if (labelOffset) { - labelOffsetPadded -= lineHeightCorrection; - labelOffsetPadded += directionFactor * (horiz ? pick(labelOptions.y, axis.tickRotCorr.y + directionFactor * 8) : labelOptions.x); - } - axis.axisTitleMargin = pick(titleOffsetOption, labelOffsetPadded); - - axisOffset[side] = mathMax( - axisOffset[side], - axis.axisTitleMargin + titleOffset + directionFactor * axis.offset, - labelOffsetPadded, // #3027 - hasData && tickPositions.length && tickSize ? tickSize[0] : 0 // #4866 - ); - - // Decide the clipping needed to keep the graph inside the plot area and axis lines - clip = options.offset ? 0 : mathFloor(options.lineWidth / 2) * 2; // #4308, #4371 - clipOffset[invertedSide] = mathMax(clipOffset[invertedSide], clip); - }, - - /** - * Get the path for the axis line - */ - getLinePath: function (lineWidth) { - var chart = this.chart, - opposite = this.opposite, - offset = this.offset, - horiz = this.horiz, - lineLeft = this.left + (opposite ? this.width : 0) + offset, - lineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset; - - if (opposite) { - lineWidth *= -1; // crispify the other way - #1480, #1687 - } - - return chart.renderer - .crispLine([ - M, - horiz ? - this.left : - lineLeft, - horiz ? - lineTop : - this.top, - L, - horiz ? - chart.chartWidth - this.right : - lineLeft, - horiz ? - lineTop : - chart.chartHeight - this.bottom - ], lineWidth); - }, - - /** - * Position the title - */ - getTitlePosition: function () { - // compute anchor points for each of the title align options - var horiz = this.horiz, - axisLeft = this.left, - axisTop = this.top, - axisLength = this.len, - axisTitleOptions = this.options.title, - margin = horiz ? axisLeft : axisTop, - opposite = this.opposite, - offset = this.offset, - xOption = axisTitleOptions.x || 0, - yOption = axisTitleOptions.y || 0, - fontSize = pInt(axisTitleOptions.style.fontSize || 12), - - // the position in the length direction of the axis - alongAxis = { - low: margin + (horiz ? 0 : axisLength), - middle: margin + axisLength / 2, - high: margin + (horiz ? axisLength : 0) - }[axisTitleOptions.align], - - // the position in the perpendicular direction of the axis - offAxis = (horiz ? axisTop + this.height : axisLeft) + - (horiz ? 1 : -1) * // horizontal axis reverses the margin - (opposite ? -1 : 1) * // so does opposite axes - this.axisTitleMargin + - (this.side === 2 ? fontSize : 0); - - return { - x: horiz ? - alongAxis + xOption : - offAxis + (opposite ? this.width : 0) + offset + xOption, - y: horiz ? - offAxis + yOption - (opposite ? this.height : 0) + offset : - alongAxis + yOption - }; - }, - - /** - * Render the axis - */ - render: function () { - var axis = this, - chart = axis.chart, - renderer = chart.renderer, - options = axis.options, - isLog = axis.isLog, - lin2log = axis.lin2log, - isLinked = axis.isLinked, - tickPositions = axis.tickPositions, - axisTitle = axis.axisTitle, - ticks = axis.ticks, - minorTicks = axis.minorTicks, - alternateBands = axis.alternateBands, - stackLabelOptions = options.stackLabels, - alternateGridColor = options.alternateGridColor, - tickmarkOffset = axis.tickmarkOffset, - lineWidth = options.lineWidth, - linePath, - hasRendered = chart.hasRendered, - slideInTicks = hasRendered && isNumber(axis.oldMin), - showAxis = axis.showAxis, - animation = animObject(renderer.globalAnimation), - from, - to; - - // Reset - axis.labelEdge.length = 0; - //axis.justifyToPlot = overflow === 'justify'; - axis.overlap = false; - - // Mark all elements inActive before we go over and mark the active ones - each([ticks, minorTicks, alternateBands], function (coll) { - var pos; - for (pos in coll) { - coll[pos].isActive = false; - } - }); - - // If the series has data draw the ticks. Else only the line and title - if (axis.hasData() || isLinked) { - - // minor ticks - if (axis.minorTickInterval && !axis.categories) { - each(axis.getMinorTickPositions(), function (pos) { - if (!minorTicks[pos]) { - minorTicks[pos] = new Tick(axis, pos, 'minor'); - } - - // render new ticks in old position - if (slideInTicks && minorTicks[pos].isNew) { - minorTicks[pos].render(null, true); - } - - minorTicks[pos].render(null, false, 1); - }); - } - - // Major ticks. Pull out the first item and render it last so that - // we can get the position of the neighbour label. #808. - if (tickPositions.length) { // #1300 - each(tickPositions, function (pos, i) { - - // linked axes need an extra check to find out if - if (!isLinked || (pos >= axis.min && pos <= axis.max)) { - - if (!ticks[pos]) { - ticks[pos] = new Tick(axis, pos); - } - - // render new ticks in old position - if (slideInTicks && ticks[pos].isNew) { - ticks[pos].render(i, true, 0.1); - } - - ticks[pos].render(i); - } - - }); - // In a categorized axis, the tick marks are displayed between labels. So - // we need to add a tick mark and grid line at the left edge of the X axis. - if (tickmarkOffset && (axis.min === 0 || axis.single)) { - if (!ticks[-1]) { - ticks[-1] = new Tick(axis, -1, null, true); - } - ticks[-1].render(-1); - } - - } - - // alternate grid color - if (alternateGridColor) { - each(tickPositions, function (pos, i) { - to = tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] + tickmarkOffset : axis.max - tickmarkOffset; - if (i % 2 === 0 && pos < axis.max && to <= axis.max + (chart.polar ? -tickmarkOffset : tickmarkOffset)) { // #2248, #4660 - if (!alternateBands[pos]) { - alternateBands[pos] = new Highcharts.PlotLineOrBand(axis); - } - from = pos + tickmarkOffset; // #949 - alternateBands[pos].options = { - from: isLog ? lin2log(from) : from, - to: isLog ? lin2log(to) : to, - color: alternateGridColor - }; - alternateBands[pos].render(); - alternateBands[pos].isActive = true; - } - }); - } - - // custom plot lines and bands - if (!axis._addedPlotLB) { // only first time - each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) { - axis.addPlotBandOrLine(plotLineOptions); - }); - axis._addedPlotLB = true; - } - - } // end if hasData - - // Remove inactive ticks - each([ticks, minorTicks, alternateBands], function (coll) { - var pos, - i, - forDestruction = [], - delay = animation.duration, - destroyInactiveItems = function () { - i = forDestruction.length; - while (i--) { - // When resizing rapidly, the same items may be destroyed in different timeouts, - // or the may be reactivated - if (coll[forDestruction[i]] && !coll[forDestruction[i]].isActive) { - coll[forDestruction[i]].destroy(); - delete coll[forDestruction[i]]; - } - } - - }; - - for (pos in coll) { - - if (!coll[pos].isActive) { - // Render to zero opacity - coll[pos].render(pos, false, 0); - coll[pos].isActive = false; - forDestruction.push(pos); - } - } - - // When the objects are finished fading out, destroy them - syncTimeout( - destroyInactiveItems, - coll === alternateBands || !chart.hasRendered || !delay ? 0 : delay - ); - }); - - // Static items. As the axis group is cleared on subsequent calls - // to render, these items are added outside the group. - // axis line - if (lineWidth) { - linePath = axis.getLinePath(lineWidth); - if (!axis.axisLine) { - axis.axisLine = renderer.path(linePath) - .attr({ - stroke: options.lineColor, - 'stroke-width': lineWidth, - zIndex: 7 - }) - .add(axis.axisGroup); - } else { - axis.axisLine.animate({ d: linePath }); - } - - // show or hide the line depending on options.showEmpty - axis.axisLine[showAxis ? 'show' : 'hide'](true); - } - - if (axisTitle && showAxis) { - - axisTitle[axisTitle.isNew ? 'attr' : 'animate']( - axis.getTitlePosition() - ); - axisTitle.isNew = false; - } - - // Stacked totals: - if (stackLabelOptions && stackLabelOptions.enabled) { - axis.renderStackTotals(); - } - // End stacked totals - - axis.isDirty = false; - }, - - /** - * Redraw the axis to reflect changes in the data or axis extremes - */ - redraw: function () { - - if (this.visible) { - // render the axis - this.render(); - - // move plot lines and bands - each(this.plotLinesAndBands, function (plotLine) { - plotLine.render(); - }); - } - - // mark associated series as dirty and ready for redraw - each(this.series, function (series) { - series.isDirty = true; - }); - - }, - - /** - * Destroys an Axis instance. - */ - destroy: function (keepEvents) { - var axis = this, - stacks = axis.stacks, - stackKey, - plotLinesAndBands = axis.plotLinesAndBands, - i; - - // Remove the events - if (!keepEvents) { - removeEvent(axis); - } - - // Destroy each stack total - for (stackKey in stacks) { - destroyObjectProperties(stacks[stackKey]); - - stacks[stackKey] = null; - } - - // Destroy collections - each([axis.ticks, axis.minorTicks, axis.alternateBands], function (coll) { - destroyObjectProperties(coll); - }); - i = plotLinesAndBands.length; - while (i--) { // #1975 - plotLinesAndBands[i].destroy(); - } - - // Destroy local variables - each(['stackTotalGroup', 'axisLine', 'axisTitle', 'axisGroup', 'cross', 'gridGroup', 'labelGroup'], function (prop) { - if (axis[prop]) { - axis[prop] = axis[prop].destroy(); - } - }); - - // Destroy crosshair - if (this.cross) { - this.cross.destroy(); - } - }, - - /** - * Draw the crosshair - * - * @param {Object} e The event arguments from the modified pointer event - * @param {Object} point The Point object - */ - drawCrosshair: function (e, point) { - - var path, - options = this.crosshair, - pos, - attribs, - categorized, - strokeWidth; - - if ( - // Disabled in options - !this.crosshair || - // Snap - ((defined(point) || !pick(options.snap, true)) === false) - ) { - this.hideCrosshair(); - - } else { - - // Get the path - if (!pick(options.snap, true)) { - pos = (this.horiz ? e.chartX - this.pos : this.len - e.chartY + this.pos); - } else if (defined(point)) { - pos = this.isXAxis ? point.plotX : this.len - point.plotY; // #3834 - } - - if (this.isRadial) { - path = this.getPlotLinePath(this.isXAxis ? point.x : pick(point.stackY, point.y)) || null; // #3189 - } else { - path = this.getPlotLinePath(null, null, null, null, pos) || null; // #3189 - } - - if (path === null) { - this.hideCrosshair(); - return; - } - - categorized = this.categories && !this.isRadial; - strokeWidth = pick(options.width, (categorized ? this.transA : 1)); - - // Draw the cross - if (this.cross) { - this.cross - .attr({ - d: path, - visibility: 'visible', - 'stroke-width': strokeWidth // #4737 - }); - } else { - attribs = { - 'pointer-events': 'none', // #5259 - 'stroke-width': strokeWidth, - stroke: options.color || (categorized ? 'rgba(155,200,255,0.2)' : '#C0C0C0'), - zIndex: pick(options.zIndex, 2) - }; - if (options.dashStyle) { - attribs.dashstyle = options.dashStyle; - } - this.cross = this.chart.renderer.path(path).attr(attribs).add(); - } - - } - - }, - - /** - * Hide the crosshair. - */ - hideCrosshair: function () { - if (this.cross) { - this.cross.hide(); - } - } - }; // end Axis - - extend(Axis.prototype, AxisPlotLineOrBandExtension); - - /** - * Set the tick positions to a time unit that makes sense, for example - * on the first of each month or on every Monday. Return an array - * with the time positions. Used in datetime axes as well as for grouping - * data on a datetime axis. - * - * @param {Object} normalizedInterval The interval in axis values (ms) and the count - * @param {Number} min The minimum in axis values - * @param {Number} max The maximum in axis values - * @param {Number} startOfWeek - */ - Axis.prototype.getTimeTicks = function (normalizedInterval, min, max, startOfWeek) { - var tickPositions = [], - i, - higherRanks = {}, - useUTC = defaultOptions.global.useUTC, - minYear, // used in months and years as a basis for Date.UTC() - minDate = new Date(min - getTZOffset(min)), - interval = normalizedInterval.unitRange, - count = normalizedInterval.count; - - if (defined(min)) { // #1300 - minDate[setMilliseconds](interval >= timeUnits.second ? 0 : // #3935 - count * mathFloor(minDate.getMilliseconds() / count)); // #3652, #3654 - - if (interval >= timeUnits.second) { // second - minDate[setSeconds](interval >= timeUnits.minute ? 0 : // #3935 - count * mathFloor(minDate.getSeconds() / count)); - } - - if (interval >= timeUnits.minute) { // minute - minDate[setMinutes](interval >= timeUnits.hour ? 0 : - count * mathFloor(minDate[getMinutes]() / count)); - } - - if (interval >= timeUnits.hour) { // hour - minDate[setHours](interval >= timeUnits.day ? 0 : - count * mathFloor(minDate[getHours]() / count)); - } - - if (interval >= timeUnits.day) { // day - minDate[setDate](interval >= timeUnits.month ? 1 : - count * mathFloor(minDate[getDate]() / count)); - } - - if (interval >= timeUnits.month) { // month - minDate[setMonth](interval >= timeUnits.year ? 0 : - count * mathFloor(minDate[getMonth]() / count)); - minYear = minDate[getFullYear](); - } - - if (interval >= timeUnits.year) { // year - minYear -= minYear % count; - minDate[setFullYear](minYear); - } - - // week is a special case that runs outside the hierarchy - if (interval === timeUnits.week) { - // get start of current week, independent of count - minDate[setDate](minDate[getDate]() - minDate[getDay]() + - pick(startOfWeek, 1)); - } - - - // get tick positions - i = 1; - if (timezoneOffset || getTimezoneOffset) { - minDate = minDate.getTime(); - minDate = new Date(minDate + getTZOffset(minDate)); - } - minYear = minDate[getFullYear](); - var time = minDate.getTime(), - minMonth = minDate[getMonth](), - minDateDate = minDate[getDate](), - variableDayLength = !useUTC || !!getTimezoneOffset, // #4951 - localTimezoneOffset = (timeUnits.day + - (useUTC ? getTZOffset(minDate) : minDate.getTimezoneOffset() * 60 * 1000) - ) % timeUnits.day; // #950, #3359 - - // iterate and add tick positions at appropriate values - while (time < max) { - tickPositions.push(time); - - // if the interval is years, use Date.UTC to increase years - if (interval === timeUnits.year) { - time = makeTime(minYear + i * count, 0); - - // if the interval is months, use Date.UTC to increase months - } else if (interval === timeUnits.month) { - time = makeTime(minYear, minMonth + i * count); - - // if we're using global time, the interval is not fixed as it jumps - // one hour at the DST crossover - } else if (variableDayLength && (interval === timeUnits.day || interval === timeUnits.week)) { - time = makeTime(minYear, minMonth, minDateDate + - i * count * (interval === timeUnits.day ? 1 : 7)); - - // else, the interval is fixed and we use simple addition - } else { - time += interval * count; - } - - i++; - } - - // push the last time - tickPositions.push(time); - - - // mark new days if the time is dividible by day (#1649, #1760) - each(grep(tickPositions, function (time) { - return interval <= timeUnits.hour && time % timeUnits.day === localTimezoneOffset; - }), function (time) { - higherRanks[time] = 'day'; - }); - } - - - // record information on the chosen unit - for dynamic label formatter - tickPositions.info = extend(normalizedInterval, { - higherRanks: higherRanks, - totalRange: interval * count - }); - - return tickPositions; - }; - - /** - * Get a normalized tick interval for dates. Returns a configuration object with - * unit range (interval), count and name. Used to prepare data for getTimeTicks. - * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs - * of segments in stock charts, the normalizing logic was extracted in order to - * prevent it for running over again for each segment having the same interval. - * #662, #697. - */ - Axis.prototype.normalizeTimeTickInterval = function (tickInterval, unitsOption) { - var units = unitsOption || [[ - 'millisecond', // unit name - [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples - ], [ - 'second', - [1, 2, 5, 10, 15, 30] - ], [ - 'minute', - [1, 2, 5, 10, 15, 30] - ], [ - 'hour', - [1, 2, 3, 4, 6, 8, 12] - ], [ - 'day', - [1, 2] - ], [ - 'week', - [1, 2] - ], [ - 'month', - [1, 2, 3, 4, 6] - ], [ - 'year', - null - ]], - unit = units[units.length - 1], // default unit is years - interval = timeUnits[unit[0]], - multiples = unit[1], - count, - i; - - // loop through the units to find the one that best fits the tickInterval - for (i = 0; i < units.length; i++) { - unit = units[i]; - interval = timeUnits[unit[0]]; - multiples = unit[1]; - - - if (units[i + 1]) { - // lessThan is in the middle between the highest multiple and the next unit. - var lessThan = (interval * multiples[multiples.length - 1] + - timeUnits[units[i + 1][0]]) / 2; - - // break and keep the current unit - if (tickInterval <= lessThan) { - break; - } - } - } - - // prevent 2.5 years intervals, though 25, 250 etc. are allowed - if (interval === timeUnits.year && tickInterval < 5 * interval) { - multiples = [1, 2, 5]; - } - - // get the count - count = normalizeTickInterval( - tickInterval / interval, - multiples, - unit[0] === 'year' ? mathMax(getMagnitude(tickInterval / interval), 1) : 1 // #1913, #2360 - ); - - return { - unitRange: interval, - count: count, - unitName: unit[0] - }; - }; - /** - * Methods defined on the Axis prototype - */ - - /** - * Set the tick positions of a logarithmic axis - */ - Axis.prototype.getLogTickPositions = function (interval, min, max, minor) { - var axis = this, - options = axis.options, - axisLength = axis.len, - lin2log = axis.lin2log, - log2lin = axis.log2lin, - // Since we use this method for both major and minor ticks, - // use a local variable and return the result - positions = []; - - // Reset - if (!minor) { - axis._minorAutoInterval = null; - } - - // First case: All ticks fall on whole logarithms: 1, 10, 100 etc. - if (interval >= 0.5) { - interval = mathRound(interval); - positions = axis.getLinearTickPositions(interval, min, max); - - // Second case: We need intermediary ticks. For example - // 1, 2, 4, 6, 8, 10, 20, 40 etc. - } else if (interval >= 0.08) { - var roundedMin = mathFloor(min), - intermediate, - i, - j, - len, - pos, - lastPos, - break2; - - if (interval > 0.3) { - intermediate = [1, 2, 4]; - } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc - intermediate = [1, 2, 4, 6, 8]; - } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc - intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - } - - for (i = roundedMin; i < max + 1 && !break2; i++) { - len = intermediate.length; - for (j = 0; j < len && !break2; j++) { - pos = log2lin(lin2log(i) * intermediate[j]); - if (pos > min && (!minor || lastPos <= max) && lastPos !== UNDEFINED) { // #1670, lastPos is #3113 - positions.push(lastPos); - } - - if (lastPos > max) { - break2 = true; - } - lastPos = pos; - } - } - - // Third case: We are so deep in between whole logarithmic values that - // we might as well handle the tick positions like a linear axis. For - // example 1.01, 1.02, 1.03, 1.04. - } else { - var realMin = lin2log(min), - realMax = lin2log(max), - tickIntervalOption = options[minor ? 'minorTickInterval' : 'tickInterval'], - filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption, - tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1), - totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength; - - interval = pick( - filteredTickIntervalOption, - axis._minorAutoInterval, - (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1) - ); - - interval = normalizeTickInterval( - interval, - null, - getMagnitude(interval) - ); - - positions = map(axis.getLinearTickPositions( - interval, - realMin, - realMax - ), log2lin); - - if (!minor) { - axis._minorAutoInterval = interval / 5; - } - } - - // Set the axis-level tickInterval variable - if (!minor) { - axis.tickInterval = interval; - } - return positions; - }; - - Axis.prototype.log2lin = function (num) { - return math.log(num) / math.LN10; - }; - - Axis.prototype.lin2log = function (num) { - return math.pow(10, num); - }; - /** - * The tooltip object - * @param {Object} chart The chart instance - * @param {Object} options Tooltip options - */ - var Tooltip = Highcharts.Tooltip = function () { - this.init.apply(this, arguments); - }; - - Tooltip.prototype = { - - init: function (chart, options) { - - var borderWidth = options.borderWidth, - style = options.style, - padding = pInt(style.padding); - - // Save the chart and options - this.chart = chart; - this.options = options; - - // Keep track of the current series - //this.currentSeries = UNDEFINED; - - // List of crosshairs - this.crosshairs = []; - - // Current values of x and y when animating - this.now = { x: 0, y: 0 }; - - // The tooltip is initially hidden - this.isHidden = true; - - - // create the label - this.label = chart.renderer.label('', 0, 0, options.shape || 'callout', null, null, options.useHTML, null, 'tooltip') - .attr({ - padding: padding, - fill: options.backgroundColor, - 'stroke-width': borderWidth, - r: options.borderRadius, - zIndex: 8 - }) - .css(style) - .css({ padding: 0 }) // Remove it from VML, the padding is applied as an attribute instead (#1117) - .add() - .attr({ y: -9999 }); // #2301, #2657 - - // When using canVG the shadow shows up as a gray circle - // even if the tooltip is hidden. - if (!useCanVG) { - this.label.shadow(options.shadow); - } - - // Public property for getting the shared state. - this.shared = options.shared; - }, - - /** - * Destroy the tooltip and its elements. - */ - destroy: function () { - // Destroy and clear local variables - if (this.label) { - this.label = this.label.destroy(); - } - clearTimeout(this.hideTimer); - clearTimeout(this.tooltipTimeout); - }, - - /** - * Provide a soft movement for the tooltip - * - * @param {Number} x - * @param {Number} y - * @private - */ - move: function (x, y, anchorX, anchorY) { - var tooltip = this, - now = tooltip.now, - animate = tooltip.options.animation !== false && !tooltip.isHidden && - // When we get close to the target position, abort animation and land on the right place (#3056) - (mathAbs(x - now.x) > 1 || mathAbs(y - now.y) > 1), - skipAnchor = tooltip.followPointer || tooltip.len > 1; - - // Get intermediate values for animation - extend(now, { - x: animate ? (2 * now.x + x) / 3 : x, - y: animate ? (now.y + y) / 2 : y, - anchorX: skipAnchor ? UNDEFINED : animate ? (2 * now.anchorX + anchorX) / 3 : anchorX, - anchorY: skipAnchor ? UNDEFINED : animate ? (now.anchorY + anchorY) / 2 : anchorY - }); - - // Move to the intermediate value - tooltip.label.attr(now); - - - // Run on next tick of the mouse tracker - if (animate) { - - // Never allow two timeouts - clearTimeout(this.tooltipTimeout); - - // Set the fixed interval ticking for the smooth tooltip - this.tooltipTimeout = setTimeout(function () { - // The interval function may still be running during destroy, so check that the chart is really there before calling. - if (tooltip) { - tooltip.move(x, y, anchorX, anchorY); - } - }, 32); - - } - }, - - /** - * Hide the tooltip - */ - hide: function (delay) { - var tooltip = this; - clearTimeout(this.hideTimer); // disallow duplicate timers (#1728, #1766) - delay = pick(delay, this.options.hideDelay, 500); - if (!this.isHidden) { - this.hideTimer = syncTimeout(function () { - tooltip.label[delay ? 'fadeOut' : 'hide'](); - tooltip.isHidden = true; - }, delay); - } - }, - - /** - * Extendable method to get the anchor position of the tooltip - * from a point or set of points - */ - getAnchor: function (points, mouseEvent) { - var ret, - chart = this.chart, - inverted = chart.inverted, - plotTop = chart.plotTop, - plotLeft = chart.plotLeft, - plotX = 0, - plotY = 0, - yAxis, - xAxis; - - points = splat(points); - - // Pie uses a special tooltipPos - ret = points[0].tooltipPos; - - // When tooltip follows mouse, relate the position to the mouse - if (this.followPointer && mouseEvent) { - if (mouseEvent.chartX === UNDEFINED) { - mouseEvent = chart.pointer.normalize(mouseEvent); - } - ret = [ - mouseEvent.chartX - chart.plotLeft, - mouseEvent.chartY - plotTop - ]; - } - // When shared, use the average position - if (!ret) { - each(points, function (point) { - yAxis = point.series.yAxis; - xAxis = point.series.xAxis; - plotX += point.plotX + (!inverted && xAxis ? xAxis.left - plotLeft : 0); - plotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) + - (!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151 - }); - - plotX /= points.length; - plotY /= points.length; - - ret = [ - inverted ? chart.plotWidth - plotY : plotX, - this.shared && !inverted && points.length > 1 && mouseEvent ? - mouseEvent.chartY - plotTop : // place shared tooltip next to the mouse (#424) - inverted ? chart.plotHeight - plotX : plotY - ]; - } - - return map(ret, mathRound); - }, - - /** - * Place the tooltip in a chart without spilling over - * and not covering the point it self. - */ - getPosition: function (boxWidth, boxHeight, point) { - - var chart = this.chart, - distance = this.distance, - ret = {}, - h = point.h || 0, // #4117 - swapped, - first = ['y', chart.chartHeight, boxHeight, point.plotY + chart.plotTop, chart.plotTop, chart.plotTop + chart.plotHeight], - second = ['x', chart.chartWidth, boxWidth, point.plotX + chart.plotLeft, chart.plotLeft, chart.plotLeft + chart.plotWidth], - // The far side is right or bottom - preferFarSide = !this.followPointer && pick(point.ttBelow, !chart.inverted === !!point.negative), // #4984 - /** - * Handle the preferred dimension. When the preferred dimension is tooltip - * on top or bottom of the point, it will look for space there. - */ - firstDimension = function (dim, outerSize, innerSize, point, min, max) { - var roomLeft = innerSize < point - distance, - roomRight = point + distance + innerSize < outerSize, - alignedLeft = point - distance - innerSize, - alignedRight = point + distance; - - if (preferFarSide && roomRight) { - ret[dim] = alignedRight; - } else if (!preferFarSide && roomLeft) { - ret[dim] = alignedLeft; - } else if (roomLeft) { - ret[dim] = mathMin(max - innerSize, alignedLeft - h < 0 ? alignedLeft : alignedLeft - h); - } else if (roomRight) { - ret[dim] = mathMax(min, alignedRight + h + innerSize > outerSize ? alignedRight : alignedRight + h); - } else { - return false; - } - }, - /** - * Handle the secondary dimension. If the preferred dimension is tooltip - * on top or bottom of the point, the second dimension is to align the tooltip - * above the point, trying to align center but allowing left or right - * align within the chart box. - */ - secondDimension = function (dim, outerSize, innerSize, point) { - var retVal; - - // Too close to the edge, return false and swap dimensions - if (point < distance || point > outerSize - distance) { - retVal = false; - // Align left/top - } else if (point < innerSize / 2) { - ret[dim] = 1; - // Align right/bottom - } else if (point > outerSize - innerSize / 2) { - ret[dim] = outerSize - innerSize - 2; - // Align center - } else { - ret[dim] = point - innerSize / 2; - } - return retVal; - }, - /** - * Swap the dimensions - */ - swap = function (count) { - var temp = first; - first = second; - second = temp; - swapped = count; - }, - run = function () { - if (firstDimension.apply(0, first) !== false) { - if (secondDimension.apply(0, second) === false && !swapped) { - swap(true); - run(); - } - } else if (!swapped) { - swap(true); - run(); - } else { - ret.x = ret.y = 0; - } - }; - - // Under these conditions, prefer the tooltip on the side of the point - if (chart.inverted || this.len > 1) { - swap(); - } - run(); - - return ret; - - }, - - /** - * In case no user defined formatter is given, this will be used. Note that the context - * here is an object holding point, series, x, y etc. - */ - defaultFormatter: function (tooltip) { - var items = this.points || splat(this), - s; - - // build the header - s = [tooltip.tooltipFooterHeaderFormatter(items[0])]; //#3397: abstraction to enable formatting of footer and header - - // build the values - s = s.concat(tooltip.bodyFormatter(items)); - - // footer - s.push(tooltip.tooltipFooterHeaderFormatter(items[0], true)); //#3397: abstraction to enable formatting of footer and header - - return s.join(''); - }, - - /** - * Refresh the tooltip's text and position. - * @param {Object} point - */ - refresh: function (point, mouseEvent) { - var tooltip = this, - chart = tooltip.chart, - label = tooltip.label, - options = tooltip.options, - x, - y, - anchor, - textConfig = {}, - text, - pointConfig = [], - formatter = options.formatter || tooltip.defaultFormatter, - hoverPoints = chart.hoverPoints, - borderColor, - shared = tooltip.shared, - currentSeries; - - clearTimeout(this.hideTimer); - - // get the reference point coordinates (pie charts use tooltipPos) - tooltip.followPointer = splat(point)[0].series.tooltipOptions.followPointer; - anchor = tooltip.getAnchor(point, mouseEvent); - x = anchor[0]; - y = anchor[1]; - - // shared tooltip, array is sent over - if (shared && !(point.series && point.series.noSharedTooltip)) { - - // hide previous hoverPoints and set new - - chart.hoverPoints = point; - if (hoverPoints) { - each(hoverPoints, function (point) { - point.setState(); - }); - } - - each(point, function (item) { - item.setState(HOVER_STATE); - - pointConfig.push(item.getLabelConfig()); - }); - - textConfig = { - x: point[0].category, - y: point[0].y - }; - textConfig.points = pointConfig; - this.len = pointConfig.length; - point = point[0]; - - // single point tooltip - } else { - textConfig = point.getLabelConfig(); - } - text = formatter.call(textConfig, tooltip); - - // register the current series - currentSeries = point.series; - this.distance = pick(currentSeries.tooltipOptions.distance, 16); - - // update the inner HTML - if (text === false) { - this.hide(); - } else { - - // show it - if (tooltip.isHidden) { - stop(label); - label.attr('opacity', 1).show(); - } - - // update text - label.attr({ - text: text - }); - - // set the stroke color of the box - borderColor = options.borderColor || point.color || currentSeries.color || '#606060'; - label.attr({ - stroke: borderColor - }); - tooltip.updatePosition({ - plotX: x, - plotY: y, - negative: point.negative, - ttBelow: point.ttBelow, - h: anchor[2] || 0 - }); - - this.isHidden = false; - } - fireEvent(chart, 'tooltipRefresh', { - text: text, - x: x + chart.plotLeft, - y: y + chart.plotTop, - borderColor: borderColor - }); - }, - - /** - * Find the new position and perform the move - */ - updatePosition: function (point) { - var chart = this.chart, - label = this.label, - pos = (this.options.positioner || this.getPosition).call( - this, - label.width, - label.height, - point - ); - - // do the move - this.move( - mathRound(pos.x), - mathRound(pos.y || 0), // can be undefined (#3977) - point.plotX + chart.plotLeft, - point.plotY + chart.plotTop - ); - }, - - /** - * Get the best X date format based on the closest point range on the axis. - */ - getXDateFormat: function (point, options, xAxis) { - var xDateFormat, - dateTimeLabelFormats = options.dateTimeLabelFormats, - closestPointRange = xAxis && xAxis.closestPointRange, - n, - blank = '01-01 00:00:00.000', - strpos = { - millisecond: 15, - second: 12, - minute: 9, - hour: 6, - day: 3 - }, - date, - lastN = 'millisecond'; // for sub-millisecond data, #4223 - - if (closestPointRange) { - date = dateFormat('%m-%d %H:%M:%S.%L', point.x); - for (n in timeUnits) { - - // If the range is exactly one week and we're looking at a Sunday/Monday, go for the week format - if (closestPointRange === timeUnits.week && +dateFormat('%w', point.x) === xAxis.options.startOfWeek && - date.substr(6) === blank.substr(6)) { - n = 'week'; - break; - } - - // The first format that is too great for the range - if (timeUnits[n] > closestPointRange) { - n = lastN; - break; - } - - // If the point is placed every day at 23:59, we need to show - // the minutes as well. #2637. - if (strpos[n] && date.substr(strpos[n]) !== blank.substr(strpos[n])) { - break; - } - - // Weeks are outside the hierarchy, only apply them on Mondays/Sundays like in the first condition - if (n !== 'week') { - lastN = n; - } - } - - if (n) { - xDateFormat = dateTimeLabelFormats[n]; - } - } else { - xDateFormat = dateTimeLabelFormats.day; - } - - return xDateFormat || dateTimeLabelFormats.year; // #2546, 2581 - }, - - /** - * Format the footer/header of the tooltip - * #3397: abstraction to enable formatting of footer and header - */ - tooltipFooterHeaderFormatter: function (point, isFooter) { - var footOrHead = isFooter ? 'footer' : 'header', - series = point.series, - tooltipOptions = series.tooltipOptions, - xDateFormat = tooltipOptions.xDateFormat, - xAxis = series.xAxis, - isDateTime = xAxis && xAxis.options.type === 'datetime' && isNumber(point.key), - formatString = tooltipOptions[footOrHead + 'Format']; - - // Guess the best date format based on the closest point distance (#568, #3418) - if (isDateTime && !xDateFormat) { - xDateFormat = this.getXDateFormat(point, tooltipOptions, xAxis); - } - - // Insert the footer date format if any - if (isDateTime && xDateFormat) { - formatString = formatString.replace('{point.key}', '{point.key:' + xDateFormat + '}'); - } - - return format(formatString, { - point: point, - series: series - }); - }, - - /** - * Build the body (lines) of the tooltip by iterating over the items and returning one entry for each item, - * abstracting this functionality allows to easily overwrite and extend it. - */ - bodyFormatter: function (items) { - return map(items, function (item) { - var tooltipOptions = item.series.tooltipOptions; - return (tooltipOptions.pointFormatter || item.point.tooltipFormatter).call(item.point, tooltipOptions.pointFormat); - }); - } - - }; - - var hoverChartIndex; - - // Global flag for touch support - hasTouch = doc && doc.documentElement.ontouchstart !== UNDEFINED; - - /** - * The mouse tracker object. All methods starting with "on" are primary DOM event handlers. - * Subsequent methods should be named differently from what they are doing. - * @param {Object} chart The Chart instance - * @param {Object} options The root options object - */ - var Pointer = Highcharts.Pointer = function (chart, options) { - this.init(chart, options); - }; - - Pointer.prototype = { - /** - * Initialize Pointer - */ - init: function (chart, options) { - - var chartOptions = options.chart, - chartEvents = chartOptions.events, - zoomType = useCanVG ? '' : chartOptions.zoomType, - inverted = chart.inverted, - zoomX, - zoomY; - - // Store references - this.options = options; - this.chart = chart; - - // Zoom status - this.zoomX = zoomX = /x/.test(zoomType); - this.zoomY = zoomY = /y/.test(zoomType); - this.zoomHor = (zoomX && !inverted) || (zoomY && inverted); - this.zoomVert = (zoomY && !inverted) || (zoomX && inverted); - this.hasZoom = zoomX || zoomY; - - // Do we need to handle click on a touch device? - this.runChartClick = chartEvents && !!chartEvents.click; - - this.pinchDown = []; - this.lastValidTouch = {}; - - if (Highcharts.Tooltip && options.tooltip.enabled) { - chart.tooltip = new Tooltip(chart, options.tooltip); - this.followTouchMove = pick(options.tooltip.followTouchMove, true); - } - - this.setDOMEvents(); - }, - - /** - * Add crossbrowser support for chartX and chartY - * @param {Object} e The event object in standard browsers - */ - normalize: function (e, chartPosition) { - var chartX, - chartY, - ePos; - - // IE normalizing - e = e || win.event; - if (!e.target) { - e.target = e.srcElement; - } - - // iOS (#2757) - ePos = e.touches ? (e.touches.length ? e.touches.item(0) : e.changedTouches[0]) : e; - - // Get mouse position - if (!chartPosition) { - this.chartPosition = chartPosition = offset(this.chart.container); - } - - // chartX and chartY - if (ePos.pageX === UNDEFINED) { // IE < 9. #886. - chartX = mathMax(e.x, e.clientX - chartPosition.left); // #2005, #2129: the second case is - // for IE10 quirks mode within framesets - chartY = e.y; - } else { - chartX = ePos.pageX - chartPosition.left; - chartY = ePos.pageY - chartPosition.top; - } - - return extend(e, { - chartX: mathRound(chartX), - chartY: mathRound(chartY) - }); - }, - - /** - * Get the click position in terms of axis values. - * - * @param {Object} e A pointer event - */ - getCoordinates: function (e) { - var coordinates = { - xAxis: [], - yAxis: [] - }; - - each(this.chart.axes, function (axis) { - coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({ - axis: axis, - value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY']) - }); - }); - return coordinates; - }, - - /** - * With line type charts with a single tracker, get the point closest to the mouse. - * Run Point.onMouseOver and display tooltip for the point or points. - */ - runPointActions: function (e) { - - var pointer = this, - chart = pointer.chart, - series = chart.series, - tooltip = chart.tooltip, - shared = tooltip ? tooltip.shared : false, - followPointer, - hoverPoint = chart.hoverPoint, - hoverSeries = chart.hoverSeries, - i, - distance = [Number.MAX_VALUE, Number.MAX_VALUE], // #4511 - anchor, - noSharedTooltip, - stickToHoverSeries, - directTouch, - kdpoints = [], - kdpoint = [], - kdpointT; - - // For hovering over the empty parts of the plot area (hoverSeries is undefined). - // If there is one series with point tracking (combo chart), don't go to nearest neighbour. - if (!shared && !hoverSeries) { - for (i = 0; i < series.length; i++) { - if (series[i].directTouch || !series[i].options.stickyTracking) { - series = []; - } - } - } - - // If it has a hoverPoint and that series requires direct touch (like columns, #3899), or we're on - // a noSharedTooltip series among shared tooltip series (#4546), use the hoverPoint . Otherwise, - // search the k-d tree. - stickToHoverSeries = hoverSeries && (shared ? hoverSeries.noSharedTooltip : hoverSeries.directTouch); - if (stickToHoverSeries && hoverPoint) { - kdpoint = [hoverPoint]; - - // Handle shared tooltip or cases where a series is not yet hovered - } else { - // Find nearest points on all series - each(series, function (s) { - // Skip hidden series - noSharedTooltip = s.noSharedTooltip && shared; - directTouch = !shared && s.directTouch; - if (s.visible && !noSharedTooltip && !directTouch && pick(s.options.enableMouseTracking, true)) { // #3821 - kdpointT = s.searchPoint(e, !noSharedTooltip && s.kdDimensions === 1); // #3828 - if (kdpointT) { - kdpoints.push(kdpointT); - } - } - }); - // Find absolute nearest point - each(kdpoints, function (p) { - if (p) { - // Store both closest points, using point.dist and point.distX comparisons (#4645): - each(['dist', 'distX'], function (dist, k) { - if (isNumber(p[dist])) { - var - // It is closer than the reference point - isCloser = p[dist] < distance[k], - // It is equally close, but above the reference point (#4679) - isAbove = p[dist] === distance[k] && p.series.group.zIndex >= kdpoint[k].series.group.zIndex; - - if (isCloser || isAbove) { - distance[k] = p[dist]; - kdpoint[k] = p; - } - } - }); - } - }); - } - - // Remove points with different x-positions, required for shared tooltip and crosshairs (#4645): - if (shared) { - i = kdpoints.length; - while (i--) { - if (kdpoints[i].clientX !== kdpoint[1].clientX || kdpoints[i].series.noSharedTooltip) { - kdpoints.splice(i, 1); - } - } - } - - // Refresh tooltip for kdpoint if new hover point or tooltip was hidden // #3926, #4200 - if (kdpoint[0] && (kdpoint[0] !== this.prevKDPoint || (tooltip && tooltip.isHidden))) { - // Draw tooltip if necessary - if (shared && !kdpoint[0].series.noSharedTooltip) { - if (kdpoints.length && tooltip) { - tooltip.refresh(kdpoints, e); - } - - // Do mouseover on all points (#3919, #3985, #4410) - each(kdpoints, function (point) { - point.onMouseOver(e, point !== ((hoverSeries && hoverSeries.directTouch && hoverPoint) || kdpoint[0])); - }); - this.prevKDPoint = kdpoint[1]; - } else { - if (tooltip) { - tooltip.refresh(kdpoint[0], e); - } - if (!hoverSeries || !hoverSeries.directTouch) { // #4448 - kdpoint[0].onMouseOver(e); - } - this.prevKDPoint = kdpoint[0]; - } - - // Update positions (regardless of kdpoint or hoverPoint) - } else { - followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer; - if (tooltip && followPointer && !tooltip.isHidden) { - anchor = tooltip.getAnchor([{}], e); - tooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] }); - } - } - - // Start the event listener to pick up the tooltip and crosshairs - if (!pointer._onDocumentMouseMove) { - pointer._onDocumentMouseMove = function (e) { - if (charts[hoverChartIndex]) { - charts[hoverChartIndex].pointer.onDocumentMouseMove(e); - } - }; - addEvent(doc, 'mousemove', pointer._onDocumentMouseMove); - } - - // Crosshair. For each hover point, loop over axes and draw cross if that point - // belongs to the axis (#4927). - each(shared ? kdpoints : [pick(hoverPoint, kdpoint[1])], function (point) { // #5269 - each(chart.axes, function (axis) { - // In case of snap = false, point is undefined, and we draw the crosshair anyway (#5066) - if (!point || point.series[axis.coll] === axis) { - axis.drawCrosshair(e, point); - } - }); - }); - }, - - /** - * Reset the tracking by hiding the tooltip, the hover series state and the hover point - * - * @param allowMove {Boolean} Instead of destroying the tooltip altogether, allow moving it if possible - */ - reset: function (allowMove, delay) { - var pointer = this, - chart = pointer.chart, - hoverSeries = chart.hoverSeries, - hoverPoint = chart.hoverPoint, - hoverPoints = chart.hoverPoints, - tooltip = chart.tooltip, - tooltipPoints = tooltip && tooltip.shared ? hoverPoints : hoverPoint; - - // Check if the points have moved outside the plot area (#1003, #4736, #5101) - if (allowMove && tooltipPoints) { - each(splat(tooltipPoints), function (point) { - if (point.series.isCartesian && point.plotX === undefined) { - allowMove = false; - } - }); - } - - // Just move the tooltip, #349 - if (allowMove) { - if (tooltip && tooltipPoints) { - tooltip.refresh(tooltipPoints); - if (hoverPoint) { // #2500 - hoverPoint.setState(hoverPoint.state, true); - each(chart.axes, function (axis) { - if (pick(axis.crosshair && axis.crosshair.snap, true)) { - axis.drawCrosshair(null, hoverPoint); - } else { - axis.hideCrosshair(); - } - }); - - } - } - - // Full reset - } else { - - if (hoverPoint) { - hoverPoint.onMouseOut(); - } - - if (hoverPoints) { - each(hoverPoints, function (point) { - point.setState(); - }); - } - - if (hoverSeries) { - hoverSeries.onMouseOut(); - } - - if (tooltip) { - tooltip.hide(delay); - } - - if (pointer._onDocumentMouseMove) { - removeEvent(doc, 'mousemove', pointer._onDocumentMouseMove); - pointer._onDocumentMouseMove = null; - } - - // Remove crosshairs - each(chart.axes, function (axis) { - axis.hideCrosshair(); - }); - - pointer.hoverX = chart.hoverPoints = chart.hoverPoint = null; - - } - }, - - /** - * Scale series groups to a certain scale and translation - */ - scaleGroups: function (attribs, clip) { - - var chart = this.chart, - seriesAttribs; - - // Scale each series - each(chart.series, function (series) { - seriesAttribs = attribs || series.getPlotBox(); // #1701 - if (series.xAxis && series.xAxis.zoomEnabled) { - series.group.attr(seriesAttribs); - if (series.markerGroup) { - series.markerGroup.attr(seriesAttribs); - series.markerGroup.clip(clip ? chart.clipRect : null); - } - if (series.dataLabelsGroup) { - series.dataLabelsGroup.attr(seriesAttribs); - } - } - }); - - // Clip - chart.clipRect.attr(clip || chart.clipBox); - }, - - /** - * Start a drag operation - */ - dragStart: function (e) { - var chart = this.chart; - - // Record the start position - chart.mouseIsDown = e.type; - chart.cancelClick = false; - chart.mouseDownX = this.mouseDownX = e.chartX; - chart.mouseDownY = this.mouseDownY = e.chartY; - }, - - /** - * Perform a drag operation in response to a mousemove event while the mouse is down - */ - drag: function (e) { - - var chart = this.chart, - chartOptions = chart.options.chart, - chartX = e.chartX, - chartY = e.chartY, - zoomHor = this.zoomHor, - zoomVert = this.zoomVert, - plotLeft = chart.plotLeft, - plotTop = chart.plotTop, - plotWidth = chart.plotWidth, - plotHeight = chart.plotHeight, - clickedInside, - size, - selectionMarker = this.selectionMarker, - mouseDownX = this.mouseDownX, - mouseDownY = this.mouseDownY, - panKey = chartOptions.panKey && e[chartOptions.panKey + 'Key']; - - // If the device supports both touch and mouse (like IE11), and we are touch-dragging - // inside the plot area, don't handle the mouse event. #4339. - if (selectionMarker && selectionMarker.touch) { - return; - } - - // If the mouse is outside the plot area, adjust to cooordinates - // inside to prevent the selection marker from going outside - if (chartX < plotLeft) { - chartX = plotLeft; - } else if (chartX > plotLeft + plotWidth) { - chartX = plotLeft + plotWidth; - } - - if (chartY < plotTop) { - chartY = plotTop; - } else if (chartY > plotTop + plotHeight) { - chartY = plotTop + plotHeight; - } - - // determine if the mouse has moved more than 10px - this.hasDragged = Math.sqrt( - Math.pow(mouseDownX - chartX, 2) + - Math.pow(mouseDownY - chartY, 2) - ); - - if (this.hasDragged > 10) { - clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop); - - // make a selection - if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside && !panKey) { - if (!selectionMarker) { - this.selectionMarker = selectionMarker = chart.renderer.rect( - plotLeft, - plotTop, - zoomHor ? 1 : plotWidth, - zoomVert ? 1 : plotHeight, - 0 - ) - .attr({ - fill: chartOptions.selectionMarkerFill || 'rgba(69,114,167,0.25)', - zIndex: 7 - }) - .add(); - } - } - - // adjust the width of the selection marker - if (selectionMarker && zoomHor) { - size = chartX - mouseDownX; - selectionMarker.attr({ - width: mathAbs(size), - x: (size > 0 ? 0 : size) + mouseDownX - }); - } - // adjust the height of the selection marker - if (selectionMarker && zoomVert) { - size = chartY - mouseDownY; - selectionMarker.attr({ - height: mathAbs(size), - y: (size > 0 ? 0 : size) + mouseDownY - }); - } - - // panning - if (clickedInside && !selectionMarker && chartOptions.panning) { - chart.pan(e, chartOptions.panning); - } - } - }, - - /** - * On mouse up or touch end across the entire document, drop the selection. - */ - drop: function (e) { - var pointer = this, - chart = this.chart, - hasPinched = this.hasPinched; - - if (this.selectionMarker) { - var selectionData = { - originalEvent: e, // #4890 - xAxis: [], - yAxis: [] - }, - selectionBox = this.selectionMarker, - selectionLeft = selectionBox.attr ? selectionBox.attr('x') : selectionBox.x, - selectionTop = selectionBox.attr ? selectionBox.attr('y') : selectionBox.y, - selectionWidth = selectionBox.attr ? selectionBox.attr('width') : selectionBox.width, - selectionHeight = selectionBox.attr ? selectionBox.attr('height') : selectionBox.height, - runZoom; - - // a selection has been made - if (this.hasDragged || hasPinched) { - - // record each axis' min and max - each(chart.axes, function (axis) { - if (axis.zoomEnabled && defined(axis.min) && (hasPinched || pointer[{ xAxis: 'zoomX', yAxis: 'zoomY' }[axis.coll]])) { // #859, #3569 - var horiz = axis.horiz, - minPixelPadding = e.type === 'touchend' ? axis.minPixelPadding : 0, // #1207, #3075 - selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop) + minPixelPadding), - selectionMax = axis.toValue((horiz ? selectionLeft + selectionWidth : selectionTop + selectionHeight) - minPixelPadding); - - selectionData[axis.coll].push({ - axis: axis, - min: mathMin(selectionMin, selectionMax), // for reversed axes - max: mathMax(selectionMin, selectionMax) - }); - runZoom = true; - } - }); - if (runZoom) { - fireEvent(chart, 'selection', selectionData, function (args) { - chart.zoom(extend(args, hasPinched ? { animation: false } : null)); - }); - } - - } - this.selectionMarker = this.selectionMarker.destroy(); - - // Reset scaling preview - if (hasPinched) { - this.scaleGroups(); - } - } - - // Reset all - if (chart) { // it may be destroyed on mouse up - #877 - css(chart.container, { cursor: chart._cursor }); - chart.cancelClick = this.hasDragged > 10; // #370 - chart.mouseIsDown = this.hasDragged = this.hasPinched = false; - this.pinchDown = []; - } - }, - - onContainerMouseDown: function (e) { - - e = this.normalize(e); - - // issue #295, dragging not always working in Firefox - if (e.preventDefault) { - e.preventDefault(); - } - - this.dragStart(e); - }, - - - - onDocumentMouseUp: function (e) { - if (charts[hoverChartIndex]) { - charts[hoverChartIndex].pointer.drop(e); - } - }, - - /** - * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea. - * Issue #149 workaround. The mouseleave event does not always fire. - */ - onDocumentMouseMove: function (e) { - var chart = this.chart, - chartPosition = this.chartPosition; - - e = this.normalize(e, chartPosition); - - // If we're outside, hide the tooltip - if (chartPosition && !this.inClass(e.target, 'highcharts-tracker') && - !chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) { - this.reset(); - } - }, - - /** - * When mouse leaves the container, hide the tooltip. - */ - onContainerMouseLeave: function (e) { - var chart = charts[hoverChartIndex]; - if (chart && (e.relatedTarget || e.toElement)) { // #4886, MS Touch end fires mouseleave but with no related target - chart.pointer.reset(); - chart.pointer.chartPosition = null; // also reset the chart position, used in #149 fix - } - }, - - // The mousemove, touchmove and touchstart event handler - onContainerMouseMove: function (e) { - - var chart = this.chart; - - if (!defined(hoverChartIndex) || !charts[hoverChartIndex] || !charts[hoverChartIndex].mouseIsDown) { - hoverChartIndex = chart.index; - } - - e = this.normalize(e); - e.returnValue = false; // #2251, #3224 - - if (chart.mouseIsDown === 'mousedown') { - this.drag(e); - } - - // Show the tooltip and run mouse over events (#977) - if ((this.inClass(e.target, 'highcharts-tracker') || - chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) && !chart.openMenu) { - this.runPointActions(e); - } - }, - - /** - * Utility to detect whether an element has, or has a parent with, a specific - * class name. Used on detection of tracker objects and on deciding whether - * hovering the tooltip should cause the active series to mouse out. - */ - inClass: function (element, className) { - var elemClassName; - while (element) { - elemClassName = attr(element, 'class'); - if (elemClassName) { - if (elemClassName.indexOf(className) !== -1) { - return true; - } - if (elemClassName.indexOf(PREFIX + 'container') !== -1) { - return false; - } - } - element = element.parentNode; - } - }, - - onTrackerMouseOut: function (e) { - var series = this.chart.hoverSeries, - relatedTarget = e.relatedTarget || e.toElement; - - if (series && relatedTarget && !series.options.stickyTracking && // #4886 - !this.inClass(relatedTarget, PREFIX + 'tooltip') && - !this.inClass(relatedTarget, PREFIX + 'series-' + series.index)) { // #2499, #4465 - series.onMouseOut(); - } - }, - - onContainerClick: function (e) { - var chart = this.chart, - hoverPoint = chart.hoverPoint, - plotLeft = chart.plotLeft, - plotTop = chart.plotTop; - - e = this.normalize(e); - - if (!chart.cancelClick) { - - // On tracker click, fire the series and point events. #783, #1583 - if (hoverPoint && this.inClass(e.target, PREFIX + 'tracker')) { - - // the series click event - fireEvent(hoverPoint.series, 'click', extend(e, { - point: hoverPoint - })); - - // the point click event - if (chart.hoverPoint) { // it may be destroyed (#1844) - hoverPoint.firePointEvent('click', e); - } - - // When clicking outside a tracker, fire a chart event - } else { - extend(e, this.getCoordinates(e)); - - // fire a click event in the chart - if (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) { - fireEvent(chart, 'click', e); - } - } - - - } - }, - - /** - * Set the JS DOM events on the container and document. This method should contain - * a one-to-one assignment between methods and their handlers. Any advanced logic should - * be moved to the handler reflecting the event's name. - */ - setDOMEvents: function () { - - var pointer = this, - container = pointer.chart.container; - - container.onmousedown = function (e) { - pointer.onContainerMouseDown(e); - }; - container.onmousemove = function (e) { - pointer.onContainerMouseMove(e); - }; - container.onclick = function (e) { - pointer.onContainerClick(e); - }; - addEvent(container, 'mouseleave', pointer.onContainerMouseLeave); - if (chartCount === 1) { - addEvent(doc, 'mouseup', pointer.onDocumentMouseUp); - } - if (hasTouch) { - container.ontouchstart = function (e) { - pointer.onContainerTouchStart(e); - }; - container.ontouchmove = function (e) { - pointer.onContainerTouchMove(e); - }; - if (chartCount === 1) { - addEvent(doc, 'touchend', pointer.onDocumentTouchEnd); - } - } - - }, - - /** - * Destroys the Pointer object and disconnects DOM events. - */ - destroy: function () { - var prop; - - removeEvent(this.chart.container, 'mouseleave', this.onContainerMouseLeave); - if (!chartCount) { - removeEvent(doc, 'mouseup', this.onDocumentMouseUp); - removeEvent(doc, 'touchend', this.onDocumentTouchEnd); - } - - // memory and CPU leak - clearInterval(this.tooltipTimeout); - - for (prop in this) { - this[prop] = null; - } - } - }; - - - /* Support for touch devices */ - extend(Highcharts.Pointer.prototype, { - - /** - * Run translation operations - */ - pinchTranslate: function (pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) { - if (this.zoomHor || this.pinchHor) { - this.pinchTranslateDirection(true, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); - } - if (this.zoomVert || this.pinchVert) { - this.pinchTranslateDirection(false, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); - } - }, - - /** - * Run translation operations for each direction (horizontal and vertical) independently - */ - pinchTranslateDirection: function (horiz, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch, forcedScale) { - var chart = this.chart, - xy = horiz ? 'x' : 'y', - XY = horiz ? 'X' : 'Y', - sChartXY = 'chart' + XY, - wh = horiz ? 'width' : 'height', - plotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')], - selectionWH, - selectionXY, - clipXY, - scale = forcedScale || 1, - inverted = chart.inverted, - bounds = chart.bounds[horiz ? 'h' : 'v'], - singleTouch = pinchDown.length === 1, - touch0Start = pinchDown[0][sChartXY], - touch0Now = touches[0][sChartXY], - touch1Start = !singleTouch && pinchDown[1][sChartXY], - touch1Now = !singleTouch && touches[1][sChartXY], - outOfBounds, - transformScale, - scaleKey, - setScale = function () { - if (!singleTouch && mathAbs(touch0Start - touch1Start) > 20) { // Don't zoom if fingers are too close on this axis - scale = forcedScale || mathAbs(touch0Now - touch1Now) / mathAbs(touch0Start - touch1Start); - } - - clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start; - selectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] / scale; - }; - - // Set the scale, first pass - setScale(); - - selectionXY = clipXY; // the clip position (x or y) is altered if out of bounds, the selection position is not - - // Out of bounds - if (selectionXY < bounds.min) { - selectionXY = bounds.min; - outOfBounds = true; - } else if (selectionXY + selectionWH > bounds.max) { - selectionXY = bounds.max - selectionWH; - outOfBounds = true; - } - - // Is the chart dragged off its bounds, determined by dataMin and dataMax? - if (outOfBounds) { - - // Modify the touchNow position in order to create an elastic drag movement. This indicates - // to the user that the chart is responsive but can't be dragged further. - touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]); - if (!singleTouch) { - touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]); - } - - // Set the scale, second pass to adapt to the modified touchNow positions - setScale(); - - } else { - lastValidTouch[xy] = [touch0Now, touch1Now]; - } - - // Set geometry for clipping, selection and transformation - if (!inverted) { - clip[xy] = clipXY - plotLeftTop; - clip[wh] = selectionWH; - } - scaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY; - transformScale = inverted ? 1 / scale : scale; - - selectionMarker[wh] = selectionWH; - selectionMarker[xy] = selectionXY; - transform[scaleKey] = scale; - transform['translate' + XY] = (transformScale * plotLeftTop) + (touch0Now - (transformScale * touch0Start)); - }, - - /** - * Handle touch events with two touches - */ - pinch: function (e) { - - var self = this, - chart = self.chart, - pinchDown = self.pinchDown, - touches = e.touches, - touchesLength = touches.length, - lastValidTouch = self.lastValidTouch, - hasZoom = self.hasZoom, - selectionMarker = self.selectionMarker, - transform = {}, - fireClickEvent = touchesLength === 1 && ((self.inClass(e.target, PREFIX + 'tracker') && - chart.runTrackerClick) || self.runChartClick), - clip = {}; - - // Don't initiate panning until the user has pinched. This prevents us from - // blocking page scrolling as users scroll down a long page (#4210). - if (touchesLength > 1) { - self.initiated = true; - } - - // On touch devices, only proceed to trigger click if a handler is defined - if (hasZoom && self.initiated && !fireClickEvent) { - e.preventDefault(); - } - - // Normalize each touch - map(touches, function (e) { - return self.normalize(e); - }); - - // Register the touch start position - if (e.type === 'touchstart') { - each(touches, function (e, i) { - pinchDown[i] = { chartX: e.chartX, chartY: e.chartY }; - }); - lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] && pinchDown[1].chartX]; - lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] && pinchDown[1].chartY]; - - // Identify the data bounds in pixels - each(chart.axes, function (axis) { - if (axis.zoomEnabled) { - var bounds = chart.bounds[axis.horiz ? 'h' : 'v'], - minPixelPadding = axis.minPixelPadding, - min = axis.toPixels(pick(axis.options.min, axis.dataMin)), - max = axis.toPixels(pick(axis.options.max, axis.dataMax)), - absMin = mathMin(min, max), - absMax = mathMax(min, max); - - // Store the bounds for use in the touchmove handler - bounds.min = mathMin(axis.pos, absMin - minPixelPadding); - bounds.max = mathMax(axis.pos + axis.len, absMax + minPixelPadding); - } - }); - self.res = true; // reset on next move - - // Event type is touchmove, handle panning and pinching - } else if (pinchDown.length) { // can be 0 when releasing, if touchend fires first - - - // Set the marker - if (!selectionMarker) { - self.selectionMarker = selectionMarker = extend({ - destroy: noop, - touch: true - }, chart.plotBox); - } - - self.pinchTranslate(pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); - - self.hasPinched = hasZoom; - - // Scale and translate the groups to provide visual feedback during pinching - self.scaleGroups(transform, clip); - - // Optionally move the tooltip on touchmove - if (!hasZoom && self.followTouchMove && touchesLength === 1) { - this.runPointActions(self.normalize(e)); - } else if (self.res) { - self.res = false; - this.reset(false, 0); - } - } - }, - - /** - * General touch handler shared by touchstart and touchmove. - */ - touch: function (e, start) { - var chart = this.chart, - hasMoved, - pinchDown; - - hoverChartIndex = chart.index; - - if (e.touches.length === 1) { - - e = this.normalize(e); - - if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop) && !chart.openMenu) { - - // Run mouse events and display tooltip etc - if (start) { - this.runPointActions(e); - } - - // Android fires touchmove events after the touchstart even if the - // finger hasn't moved, or moved only a pixel or two. In iOS however, - // the touchmove doesn't fire unless the finger moves more than ~4px. - // So we emulate this behaviour in Android by checking how much it - // moved, and cancelling on small distances. #3450. - if (e.type === 'touchmove') { - pinchDown = this.pinchDown; - hasMoved = pinchDown[0] ? Math.sqrt( // #5266 - Math.pow(pinchDown[0].chartX - e.chartX, 2) + - Math.pow(pinchDown[0].chartY - e.chartY, 2) - ) >= 4 : false; - } - - if (pick(hasMoved, true)) { - this.pinch(e); - } - - } else if (start) { - // Hide the tooltip on touching outside the plot area (#1203) - this.reset(); - } - - } else if (e.touches.length === 2) { - this.pinch(e); - } - }, - - onContainerTouchStart: function (e) { - this.touch(e, true); - }, - - onContainerTouchMove: function (e) { - this.touch(e); - }, - - onDocumentTouchEnd: function (e) { - if (charts[hoverChartIndex]) { - charts[hoverChartIndex].pointer.drop(e); - } - } - - }); - if (win.PointerEvent || win.MSPointerEvent) { - - // The touches object keeps track of the points being touched at all times - var touches = {}, - hasPointerEvent = !!win.PointerEvent, - getWebkitTouches = function () { - var key, - fake = []; - fake.item = function (i) { - return this[i]; - }; - for (key in touches) { - if (touches.hasOwnProperty(key)) { - fake.push({ - pageX: touches[key].pageX, - pageY: touches[key].pageY, - target: touches[key].target - }); - } - } - return fake; - }, - translateMSPointer = function (e, method, wktype, func) { - var p; - if ((e.pointerType === 'touch' || e.pointerType === e.MSPOINTER_TYPE_TOUCH) && charts[hoverChartIndex]) { - func(e); - p = charts[hoverChartIndex].pointer; - p[method]({ - type: wktype, - target: e.currentTarget, - preventDefault: noop, - touches: getWebkitTouches() - }); - } - }; - - /** - * Extend the Pointer prototype with methods for each event handler and more - */ - extend(Pointer.prototype, { - onContainerPointerDown: function (e) { - translateMSPointer(e, 'onContainerTouchStart', 'touchstart', function (e) { - touches[e.pointerId] = { pageX: e.pageX, pageY: e.pageY, target: e.currentTarget }; - }); - }, - onContainerPointerMove: function (e) { - translateMSPointer(e, 'onContainerTouchMove', 'touchmove', function (e) { - touches[e.pointerId] = { pageX: e.pageX, pageY: e.pageY }; - if (!touches[e.pointerId].target) { - touches[e.pointerId].target = e.currentTarget; - } - }); - }, - onDocumentPointerUp: function (e) { - translateMSPointer(e, 'onDocumentTouchEnd', 'touchend', function (e) { - delete touches[e.pointerId]; - }); - }, - - /** - * Add or remove the MS Pointer specific events - */ - batchMSEvents: function (fn) { - fn(this.chart.container, hasPointerEvent ? 'pointerdown' : 'MSPointerDown', this.onContainerPointerDown); - fn(this.chart.container, hasPointerEvent ? 'pointermove' : 'MSPointerMove', this.onContainerPointerMove); - fn(doc, hasPointerEvent ? 'pointerup' : 'MSPointerUp', this.onDocumentPointerUp); - } - }); - - // Disable default IE actions for pinch and such on chart element - wrap(Pointer.prototype, 'init', function (proceed, chart, options) { - proceed.call(this, chart, options); - if (this.hasZoom) { // #4014 - css(chart.container, { - '-ms-touch-action': NONE, - 'touch-action': NONE - }); - } - }); - - // Add IE specific touch events to chart - wrap(Pointer.prototype, 'setDOMEvents', function (proceed) { - proceed.apply(this); - if (this.hasZoom || this.followTouchMove) { - this.batchMSEvents(addEvent); - } - }); - // Destroy MS events also - wrap(Pointer.prototype, 'destroy', function (proceed) { - this.batchMSEvents(removeEvent); - proceed.call(this); - }); - } - /** - * The overview of the chart's series - */ - var Legend = Highcharts.Legend = function (chart, options) { - this.init(chart, options); - }; - - Legend.prototype = { - - /** - * Initialize the legend - */ - init: function (chart, options) { - - var legend = this, - itemStyle = options.itemStyle, - padding, - itemMarginTop = options.itemMarginTop || 0; - - this.options = options; - - if (!options.enabled) { - return; - } - - legend.itemStyle = itemStyle; - legend.itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle); - legend.itemMarginTop = itemMarginTop; - legend.padding = padding = pick(options.padding, 8); - legend.initialItemX = padding; - legend.initialItemY = padding - 5; // 5 is the number of pixels above the text - legend.maxItemWidth = 0; - legend.chart = chart; - legend.itemHeight = 0; - legend.symbolWidth = pick(options.symbolWidth, 16); - legend.pages = []; - - - // Render it - legend.render(); - - // move checkboxes - addEvent(legend.chart, 'endResize', function () { - legend.positionCheckboxes(); - }); - - }, - - /** - * Set the colors for the legend item - * @param {Object} item A Series or Point instance - * @param {Object} visible Dimmed or colored - */ - colorizeItem: function (item, visible) { - var legend = this, - options = legend.options, - legendItem = item.legendItem, - legendLine = item.legendLine, - legendSymbol = item.legendSymbol, - hiddenColor = legend.itemHiddenStyle.color, - textColor = visible ? options.itemStyle.color : hiddenColor, - symbolColor = visible ? (item.legendColor || item.color || '#CCC') : hiddenColor, - markerOptions = item.options && item.options.marker, - symbolAttr = { fill: symbolColor }, - key, - val; - - if (legendItem) { - legendItem.css({ fill: textColor, color: textColor }); // color for #1553, oldIE - } - if (legendLine) { - legendLine.attr({ stroke: symbolColor }); - } - - if (legendSymbol) { - - // Apply marker options - if (markerOptions && legendSymbol.isMarker) { // #585 - symbolAttr.stroke = symbolColor; - markerOptions = item.convertAttribs(markerOptions); - for (key in markerOptions) { - val = markerOptions[key]; - if (val !== UNDEFINED) { - symbolAttr[key] = val; - } - } - } - - legendSymbol.attr(symbolAttr); - } - }, - - /** - * Position the legend item - * @param {Object} item A Series or Point instance - */ - positionItem: function (item) { - var legend = this, - options = legend.options, - symbolPadding = options.symbolPadding, - ltr = !options.rtl, - legendItemPos = item._legendItemPos, - itemX = legendItemPos[0], - itemY = legendItemPos[1], - checkbox = item.checkbox, - legendGroup = item.legendGroup; - - if (legendGroup && legendGroup.element) { - legendGroup.translate( - ltr ? itemX : legend.legendWidth - itemX - 2 * symbolPadding - 4, - itemY - ); - } - - if (checkbox) { - checkbox.x = itemX; - checkbox.y = itemY; - } - }, - - /** - * Destroy a single legend item - * @param {Object} item The series or point - */ - destroyItem: function (item) { - var checkbox = item.checkbox; - - // destroy SVG elements - each(['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'], function (key) { - if (item[key]) { - item[key] = item[key].destroy(); - } - }); - - if (checkbox) { - discardElement(item.checkbox); - } - }, - - /** - * Destroys the legend. - */ - destroy: function () { - var legend = this, - legendGroup = legend.group, - box = legend.box; - - if (box) { - legend.box = box.destroy(); - } - - if (legendGroup) { - legend.group = legendGroup.destroy(); - } - }, - - /** - * Position the checkboxes after the width is determined - */ - positionCheckboxes: function (scrollOffset) { - var alignAttr = this.group.alignAttr, - translateY, - clipHeight = this.clipHeight || this.legendHeight, - titleHeight = this.titleHeight; - - if (alignAttr) { - translateY = alignAttr.translateY; - each(this.allItems, function (item) { - var checkbox = item.checkbox, - top; - - if (checkbox) { - top = translateY + titleHeight + checkbox.y + (scrollOffset || 0) + 3; - css(checkbox, { - left: (alignAttr.translateX + item.checkboxOffset + checkbox.x - 20) + PX, - top: top + PX, - display: top > translateY - 6 && top < translateY + clipHeight - 6 ? '' : NONE - }); - } - }); - } - }, - - /** - * Render the legend title on top of the legend - */ - renderTitle: function () { - var options = this.options, - padding = this.padding, - titleOptions = options.title, - titleHeight = 0, - bBox; - - if (titleOptions.text) { - if (!this.title) { - this.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, null, null, null, null, null, 'legend-title') - .attr({ zIndex: 1 }) - .css(titleOptions.style) - .add(this.group); - } - bBox = this.title.getBBox(); - titleHeight = bBox.height; - this.offsetWidth = bBox.width; // #1717 - this.contentGroup.attr({ translateY: titleHeight }); - } - this.titleHeight = titleHeight; - }, - - /** - * Set the legend item text - */ - setText: function (item) { - var options = this.options; - item.legendItem.attr({ - text: options.labelFormat ? format(options.labelFormat, item) : options.labelFormatter.call(item) - }); - }, - - /** - * Render a single specific legend item - * @param {Object} item A series or point - */ - renderItem: function (item) { - var legend = this, - chart = legend.chart, - renderer = chart.renderer, - options = legend.options, - horizontal = options.layout === 'horizontal', - symbolWidth = legend.symbolWidth, - symbolPadding = options.symbolPadding, - itemStyle = legend.itemStyle, - itemHiddenStyle = legend.itemHiddenStyle, - padding = legend.padding, - itemDistance = horizontal ? pick(options.itemDistance, 20) : 0, - ltr = !options.rtl, - itemHeight, - widthOption = options.width, - itemMarginBottom = options.itemMarginBottom || 0, - itemMarginTop = legend.itemMarginTop, - initialItemX = legend.initialItemX, - bBox, - itemWidth, - li = item.legendItem, - series = item.series && item.series.drawLegendSymbol ? item.series : item, - seriesOptions = series.options, - showCheckbox = legend.createCheckboxForItem && seriesOptions && seriesOptions.showCheckbox, - useHTML = options.useHTML; - - if (!li) { // generate it once, later move it - - // Generate the group box - // A group to hold the symbol and text. Text is to be appended in Legend class. - item.legendGroup = renderer.g('legend-item') - .attr({ zIndex: 1 }) - .add(legend.scrollGroup); - - // Generate the list item text and add it to the group - item.legendItem = li = renderer.text( - '', - ltr ? symbolWidth + symbolPadding : -symbolPadding, - legend.baseline || 0, - useHTML - ) - .css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021) - .attr({ - align: ltr ? 'left' : 'right', - zIndex: 2 - }) - .add(item.legendGroup); - - // Get the baseline for the first item - the font size is equal for all - if (!legend.baseline) { - legend.fontMetrics = renderer.fontMetrics(itemStyle.fontSize, li); - legend.baseline = legend.fontMetrics.f + 3 + itemMarginTop; - li.attr('y', legend.baseline); - } - - // Draw the legend symbol inside the group box - series.drawLegendSymbol(legend, item); - - if (legend.setItemEvents) { - legend.setItemEvents(item, li, useHTML, itemStyle, itemHiddenStyle); - } - - // add the HTML checkbox on top - if (showCheckbox) { - legend.createCheckboxForItem(item); - } - } - - // Colorize the items - legend.colorizeItem(item, item.visible); - - // Always update the text - legend.setText(item); - - // calculate the positions for the next line - bBox = li.getBBox(); - - itemWidth = item.checkboxOffset = - options.itemWidth || - item.legendItemWidth || - symbolWidth + symbolPadding + bBox.width + itemDistance + (showCheckbox ? 20 : 0); - legend.itemHeight = itemHeight = mathRound(item.legendItemHeight || bBox.height); - - // if the item exceeds the width, start a new line - if (horizontal && legend.itemX - initialItemX + itemWidth > - (widthOption || (chart.chartWidth - 2 * padding - initialItemX - options.x))) { - legend.itemX = initialItemX; - legend.itemY += itemMarginTop + legend.lastLineHeight + itemMarginBottom; - legend.lastLineHeight = 0; // reset for next line (#915, #3976) - } - - // If the item exceeds the height, start a new column - /*if (!horizontal && legend.itemY + options.y + itemHeight > chart.chartHeight - spacingTop - spacingBottom) { - legend.itemY = legend.initialItemY; - legend.itemX += legend.maxItemWidth; - legend.maxItemWidth = 0; - }*/ - - // Set the edge positions - legend.maxItemWidth = mathMax(legend.maxItemWidth, itemWidth); - legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom; - legend.lastLineHeight = mathMax(itemHeight, legend.lastLineHeight); // #915 - - // cache the position of the newly generated or reordered items - item._legendItemPos = [legend.itemX, legend.itemY]; - - // advance - if (horizontal) { - legend.itemX += itemWidth; - - } else { - legend.itemY += itemMarginTop + itemHeight + itemMarginBottom; - legend.lastLineHeight = itemHeight; - } - - // the width of the widest item - legend.offsetWidth = widthOption || mathMax( - (horizontal ? legend.itemX - initialItemX - itemDistance : itemWidth) + padding, - legend.offsetWidth - ); - }, - - /** - * Get all items, which is one item per series for normal series and one item per point - * for pie series. - */ - getAllItems: function () { - var allItems = []; - each(this.chart.series, function (series) { - var seriesOptions = series.options; - - // Handle showInLegend. If the series is linked to another series, defaults to false. - if (!pick(seriesOptions.showInLegend, !defined(seriesOptions.linkedTo) ? UNDEFINED : false, true)) { - return; - } - - // use points or series for the legend item depending on legendType - allItems = allItems.concat( - series.legendItems || - (seriesOptions.legendType === 'point' ? - series.data : - series) - ); - }); - return allItems; - }, - - /** - * Adjust the chart margins by reserving space for the legend on only one side - * of the chart. If the position is set to a corner, top or bottom is reserved - * for horizontal legends and left or right for vertical ones. - */ - adjustMargins: function (margin, spacing) { - var chart = this.chart, - options = this.options, - // Use the first letter of each alignment option in order to detect the side - alignment = options.align.charAt(0) + options.verticalAlign.charAt(0) + options.layout.charAt(0); // #4189 - use charAt(x) notation instead of [x] for IE7 - - if (this.display && !options.floating) { - - each([ - /(lth|ct|rth)/, - /(rtv|rm|rbv)/, - /(rbh|cb|lbh)/, - /(lbv|lm|ltv)/ - ], function (alignments, side) { - if (alignments.test(alignment) && !defined(margin[side])) { - // Now we have detected on which side of the chart we should reserve space for the legend - chart[marginNames[side]] = mathMax( - chart[marginNames[side]], - chart.legend[(side + 1) % 2 ? 'legendHeight' : 'legendWidth'] + - [1, -1, -1, 1][side] * options[(side % 2) ? 'x' : 'y'] + - pick(options.margin, 12) + - spacing[side] - ); - } - }); - } - }, - - /** - * Render the legend. This method can be called both before and after - * chart.render. If called after, it will only rearrange items instead - * of creating new ones. - */ - render: function () { - var legend = this, - chart = legend.chart, - renderer = chart.renderer, - legendGroup = legend.group, - allItems, - display, - legendWidth, - legendHeight, - box = legend.box, - options = legend.options, - padding = legend.padding, - legendBorderWidth = options.borderWidth, - legendBackgroundColor = options.backgroundColor; - - legend.itemX = legend.initialItemX; - legend.itemY = legend.initialItemY; - legend.offsetWidth = 0; - legend.lastItemY = 0; - - if (!legendGroup) { - legend.group = legendGroup = renderer.g('legend') - .attr({ zIndex: 7 }) - .add(); - legend.contentGroup = renderer.g() - .attr({ zIndex: 1 }) // above background - .add(legendGroup); - legend.scrollGroup = renderer.g() - .add(legend.contentGroup); - } - - legend.renderTitle(); - - // add each series or point - allItems = legend.getAllItems(); - - // sort by legendIndex - stableSort(allItems, function (a, b) { - return ((a.options && a.options.legendIndex) || 0) - ((b.options && b.options.legendIndex) || 0); - }); - - // reversed legend - if (options.reversed) { - allItems.reverse(); - } - - legend.allItems = allItems; - legend.display = display = !!allItems.length; - - // render the items - legend.lastLineHeight = 0; - each(allItems, function (item) { - legend.renderItem(item); - }); - - // Get the box - legendWidth = (options.width || legend.offsetWidth) + padding; - legendHeight = legend.lastItemY + legend.lastLineHeight + legend.titleHeight; - legendHeight = legend.handleOverflow(legendHeight); - legendHeight += padding; - - // Draw the border and/or background - if (legendBorderWidth || legendBackgroundColor) { - - if (!box) { - legend.box = box = renderer.rect( - 0, - 0, - legendWidth, - legendHeight, - options.borderRadius, - legendBorderWidth || 0 - ).attr({ - stroke: options.borderColor, - 'stroke-width': legendBorderWidth || 0, - fill: legendBackgroundColor || NONE - }) - .add(legendGroup) - .shadow(options.shadow); - box.isNew = true; - - } else if (legendWidth > 0 && legendHeight > 0) { - box[box.isNew ? 'attr' : 'animate']( - box.crisp({ width: legendWidth, height: legendHeight }) - ); - box.isNew = false; - } - - // hide the border if no items - box[display ? 'show' : 'hide'](); - } - - legend.legendWidth = legendWidth; - legend.legendHeight = legendHeight; - - // Now that the legend width and height are established, put the items in the - // final position - each(allItems, function (item) { - legend.positionItem(item); - }); - - // 1.x compatibility: positioning based on style - /*var props = ['left', 'right', 'top', 'bottom'], - prop, - i = 4; - while (i--) { - prop = props[i]; - if (options.style[prop] && options.style[prop] !== 'auto') { - options[i < 2 ? 'align' : 'verticalAlign'] = prop; - options[i < 2 ? 'x' : 'y'] = pInt(options.style[prop]) * (i % 2 ? -1 : 1); - } - }*/ - - if (display) { - legendGroup.align(extend({ - width: legendWidth, - height: legendHeight - }, options), true, 'spacingBox'); - } - - if (!chart.isResizing) { - this.positionCheckboxes(); - } - }, - - /** - * Set up the overflow handling by adding navigation with up and down arrows below the - * legend. - */ - handleOverflow: function (legendHeight) { - var legend = this, - chart = this.chart, - renderer = chart.renderer, - options = this.options, - optionsY = options.y, - alignTop = options.verticalAlign === 'top', - spaceHeight = chart.spacingBox.height + (alignTop ? -optionsY : optionsY) - this.padding, - maxHeight = options.maxHeight, - clipHeight, - clipRect = this.clipRect, - navOptions = options.navigation, - animation = pick(navOptions.animation, true), - arrowSize = navOptions.arrowSize || 12, - nav = this.nav, - pages = this.pages, - padding = this.padding, - lastY, - allItems = this.allItems, - clipToHeight = function (height) { - clipRect.attr({ - height: height - }); - - // useHTML - if (legend.contentGroup.div) { - legend.contentGroup.div.style.clip = 'rect(' + padding + 'px,9999px,' + (padding + height) + 'px,0)'; - } - }; - - - // Adjust the height - if (options.layout === 'horizontal') { - spaceHeight /= 2; - } - if (maxHeight) { - spaceHeight = mathMin(spaceHeight, maxHeight); - } - - // Reset the legend height and adjust the clipping rectangle - pages.length = 0; - if (legendHeight > spaceHeight && navOptions.enabled !== false) { - - this.clipHeight = clipHeight = mathMax(spaceHeight - 20 - this.titleHeight - padding, 0); - this.currentPage = pick(this.currentPage, 1); - this.fullHeight = legendHeight; - - // Fill pages with Y positions so that the top of each a legend item defines - // the scroll top for each page (#2098) - each(allItems, function (item, i) { - var y = item._legendItemPos[1], - h = mathRound(item.legendItem.getBBox().height), - len = pages.length; - - if (!len || (y - pages[len - 1] > clipHeight && (lastY || y) !== pages[len - 1])) { - pages.push(lastY || y); - len++; - } - - if (i === allItems.length - 1 && y + h - pages[len - 1] > clipHeight) { - pages.push(y); - } - if (y !== lastY) { - lastY = y; - } - }); - - // Only apply clipping if needed. Clipping causes blurred legend in PDF export (#1787) - if (!clipRect) { - clipRect = legend.clipRect = renderer.clipRect(0, padding, 9999, 0); - legend.contentGroup.clip(clipRect); - } - - clipToHeight(clipHeight); - - // Add navigation elements - if (!nav) { - this.nav = nav = renderer.g().attr({ zIndex: 1 }).add(this.group); - this.up = renderer.symbol('triangle', 0, 0, arrowSize, arrowSize) - .on('click', function () { - legend.scroll(-1, animation); - }) - .add(nav); - this.pager = renderer.text('', 15, 10) - .css(navOptions.style) - .add(nav); - this.down = renderer.symbol('triangle-down', 0, 0, arrowSize, arrowSize) - .on('click', function () { - legend.scroll(1, animation); - }) - .add(nav); - } - - // Set initial position - legend.scroll(0); - - legendHeight = spaceHeight; - - } else if (nav) { - clipToHeight(chart.chartHeight); - nav.hide(); - this.scrollGroup.attr({ - translateY: 1 - }); - this.clipHeight = 0; // #1379 - } - - return legendHeight; - }, - - /** - * Scroll the legend by a number of pages - * @param {Object} scrollBy - * @param {Object} animation - */ - scroll: function (scrollBy, animation) { - var pages = this.pages, - pageCount = pages.length, - currentPage = this.currentPage + scrollBy, - clipHeight = this.clipHeight, - navOptions = this.options.navigation, - activeColor = navOptions.activeColor, - inactiveColor = navOptions.inactiveColor, - pager = this.pager, - padding = this.padding, - scrollOffset; - - // When resizing while looking at the last page - if (currentPage > pageCount) { - currentPage = pageCount; - } - - if (currentPage > 0) { - - if (animation !== UNDEFINED) { - setAnimation(animation, this.chart); - } - - this.nav.attr({ - translateX: padding, - translateY: clipHeight + this.padding + 7 + this.titleHeight, - visibility: VISIBLE - }); - this.up.attr({ - fill: currentPage === 1 ? inactiveColor : activeColor - }) - .css({ - cursor: currentPage === 1 ? 'default' : 'pointer' - }); - pager.attr({ - text: currentPage + '/' + pageCount - }); - this.down.attr({ - x: 18 + this.pager.getBBox().width, // adjust to text width - fill: currentPage === pageCount ? inactiveColor : activeColor - }) - .css({ - cursor: currentPage === pageCount ? 'default' : 'pointer' - }); - - scrollOffset = -pages[currentPage - 1] + this.initialItemY; - - this.scrollGroup.animate({ - translateY: scrollOffset - }); - - this.currentPage = currentPage; - this.positionCheckboxes(scrollOffset); - } - - } - - }; - - /* - * LegendSymbolMixin - */ - - var LegendSymbolMixin = Highcharts.LegendSymbolMixin = { - - /** - * Get the series' symbol in the legend - * - * @param {Object} legend The legend object - * @param {Object} item The series (this) or point - */ - drawRectangle: function (legend, item) { - var symbolHeight = legend.options.symbolHeight || legend.fontMetrics.f; - - item.legendSymbol = this.chart.renderer.rect( - 0, - legend.baseline - symbolHeight + 1, // #3988 - legend.symbolWidth, - symbolHeight, - legend.options.symbolRadius || 0 - ).attr({ - zIndex: 3 - }).add(item.legendGroup); - - }, - - /** - * Get the series' symbol in the legend. This method should be overridable to create custom - * symbols through Highcharts.seriesTypes[type].prototype.drawLegendSymbols. - * - * @param {Object} legend The legend object - */ - drawLineMarker: function (legend) { - - var options = this.options, - markerOptions = options.marker, - radius, - legendSymbol, - symbolWidth = legend.symbolWidth, - renderer = this.chart.renderer, - legendItemGroup = this.legendGroup, - verticalCenter = legend.baseline - mathRound(legend.fontMetrics.b * 0.3), - attr; - - // Draw the line - if (options.lineWidth) { - attr = { - 'stroke-width': options.lineWidth - }; - if (options.dashStyle) { - attr.dashstyle = options.dashStyle; - } - this.legendLine = renderer.path([ - M, - 0, - verticalCenter, - L, - symbolWidth, - verticalCenter - ]) - .attr(attr) - .add(legendItemGroup); - } - - // Draw the marker - if (markerOptions && markerOptions.enabled !== false) { - radius = markerOptions.radius; - this.legendSymbol = legendSymbol = renderer.symbol( - this.symbol, - (symbolWidth / 2) - radius, - verticalCenter - radius, - 2 * radius, - 2 * radius, - markerOptions - ) - .add(legendItemGroup); - legendSymbol.isMarker = true; - } - } - }; - - // Workaround for #2030, horizontal legend items not displaying in IE11 Preview, - // and for #2580, a similar drawing flaw in Firefox 26. - // Explore if there's a general cause for this. The problem may be related - // to nested group elements, as the legend item texts are within 4 group elements. - if (/Trident\/7\.0/.test(userAgent) || isFirefox) { - wrap(Legend.prototype, 'positionItem', function (proceed, item) { - var legend = this, - runPositionItem = function () { // If chart destroyed in sync, this is undefined (#2030) - if (item._legendItemPos) { - proceed.call(legend, item); - } - }; - - // Do it now, for export and to get checkbox placement - runPositionItem(); - - // Do it after to work around the core issue - setTimeout(runPositionItem); - }); - } - /** - * The Chart class - * @param {String|Object} renderTo The DOM element to render to, or its id - * @param {Object} options - * @param {Function} callback Function to run when the chart has loaded - */ - var Chart = Highcharts.Chart = function () { - this.getArgs.apply(this, arguments); - }; - - Highcharts.chart = function (a, b, c) { - return new Chart(a, b, c); - }; - - Chart.prototype = { - - /** - * Hook for modules - */ - callbacks: [], - - /** - * Handle the arguments passed to the constructor - * @returns {Array} Arguments without renderTo - */ - getArgs: function () { - var args = [].slice.call(arguments); - - // Remove the optional first argument, renderTo, and - // set it on this. - if (isString(args[0]) || args[0].nodeName) { - this.renderTo = args.shift(); - } - this.init(args[0], args[1]); - }, - - /** - * Initialize the chart - */ - init: function (userOptions, callback) { - - // Handle regular options - var options, - seriesOptions = userOptions.series; // skip merging data points to increase performance - - userOptions.series = null; - options = merge(defaultOptions, userOptions); // do the merge - options.series = userOptions.series = seriesOptions; // set back the series data - this.userOptions = userOptions; - - var optionsChart = options.chart; - - // Create margin & spacing array - this.margin = this.splashArray('margin', optionsChart); - this.spacing = this.splashArray('spacing', optionsChart); - - var chartEvents = optionsChart.events; - - //this.runChartClick = chartEvents && !!chartEvents.click; - this.bounds = { h: {}, v: {} }; // Pixel data bounds for touch zoom - - this.callback = callback; - this.isResizing = 0; - this.options = options; - //chartTitleOptions = UNDEFINED; - //chartSubtitleOptions = UNDEFINED; - - this.axes = []; - this.series = []; - this.hasCartesianSeries = optionsChart.showAxes; - //this.axisOffset = UNDEFINED; - //this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes - //this.inverted = UNDEFINED; - //this.loadingShown = UNDEFINED; - //this.container = UNDEFINED; - //this.chartWidth = UNDEFINED; - //this.chartHeight = UNDEFINED; - //this.marginRight = UNDEFINED; - //this.marginBottom = UNDEFINED; - //this.containerWidth = UNDEFINED; - //this.containerHeight = UNDEFINED; - //this.oldChartWidth = UNDEFINED; - //this.oldChartHeight = UNDEFINED; - - //this.renderTo = UNDEFINED; - //this.renderToClone = UNDEFINED; - - //this.spacingBox = UNDEFINED - - //this.legend = UNDEFINED; - - // Elements - //this.chartBackground = UNDEFINED; - //this.plotBackground = UNDEFINED; - //this.plotBGImage = UNDEFINED; - //this.plotBorder = UNDEFINED; - //this.loadingDiv = UNDEFINED; - //this.loadingSpan = UNDEFINED; - - var chart = this, - eventType; - - // Add the chart to the global lookup - chart.index = charts.length; - charts.push(chart); - chartCount++; - - // Set up auto resize - if (optionsChart.reflow !== false) { - addEvent(chart, 'load', function () { - chart.initReflow(); - }); - } - - // Chart event handlers - if (chartEvents) { - for (eventType in chartEvents) { - addEvent(chart, eventType, chartEvents[eventType]); - } - } - - chart.xAxis = []; - chart.yAxis = []; - - // Expose methods and variables - chart.animation = useCanVG ? false : pick(optionsChart.animation, true); - chart.pointCount = chart.colorCounter = chart.symbolCounter = 0; - - chart.firstRender(); - }, - - /** - * Initialize an individual series, called internally before render time - */ - initSeries: function (options) { - var chart = this, - optionsChart = chart.options.chart, - type = options.type || optionsChart.type || optionsChart.defaultSeriesType, - series, - constr = seriesTypes[type]; - - // No such series type - if (!constr) { - error(17, true); - } - - series = new constr(); - series.init(this, options); - return series; - }, - - /** - * Check whether a given point is within the plot area - * - * @param {Number} plotX Pixel x relative to the plot area - * @param {Number} plotY Pixel y relative to the plot area - * @param {Boolean} inverted Whether the chart is inverted - */ - isInsidePlot: function (plotX, plotY, inverted) { - var x = inverted ? plotY : plotX, - y = inverted ? plotX : plotY; - - return x >= 0 && - x <= this.plotWidth && - y >= 0 && - y <= this.plotHeight; - }, - - /** - * Redraw legend, axes or series based on updated data - * - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - */ - redraw: function (animation) { - var chart = this, - axes = chart.axes, - series = chart.series, - pointer = chart.pointer, - legend = chart.legend, - redrawLegend = chart.isDirtyLegend, - hasStackedSeries, - hasDirtyStacks, - hasCartesianSeries = chart.hasCartesianSeries, - isDirtyBox = chart.isDirtyBox, - seriesLength = series.length, - i = seriesLength, - serie, - renderer = chart.renderer, - isHiddenChart = renderer.isHidden(), - afterRedraw = []; - - setAnimation(animation, chart); - - if (isHiddenChart) { - chart.cloneRenderTo(); - } - - // Adjust title layout (reflow multiline text) - chart.layOutTitles(); - - // link stacked series - while (i--) { - serie = series[i]; - - if (serie.options.stacking) { - hasStackedSeries = true; - - if (serie.isDirty) { - hasDirtyStacks = true; - break; - } - } - } - if (hasDirtyStacks) { // mark others as dirty - i = seriesLength; - while (i--) { - serie = series[i]; - if (serie.options.stacking) { - serie.isDirty = true; - } - } - } - - // Handle updated data in the series - each(series, function (serie) { - if (serie.isDirty) { - if (serie.options.legendType === 'point') { - if (serie.updateTotals) { - serie.updateTotals(); - } - redrawLegend = true; - } - } - if (serie.isDirtyData) { - fireEvent(serie, 'updatedData'); - } - }); - - // handle added or removed series - if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed - // draw legend graphics - legend.render(); - - chart.isDirtyLegend = false; - } - - // reset stacks - if (hasStackedSeries) { - chart.getStacks(); - } - - - if (hasCartesianSeries) { - if (!chart.isResizing) { - - // reset maxTicks - chart.maxTicks = null; - - // set axes scales - each(axes, function (axis) { - axis.setScale(); - }); - } - } - - chart.getMargins(); // #3098 - - if (hasCartesianSeries) { - // If one axis is dirty, all axes must be redrawn (#792, #2169) - each(axes, function (axis) { - if (axis.isDirty) { - isDirtyBox = true; - } - }); - - // redraw axes - each(axes, function (axis) { - - // Fire 'afterSetExtremes' only if extremes are set - var key = axis.min + ',' + axis.max; - if (axis.extKey !== key) { // #821, #4452 - axis.extKey = key; - afterRedraw.push(function () { // prevent a recursive call to chart.redraw() (#1119) - fireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751 - delete axis.eventArgs; - }); - } - if (isDirtyBox || hasStackedSeries) { - axis.redraw(); - } - }); - } - - // the plot areas size has changed - if (isDirtyBox) { - chart.drawChartBox(); - } - - - // redraw affected series - each(series, function (serie) { - if (serie.isDirty && serie.visible && - (!serie.isCartesian || serie.xAxis)) { // issue #153 - serie.redraw(); - } - }); - - // move tooltip or reset - if (pointer) { - pointer.reset(true); - } - - // redraw if canvas - renderer.draw(); - - // fire the event - fireEvent(chart, 'redraw'); - - if (isHiddenChart) { - chart.cloneRenderTo(true); - } - - // Fire callbacks that are put on hold until after the redraw - each(afterRedraw, function (callback) { - callback.call(); - }); - }, - - /** - * Get an axis, series or point object by id. - * @param id {String} The id as given in the configuration options - */ - get: function (id) { - var chart = this, - axes = chart.axes, - series = chart.series; - - var i, - j, - points; - - // search axes - for (i = 0; i < axes.length; i++) { - if (axes[i].options.id === id) { - return axes[i]; - } - } - - // search series - for (i = 0; i < series.length; i++) { - if (series[i].options.id === id) { - return series[i]; - } - } - - // search points - for (i = 0; i < series.length; i++) { - points = series[i].points || []; - for (j = 0; j < points.length; j++) { - if (points[j].id === id) { - return points[j]; - } - } - } - return null; - }, - - /** - * Create the Axis instances based on the config options - */ - getAxes: function () { - var chart = this, - options = this.options, - xAxisOptions = options.xAxis = splat(options.xAxis || {}), - yAxisOptions = options.yAxis = splat(options.yAxis || {}), - optionsArray; - - // make sure the options are arrays and add some members - each(xAxisOptions, function (axis, i) { - axis.index = i; - axis.isX = true; - }); - - each(yAxisOptions, function (axis, i) { - axis.index = i; - }); - - // concatenate all axis options into one array - optionsArray = xAxisOptions.concat(yAxisOptions); - - each(optionsArray, function (axisOptions) { - new Axis(chart, axisOptions); // eslint-disable-line no-new - }); - }, - - - /** - * Get the currently selected points from all series - */ - getSelectedPoints: function () { - var points = []; - each(this.series, function (serie) { - points = points.concat(grep(serie.points || [], function (point) { - return point.selected; - })); - }); - return points; - }, - - /** - * Get the currently selected series - */ - getSelectedSeries: function () { - return grep(this.series, function (serie) { - return serie.selected; - }); - }, - - /** - * Show the title and subtitle of the chart - * - * @param titleOptions {Object} New title options - * @param subtitleOptions {Object} New subtitle options - * - */ - setTitle: function (titleOptions, subtitleOptions, redraw) { - var chart = this, - options = chart.options, - chartTitleOptions, - chartSubtitleOptions; - - chartTitleOptions = options.title = merge(options.title, titleOptions); - chartSubtitleOptions = options.subtitle = merge(options.subtitle, subtitleOptions); - - // add title and subtitle - each([ - ['title', titleOptions, chartTitleOptions], - ['subtitle', subtitleOptions, chartSubtitleOptions] - ], function (arr) { - var name = arr[0], - title = chart[name], - titleOptions = arr[1], - chartTitleOptions = arr[2]; - - if (title && titleOptions) { - chart[name] = title = title.destroy(); // remove old - } - - if (chartTitleOptions && chartTitleOptions.text && !title) { - chart[name] = chart.renderer.text( - chartTitleOptions.text, - 0, - 0, - chartTitleOptions.useHTML - ) - .attr({ - align: chartTitleOptions.align, - 'class': PREFIX + name, - zIndex: chartTitleOptions.zIndex || 4 - }) - .css(chartTitleOptions.style) - .add(); - - } - }); - chart.layOutTitles(redraw); - }, - - /** - * Lay out the chart titles and cache the full offset height for use in getMargins - */ - layOutTitles: function (redraw) { - var titleOffset = 0, - title = this.title, - subtitle = this.subtitle, - options = this.options, - titleOptions = options.title, - subtitleOptions = options.subtitle, - requiresDirtyBox, - renderer = this.renderer, - spacingBox = this.spacingBox; - - if (title) { - title - .css({ width: (titleOptions.width || spacingBox.width + titleOptions.widthAdjust) + PX }) - .align(extend({ - y: renderer.fontMetrics(titleOptions.style.fontSize, title).b - 3 - }, titleOptions), false, spacingBox); - - if (!titleOptions.floating && !titleOptions.verticalAlign) { - titleOffset = title.getBBox().height; - } - } - if (subtitle) { - subtitle - .css({ width: (subtitleOptions.width || spacingBox.width + subtitleOptions.widthAdjust) + PX }) - .align(extend({ - y: titleOffset + (titleOptions.margin - 13) + renderer.fontMetrics(subtitleOptions.style.fontSize, title).b - }, subtitleOptions), false, spacingBox); - - if (!subtitleOptions.floating && !subtitleOptions.verticalAlign) { - titleOffset = mathCeil(titleOffset + subtitle.getBBox().height); - } - } - - requiresDirtyBox = this.titleOffset !== titleOffset; - this.titleOffset = titleOffset; // used in getMargins - - if (!this.isDirtyBox && requiresDirtyBox) { - this.isDirtyBox = requiresDirtyBox; - // Redraw if necessary (#2719, #2744) - if (this.hasRendered && pick(redraw, true) && this.isDirtyBox) { - this.redraw(); - } - } - }, - - /** - * Get chart width and height according to options and container size - */ - getChartSize: function () { - var chart = this, - optionsChart = chart.options.chart, - widthOption = optionsChart.width, - heightOption = optionsChart.height, - renderTo = chart.renderToClone || chart.renderTo; - - // Get inner width and height - if (!defined(widthOption)) { - chart.containerWidth = getStyle(renderTo, 'width'); - } - if (!defined(heightOption)) { - chart.containerHeight = getStyle(renderTo, 'height'); - } - - chart.chartWidth = mathMax(0, widthOption || chart.containerWidth || 600); // #1393, 1460 - chart.chartHeight = mathMax(0, pick(heightOption, - // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7: - chart.containerHeight > 19 ? chart.containerHeight : 400)); - }, - - /** - * Create a clone of the chart's renderTo div and place it outside the viewport to allow - * size computation on chart.render and chart.redraw - */ - cloneRenderTo: function (revert) { - var clone = this.renderToClone, - container = this.container; - - // Destroy the clone and bring the container back to the real renderTo div - if (revert) { - if (clone) { - this.renderTo.appendChild(container); - discardElement(clone); - delete this.renderToClone; - } - - // Set up the clone - } else { - if (container && container.parentNode === this.renderTo) { - this.renderTo.removeChild(container); // do not clone this - } - this.renderToClone = clone = this.renderTo.cloneNode(0); - css(clone, { - position: ABSOLUTE, - top: '-9999px', - display: 'block' // #833 - }); - if (clone.style.setProperty) { // #2631 - clone.style.setProperty('display', 'block', 'important'); - } - doc.body.appendChild(clone); - if (container) { - clone.appendChild(container); - } - } - }, - - /** - * Get the containing element, determine the size and create the inner container - * div to hold the chart - */ - getContainer: function () { - var chart = this, - container, - options = chart.options, - optionsChart = options.chart, - chartWidth, - chartHeight, - renderTo = chart.renderTo, - indexAttrName = 'data-highcharts-chart', - oldChartIndex, - Ren, - containerId = 'highcharts-' + idCounter++; - - if (!renderTo) { - chart.renderTo = renderTo = optionsChart.renderTo; - } - - if (isString(renderTo)) { - chart.renderTo = renderTo = doc.getElementById(renderTo); - } - - // Display an error if the renderTo is wrong - if (!renderTo) { - error(13, true); - } - - // If the container already holds a chart, destroy it. The check for hasRendered is there - // because web pages that are saved to disk from the browser, will preserve the data-highcharts-chart - // attribute and the SVG contents, but not an interactive chart. So in this case, - // charts[oldChartIndex] will point to the wrong chart if any (#2609). - oldChartIndex = pInt(attr(renderTo, indexAttrName)); - if (isNumber(oldChartIndex) && charts[oldChartIndex] && charts[oldChartIndex].hasRendered) { - charts[oldChartIndex].destroy(); - } - - // Make a reference to the chart from the div - attr(renderTo, indexAttrName, chart.index); - - // remove previous chart - renderTo.innerHTML = ''; - - // If the container doesn't have an offsetWidth, it has or is a child of a node - // that has display:none. We need to temporarily move it out to a visible - // state to determine the size, else the legend and tooltips won't render - // properly. The allowClone option is used in sparklines as a micro optimization, - // saving about 1-2 ms each chart. - if (!optionsChart.skipClone && !renderTo.offsetWidth) { - chart.cloneRenderTo(); - } - - // get the width and height - chart.getChartSize(); - chartWidth = chart.chartWidth; - chartHeight = chart.chartHeight; - - // create the inner container - chart.container = container = createElement(DIV, { - className: PREFIX + 'container' + - (optionsChart.className ? ' ' + optionsChart.className : ''), - id: containerId - }, extend({ - position: RELATIVE, - overflow: HIDDEN, // needed for context menu (avoid scrollbars) and - // content overflow in IE - width: chartWidth + PX, - height: chartHeight + PX, - textAlign: 'left', - lineHeight: 'normal', // #427 - zIndex: 0, // #1072 - '-webkit-tap-highlight-color': 'rgba(0,0,0,0)' - }, optionsChart.style), - chart.renderToClone || renderTo - ); - - // cache the cursor (#1650) - chart._cursor = container.style.cursor; - - // Initialize the renderer - Ren = Highcharts[optionsChart.renderer] || Renderer; - chart.renderer = new Ren( - container, - chartWidth, - chartHeight, - optionsChart.style, - optionsChart.forExport, - options.exporting && options.exporting.allowHTML - ); - - if (useCanVG) { - // If we need canvg library, extend and configure the renderer - // to get the tracker for translating mouse events - chart.renderer.create(chart, container, chartWidth, chartHeight); - } - // Add a reference to the charts index - chart.renderer.chartIndex = chart.index; - }, - - /** - * Calculate margins by rendering axis labels in a preliminary position. Title, - * subtitle and legend have already been rendered at this stage, but will be - * moved into their final positions - */ - getMargins: function (skipAxes) { - var chart = this, - spacing = chart.spacing, - margin = chart.margin, - titleOffset = chart.titleOffset; - - chart.resetMargins(); - - // Adjust for title and subtitle - if (titleOffset && !defined(margin[0])) { - chart.plotTop = mathMax(chart.plotTop, titleOffset + chart.options.title.margin + spacing[0]); - } - - // Adjust for legend - chart.legend.adjustMargins(margin, spacing); - - // adjust for scroller - if (chart.extraBottomMargin) { - chart.marginBottom += chart.extraBottomMargin; - } - if (chart.extraTopMargin) { - chart.plotTop += chart.extraTopMargin; - } - if (!skipAxes) { - this.getAxisMargins(); - } - }, - - getAxisMargins: function () { - - var chart = this, - axisOffset = chart.axisOffset = [0, 0, 0, 0], // top, right, bottom, left - margin = chart.margin; - - // pre-render axes to get labels offset width - if (chart.hasCartesianSeries) { - each(chart.axes, function (axis) { - if (axis.visible) { - axis.getOffset(); - } - }); - } - - // Add the axis offsets - each(marginNames, function (m, side) { - if (!defined(margin[side])) { - chart[m] += axisOffset[side]; - } - }); - - chart.setChartSize(); - - }, - - /** - * Resize the chart to its container if size is not explicitly set - */ - reflow: function (e) { - var chart = this, - optionsChart = chart.options.chart, - renderTo = chart.renderTo, - width = optionsChart.width || getStyle(renderTo, 'width'), - height = optionsChart.height || getStyle(renderTo, 'height'), - target = e ? e.target : win; - - // Width and height checks for display:none. Target is doc in IE8 and Opera, - // win in Firefox, Chrome and IE9. - if (!chart.hasUserSize && !chart.isPrinting && width && height && (target === win || target === doc)) { // #1093 - if (width !== chart.containerWidth || height !== chart.containerHeight) { - clearTimeout(chart.reflowTimeout); - // When called from window.resize, e is set, else it's called directly (#2224) - chart.reflowTimeout = syncTimeout(function () { - if (chart.container) { // It may have been destroyed in the meantime (#1257) - chart.setSize(width, height, false); - chart.hasUserSize = null; - } - }, e ? 100 : 0); - } - chart.containerWidth = width; - chart.containerHeight = height; - } - }, - - /** - * Add the event handlers necessary for auto resizing - */ - initReflow: function () { - var chart = this, - reflow = function (e) { - chart.reflow(e); - }; - - - addEvent(win, 'resize', reflow); - addEvent(chart, 'destroy', function () { - removeEvent(win, 'resize', reflow); - }); - }, - - /** - * Resize the chart to a given width and height - * @param {Number} width - * @param {Number} height - * @param {Object|Boolean} animation - */ - setSize: function (width, height, animation) { - var chart = this, - chartWidth, - chartHeight, - renderer = chart.renderer, - globalAnimation; - - // Handle the isResizing counter - chart.isResizing += 1; - - // set the animation for the current process - setAnimation(animation, chart); - - chart.oldChartHeight = chart.chartHeight; - chart.oldChartWidth = chart.chartWidth; - if (defined(width)) { - chart.chartWidth = chartWidth = mathMax(0, mathRound(width)); - chart.hasUserSize = !!chartWidth; - } - if (defined(height)) { - chart.chartHeight = chartHeight = mathMax(0, mathRound(height)); - } - - // Resize the container with the global animation applied if enabled (#2503) - globalAnimation = renderer.globalAnimation; - (globalAnimation ? animate : css)(chart.container, { - width: chartWidth + PX, - height: chartHeight + PX - }, globalAnimation); - - chart.setChartSize(true); - renderer.setSize(chartWidth, chartHeight, animation); - - // handle axes - chart.maxTicks = null; - each(chart.axes, function (axis) { - axis.isDirty = true; - axis.setScale(); - }); - - // make sure non-cartesian series are also handled - each(chart.series, function (serie) { - serie.isDirty = true; - }); - - chart.isDirtyLegend = true; // force legend redraw - chart.isDirtyBox = true; // force redraw of plot and chart border - - chart.layOutTitles(); // #2857 - chart.getMargins(); - - chart.redraw(animation); - - - chart.oldChartHeight = null; - fireEvent(chart, 'resize'); - - // Fire endResize and set isResizing back. If animation is disabled, fire without delay - syncTimeout(function () { - if (chart) { - fireEvent(chart, 'endResize', null, function () { - chart.isResizing -= 1; - }); - } - }, animObject(globalAnimation).duration); - }, - - /** - * Set the public chart properties. This is done before and after the pre-render - * to determine margin sizes - */ - setChartSize: function (skipAxes) { - var chart = this, - inverted = chart.inverted, - renderer = chart.renderer, - chartWidth = chart.chartWidth, - chartHeight = chart.chartHeight, - optionsChart = chart.options.chart, - spacing = chart.spacing, - clipOffset = chart.clipOffset, - clipX, - clipY, - plotLeft, - plotTop, - plotWidth, - plotHeight, - plotBorderWidth; - - chart.plotLeft = plotLeft = mathRound(chart.plotLeft); - chart.plotTop = plotTop = mathRound(chart.plotTop); - chart.plotWidth = plotWidth = mathMax(0, mathRound(chartWidth - plotLeft - chart.marginRight)); - chart.plotHeight = plotHeight = mathMax(0, mathRound(chartHeight - plotTop - chart.marginBottom)); - - chart.plotSizeX = inverted ? plotHeight : plotWidth; - chart.plotSizeY = inverted ? plotWidth : plotHeight; - - chart.plotBorderWidth = optionsChart.plotBorderWidth || 0; - - // Set boxes used for alignment - chart.spacingBox = renderer.spacingBox = { - x: spacing[3], - y: spacing[0], - width: chartWidth - spacing[3] - spacing[1], - height: chartHeight - spacing[0] - spacing[2] - }; - chart.plotBox = renderer.plotBox = { - x: plotLeft, - y: plotTop, - width: plotWidth, - height: plotHeight - }; - - plotBorderWidth = 2 * mathFloor(chart.plotBorderWidth / 2); - clipX = mathCeil(mathMax(plotBorderWidth, clipOffset[3]) / 2); - clipY = mathCeil(mathMax(plotBorderWidth, clipOffset[0]) / 2); - chart.clipBox = { - x: clipX, - y: clipY, - width: mathFloor(chart.plotSizeX - mathMax(plotBorderWidth, clipOffset[1]) / 2 - clipX), - height: mathMax(0, mathFloor(chart.plotSizeY - mathMax(plotBorderWidth, clipOffset[2]) / 2 - clipY)) - }; - - if (!skipAxes) { - each(chart.axes, function (axis) { - axis.setAxisSize(); - axis.setAxisTranslation(); - }); - } - }, - - /** - * Initial margins before auto size margins are applied - */ - resetMargins: function () { - var chart = this; - - each(marginNames, function (m, side) { - chart[m] = pick(chart.margin[side], chart.spacing[side]); - }); - chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left - chart.clipOffset = [0, 0, 0, 0]; - }, - - /** - * Draw the borders and backgrounds for chart and plot area - */ - drawChartBox: function () { - var chart = this, - optionsChart = chart.options.chart, - renderer = chart.renderer, - chartWidth = chart.chartWidth, - chartHeight = chart.chartHeight, - chartBackground = chart.chartBackground, - plotBackground = chart.plotBackground, - plotBorder = chart.plotBorder, - plotBGImage = chart.plotBGImage, - chartBorderWidth = optionsChart.borderWidth || 0, - chartBackgroundColor = optionsChart.backgroundColor, - plotBackgroundColor = optionsChart.plotBackgroundColor, - plotBackgroundImage = optionsChart.plotBackgroundImage, - plotBorderWidth = optionsChart.plotBorderWidth || 0, - mgn, - bgAttr, - plotLeft = chart.plotLeft, - plotTop = chart.plotTop, - plotWidth = chart.plotWidth, - plotHeight = chart.plotHeight, - plotBox = chart.plotBox, - clipRect = chart.clipRect, - clipBox = chart.clipBox; - - // Chart area - mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0); - - if (chartBorderWidth || chartBackgroundColor) { - if (!chartBackground) { - - bgAttr = { - fill: chartBackgroundColor || NONE - }; - if (chartBorderWidth) { // #980 - bgAttr.stroke = optionsChart.borderColor; - bgAttr['stroke-width'] = chartBorderWidth; - } - chart.chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn, - optionsChart.borderRadius, chartBorderWidth) - .attr(bgAttr) - .addClass(PREFIX + 'background') - .add() - .shadow(optionsChart.shadow); - - } else { // resize - chartBackground.animate( - chartBackground.crisp({ width: chartWidth - mgn, height: chartHeight - mgn }) - ); - } - } - - - // Plot background - if (plotBackgroundColor) { - if (!plotBackground) { - chart.plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0) - .attr({ - fill: plotBackgroundColor - }) - .add() - .shadow(optionsChart.plotShadow); - } else { - plotBackground.animate(plotBox); - } - } - if (plotBackgroundImage) { - if (!plotBGImage) { - chart.plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight) - .add(); - } else { - plotBGImage.animate(plotBox); - } - } - - // Plot clip - if (!clipRect) { - chart.clipRect = renderer.clipRect(clipBox); - } else { - clipRect.animate({ - width: clipBox.width, - height: clipBox.height - }); - } - - // Plot area border - if (plotBorderWidth) { - if (!plotBorder) { - chart.plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, -plotBorderWidth) - .attr({ - stroke: optionsChart.plotBorderColor, - 'stroke-width': plotBorderWidth, - fill: NONE, - zIndex: 1 - }) - .add(); - } else { - plotBorder.strokeWidth = -plotBorderWidth; - plotBorder.animate( - plotBorder.crisp({ x: plotLeft, y: plotTop, width: plotWidth, height: plotHeight }) //#3282 plotBorder should be negative - ); - } - } - - // reset - chart.isDirtyBox = false; - }, - - /** - * Detect whether a certain chart property is needed based on inspecting its options - * and series. This mainly applies to the chart.invert property, and in extensions to - * the chart.angular and chart.polar properties. - */ - propFromSeries: function () { - var chart = this, - optionsChart = chart.options.chart, - klass, - seriesOptions = chart.options.series, - i, - value; - - - each(['inverted', 'angular', 'polar'], function (key) { - - // The default series type's class - klass = seriesTypes[optionsChart.type || optionsChart.defaultSeriesType]; - - // Get the value from available chart-wide properties - value = ( - chart[key] || // 1. it is set before - optionsChart[key] || // 2. it is set in the options - (klass && klass.prototype[key]) // 3. it's default series class requires it - ); - - // 4. Check if any the chart's series require it - i = seriesOptions && seriesOptions.length; - while (!value && i--) { - klass = seriesTypes[seriesOptions[i].type]; - if (klass && klass.prototype[key]) { - value = true; - } - } - - // Set the chart property - chart[key] = value; - }); - - }, - - /** - * Link two or more series together. This is done initially from Chart.render, - * and after Chart.addSeries and Series.remove. - */ - linkSeries: function () { - var chart = this, - chartSeries = chart.series; - - // Reset links - each(chartSeries, function (series) { - series.linkedSeries.length = 0; - }); - - // Apply new links - each(chartSeries, function (series) { - var linkedTo = series.options.linkedTo; - if (isString(linkedTo)) { - if (linkedTo === ':previous') { - linkedTo = chart.series[series.index - 1]; - } else { - linkedTo = chart.get(linkedTo); - } - if (linkedTo) { - linkedTo.linkedSeries.push(series); - series.linkedParent = linkedTo; - series.visible = pick(series.options.visible, linkedTo.options.visible, series.visible); // #3879 - } - } - }); - }, - - /** - * Render series for the chart - */ - renderSeries: function () { - each(this.series, function (serie) { - serie.translate(); - serie.render(); - }); - }, - - /** - * Render labels for the chart - */ - renderLabels: function () { - var chart = this, - labels = chart.options.labels; - if (labels.items) { - each(labels.items, function (label) { - var style = extend(labels.style, label.style), - x = pInt(style.left) + chart.plotLeft, - y = pInt(style.top) + chart.plotTop + 12; - - // delete to prevent rewriting in IE - delete style.left; - delete style.top; - - chart.renderer.text( - label.html, - x, - y - ) - .attr({ zIndex: 2 }) - .css(style) - .add(); - - }); - } - }, - - /** - * Render all graphics for the chart - */ - render: function () { - var chart = this, - axes = chart.axes, - renderer = chart.renderer, - options = chart.options, - tempWidth, - tempHeight, - redoHorizontal, - redoVertical; - - // Title - chart.setTitle(); - - - // Legend - chart.legend = new Legend(chart, options.legend); - - // Get stacks - if (chart.getStacks) { - chart.getStacks(); - } - - // Get chart margins - chart.getMargins(true); - chart.setChartSize(); - - // Record preliminary dimensions for later comparison - tempWidth = chart.plotWidth; - tempHeight = chart.plotHeight = chart.plotHeight - 21; // 21 is the most common correction for X axis labels - - // Get margins by pre-rendering axes - each(axes, function (axis) { - axis.setScale(); - }); - chart.getAxisMargins(); - - // If the plot area size has changed significantly, calculate tick positions again - redoHorizontal = tempWidth / chart.plotWidth > 1.1; - redoVertical = tempHeight / chart.plotHeight > 1.05; // Height is more sensitive - - if (redoHorizontal || redoVertical) { - - chart.maxTicks = null; // reset for second pass - each(axes, function (axis) { - if ((axis.horiz && redoHorizontal) || (!axis.horiz && redoVertical)) { - axis.setTickInterval(true); // update to reflect the new margins - } - }); - chart.getMargins(); // second pass to check for new labels - } - - // Draw the borders and backgrounds - chart.drawChartBox(); - - - // Axes - if (chart.hasCartesianSeries) { - each(axes, function (axis) { - if (axis.visible) { - axis.render(); - } - }); - } - - // The series - if (!chart.seriesGroup) { - chart.seriesGroup = renderer.g('series-group') - .attr({ zIndex: 3 }) - .add(); - } - chart.renderSeries(); - - // Labels - chart.renderLabels(); - - // Credits - chart.showCredits(options.credits); - - // Set flag - chart.hasRendered = true; - - }, - - /** - * Show chart credits based on config options - */ - showCredits: function (credits) { - if (credits.enabled && !this.credits) { - this.credits = this.renderer.text( - credits.text, - 0, - 0 - ) - .on('click', function () { - if (credits.href) { - win.location.href = credits.href; - } - }) - .attr({ - align: credits.position.align, - zIndex: 8 - }) - .css(credits.style) - .add() - .align(credits.position); - } - }, - - /** - * Clean up memory usage - */ - destroy: function () { - var chart = this, - axes = chart.axes, - series = chart.series, - container = chart.container, - i, - parentNode = container && container.parentNode; - - // fire the chart.destoy event - fireEvent(chart, 'destroy'); - - // Delete the chart from charts lookup array - charts[chart.index] = UNDEFINED; - chartCount--; - chart.renderTo.removeAttribute('data-highcharts-chart'); - - // remove events - removeEvent(chart); - - // ==== Destroy collections: - // Destroy axes - i = axes.length; - while (i--) { - axes[i] = axes[i].destroy(); - } - - // Destroy each series - i = series.length; - while (i--) { - series[i] = series[i].destroy(); - } - - // ==== Destroy chart properties: - each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage', - 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'pointer', 'scroller', - 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) { - var prop = chart[name]; - - if (prop && prop.destroy) { - chart[name] = prop.destroy(); - } - }); - - // remove container and all SVG - if (container) { // can break in IE when destroyed before finished loading - container.innerHTML = ''; - removeEvent(container); - if (parentNode) { - discardElement(container); - } - - } - - // clean it all up - for (i in chart) { - delete chart[i]; - } - - }, - - - /** - * VML namespaces can't be added until after complete. Listening - * for Perini's doScroll hack is not enough. - */ - isReadyToRender: function () { - var chart = this; - - // Note: win == win.top is required - if ((!hasSVG && (win == win.top && doc.readyState !== 'complete')) || (useCanVG && !win.canvg)) { // eslint-disable-line eqeqeq - if (useCanVG) { - // Delay rendering until canvg library is downloaded and ready - CanVGController.push(function () { - chart.firstRender(); - }, chart.options.global.canvasToolsURL); - } else { - doc.attachEvent('onreadystatechange', function () { - doc.detachEvent('onreadystatechange', chart.firstRender); - if (doc.readyState === 'complete') { - chart.firstRender(); - } - }); - } - return false; - } - return true; - }, - - /** - * Prepare for first rendering after all data are loaded - */ - firstRender: function () { - var chart = this, - options = chart.options; - - // Check whether the chart is ready to render - if (!chart.isReadyToRender()) { - return; - } - - // Create the container - chart.getContainer(); - - // Run an early event after the container and renderer are established - fireEvent(chart, 'init'); - - - chart.resetMargins(); - chart.setChartSize(); - - // Set the common chart properties (mainly invert) from the given series - chart.propFromSeries(); - - // get axes - chart.getAxes(); - - // Initialize the series - each(options.series || [], function (serieOptions) { - chart.initSeries(serieOptions); - }); - - chart.linkSeries(); - - // Run an event after axes and series are initialized, but before render. At this stage, - // the series data is indexed and cached in the xData and yData arrays, so we can access - // those before rendering. Used in Highstock. - fireEvent(chart, 'beforeRender'); - - // depends on inverted and on margins being set - if (Highcharts.Pointer) { - chart.pointer = new Pointer(chart, options); - } - - chart.render(); - - // add canvas - chart.renderer.draw(); - - // Fire the load event if there are no external images - if (!chart.renderer.imgCount && chart.onload) { - chart.onload(); - } - - // If the chart was rendered outside the top container, put it back in (#3679) - chart.cloneRenderTo(true); - - }, - - /** - * On chart load - */ - onload: function () { - var chart = this; - - // Run callbacks - each([this.callback].concat(this.callbacks), function (fn) { - if (fn && chart.index !== undefined) { // Chart destroyed in its own callback (#3600) - fn.apply(chart, [chart]); - } - }); - - fireEvent(chart, 'load'); - - // Don't run again - this.onload = null; - }, - - /** - * Creates arrays for spacing and margin from given options. - */ - splashArray: function (target, options) { - var oVar = options[target], - tArray = isObject(oVar) ? oVar : [oVar, oVar, oVar, oVar]; - - return [pick(options[target + 'Top'], tArray[0]), - pick(options[target + 'Right'], tArray[1]), - pick(options[target + 'Bottom'], tArray[2]), - pick(options[target + 'Left'], tArray[3])]; - } - }; // end Chart - - var CenteredSeriesMixin = Highcharts.CenteredSeriesMixin = { - /** - * Get the center of the pie based on the size and center options relative to the - * plot area. Borrowed by the polar and gauge series types. - */ - getCenter: function () { - - var options = this.options, - chart = this.chart, - slicingRoom = 2 * (options.slicedOffset || 0), - handleSlicingRoom, - plotWidth = chart.plotWidth - 2 * slicingRoom, - plotHeight = chart.plotHeight - 2 * slicingRoom, - centerOption = options.center, - positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0], - smallestSize = mathMin(plotWidth, plotHeight), - i, - value; - - for (i = 0; i < 4; ++i) { - value = positions[i]; - handleSlicingRoom = i < 2 || (i === 2 && /%$/.test(value)); - - // i == 0: centerX, relative to width - // i == 1: centerY, relative to height - // i == 2: size, relative to smallestSize - // i == 3: innerSize, relative to size - positions[i] = relativeLength(value, [plotWidth, plotHeight, smallestSize, positions[2]][i]) + - (handleSlicingRoom ? slicingRoom : 0); - - } - // innerSize cannot be larger than size (#3632) - if (positions[3] > positions[2]) { - positions[3] = positions[2]; - } - return positions; - } - }; - - /** - * The Point object and prototype. Inheritable and used as base for PiePoint - */ - var Point = function () {}; - Point.prototype = { - - /** - * Initialize the point - * @param {Object} series The series object containing this point - * @param {Object} options The data in either number, array or object format - */ - init: function (series, options, x) { - - var point = this, - colors; - point.series = series; - point.color = series.color; // #3445 - point.applyOptions(options, x); - point.pointAttr = {}; - - if (series.options.colorByPoint) { - colors = series.options.colors || series.chart.options.colors; - point.color = point.color || colors[series.colorCounter++]; - // loop back to zero - if (series.colorCounter === colors.length) { - series.colorCounter = 0; - } - } - - series.chart.pointCount++; - return point; - }, - /** - * Apply the options containing the x and y data and possible some extra properties. - * This is called on point init or from point.update. - * - * @param {Object} options - */ - applyOptions: function (options, x) { - var point = this, - series = point.series, - pointValKey = series.options.pointValKey || series.pointValKey; - - options = Point.prototype.optionsToObject.call(this, options); - - // copy options directly to point - extend(point, options); - point.options = point.options ? extend(point.options, options) : options; - - // For higher dimension series types. For instance, for ranges, point.y is mapped to point.low. - if (pointValKey) { - point.y = point[pointValKey]; - } - point.isNull = point.x === null || point.y === null; - - // If no x is set by now, get auto incremented value. All points must have an - // x value, however the y value can be null to create a gap in the series - if (point.x === undefined && series) { - point.x = x === undefined ? series.autoIncrement() : x; - } - - return point; - }, - - /** - * Transform number or array configs into objects - */ - optionsToObject: function (options) { - var ret = {}, - series = this.series, - keys = series.options.keys, - pointArrayMap = keys || series.pointArrayMap || ['y'], - valueCount = pointArrayMap.length, - firstItemType, - i = 0, - j = 0; - - if (isNumber(options) || options === null) { - ret[pointArrayMap[0]] = options; - - } else if (isArray(options)) { - // with leading x value - if (!keys && options.length > valueCount) { - firstItemType = typeof options[0]; - if (firstItemType === 'string') { - ret.name = options[0]; - } else if (firstItemType === 'number') { - ret.x = options[0]; - } - i++; - } - while (j < valueCount) { - if (!keys || options[i] !== undefined) { // Skip undefined positions for keys - ret[pointArrayMap[j]] = options[i]; - } - i++; - j++; - } - } else if (typeof options === 'object') { - ret = options; - - // This is the fastest way to detect if there are individual point dataLabels that need - // to be considered in drawDataLabels. These can only occur in object configs. - if (options.dataLabels) { - series._hasPointLabels = true; - } - - // Same approach as above for markers - if (options.marker) { - series._hasPointMarkers = true; - } - } - return ret; - }, - - /** - * Destroy a point to clear memory. Its reference still stays in series.data. - */ - destroy: function () { - var point = this, - series = point.series, - chart = series.chart, - hoverPoints = chart.hoverPoints, - prop; - - chart.pointCount--; - - if (hoverPoints) { - point.setState(); - erase(hoverPoints, point); - if (!hoverPoints.length) { - chart.hoverPoints = null; - } - - } - if (point === chart.hoverPoint) { - point.onMouseOut(); - } - - // remove all events - if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive - removeEvent(point); - point.destroyElements(); - } - - if (point.legendItem) { // pies have legend items - chart.legend.destroyItem(point); - } - - for (prop in point) { - point[prop] = null; - } - - - }, - - /** - * Destroy SVG elements associated with the point - */ - destroyElements: function () { - var point = this, - props = ['graphic', 'dataLabel', 'dataLabelUpper', 'connector', 'shadowGroup'], - prop, - i = 6; - while (i--) { - prop = props[i]; - if (point[prop]) { - point[prop] = point[prop].destroy(); - } - } - }, - - /** - * Return the configuration hash needed for the data label and tooltip formatters - */ - getLabelConfig: function () { - return { - x: this.category, - y: this.y, - color: this.color, - key: this.name || this.category, - series: this.series, - point: this, - percentage: this.percentage, - total: this.total || this.stackTotal - }; - }, - - /** - * Extendable method for formatting each point's tooltip line - * - * @return {String} A string to be concatenated in to the common tooltip text - */ - tooltipFormatter: function (pointFormat) { - - // Insert options for valueDecimals, valuePrefix, and valueSuffix - var series = this.series, - seriesTooltipOptions = series.tooltipOptions, - valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''), - valuePrefix = seriesTooltipOptions.valuePrefix || '', - valueSuffix = seriesTooltipOptions.valueSuffix || ''; - - // Loop over the point array map and replace unformatted values with sprintf formatting markup - each(series.pointArrayMap || ['y'], function (key) { - key = '{point.' + key; // without the closing bracket - if (valuePrefix || valueSuffix) { - pointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix); - } - pointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}'); - }); - - return format(pointFormat, { - point: this, - series: this.series - }); - }, - - /** - * Fire an event on the Point object. - * @param {String} eventType - * @param {Object} eventArgs Additional event arguments - * @param {Function} defaultFunction Default event handler - */ - firePointEvent: function (eventType, eventArgs, defaultFunction) { - var point = this, - series = this.series, - seriesOptions = series.options; - - // load event handlers on demand to save time on mouseover/out - if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) { - this.importEvents(); - } - - // add default handler if in selection mode - if (eventType === 'click' && seriesOptions.allowPointSelect) { - defaultFunction = function (event) { - // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera - if (point.select) { // Could be destroyed by prior event handlers (#2911) - point.select(null, event.ctrlKey || event.metaKey || event.shiftKey); - } - }; - } - - fireEvent(this, eventType, eventArgs, defaultFunction); - }, - visible: true - };/** - * @classDescription The base function which all other series types inherit from. The data in the series is stored - * in various arrays. - * - * - First, series.options.data contains all the original config options for - * each point whether added by options or methods like series.addPoint. - * - Next, series.data contains those values converted to points, but in case the series data length - * exceeds the cropThreshold, or if the data is grouped, series.data doesn't contain all the points. It - * only contains the points that have been created on demand. - * - Then there's series.points that contains all currently visible point objects. In case of cropping, - * the cropped-away points are not part of this array. The series.points array starts at series.cropStart - * compared to series.data and series.options.data. If however the series data is grouped, these can't - * be correlated one to one. - * - series.xData and series.processedXData contain clean x values, equivalent to series.data and series.points. - * - series.yData and series.processedYData contain clean x values, equivalent to series.data and series.points. - * - * @param {Object} chart - * @param {Object} options - */ - var Series = Highcharts.Series = function () {}; - - Series.prototype = { - - isCartesian: true, - type: 'line', - pointClass: Point, - sorted: true, // requires the data to be sorted - requireSorting: true, - pointAttrToOptions: { // mapping between SVG attributes and the corresponding options - stroke: 'lineColor', - 'stroke-width': 'lineWidth', - fill: 'fillColor', - r: 'radius' - }, - directTouch: false, - axisTypes: ['xAxis', 'yAxis'], - colorCounter: 0, - parallelArrays: ['x', 'y'], // each point's x and y values are stored in this.xData and this.yData - init: function (chart, options) { - var series = this, - eventType, - events, - chartSeries = chart.series, - sortByIndex = function (a, b) { - return pick(a.options.index, a._i) - pick(b.options.index, b._i); - }; - - series.chart = chart; - series.options = options = series.setOptions(options); // merge with plotOptions - series.linkedSeries = []; - - // bind the axes - series.bindAxes(); - - // set some variables - extend(series, { - name: options.name, - state: NORMAL_STATE, - pointAttr: {}, - visible: options.visible !== false, // true by default - selected: options.selected === true // false by default - }); - - // special - if (useCanVG) { - options.animation = false; - } - - // register event listeners - events = options.events; - for (eventType in events) { - addEvent(series, eventType, events[eventType]); - } - if ( - (events && events.click) || - (options.point && options.point.events && options.point.events.click) || - options.allowPointSelect - ) { - chart.runTrackerClick = true; - } - - series.getColor(); - series.getSymbol(); - - // Set the data - each(series.parallelArrays, function (key) { - series[key + 'Data'] = []; - }); - series.setData(options.data, false); - - // Mark cartesian - if (series.isCartesian) { - chart.hasCartesianSeries = true; - } - - // Register it in the chart - chartSeries.push(series); - series._i = chartSeries.length - 1; - - // Sort series according to index option (#248, #1123, #2456) - stableSort(chartSeries, sortByIndex); - if (this.yAxis) { - stableSort(this.yAxis.series, sortByIndex); - } - - each(chartSeries, function (series, i) { - series.index = i; - series.name = series.name || 'Series ' + (i + 1); - }); - - }, - - /** - * Set the xAxis and yAxis properties of cartesian series, and register the series - * in the axis.series array - */ - bindAxes: function () { - var series = this, - seriesOptions = series.options, - chart = series.chart, - axisOptions; - - each(series.axisTypes || [], function (AXIS) { // repeat for xAxis and yAxis - - each(chart[AXIS], function (axis) { // loop through the chart's axis objects - axisOptions = axis.options; - - // apply if the series xAxis or yAxis option mathches the number of the - // axis, or if undefined, use the first axis - if ((seriesOptions[AXIS] === axisOptions.index) || - (seriesOptions[AXIS] !== UNDEFINED && seriesOptions[AXIS] === axisOptions.id) || - (seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) { - - // register this series in the axis.series lookup - axis.series.push(series); - - // set this series.xAxis or series.yAxis reference - series[AXIS] = axis; - - // mark dirty for redraw - axis.isDirty = true; - } - }); - - // The series needs an X and an Y axis - if (!series[AXIS] && series.optionalAxis !== AXIS) { - error(18, true); - } - - }); - }, - - /** - * For simple series types like line and column, the data values are held in arrays like - * xData and yData for quick lookup to find extremes and more. For multidimensional series - * like bubble and map, this can be extended with arrays like zData and valueData by - * adding to the series.parallelArrays array. - */ - updateParallelArrays: function (point, i) { - var series = point.series, - args = arguments, - fn = isNumber(i) ? - // Insert the value in the given position - function (key) { - var val = key === 'y' && series.toYData ? series.toYData(point) : point[key]; - series[key + 'Data'][i] = val; - } : - // Apply the method specified in i with the following arguments as arguments - function (key) { - Array.prototype[i].apply(series[key + 'Data'], Array.prototype.slice.call(args, 2)); - }; - - each(series.parallelArrays, fn); - }, - - /** - * Return an auto incremented x value based on the pointStart and pointInterval options. - * This is only used if an x value is not given for the point that calls autoIncrement. - */ - autoIncrement: function () { - - var options = this.options, - xIncrement = this.xIncrement, - date, - pointInterval, - pointIntervalUnit = options.pointIntervalUnit; - - xIncrement = pick(xIncrement, options.pointStart, 0); - - this.pointInterval = pointInterval = pick(this.pointInterval, options.pointInterval, 1); - - // Added code for pointInterval strings - if (pointIntervalUnit) { - date = new Date(xIncrement); - - if (pointIntervalUnit === 'day') { - date = +date[setDate](date[getDate]() + pointInterval); - } else if (pointIntervalUnit === 'month') { - date = +date[setMonth](date[getMonth]() + pointInterval); - } else if (pointIntervalUnit === 'year') { - date = +date[setFullYear](date[getFullYear]() + pointInterval); - } - pointInterval = date - xIncrement; - } - - this.xIncrement = xIncrement + pointInterval; - return xIncrement; - }, - - /** - * Set the series options by merging from the options tree - * @param {Object} itemOptions - */ - setOptions: function (itemOptions) { - var chart = this.chart, - chartOptions = chart.options, - plotOptions = chartOptions.plotOptions, - userOptions = chart.userOptions || {}, - userPlotOptions = userOptions.plotOptions || {}, - typeOptions = plotOptions[this.type], - options, - zones; - - this.userOptions = itemOptions; - - // General series options take precedence over type options because otherwise, default - // type options like column.animation would be overwritten by the general option. - // But issues have been raised here (#3881), and the solution may be to distinguish - // between default option and userOptions like in the tooltip below. - options = merge( - typeOptions, - plotOptions.series, - itemOptions - ); - - // The tooltip options are merged between global and series specific options - this.tooltipOptions = merge( - defaultOptions.tooltip, - defaultOptions.plotOptions[this.type].tooltip, - userOptions.tooltip, - userPlotOptions.series && userPlotOptions.series.tooltip, - userPlotOptions[this.type] && userPlotOptions[this.type].tooltip, - itemOptions.tooltip - ); - - // Delete marker object if not allowed (#1125) - if (typeOptions.marker === null) { - delete options.marker; - } - - // Handle color zones - this.zoneAxis = options.zoneAxis; - zones = this.zones = (options.zones || []).slice(); - if ((options.negativeColor || options.negativeFillColor) && !options.zones) { - zones.push({ - value: options[this.zoneAxis + 'Threshold'] || options.threshold || 0, - color: options.negativeColor, - fillColor: options.negativeFillColor - }); - } - if (zones.length) { // Push one extra zone for the rest - if (defined(zones[zones.length - 1].value)) { - zones.push({ - color: this.color, - fillColor: this.fillColor - }); - } - } - return options; - }, - - getCyclic: function (prop, value, defaults) { - var i, - userOptions = this.userOptions, - indexName = '_' + prop + 'Index', - counterName = prop + 'Counter'; - - if (!value) { - if (defined(userOptions[indexName])) { // after Series.update() - i = userOptions[indexName]; - } else { - userOptions[indexName] = i = this.chart[counterName] % defaults.length; - this.chart[counterName] += 1; - } - value = defaults[i]; - } - this[prop] = value; - }, - - /** - * Get the series' color - */ - getColor: function () { - if (this.options.colorByPoint) { - this.options.color = null; // #4359, selected slice got series.color even when colorByPoint was set. - } else { - this.getCyclic('color', this.options.color || defaultPlotOptions[this.type].color, this.chart.options.colors); - } - }, - /** - * Get the series' symbol - */ - getSymbol: function () { - var seriesMarkerOption = this.options.marker; - - this.getCyclic('symbol', seriesMarkerOption.symbol, this.chart.options.symbols); - - // don't substract radius in image symbols (#604) - if (/^url/.test(this.symbol)) { - seriesMarkerOption.radius = 0; - } - }, - - drawLegendSymbol: LegendSymbolMixin.drawLineMarker, - - /** - * Replace the series data with a new set of data - * @param {Object} data - * @param {Object} redraw - */ - setData: function (data, redraw, animation, updatePoints) { - var series = this, - oldData = series.points, - oldDataLength = (oldData && oldData.length) || 0, - dataLength, - options = series.options, - chart = series.chart, - firstPoint = null, - xAxis = series.xAxis, - hasCategories = xAxis && !!xAxis.categories, - i, - turboThreshold = options.turboThreshold, - pt, - xData = this.xData, - yData = this.yData, - pointArrayMap = series.pointArrayMap, - valueCount = pointArrayMap && pointArrayMap.length; - - data = data || []; - dataLength = data.length; - redraw = pick(redraw, true); - - // If the point count is the same as is was, just run Point.update which is - // cheaper, allows animation, and keeps references to points. - if (updatePoints !== false && dataLength && oldDataLength === dataLength && !series.cropped && !series.hasGroupedData && series.visible) { - each(data, function (point, i) { - // .update doesn't exist on a linked, hidden series (#3709) - if (oldData[i].update && point !== options.data[i]) { - oldData[i].update(point, false, null, false); - } - }); - - } else { - - // Reset properties - series.xIncrement = null; - - series.colorCounter = 0; // for series with colorByPoint (#1547) - - // Update parallel arrays - each(this.parallelArrays, function (key) { - series[key + 'Data'].length = 0; - }); - - // In turbo mode, only one- or twodimensional arrays of numbers are allowed. The - // first value is tested, and we assume that all the rest are defined the same - // way. Although the 'for' loops are similar, they are repeated inside each - // if-else conditional for max performance. - if (turboThreshold && dataLength > turboThreshold) { - - // find the first non-null point - i = 0; - while (firstPoint === null && i < dataLength) { - firstPoint = data[i]; - i++; - } - - - if (isNumber(firstPoint)) { // assume all points are numbers - var x = pick(options.pointStart, 0), - pointInterval = pick(options.pointInterval, 1); - - for (i = 0; i < dataLength; i++) { - xData[i] = x; - yData[i] = data[i]; - x += pointInterval; - } - series.xIncrement = x; - } else if (isArray(firstPoint)) { // assume all points are arrays - if (valueCount) { // [x, low, high] or [x, o, h, l, c] - for (i = 0; i < dataLength; i++) { - pt = data[i]; - xData[i] = pt[0]; - yData[i] = pt.slice(1, valueCount + 1); - } - } else { // [x, y] - for (i = 0; i < dataLength; i++) { - pt = data[i]; - xData[i] = pt[0]; - yData[i] = pt[1]; - } - } - } else { - error(12); // Highcharts expects configs to be numbers or arrays in turbo mode - } - } else { - for (i = 0; i < dataLength; i++) { - if (data[i] !== UNDEFINED) { // stray commas in oldIE - pt = { series: series }; - series.pointClass.prototype.applyOptions.apply(pt, [data[i]]); - series.updateParallelArrays(pt, i); - if (hasCategories && defined(pt.name)) { // #4401 - xAxis.names[pt.x] = pt.name; // #2046 - } - } - } - } - - // Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON - if (isString(yData[0])) { - error(14, true); - } - - series.data = []; - series.options.data = series.userOptions.data = data; - - // destroy old points - i = oldDataLength; - while (i--) { - if (oldData[i] && oldData[i].destroy) { - oldData[i].destroy(); - } - } - - // reset minRange (#878) - if (xAxis) { - xAxis.minRange = xAxis.userMinRange; - } - - // redraw - series.isDirty = series.isDirtyData = chart.isDirtyBox = true; - animation = false; - } - - // Typically for pie series, points need to be processed and generated - // prior to rendering the legend - if (options.legendType === 'point') { - this.processData(); - this.generatePoints(); - } - - if (redraw) { - chart.redraw(animation); - } - }, - - /** - * Process the data by cropping away unused data points if the series is longer - * than the crop threshold. This saves computing time for lage series. - */ - 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, - getExtremesFromAll = series.getExtremesFromAll || options.getExtremesFromAll, // #4599 - isCartesian = series.isCartesian, - xExtremes, - val2lin = xAxis && xAxis.val2lin, - isLog = xAxis && xAxis.isLog, - min, - max; - - // 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; - } - - if (xAxis) { - xExtremes = xAxis.getExtremes(); // corrected for log axis (#3053) - min = xExtremes.min; - max = xExtremes.max; - } - - // optionally filter out points outside the plot area - if (isCartesian && series.sorted && !getExtremesFromAll && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) { - - // it's outside current extremes - if (processedXData[dataLength - 1] < min || processedXData[0] > max) { - processedXData = []; - processedYData = []; - - // only crop if it's actually spilling out - } else if (processedXData[0] < min || processedXData[dataLength - 1] > max) { - croppedData = this.cropData(series.xData, series.yData, min, max); - processedXData = croppedData.xData; - processedYData = croppedData.yData; - cropStart = croppedData.start; - cropped = true; - } - } - - - // Find the closest distance between processed points - i = processedXData.length || 1; - while (--i) { - distance = isLog ? - val2lin(processedXData[i]) - val2lin(processedXData[i - 1]) : - 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; - - series.closestPointRange = closestPointRange; - - }, - - /** - * Iterate over xData and crop values between min and max. Returns object containing crop start/end - * cropped xData with corresponding part of yData, dataMin and dataMax within the cropped range - */ - cropData: function (xData, yData, min, max) { - var dataLength = xData.length, - cropStart = 0, - cropEnd = dataLength, - cropShoulder = pick(this.cropShoulder, 1), // line-type series need one point outside - i, - j; - - // iterate up to find slice start - for (i = 0; i < dataLength; i++) { - if (xData[i] >= min) { - cropStart = mathMax(0, i - cropShoulder); - break; - } - } - - // proceed to find slice end - for (j = i; j < dataLength; j++) { - if (xData[j] > max) { - cropEnd = j + cropShoulder; - break; - } - } - - return { - xData: xData.slice(cropStart, cropEnd), - yData: yData.slice(cropStart, cropEnd), - start: cropStart, - end: cropEnd - }; - }, - - - /** - * Generate the data point after the data has been processed by cropping away - * unused points and optionally grouped in Highcharts Stock. - */ - generatePoints: function () { - var series = this, - options = series.options, - dataOptions = options.data, - data = series.data, - dataLength, - processedXData = series.processedXData, - processedYData = series.processedYData, - pointClass = series.pointClass, - processedDataLength = processedXData.length, - cropStart = series.cropStart || 0, - cursor, - hasGroupedData = series.hasGroupedData, - point, - points = [], - i; - - if (!data && !hasGroupedData) { - var arr = []; - arr.length = dataOptions.length; - data = series.data = arr; - } - - for (i = 0; i < processedDataLength; i++) { - cursor = cropStart + i; - if (!hasGroupedData) { - if (data[cursor]) { - point = data[cursor]; - } else if (dataOptions[cursor] !== UNDEFINED) { // #970 - data[cursor] = point = (new pointClass()).init(series, dataOptions[cursor], processedXData[i]); - } - points[i] = point; - } else { - // splat the y data in case of ohlc data array - points[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i]))); - points[i].dataGroup = series.groupMap[i]; - } - points[i].index = cursor; // For faster access in Point.update - } - - // Hide cropped-away points - this only runs when the number of points is above cropThreshold, or when - // swithching view from non-grouped data to grouped data (#637) - if (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) { - for (i = 0; i < dataLength; i++) { - if (i === cropStart && !hasGroupedData) { // when has grouped data, clear all points - i += processedDataLength; - } - if (data[i]) { - data[i].destroyElements(); - data[i].plotX = UNDEFINED; // #1003 - } - } - } - - series.data = data; - series.points = points; - }, - - /** - * Calculate Y extremes for visible data - */ - getExtremes: function (yData) { - var xAxis = this.xAxis, - yAxis = this.yAxis, - xData = this.processedXData, - yDataLength, - activeYData = [], - activeCounter = 0, - xExtremes = xAxis.getExtremes(), // #2117, need to compensate for log X axis - xMin = xExtremes.min, - xMax = xExtremes.max, - validValue, - withinRange, - x, - y, - i, - j; - - yData = yData || this.stackedYData || this.processedYData || []; - yDataLength = yData.length; - - for (i = 0; i < yDataLength; i++) { - - x = xData[i]; - y = yData[i]; - - // For points within the visible range, including the first point outside the - // visible range, consider y extremes - validValue = y !== null && y !== UNDEFINED && (!yAxis.isLog || (y.length || y > 0)); - withinRange = this.getExtremesFromAll || this.options.getExtremesFromAll || this.cropped || - ((xData[i + 1] || x) >= xMin && (xData[i - 1] || x) <= xMax); - - if (validValue && withinRange) { - - j = y.length; - if (j) { // array, like ohlc or range data - while (j--) { - if (y[j] !== null) { - activeYData[activeCounter++] = y[j]; - } - } - } else { - activeYData[activeCounter++] = y; - } - } - } - this.dataMin = arrayMin(activeYData); - this.dataMax = arrayMax(activeYData); - }, - - /** - * Translate data points from raw data values to chart specific positioning data - * needed later in drawPoints, drawGraph and drawTracker. - */ - translate: function () { - if (!this.processedXData) { // hidden series - this.processData(); - } - this.generatePoints(); - var series = this, - options = series.options, - stacking = options.stacking, - xAxis = series.xAxis, - categories = xAxis.categories, - yAxis = series.yAxis, - points = series.points, - dataLength = points.length, - hasModifyValue = !!series.modifyValue, - i, - pointPlacement = options.pointPlacement, - dynamicallyPlaced = pointPlacement === 'between' || isNumber(pointPlacement), - threshold = options.threshold, - stackThreshold = options.startFromThreshold ? threshold : 0, - plotX, - plotY, - lastPlotX, - stackIndicator, - closestPointRangePx = Number.MAX_VALUE; - - // Translate each point - for (i = 0; i < dataLength; i++) { - var point = points[i], - xValue = point.x, - yValue = point.y, - yBottom = point.low, - stack = stacking && yAxis.stacks[(series.negStacks && yValue < (stackThreshold ? 0 : threshold) ? '-' : '') + series.stackKey], - pointStack, - stackValues; - - // Discard disallowed y values for log axes (#3434) - if (yAxis.isLog && yValue !== null && yValue <= 0) { - point.y = yValue = null; - error(10); - } - - // Get the plotX translation - point.plotX = plotX = correctFloat( // #5236 - mathMin(mathMax(-1e5, xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement, this.type === 'flags')), 1e5) // #3923 - ); - - // Calculate the bottom y value for stacked series - if (stacking && series.visible && !point.isNull && stack && stack[xValue]) { - stackIndicator = series.getStackIndicator(stackIndicator, xValue, series.index); - pointStack = stack[xValue]; - stackValues = pointStack.points[stackIndicator.key]; - yBottom = stackValues[0]; - yValue = stackValues[1]; - - if (yBottom === stackThreshold) { - yBottom = pick(threshold, yAxis.min); - } - if (yAxis.isLog && yBottom <= 0) { // #1200, #1232 - yBottom = null; - } - - point.total = point.stackTotal = pointStack.total; - point.percentage = pointStack.total && (point.y / pointStack.total * 100); - point.stackY = yValue; - - // Place the stack label - pointStack.setOffset(series.pointXOffset || 0, series.barW || 0); - - } - - // Set translated yBottom or remove it - point.yBottom = defined(yBottom) ? - yAxis.translate(yBottom, 0, 1, 0, 1) : - null; - - // general hook, used for Highstock compare mode - if (hasModifyValue) { - yValue = series.modifyValue(yValue, point); - } - - // Set the the plotY value, reset it for redraws - point.plotY = plotY = (typeof yValue === 'number' && yValue !== Infinity) ? - mathMin(mathMax(-1e5, yAxis.translate(yValue, 0, 1, 0, 1)), 1e5) : // #3201 - UNDEFINED; - point.isInside = plotY !== UNDEFINED && plotY >= 0 && plotY <= yAxis.len && // #3519 - plotX >= 0 && plotX <= xAxis.len; - - - // Set client related positions for mouse tracking - point.clientX = dynamicallyPlaced ? xAxis.translate(xValue, 0, 0, 0, 1) : plotX; // #1514 - - point.negative = point.y < (threshold || 0); - - // some API data - point.category = categories && categories[point.x] !== UNDEFINED ? - categories[point.x] : point.x; - - // Determine auto enabling of markers (#3635, #5099) - if (!point.isNull) { - if (lastPlotX !== undefined) { - closestPointRangePx = mathMin(closestPointRangePx, mathAbs(plotX - lastPlotX)); - } - lastPlotX = plotX; - } - - } - series.closestPointRangePx = closestPointRangePx; - }, - - /** - * Return the series points with null points filtered out - */ - getValidPoints: function (points, insideOnly) { - var chart = this.chart; - return grep(points || this.points || [], function isValidPoint(point) { // #3916, #5029 - if (insideOnly && !chart.isInsidePlot(point.plotX, point.plotY, chart.inverted)) { // #5085 - return false; - } - return !point.isNull; - }); - }, - - /** - * Set the clipping for the series. For animated series it is called twice, first to initiate - * animating the clip then the second time without the animation to set the final clip. - */ - setClip: function (animation) { - var chart = this.chart, - options = this.options, - renderer = chart.renderer, - inverted = chart.inverted, - seriesClipBox = this.clipBox, - clipBox = seriesClipBox || chart.clipBox, - sharedClipKey = this.sharedClipKey || ['_sharedClip', animation && animation.duration, animation && animation.easing, clipBox.height, options.xAxis, options.yAxis].join(','), // #4526 - clipRect = chart[sharedClipKey], - markerClipRect = chart[sharedClipKey + 'm']; - - // If a clipping rectangle with the same properties is currently present in the chart, use that. - if (!clipRect) { - - // When animation is set, prepare the initial positions - if (animation) { - clipBox.width = 0; - - chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect( - -99, // include the width of the first marker - inverted ? -chart.plotLeft : -chart.plotTop, - 99, - inverted ? chart.chartWidth : chart.chartHeight - ); - } - chart[sharedClipKey] = clipRect = renderer.clipRect(clipBox); - - } - if (animation) { - clipRect.count += 1; - } - - if (options.clip !== false) { - this.group.clip(animation || seriesClipBox ? clipRect : chart.clipRect); - this.markerGroup.clip(markerClipRect); - this.sharedClipKey = sharedClipKey; - } - - // Remove the shared clipping rectangle when all series are shown - if (!animation) { - clipRect.count -= 1; - if (clipRect.count <= 0 && sharedClipKey && chart[sharedClipKey]) { - if (!seriesClipBox) { - chart[sharedClipKey] = chart[sharedClipKey].destroy(); - } - if (chart[sharedClipKey + 'm']) { - chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy(); - } - } - } - }, - - /** - * Animate in the series - */ - animate: function (init) { - var series = this, - chart = series.chart, - clipRect, - animation = series.options.animation, - sharedClipKey; - - // Animation option is set to true - if (animation && !isObject(animation)) { - animation = defaultPlotOptions[series.type].animation; - } - - // Initialize the animation. Set up the clipping rectangle. - if (init) { - - series.setClip(animation); - - // Run the animation - } else { - sharedClipKey = this.sharedClipKey; - clipRect = chart[sharedClipKey]; - if (clipRect) { - clipRect.animate({ - width: chart.plotSizeX - }, animation); - } - if (chart[sharedClipKey + 'm']) { - chart[sharedClipKey + 'm'].animate({ - width: chart.plotSizeX + 99 - }, animation); - } - - // Delete this function to allow it only once - series.animate = null; - - } - }, - - /** - * This runs after animation to land on the final plot clipping - */ - afterAnimate: function () { - this.setClip(); - fireEvent(this, 'afterAnimate'); - }, - - /** - * Draw the markers - */ - drawPoints: function () { - var series = this, - pointAttr, - points = series.points, - chart = series.chart, - plotX, - plotY, - i, - point, - radius, - symbol, - isImage, - graphic, - options = series.options, - seriesMarkerOptions = options.marker, - seriesPointAttr = series.pointAttr[''], - pointMarkerOptions, - hasPointMarker, - enabled, - isInside, - markerGroup = series.markerGroup, - xAxis = series.xAxis, - globallyEnabled = pick( - seriesMarkerOptions.enabled, - xAxis.isRadial, - series.closestPointRangePx > 2 * seriesMarkerOptions.radius - ); - - if (seriesMarkerOptions.enabled !== false || series._hasPointMarkers) { - - i = points.length; - while (i--) { - point = points[i]; - plotX = mathFloor(point.plotX); // #1843 - plotY = point.plotY; - graphic = point.graphic; - pointMarkerOptions = point.marker || {}; - hasPointMarker = !!point.marker; - enabled = (globallyEnabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled; - isInside = point.isInside; - - // only draw the point if y is defined - if (enabled && isNumber(plotY) && point.y !== null) { - - // shortcuts - pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE] || seriesPointAttr; - radius = pointAttr.r; - symbol = pick(pointMarkerOptions.symbol, series.symbol); - isImage = symbol.indexOf('url') === 0; - - if (graphic) { // update - graphic[isInside ? 'show' : 'hide'](true) // Since the marker group isn't clipped, each individual marker must be toggled - .attr(pointAttr) // #4759 - .animate(extend({ - x: plotX - radius, - y: plotY - radius - }, graphic.symbolName ? { // don't apply to image symbols #507 - width: 2 * radius, - height: 2 * radius - } : {})); - } else if (isInside && (radius > 0 || isImage)) { - point.graphic = graphic = chart.renderer.symbol( - symbol, - plotX - radius, - plotY - radius, - 2 * radius, - 2 * radius, - hasPointMarker ? pointMarkerOptions : seriesMarkerOptions - ) - .attr(pointAttr) - .add(markerGroup); - } - - } else if (graphic) { - point.graphic = graphic.destroy(); // #1269 - } - } - } - - }, - - /** - * Convert state properties from API naming conventions to SVG attributes - * - * @param {Object} options API options object - * @param {Object} base1 SVG attribute object to inherit from - * @param {Object} base2 Second level SVG attribute object to inherit from - */ - convertAttribs: function (options, base1, base2, base3) { - var conversion = this.pointAttrToOptions, - attr, - option, - obj = {}; - - options = options || {}; - base1 = base1 || {}; - base2 = base2 || {}; - base3 = base3 || {}; - - for (attr in conversion) { - option = conversion[attr]; - obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]); - } - return obj; - }, - - /** - * Get the state attributes. Each series type has its own set of attributes - * that are allowed to change on a point's state change. Series wide attributes are stored for - * all series, and additionally point specific attributes are stored for all - * points with individual marker options. If such options are not defined for the point, - * a reference to the series wide attributes is stored in point.pointAttr. - */ - getAttribs: function () { - var series = this, - seriesOptions = series.options, - normalOptions = defaultPlotOptions[series.type].marker ? seriesOptions.marker : seriesOptions, - stateOptions = normalOptions.states, - stateOptionsHover = stateOptions[HOVER_STATE], - pointStateOptionsHover, - seriesColor = series.color, - seriesNegativeColor = series.options.negativeColor, - normalDefaults = { - stroke: seriesColor, - fill: seriesColor - }, - points = series.points || [], // #927 - i, - j, - threshold, - point, - seriesPointAttr = [], - pointAttr, - pointAttrToOptions = series.pointAttrToOptions, - hasPointSpecificOptions = series.hasPointSpecificOptions, - defaultLineColor = normalOptions.lineColor, - defaultFillColor = normalOptions.fillColor, - turboThreshold = seriesOptions.turboThreshold, - zones = series.zones, - zoneAxis = series.zoneAxis || 'y', - zoneColor, - attr, - key; - - // series type specific modifications - if (seriesOptions.marker) { // line, spline, area, areaspline, scatter - - // if no hover radius is given, default to normal radius + 2 - stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + stateOptionsHover.radiusPlus; - stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + stateOptionsHover.lineWidthPlus; - - } else { // column, bar, pie - - // if no hover color is given, brighten the normal color - stateOptionsHover.color = stateOptionsHover.color || - Color(stateOptionsHover.color || seriesColor) - .brighten(stateOptionsHover.brightness).get(); - - // if no hover negativeColor is given, brighten the normal negativeColor - stateOptionsHover.negativeColor = stateOptionsHover.negativeColor || - Color(stateOptionsHover.negativeColor || seriesNegativeColor) - .brighten(stateOptionsHover.brightness).get(); - } - - // general point attributes for the series normal state - seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults); - - // HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius - each([HOVER_STATE, SELECT_STATE], function (state) { - seriesPointAttr[state] = - series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]); - }); - - // set it - series.pointAttr = seriesPointAttr; - - - // Generate the point-specific attribute collections if specific point - // options are given. If not, create a referance to the series wide point - // attributes - i = points.length; - if (!turboThreshold || i < turboThreshold || hasPointSpecificOptions) { - while (i--) { - point = points[i]; - normalOptions = (point.options && point.options.marker) || point.options; - if (normalOptions && normalOptions.enabled === false) { - normalOptions.radius = 0; - } - - zoneColor = null; - if (zones.length) { - j = 0; - threshold = zones[j]; - while (point[zoneAxis] >= threshold.value) { - threshold = zones[++j]; - } - - point.color = point.fillColor = zoneColor = pick(threshold.color, series.color); // #3636, #4267, #4430 - inherit color from series, when color is undefined - - } - - hasPointSpecificOptions = seriesOptions.colorByPoint || point.color; // #868 - - // check if the point has specific visual options - if (point.options) { - for (key in pointAttrToOptions) { - if (defined(normalOptions[pointAttrToOptions[key]])) { - hasPointSpecificOptions = true; - } - } - } - - // a specific marker config object is defined for the individual point: - // create it's own attribute collection - if (hasPointSpecificOptions) { - normalOptions = normalOptions || {}; - pointAttr = []; - stateOptions = normalOptions.states || {}; // reassign for individual point - pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {}; - - // Handle colors for column and pies - if (!seriesOptions.marker || (point.negative && !pointStateOptionsHover.fillColor && !stateOptionsHover.fillColor)) { // column, bar, point or negative threshold for series with markers (#3636) - // If no hover color is given, brighten the normal color. #1619, #2579 - pointStateOptionsHover[series.pointAttrToOptions.fill] = pointStateOptionsHover.color || (!point.options.color && stateOptionsHover[(point.negative && seriesNegativeColor ? 'negativeColor' : 'color')]) || - Color(point.color) - .brighten(pointStateOptionsHover.brightness || stateOptionsHover.brightness) - .get(); - } - - // normal point state inherits series wide normal state - attr = { color: point.color }; // #868 - if (!defaultFillColor) { // Individual point color or negative color markers (#2219) - attr.fillColor = point.color; - } - if (!defaultLineColor) { - attr.lineColor = point.color; // Bubbles take point color, line markers use white - } - // Color is explicitly set to null or undefined (#1288, #4068) - if (normalOptions.hasOwnProperty('color') && !normalOptions.color) { - delete normalOptions.color; - } - - // When zone is set, but series.states.hover.color is not set, apply zone color on hover, #4670: - if (zoneColor && !stateOptionsHover.fillColor) { - pointStateOptionsHover.fillColor = zoneColor; - } - - pointAttr[NORMAL_STATE] = series.convertAttribs(extend(attr, normalOptions), seriesPointAttr[NORMAL_STATE]); - - // inherit from point normal and series hover - pointAttr[HOVER_STATE] = series.convertAttribs( - stateOptions[HOVER_STATE], - seriesPointAttr[HOVER_STATE], - pointAttr[NORMAL_STATE] - ); - - // inherit from point normal and series hover - pointAttr[SELECT_STATE] = series.convertAttribs( - stateOptions[SELECT_STATE], - seriesPointAttr[SELECT_STATE], - pointAttr[NORMAL_STATE] - ); - - - // no marker config object is created: copy a reference to the series-wide - // attribute collection - } else { - pointAttr = seriesPointAttr; - } - - point.pointAttr = pointAttr; - } - } - }, - - /** - * Clear DOM objects and free up memory - */ - destroy: function () { - var series = this, - chart = series.chart, - issue134 = /AppleWebKit\/533/.test(userAgent), - destroy, - i, - data = series.data || [], - point, - prop, - axis; - - // add event hook - fireEvent(series, 'destroy'); - - // remove all events - removeEvent(series); - - // erase from axes - each(series.axisTypes || [], function (AXIS) { - axis = series[AXIS]; - if (axis) { - erase(axis.series, series); - axis.isDirty = axis.forceRedraw = true; - } - }); - - // remove legend items - if (series.legendItem) { - series.chart.legend.destroyItem(series); - } - - // destroy all points with their elements - i = data.length; - while (i--) { - point = data[i]; - if (point && point.destroy) { - point.destroy(); - } - } - series.points = null; - - // Clear the animation timeout if we are destroying the series during initial animation - clearTimeout(series.animationTimeout); - - // Destroy all SVGElements associated to the series - for (prop in series) { - if (series[prop] instanceof SVGElement && !series[prop].survive) { // Survive provides a hook for not destroying - - // issue 134 workaround - destroy = issue134 && prop === 'group' ? - 'hide' : - 'destroy'; - - series[prop][destroy](); - } - } - - // remove from hoverSeries - if (chart.hoverSeries === series) { - chart.hoverSeries = null; - } - erase(chart.series, series); - - // clear all members - for (prop in series) { - delete series[prop]; - } - }, - - /** - * Get the graph path - */ - getGraphPath: function (points, nullsAsZeroes, connectCliffs) { - var series = this, - options = series.options, - step = options.step, - reversed, - graphPath = [], - gap; - - points = points || series.points; - - // Bottom of a stack is reversed - reversed = points.reversed; - if (reversed) { - points.reverse(); - } - // Reverse the steps (#5004) - step = { right: 1, center: 2 }[step] || (step && 3); - if (step && reversed) { - step = 4 - step; - } - - // Remove invalid points, especially in spline (#5015) - if (options.connectNulls && !nullsAsZeroes && !connectCliffs) { - points = this.getValidPoints(points); - } - - // Build the line - each(points, function (point, i) { - - var plotX = point.plotX, - plotY = point.plotY, - lastPoint = points[i - 1], - pathToPoint; // the path to this point from the previous - - if ((point.leftCliff || (lastPoint && lastPoint.rightCliff)) && !connectCliffs) { - gap = true; // ... and continue - } - - // Line series, nullsAsZeroes is not handled - if (point.isNull && !defined(nullsAsZeroes) && i > 0) { - gap = !options.connectNulls; - - // Area series, nullsAsZeroes is set - } else if (point.isNull && !nullsAsZeroes) { - gap = true; - - } else { - - if (i === 0 || gap) { - pathToPoint = [M, point.plotX, point.plotY]; - - } else if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object - - pathToPoint = series.getPointSpline(points, point, i); - - } else if (step) { - - if (step === 1) { // right - pathToPoint = [ - L, - lastPoint.plotX, - plotY - ]; - - } else if (step === 2) { // center - pathToPoint = [ - L, - (lastPoint.plotX + plotX) / 2, - lastPoint.plotY, - L, - (lastPoint.plotX + plotX) / 2, - plotY - ]; - - } else { - pathToPoint = [ - L, - plotX, - lastPoint.plotY - ]; - } - pathToPoint.push(L, plotX, plotY); - - } else { - // normal line to next point - pathToPoint = [ - L, - plotX, - plotY - ]; - } - - - graphPath.push.apply(graphPath, pathToPoint); - gap = false; - } - }); - - series.graphPath = graphPath; - - return graphPath; - - }, - - /** - * Draw the actual graph - */ - drawGraph: function () { - var series = this, - options = this.options, - props = [['graph', options.lineColor || this.color, options.dashStyle]], - lineWidth = options.lineWidth, - roundCap = options.linecap !== 'square', - graphPath = (this.gappedPath || this.getGraphPath).call(this), - fillColor = (this.fillGraph && this.color) || NONE, // polygon series use filled graph - zones = this.zones; - - each(zones, function (threshold, i) { - props.push(['zoneGraph' + i, threshold.color || series.color, threshold.dashStyle || options.dashStyle]); - }); - - // Draw the graph - each(props, function (prop, i) { - var graphKey = prop[0], - graph = series[graphKey], - attribs; - - if (graph) { - graph.animate({ d: graphPath }); - - } else if ((lineWidth || fillColor) && graphPath.length) { // #1487 - attribs = { - stroke: prop[1], - 'stroke-width': lineWidth, - fill: fillColor, - zIndex: 1 // #1069 - }; - if (prop[2]) { - attribs.dashstyle = prop[2]; - } else if (roundCap) { - attribs['stroke-linecap'] = attribs['stroke-linejoin'] = 'round'; - } - - series[graphKey] = series.chart.renderer.path(graphPath) - .attr(attribs) - .add(series.group) - .shadow((i < 2) && options.shadow); // add shadow to normal series (0) or to first zone (1) #3932 - } - }); - }, - - /** - * Clip the graphs into the positive and negative coloured graphs - */ - applyZones: function () { - var series = this, - chart = this.chart, - renderer = chart.renderer, - zones = this.zones, - translatedFrom, - translatedTo, - clips = this.clips || [], - clipAttr, - graph = this.graph, - area = this.area, - chartSizeMax = mathMax(chart.chartWidth, chart.chartHeight), - axis = this[(this.zoneAxis || 'y') + 'Axis'], - extremes, - reversed = axis.reversed, - inverted = chart.inverted, - horiz = axis.horiz, - pxRange, - pxPosMin, - pxPosMax, - ignoreZones = false; - - if (zones.length && (graph || area) && axis.min !== UNDEFINED) { - // The use of the Color Threshold assumes there are no gaps - // so it is safe to hide the original graph and area - if (graph) { - graph.hide(); - } - if (area) { - area.hide(); - } - - // Create the clips - extremes = axis.getExtremes(); - each(zones, function (threshold, i) { - - translatedFrom = reversed ? - (horiz ? chart.plotWidth : 0) : - (horiz ? 0 : axis.toPixels(extremes.min)); - translatedFrom = mathMin(mathMax(pick(translatedTo, translatedFrom), 0), chartSizeMax); - translatedTo = mathMin(mathMax(mathRound(axis.toPixels(pick(threshold.value, extremes.max), true)), 0), chartSizeMax); - - if (ignoreZones) { - translatedFrom = translatedTo = axis.toPixels(extremes.max); - } - - pxRange = Math.abs(translatedFrom - translatedTo); - pxPosMin = mathMin(translatedFrom, translatedTo); - pxPosMax = mathMax(translatedFrom, translatedTo); - if (axis.isXAxis) { - clipAttr = { - x: inverted ? pxPosMax : pxPosMin, - y: 0, - width: pxRange, - height: chartSizeMax - }; - if (!horiz) { - clipAttr.x = chart.plotHeight - clipAttr.x; - } - } else { - clipAttr = { - x: 0, - y: inverted ? pxPosMax : pxPosMin, - width: chartSizeMax, - height: pxRange - }; - if (horiz) { - clipAttr.y = chart.plotWidth - clipAttr.y; - } - } - - /// VML SUPPPORT - if (chart.inverted && renderer.isVML) { - if (axis.isXAxis) { - clipAttr = { - x: 0, - y: reversed ? pxPosMin : pxPosMax, - height: clipAttr.width, - width: chart.chartWidth - }; - } else { - clipAttr = { - x: clipAttr.y - chart.plotLeft - chart.spacingBox.x, - y: 0, - width: clipAttr.height, - height: chart.chartHeight - }; - } - } - /// END OF VML SUPPORT - - if (clips[i]) { - clips[i].animate(clipAttr); - } else { - clips[i] = renderer.clipRect(clipAttr); - - if (graph) { - series['zoneGraph' + i].clip(clips[i]); - } - - if (area) { - series['zoneArea' + i].clip(clips[i]); - } - } - // if this zone extends out of the axis, ignore the others - ignoreZones = threshold.value > extremes.max; - }); - this.clips = clips; - } - }, - - /** - * Initialize and perform group inversion on series.group and series.markerGroup - */ - invertGroups: function () { - var series = this, - chart = series.chart; - - // Pie, go away (#1736) - if (!series.xAxis) { - return; - } - - // A fixed size is needed for inversion to work - function setInvert() { - var size = { - width: series.yAxis.len, - height: series.xAxis.len - }; - - each(['group', 'markerGroup'], function (groupName) { - if (series[groupName]) { - series[groupName].attr(size).invert(); - } - }); - } - - addEvent(chart, 'resize', setInvert); // do it on resize - addEvent(series, 'destroy', function () { - removeEvent(chart, 'resize', setInvert); - }); - - // Do it now - setInvert(); // do it now - - // On subsequent render and redraw, just do setInvert without setting up events again - series.invertGroups = setInvert; - }, - - /** - * General abstraction for creating plot groups like series.group, series.dataLabelsGroup and - * series.markerGroup. On subsequent calls, the group will only be adjusted to the updated plot size. - */ - plotGroup: function (prop, name, visibility, zIndex, parent) { - var group = this[prop], - isNew = !group; - - // Generate it on first call - if (isNew) { - this[prop] = group = this.chart.renderer.g(name) - .attr({ - zIndex: zIndex || 0.1 // IE8 and pointer logic use this - }) - .add(parent); - - group.addClass('highcharts-series-' + this.index); - } - - // Place it on first and subsequent (redraw) calls - group.attr({ visibility: visibility })[isNew ? 'attr' : 'animate'](this.getPlotBox()); - return group; - }, - - /** - * Get the translation and scale for the plot area of this series - */ - getPlotBox: function () { - var chart = this.chart, - xAxis = this.xAxis, - yAxis = this.yAxis; - - // Swap axes for inverted (#2339) - if (chart.inverted) { - xAxis = yAxis; - yAxis = this.xAxis; - } - return { - translateX: xAxis ? xAxis.left : chart.plotLeft, - translateY: yAxis ? yAxis.top : chart.plotTop, - scaleX: 1, // #1623 - scaleY: 1 - }; - }, - - /** - * Render the graph and markers - */ - render: function () { - var series = this, - chart = series.chart, - group, - options = series.options, - // Animation doesn't work in IE8 quirks when the group div is hidden, - // and looks bad in other oldIE - animDuration = !!series.animate && chart.renderer.isSVG && animObject(options.animation).duration, - visibility = series.visible ? 'inherit' : 'hidden', // #2597 - zIndex = options.zIndex, - hasRendered = series.hasRendered, - chartSeriesGroup = chart.seriesGroup; - - // the group - group = series.plotGroup( - 'group', - 'series', - visibility, - zIndex, - chartSeriesGroup - ); - - series.markerGroup = series.plotGroup( - 'markerGroup', - 'markers', - visibility, - zIndex, - chartSeriesGroup - ); - - // initiate the animation - if (animDuration) { - series.animate(true); - } - - // cache attributes for shapes - series.getAttribs(); - - // SVGRenderer needs to know this before drawing elements (#1089, #1795) - group.inverted = series.isCartesian ? chart.inverted : false; - - // draw the graph if any - if (series.drawGraph) { - series.drawGraph(); - series.applyZones(); - } - - each(series.points, function (point) { - if (point.redraw) { - point.redraw(); - } - }); - - // draw the data labels (inn pies they go before the points) - if (series.drawDataLabels) { - series.drawDataLabels(); - } - - // draw the points - if (series.visible) { - series.drawPoints(); - } - - - // draw the mouse tracking area - if (series.drawTracker && series.options.enableMouseTracking !== false) { - series.drawTracker(); - } - - // Handle inverted series and tracker groups - if (chart.inverted) { - series.invertGroups(); - } - - // Initial clipping, must be defined after inverting groups for VML. Applies to columns etc. (#3839). - if (options.clip !== false && !series.sharedClipKey && !hasRendered) { - group.clip(chart.clipRect); - } - - // Run the animation - if (animDuration) { - series.animate(); - } - - // Call the afterAnimate function on animation complete (but don't overwrite the animation.complete option - // which should be available to the user). - if (!hasRendered) { - series.animationTimeout = syncTimeout(function () { - series.afterAnimate(); - }, animDuration); - } - - series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see - // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see - series.hasRendered = true; - }, - - /** - * Redraw the series after an update in the axes. - */ - redraw: function () { - var series = this, - chart = series.chart, - wasDirty = series.isDirty || series.isDirtyData, // cache it here as it is set to false in render, but used after - group = series.group, - xAxis = series.xAxis, - yAxis = series.yAxis; - - // reposition on resize - if (group) { - if (chart.inverted) { - group.attr({ - width: chart.plotWidth, - height: chart.plotHeight - }); - } - - group.animate({ - translateX: pick(xAxis && xAxis.left, chart.plotLeft), - translateY: pick(yAxis && yAxis.top, chart.plotTop) - }); - } - - series.translate(); - series.render(); - if (wasDirty) { // #3868, #3945 - delete this.kdTree; - } - }, - - /** - * KD Tree && PointSearching Implementation - */ - - kdDimensions: 1, - kdAxisArray: ['clientX', 'plotY'], - - searchPoint: function (e, compareX) { - var series = this, - xAxis = series.xAxis, - yAxis = series.yAxis, - inverted = series.chart.inverted; - - return this.searchKDTree({ - clientX: inverted ? xAxis.len - e.chartY + xAxis.pos : e.chartX - xAxis.pos, - plotY: inverted ? yAxis.len - e.chartX + yAxis.pos : e.chartY - yAxis.pos - }, compareX); - }, - - buildKDTree: function () { - var series = this, - dimensions = series.kdDimensions; - - // Internal function - function _kdtree(points, depth, dimensions) { - var axis, - median, - length = points && points.length; - - if (length) { - - // alternate between the axis - axis = series.kdAxisArray[depth % dimensions]; - - // sort point array - points.sort(function (a, b) { - return a[axis] - b[axis]; - }); - - median = Math.floor(length / 2); - - // build and return nod - return { - point: points[median], - left: _kdtree(points.slice(0, median), depth + 1, dimensions), - right: _kdtree(points.slice(median + 1), depth + 1, dimensions) - }; - - } - } - - // Start the recursive build process with a clone of the points array and null points filtered out (#3873) - function startRecursive() { - series.kdTree = _kdtree( - series.getValidPoints( - null, - !series.directTouch // For line-type series restrict to plot area, but column-type series not (#3916, #4511) - ), - dimensions, - dimensions - ); - } - delete series.kdTree; - - // For testing tooltips, don't build async - syncTimeout(startRecursive, series.options.kdNow ? 0 : 1); - }, - - searchKDTree: function (point, compareX) { - var series = this, - kdX = this.kdAxisArray[0], - kdY = this.kdAxisArray[1], - kdComparer = compareX ? 'distX' : 'dist'; - - // Set the one and two dimensional distance on the point object - function setDistance(p1, p2) { - var x = (defined(p1[kdX]) && defined(p2[kdX])) ? Math.pow(p1[kdX] - p2[kdX], 2) : null, - y = (defined(p1[kdY]) && defined(p2[kdY])) ? Math.pow(p1[kdY] - p2[kdY], 2) : null, - r = (x || 0) + (y || 0); - - p2.dist = defined(r) ? Math.sqrt(r) : Number.MAX_VALUE; - p2.distX = defined(x) ? Math.sqrt(x) : Number.MAX_VALUE; - } - function _search(search, tree, depth, dimensions) { - var point = tree.point, - axis = series.kdAxisArray[depth % dimensions], - tdist, - sideA, - sideB, - ret = point, - nPoint1, - nPoint2; - - setDistance(search, point); - - // Pick side based on distance to splitting point - tdist = search[axis] - point[axis]; - sideA = tdist < 0 ? 'left' : 'right'; - sideB = tdist < 0 ? 'right' : 'left'; - - // End of tree - if (tree[sideA]) { - nPoint1 = _search(search, tree[sideA], depth + 1, dimensions); - - ret = (nPoint1[kdComparer] < ret[kdComparer] ? nPoint1 : point); - } - if (tree[sideB]) { - // compare distance to current best to splitting point to decide wether to check side B or not - if (Math.sqrt(tdist * tdist) < ret[kdComparer]) { - nPoint2 = _search(search, tree[sideB], depth + 1, dimensions); - ret = (nPoint2[kdComparer] < ret[kdComparer] ? nPoint2 : ret); - } - } - - return ret; - } - - if (!this.kdTree) { - this.buildKDTree(); - } - - if (this.kdTree) { - return _search(point, - this.kdTree, this.kdDimensions, this.kdDimensions); - } - } - - }; // end Series prototype - - /** - * The class for stack items - */ - function StackItem(axis, options, isNegative, x, stackOption) { - - var inverted = axis.chart.inverted; - - this.axis = axis; - - // Tells if the stack is negative - this.isNegative = isNegative; - - // Save the options to be able to style the label - this.options = options; - - // Save the x value to be able to position the label later - this.x = x; - - // Initialize total value - this.total = null; - - // This will keep each points' extremes stored by series.index and point index - this.points = {}; - - // Save the stack option on the series configuration object, and whether to treat it as percent - this.stack = stackOption; - this.leftCliff = 0; - this.rightCliff = 0; - - // The align options and text align varies on whether the stack is negative and - // if the chart is inverted or not. - // First test the user supplied value, then use the dynamic. - this.alignOptions = { - align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'), - verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')), - y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)), - x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0) - }; - - this.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center'); - } - - StackItem.prototype = { - destroy: function () { - destroyObjectProperties(this, this.axis); - }, - - /** - * Renders the stack total label and adds it to the stack label group. - */ - render: function (group) { - var options = this.options, - formatOption = options.format, - str = formatOption ? - format(formatOption, this) : - options.formatter.call(this); // format the text in the label - - // Change the text to reflect the new total and set visibility to hidden in case the serie is hidden - if (this.label) { - this.label.attr({ text: str, visibility: 'hidden' }); - // Create new label - } else { - this.label = - this.axis.chart.renderer.text(str, null, null, options.useHTML) // dummy positions, actual position updated with setOffset method in columnseries - .css(options.style) // apply style - .attr({ - align: this.textAlign, // fix the text-anchor - rotation: options.rotation, // rotation - visibility: HIDDEN // hidden until setOffset is called - }) - .add(group); // add to the labels-group - } - }, - - /** - * Sets the offset that the stack has from the x value and repositions the label. - */ - setOffset: function (xOffset, xWidth) { - var stackItem = this, - axis = stackItem.axis, - chart = axis.chart, - inverted = chart.inverted, - reversed = axis.reversed, - neg = (this.isNegative && !reversed) || (!this.isNegative && reversed), // #4056 - y = axis.translate(axis.usePercentage ? 100 : this.total, 0, 0, 0, 1), // stack value translated mapped to chart coordinates - yZero = axis.translate(0), // stack origin - h = mathAbs(y - yZero), // stack height - x = chart.xAxis[0].translate(this.x) + xOffset, // stack x position - plotHeight = chart.plotHeight, - stackBox = { // this is the box for the complete stack - x: inverted ? (neg ? y : y - h) : x, - y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y), - width: inverted ? h : xWidth, - height: inverted ? xWidth : h - }, - label = this.label, - alignAttr; - - if (label) { - label.align(this.alignOptions, null, stackBox); // align the label to the box - - // Set visibility (#678) - alignAttr = label.alignAttr; - label[this.options.crop === false || chart.isInsidePlot(alignAttr.x, alignAttr.y) ? 'show' : 'hide'](true); - } - } - }; - - /** - * Generate stacks for each series and calculate stacks total values - */ - Chart.prototype.getStacks = function () { - var chart = this; - - // reset stacks for each yAxis - each(chart.yAxis, function (axis) { - if (axis.stacks && axis.hasVisibleSeries) { - axis.oldStacks = axis.stacks; - } - }); - - each(chart.series, function (series) { - if (series.options.stacking && (series.visible === true || chart.options.chart.ignoreHiddenSeries === false)) { - series.stackKey = series.type + pick(series.options.stack, ''); - } - }); - }; - - - // Stacking methods defined on the Axis prototype - - /** - * Build the stacks from top down - */ - Axis.prototype.buildStacks = function () { - var axisSeries = this.series, - series, - reversedStacks = pick(this.options.reversedStacks, true), - len = axisSeries.length, - i; - if (!this.isXAxis) { - this.usePercentage = false; - i = len; - while (i--) { - axisSeries[reversedStacks ? i : len - i - 1].setStackedPoints(); - } - - i = len; - while (i--) { - series = axisSeries[reversedStacks ? i : len - i - 1]; - if (series.setStackCliffs) { - series.setStackCliffs(); - } - } - // Loop up again to compute percent stack - if (this.usePercentage) { - for (i = 0; i < len; i++) { - axisSeries[i].setPercentStacks(); - } - } - } - }; - - Axis.prototype.renderStackTotals = function () { - var axis = this, - chart = axis.chart, - renderer = chart.renderer, - stacks = axis.stacks, - stackKey, - oneStack, - stackCategory, - stackTotalGroup = axis.stackTotalGroup; - - // Create a separate group for the stack total labels - if (!stackTotalGroup) { - axis.stackTotalGroup = stackTotalGroup = - renderer.g('stack-labels') - .attr({ - visibility: VISIBLE, - zIndex: 6 - }) - .add(); - } - - // plotLeft/Top will change when y axis gets wider so we need to translate the - // stackTotalGroup at every render call. See bug #506 and #516 - stackTotalGroup.translate(chart.plotLeft, chart.plotTop); - - // Render each stack total - for (stackKey in stacks) { - oneStack = stacks[stackKey]; - for (stackCategory in oneStack) { - oneStack[stackCategory].render(stackTotalGroup); - } - } - }; - - /** - * Set all the stacks to initial states and destroy unused ones. - */ - Axis.prototype.resetStacks = function () { - var stacks = this.stacks, - type, - i; - if (!this.isXAxis) { - for (type in stacks) { - for (i in stacks[type]) { - - // Clean up memory after point deletion (#1044, #4320) - if (stacks[type][i].touched < this.stacksTouched) { - stacks[type][i].destroy(); - delete stacks[type][i]; - - // Reset stacks - } else { - stacks[type][i].total = null; - stacks[type][i].cum = 0; - } - } - } - } - }; - - Axis.prototype.cleanStacks = function () { - var stacks, type, i; - - if (!this.isXAxis) { - if (this.oldStacks) { - stacks = this.stacks = this.oldStacks; - } - - // reset stacks - for (type in stacks) { - for (i in stacks[type]) { - stacks[type][i].cum = stacks[type][i].total; - } - } - } - }; - - - // Stacking methods defnied for Series prototype - - /** - * Adds series' points value to corresponding stack - */ - Series.prototype.setStackedPoints = function () { - if (!this.options.stacking || (this.visible !== true && this.chart.options.chart.ignoreHiddenSeries !== false)) { - return; - } - - var series = this, - xData = series.processedXData, - yData = series.processedYData, - stackedYData = [], - yDataLength = yData.length, - seriesOptions = series.options, - threshold = seriesOptions.threshold, - stackThreshold = seriesOptions.startFromThreshold ? threshold : 0, - stackOption = seriesOptions.stack, - stacking = seriesOptions.stacking, - stackKey = series.stackKey, - negKey = '-' + stackKey, - negStacks = series.negStacks, - yAxis = series.yAxis, - stacks = yAxis.stacks, - oldStacks = yAxis.oldStacks, - stackIndicator, - isNegative, - stack, - other, - key, - pointKey, - i, - x, - y; - - - yAxis.stacksTouched += 1; - - // loop over the non-null y values and read them into a local array - for (i = 0; i < yDataLength; i++) { - x = xData[i]; - y = yData[i]; - stackIndicator = series.getStackIndicator(stackIndicator, x, series.index); - pointKey = stackIndicator.key; - // Read stacked values into a stack based on the x value, - // the sign of y and the stack key. Stacking is also handled for null values (#739) - isNegative = negStacks && y < (stackThreshold ? 0 : threshold); - key = isNegative ? negKey : stackKey; - - // Create empty object for this stack if it doesn't exist yet - if (!stacks[key]) { - stacks[key] = {}; - } - - // Initialize StackItem for this x - if (!stacks[key][x]) { - if (oldStacks[key] && oldStacks[key][x]) { - stacks[key][x] = oldStacks[key][x]; - stacks[key][x].total = null; - } else { - stacks[key][x] = new StackItem(yAxis, yAxis.options.stackLabels, isNegative, x, stackOption); - } - } - - // If the StackItem doesn't exist, create it first - stack = stacks[key][x]; - if (y !== null) { - stack.points[pointKey] = stack.points[series.index] = [pick(stack.cum, stackThreshold)]; - stack.touched = yAxis.stacksTouched; - - - // In area charts, if there are multiple points on the same X value, let the - // area fill the full span of those points - if (stackIndicator.index > 0 && series.singleStacks === false) { - stack.points[pointKey][0] = stack.points[series.index + ',' + x + ',0'][0]; - } - } - - // Add value to the stack total - if (stacking === 'percent') { - - // Percent stacked column, totals are the same for the positive and negative stacks - other = isNegative ? stackKey : negKey; - if (negStacks && stacks[other] && stacks[other][x]) { - other = stacks[other][x]; - stack.total = other.total = mathMax(other.total, stack.total) + mathAbs(y) || 0; - - // Percent stacked areas - } else { - stack.total = correctFloat(stack.total + (mathAbs(y) || 0)); - } - } else { - stack.total = correctFloat(stack.total + (y || 0)); - } - - stack.cum = pick(stack.cum, stackThreshold) + (y || 0); - - if (y !== null) { - stack.points[pointKey].push(stack.cum); - stackedYData[i] = stack.cum; - } - - } - - if (stacking === 'percent') { - yAxis.usePercentage = true; - } - - this.stackedYData = stackedYData; // To be used in getExtremes - - // Reset old stacks - yAxis.oldStacks = {}; - }; - - /** - * Iterate over all stacks and compute the absolute values to percent - */ - Series.prototype.setPercentStacks = function () { - var series = this, - stackKey = series.stackKey, - stacks = series.yAxis.stacks, - processedXData = series.processedXData, - stackIndicator; - - each([stackKey, '-' + stackKey], function (key) { - var i = processedXData.length, - x, - stack, - pointExtremes, - totalFactor; - - while (i--) { - x = processedXData[i]; - stackIndicator = series.getStackIndicator(stackIndicator, x, series.index); - stack = stacks[key] && stacks[key][x]; - pointExtremes = stack && stack.points[stackIndicator.key]; - if (pointExtremes) { - totalFactor = stack.total ? 100 / stack.total : 0; - pointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor); // Y bottom value - pointExtremes[1] = correctFloat(pointExtremes[1] * totalFactor); // Y value - series.stackedYData[i] = pointExtremes[1]; - } - } - }); - }; - - /** - * Get stack indicator, according to it's x-value, to determine points with the same x-value - */ - Series.prototype.getStackIndicator = function (stackIndicator, x, index) { - if (!defined(stackIndicator) || stackIndicator.x !== x) { - stackIndicator = { - x: x, - index: 0 - }; - } else { - stackIndicator.index++; - } - - stackIndicator.key = [index, x, stackIndicator.index].join(','); - - return stackIndicator; - }; - - // Extend the Chart prototype for dynamic methods - extend(Chart.prototype, { - - /** - * Add a series dynamically after time - * - * @param {Object} options The config options - * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true. - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - * - * @return {Object} series The newly created series object - */ - addSeries: function (options, redraw, animation) { - var series, - chart = this; - - if (options) { - redraw = pick(redraw, true); // defaults to true - - fireEvent(chart, 'addSeries', { options: options }, function () { - series = chart.initSeries(options); - - chart.isDirtyLegend = true; // the series array is out of sync with the display - chart.linkSeries(); - if (redraw) { - chart.redraw(animation); - } - }); - } - - return series; - }, - - /** - * Add an axis to the chart - * @param {Object} options The axis option - * @param {Boolean} isX Whether it is an X axis or a value axis - */ - addAxis: function (options, isX, redraw, animation) { - var key = isX ? 'xAxis' : 'yAxis', - chartOptions = this.options, - userOptions = merge(options, { - index: this[key].length, - isX: isX - }); - - new Axis(this, userOptions); // eslint-disable-line no-new - - // Push the new axis options to the chart options - chartOptions[key] = splat(chartOptions[key] || {}); - chartOptions[key].push(userOptions); - - if (pick(redraw, true)) { - this.redraw(animation); - } - }, - - /** - * Dim the chart and show a loading text or symbol - * @param {String} str An optional text to show in the loading label instead of the default one - */ - showLoading: function (str) { - var chart = this, - options = chart.options, - loadingDiv = chart.loadingDiv, - loadingOptions = options.loading, - setLoadingSize = function () { - if (loadingDiv) { - css(loadingDiv, { - left: chart.plotLeft + PX, - top: chart.plotTop + PX, - width: chart.plotWidth + PX, - height: chart.plotHeight + PX - }); - } - }; - - // create the layer at the first call - if (!loadingDiv) { - chart.loadingDiv = loadingDiv = createElement(DIV, { - className: PREFIX + 'loading' - }, extend(loadingOptions.style, { - zIndex: 10, - display: NONE - }), chart.container); - - chart.loadingSpan = createElement( - 'span', - null, - loadingOptions.labelStyle, - loadingDiv - ); - addEvent(chart, 'redraw', setLoadingSize); // #1080 - } - - // update text - chart.loadingSpan.innerHTML = str || options.lang.loading; - - // show it - if (!chart.loadingShown) { - css(loadingDiv, { - opacity: 0, - display: '' - }); - animate(loadingDiv, { - opacity: loadingOptions.style.opacity - }, { - duration: loadingOptions.showDuration || 0 - }); - chart.loadingShown = true; - } - setLoadingSize(); - }, - - /** - * Hide the loading layer - */ - hideLoading: function () { - var options = this.options, - loadingDiv = this.loadingDiv; - - if (loadingDiv) { - animate(loadingDiv, { - opacity: 0 - }, { - duration: options.loading.hideDuration || 100, - complete: function () { - css(loadingDiv, { display: NONE }); - } - }); - } - this.loadingShown = false; - } - }); - - // extend the Point prototype for dynamic methods - extend(Point.prototype, { - /** - * Update the point with new options (typically x/y data) and optionally redraw the series. - * - * @param {Object} options Point options as defined in the series.data array - * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - * - */ - update: function (options, redraw, animation, runEvent) { - var point = this, - series = point.series, - graphic = point.graphic, - i, - chart = series.chart, - seriesOptions = series.options, - names = series.xAxis && series.xAxis.names; - - redraw = pick(redraw, true); - - function update() { - - point.applyOptions(options); - - // Update visuals - if (point.y === null && graphic) { // #4146 - point.graphic = graphic.destroy(); - } - if (isObject(options) && !isArray(options)) { - // Defer the actual redraw until getAttribs has been called (#3260) - point.redraw = function () { - if (graphic && graphic.element) { - if (options && options.marker && options.marker.symbol) { - point.graphic = graphic.destroy(); - } - } - if (options && options.dataLabels && point.dataLabel) { // #2468 - point.dataLabel = point.dataLabel.destroy(); - } - point.redraw = null; - }; - } - - // record changes in the parallel arrays - i = point.index; - series.updateParallelArrays(point, i); - if (names && point.name) { - names[point.x] = point.name; - } - - // Record the options to options.data. If there is an object from before, - // use point options, otherwise use raw options. (#4701) - seriesOptions.data[i] = (isObject(seriesOptions.data[i]) && !isArray(seriesOptions.data[i])) ? point.options : options; - - // redraw - series.isDirty = series.isDirtyData = true; - if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320 - chart.isDirtyBox = true; - } - - if (seriesOptions.legendType === 'point') { // #1831, #1885 - chart.isDirtyLegend = true; - } - if (redraw) { - chart.redraw(animation); - } - } - - // Fire the event with a default handler of doing the update - if (runEvent === false) { // When called from setData - update(); - } else { - point.firePointEvent('update', { options: options }, update); - } - }, - - /** - * Remove a point and optionally redraw the series and if necessary the axes - * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - */ - remove: function (redraw, animation) { - this.series.removePoint(inArray(this, this.series.data), redraw, animation); - } - }); - - // Extend the series prototype for dynamic methods - extend(Series.prototype, { - /** - * Add a point dynamically after chart load time - * @param {Object} options Point options as given in series.data - * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call - * @param {Boolean} shift If shift is true, a point is shifted off the start - * of the series as one is appended to the end. - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - */ - addPoint: function (options, redraw, shift, animation) { - var series = this, - seriesOptions = series.options, - data = series.data, - graph = series.graph, - area = series.area, - chart = series.chart, - names = series.xAxis && series.xAxis.names, - currentShift = (graph && graph.shift) || 0, - shiftShapes = ['graph', 'area'], - dataOptions = seriesOptions.data, - point, - isInTheMiddle, - xData = series.xData, - i, - x; - - setAnimation(animation, chart); - - // Make graph animate sideways - if (shift) { - i = series.zones.length; - while (i--) { - shiftShapes.push('zoneGraph' + i, 'zoneArea' + i); - } - each(shiftShapes, function (shape) { - if (series[shape]) { - series[shape].shift = currentShift + (seriesOptions.step ? 2 : 1); - } - }); - } - if (area) { - area.isArea = true; // needed in animation, both with and without shift - } - - // Optional redraw, defaults to true - redraw = pick(redraw, true); - - // Get options and push the point to xData, yData and series.options. In series.generatePoints - // the Point instance will be created on demand and pushed to the series.data array. - point = { series: series }; - series.pointClass.prototype.applyOptions.apply(point, [options]); - x = point.x; - - // Get the insertion point - i = xData.length; - if (series.requireSorting && x < xData[i - 1]) { - isInTheMiddle = true; - while (i && xData[i - 1] > x) { - i--; - } - } - - series.updateParallelArrays(point, 'splice', i, 0, 0); // insert undefined item - series.updateParallelArrays(point, i); // update it - - if (names && point.name) { - names[x] = point.name; - } - dataOptions.splice(i, 0, options); - - if (isInTheMiddle) { - series.data.splice(i, 0, null); - series.processData(); - } - - // Generate points to be added to the legend (#1329) - if (seriesOptions.legendType === 'point') { - series.generatePoints(); - } - - // Shift the first point off the parallel arrays - if (shift) { - if (data[0] && data[0].remove) { - data[0].remove(false); - } else { - data.shift(); - series.updateParallelArrays(point, 'shift'); - - dataOptions.shift(); - } - } - - // redraw - series.isDirty = true; - series.isDirtyData = true; - if (redraw) { - series.getAttribs(); // #1937 - chart.redraw(); - } - }, - - /** - * Remove a point (rendered or not), by index - */ - removePoint: function (i, redraw, animation) { - - var series = this, - data = series.data, - point = data[i], - points = series.points, - chart = series.chart, - remove = function () { - - if (points && points.length === data.length) { // #4935 - points.splice(i, 1); - } - data.splice(i, 1); - series.options.data.splice(i, 1); - series.updateParallelArrays(point || { series: series }, 'splice', i, 1); - - if (point) { - point.destroy(); - } - - // redraw - series.isDirty = true; - series.isDirtyData = true; - if (redraw) { - chart.redraw(); - } - }; - - setAnimation(animation, chart); - redraw = pick(redraw, true); - - // Fire the event with a default handler of removing the point - if (point) { - point.firePointEvent('remove', null, remove); - } else { - remove(); - } - }, - - /** - * Remove a series and optionally redraw the chart - * - * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - */ - remove: function (redraw, animation) { - var series = this, - chart = series.chart; - - // Fire the event with a default handler of removing the point - fireEvent(series, 'remove', null, function () { - - // Destroy elements - series.destroy(); - - // Redraw - chart.isDirtyLegend = chart.isDirtyBox = true; - chart.linkSeries(); - - if (pick(redraw, true)) { - chart.redraw(animation); - } - }); - }, - - /** - * Update the series with a new set of options - */ - update: function (newOptions, redraw) { - var series = this, - chart = this.chart, - // must use user options when changing type because this.options is merged - // in with type specific plotOptions - oldOptions = this.userOptions, - oldType = this.type, - proto = seriesTypes[oldType].prototype, - preserve = ['group', 'markerGroup', 'dataLabelsGroup'], - n; - - // If we're changing type or zIndex, create new groups (#3380, #3404) - if ((newOptions.type && newOptions.type !== oldType) || newOptions.zIndex !== undefined) { - preserve.length = 0; - } - - // Make sure groups are not destroyed (#3094) - each(preserve, function (prop) { - preserve[prop] = series[prop]; - delete series[prop]; - }); - - // Do the merge, with some forced options - newOptions = merge(oldOptions, { - animation: false, - index: this.index, - pointStart: this.xData[0] // when updating after addPoint - }, { data: this.options.data }, newOptions); - - // Destroy the series and delete all properties. Reinsert all methods - // and properties from the new type prototype (#2270, #3719) - this.remove(false); - for (n in proto) { - this[n] = UNDEFINED; - } - extend(this, seriesTypes[newOptions.type || oldType].prototype); - - // Re-register groups (#3094) - each(preserve, function (prop) { - series[prop] = preserve[prop]; - }); - - this.init(chart, newOptions); - chart.linkSeries(); // Links are lost in this.remove (#3028) - if (pick(redraw, true)) { - chart.redraw(false); - } - } - }); - - // Extend the Axis.prototype for dynamic methods - extend(Axis.prototype, { - - /** - * Update the axis with a new options structure - */ - update: function (newOptions, redraw) { - var chart = this.chart; - - newOptions = chart.options[this.coll][this.options.index] = merge(this.userOptions, newOptions); - - this.destroy(true); - this._addedPlotLB = this.chart._labelPanes = UNDEFINED; // #1611, #2887, #4314 - - this.init(chart, extend(newOptions, { events: UNDEFINED })); - - chart.isDirtyBox = true; - if (pick(redraw, true)) { - chart.redraw(); - } - }, - - /** - * Remove the axis from the chart - */ - remove: function (redraw) { - var chart = this.chart, - key = this.coll, // xAxis or yAxis - axisSeries = this.series, - i = axisSeries.length; - - // Remove associated series (#2687) - while (i--) { - if (axisSeries[i]) { - axisSeries[i].remove(false); - } - } - - // Remove the axis - erase(chart.axes, this); - erase(chart[key], this); - chart.options[key].splice(this.options.index, 1); - each(chart[key], function (axis, i) { // Re-index, #1706 - axis.options.index = i; - }); - this.destroy(); - chart.isDirtyBox = true; - - if (pick(redraw, true)) { - chart.redraw(); - } - }, - - /** - * Update the axis title by options - */ - setTitle: function (newTitleOptions, redraw) { - this.update({ title: newTitleOptions }, redraw); - }, - - /** - * Set new axis categories and optionally redraw - * @param {Array} categories - * @param {Boolean} redraw - */ - setCategories: function (categories, redraw) { - this.update({ categories: categories }, redraw); - } - - }); - - - /** - * LineSeries object - */ - var LineSeries = extendClass(Series); - seriesTypes.line = LineSeries; - - /** - * Set the default options for area - */ - defaultPlotOptions.area = merge(defaultSeriesOptions, { - softThreshold: false, - threshold: 0 - // trackByArea: false, - // lineColor: null, // overrides color, but lets fillColor be unaltered - // fillOpacity: 0.75, - // fillColor: null - }); - - /** - * AreaSeries object - */ - var AreaSeries = extendClass(Series, { - type: 'area', - singleStacks: false, - /** - * Return an array of stacked points, where null and missing points are replaced by - * dummy points in order for gaps to be drawn correctly in stacks. - */ - getStackPoints: function () { - var series = this, - segment = [], - keys = [], - xAxis = this.xAxis, - yAxis = this.yAxis, - stack = yAxis.stacks[this.stackKey], - pointMap = {}, - points = this.points, - seriesIndex = series.index, - yAxisSeries = yAxis.series, - seriesLength = yAxisSeries.length, - visibleSeries, - upOrDown = pick(yAxis.options.reversedStacks, true) ? 1 : -1, - i, - x; - - if (this.options.stacking) { - // Create a map where we can quickly look up the points by their X value. - for (i = 0; i < points.length; i++) { - pointMap[points[i].x] = points[i]; - } - - // Sort the keys (#1651) - for (x in stack) { - if (stack[x].total !== null) { // nulled after switching between grouping and not (#1651, #2336) - keys.push(x); - } - } - keys.sort(function (a, b) { - return a - b; - }); - - visibleSeries = map(yAxisSeries, function () { - return this.visible; - }); - - each(keys, function (x, idx) { - var y = 0, - stackPoint, - stackedValues; - - if (pointMap[x] && !pointMap[x].isNull) { - segment.push(pointMap[x]); - - // Find left and right cliff. -1 goes left, 1 goes right. - each([-1, 1], function (direction) { - var nullName = direction === 1 ? 'rightNull' : 'leftNull', - cliffName = direction === 1 ? 'rightCliff' : 'leftCliff', - cliff = 0, - otherStack = stack[keys[idx + direction]]; - - // If there is a stack next to this one, to the left or to the right... - if (otherStack) { - i = seriesIndex; - while (i >= 0 && i < seriesLength) { // Can go either up or down, depending on reversedStacks - stackPoint = otherStack.points[i]; - if (!stackPoint) { - // If the next point in this series is missing, mark the point - // with point.leftNull or point.rightNull = true. - if (i === seriesIndex) { - pointMap[x][nullName] = true; - - // If there are missing points in the next stack in any of the - // series below this one, we need to substract the missing values - // and add a hiatus to the left or right. - } else if (visibleSeries[i]) { - stackedValues = stack[x].points[i]; - if (stackedValues) { - cliff -= stackedValues[1] - stackedValues[0]; - } - } - } - // When reversedStacks is true, loop up, else loop down - i += upOrDown; - } - } - pointMap[x][cliffName] = cliff; - }); - - - // There is no point for this X value in this series, so we - // insert a dummy point in order for the areas to be drawn - // correctly. - } else { - - // Loop down the stack to find the series below this one that has - // a value (#1991) - i = seriesIndex; - while (i >= 0 && i < seriesLength) { - stackPoint = stack[x].points[i]; - if (stackPoint) { - y = stackPoint[1]; - break; - } - // When reversedStacks is true, loop up, else loop down - i += upOrDown; - } - - y = yAxis.toPixels(y, true); - segment.push({ - isNull: true, - plotX: xAxis.toPixels(x, true), - plotY: y, - yBottom: y - }); - } - }); - - } - - return segment; - }, - - getGraphPath: function (points) { - var getGraphPath = Series.prototype.getGraphPath, - graphPath, - options = this.options, - stacking = options.stacking, - yAxis = this.yAxis, - topPath, - //topPoints = [], - bottomPath, - bottomPoints = [], - graphPoints = [], - seriesIndex = this.index, - i, - areaPath, - plotX, - stacks = yAxis.stacks[this.stackKey], - threshold = options.threshold, - translatedThreshold = yAxis.getThreshold(options.threshold), - isNull, - yBottom, - connectNulls = options.connectNulls || stacking === 'percent', - /** - * To display null points in underlying stacked series, this series graph must be - * broken, and the area also fall down to fill the gap left by the null point. #2069 - */ - addDummyPoints = function (i, otherI, side) { - var point = points[i], - stackedValues = stacking && stacks[point.x].points[seriesIndex], - nullVal = point[side + 'Null'] || 0, - cliffVal = point[side + 'Cliff'] || 0, - top, - bottom, - isNull = true; - - if (cliffVal || nullVal) { - - top = (nullVal ? stackedValues[0] : stackedValues[1]) + cliffVal; - bottom = stackedValues[0] + cliffVal; - isNull = !!nullVal; - - } else if (!stacking && points[otherI] && points[otherI].isNull) { - top = bottom = threshold; - } - - // Add to the top and bottom line of the area - if (top !== undefined) { - graphPoints.push({ - plotX: plotX, - plotY: top === null ? translatedThreshold : yAxis.getThreshold(top), - isNull: isNull - }); - bottomPoints.push({ - plotX: plotX, - plotY: bottom === null ? translatedThreshold : yAxis.getThreshold(bottom) - }); - } - }; - - // Find what points to use - points = points || this.points; - - - // Fill in missing points - if (stacking) { - points = this.getStackPoints(); - } - - for (i = 0; i < points.length; i++) { - isNull = points[i].isNull; - plotX = pick(points[i].rectPlotX, points[i].plotX); - yBottom = pick(points[i].yBottom, translatedThreshold); - - if (!isNull || connectNulls) { - - if (!connectNulls) { - addDummyPoints(i, i - 1, 'left'); - } - - if (!(isNull && !stacking && connectNulls)) { // Skip null point when stacking is false and connectNulls true - graphPoints.push(points[i]); - bottomPoints.push({ - x: i, - plotX: plotX, - plotY: yBottom - }); - } - - if (!connectNulls) { - addDummyPoints(i, i + 1, 'right'); - } - } - } - - topPath = getGraphPath.call(this, graphPoints, true, true); - - bottomPoints.reversed = true; - bottomPath = getGraphPath.call(this, bottomPoints, true, true); - if (bottomPath.length) { - bottomPath[0] = L; - } - - areaPath = topPath.concat(bottomPath); - graphPath = getGraphPath.call(this, graphPoints, false, connectNulls); // TODO: don't set leftCliff and rightCliff when connectNulls? - - this.areaPath = areaPath; - return graphPath; - }, - - /** - * Draw the graph and the underlying area. This method calls the Series base - * function and adds the area. The areaPath is calculated in the getSegmentPath - * method called from Series.prototype.drawGraph. - */ - drawGraph: function () { - - // Define or reset areaPath - this.areaPath = []; - - // Call the base method - Series.prototype.drawGraph.apply(this); - - // Define local variables - var series = this, - areaPath = this.areaPath, - options = this.options, - zones = this.zones, - props = [['area', this.color, options.fillColor]]; // area name, main color, fill color - - each(zones, function (threshold, i) { - props.push(['zoneArea' + i, threshold.color || series.color, threshold.fillColor || options.fillColor]); - }); - each(props, function (prop) { - var areaKey = prop[0], - area = series[areaKey], - attr; - - // Create or update the area - if (area) { // update - area.animate({ d: areaPath }); - - } else { // create - attr = { - fill: prop[2] || prop[1], - zIndex: 0 // #1069 - }; - if (!prop[2]) { - attr['fill-opacity'] = pick(options.fillOpacity, 0.75); - } - series[areaKey] = series.chart.renderer.path(areaPath) - .attr(attr) - .add(series.group); - } - }); - }, - - drawLegendSymbol: LegendSymbolMixin.drawRectangle - }); - - seriesTypes.area = AreaSeries; - /** - * Set the default options for spline - */ - defaultPlotOptions.spline = merge(defaultSeriesOptions); - - /** - * SplineSeries object - */ - var SplineSeries = extendClass(Series, { - type: 'spline', - - /** - * Get the spline segment from a given point's previous neighbour to the given point - */ - getPointSpline: function (points, point, i) { - var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc - denom = smoothing + 1, - plotX = point.plotX, - plotY = point.plotY, - lastPoint = points[i - 1], - nextPoint = points[i + 1], - leftContX, - leftContY, - rightContX, - rightContY, - ret; - - // Find control points - if (lastPoint && !lastPoint.isNull && nextPoint && !nextPoint.isNull) { - var lastX = lastPoint.plotX, - lastY = lastPoint.plotY, - nextX = nextPoint.plotX, - nextY = nextPoint.plotY, - correction = 0; - - leftContX = (smoothing * plotX + lastX) / denom; - leftContY = (smoothing * plotY + lastY) / denom; - rightContX = (smoothing * plotX + nextX) / denom; - rightContY = (smoothing * plotY + nextY) / denom; - - // Have the two control points make a straight line through main point - if (rightContX !== leftContX) { // #5016, division by zero - correction = ((rightContY - leftContY) * (rightContX - plotX)) / - (rightContX - leftContX) + plotY - rightContY; - } - - leftContY += correction; - rightContY += correction; - - // to prevent false extremes, check that control points are between - // neighbouring points' y values - if (leftContY > lastY && leftContY > plotY) { - leftContY = mathMax(lastY, plotY); - rightContY = 2 * plotY - leftContY; // mirror of left control point - } else if (leftContY < lastY && leftContY < plotY) { - leftContY = mathMin(lastY, plotY); - rightContY = 2 * plotY - leftContY; - } - if (rightContY > nextY && rightContY > plotY) { - rightContY = mathMax(nextY, plotY); - leftContY = 2 * plotY - rightContY; - } else if (rightContY < nextY && rightContY < plotY) { - rightContY = mathMin(nextY, plotY); - leftContY = 2 * plotY - rightContY; - } - - // record for drawing in next point - point.rightContX = rightContX; - point.rightContY = rightContY; - - - } - - // Visualize control points for debugging - /* - if (leftContX) { - this.chart.renderer.circle(leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 2) - .attr({ - stroke: 'red', - 'stroke-width': 1, - fill: 'none' - }) - .add(); - this.chart.renderer.path(['M', leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, - 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop]) - .attr({ - stroke: 'red', - 'stroke-width': 1 - }) - .add(); - this.chart.renderer.circle(rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 2) - .attr({ - stroke: 'green', - 'stroke-width': 1, - fill: 'none' - }) - .add(); - this.chart.renderer.path(['M', rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, - 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop]) - .attr({ - stroke: 'green', - 'stroke-width': 1 - }) - .add(); - } - // */ - ret = [ - 'C', - pick(lastPoint.rightContX, lastPoint.plotX), - pick(lastPoint.rightContY, lastPoint.plotY), - pick(leftContX, plotX), - pick(leftContY, plotY), - plotX, - plotY - ]; - lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later - return ret; - } - }); - seriesTypes.spline = SplineSeries; - - /** - * Set the default options for areaspline - */ - defaultPlotOptions.areaspline = merge(defaultPlotOptions.area); - - /** - * AreaSplineSeries object - */ - var areaProto = AreaSeries.prototype, - AreaSplineSeries = extendClass(SplineSeries, { - type: 'areaspline', - getStackPoints: areaProto.getStackPoints, - getGraphPath: areaProto.getGraphPath, - setStackCliffs: areaProto.setStackCliffs, - drawGraph: areaProto.drawGraph, - drawLegendSymbol: LegendSymbolMixin.drawRectangle - }); - - seriesTypes.areaspline = AreaSplineSeries; - - /** - * Set the default options for column - */ - defaultPlotOptions.column = merge(defaultSeriesOptions, { - borderColor: '#FFFFFF', - //borderWidth: 1, - borderRadius: 0, - //colorByPoint: undefined, - groupPadding: 0.2, - //grouping: true, - marker: null, // point options are specified in the base options - pointPadding: 0.1, - //pointWidth: null, - minPointLength: 0, - cropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes - pointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories - states: { - hover: { - brightness: 0.1, - shadow: false, - halo: false - }, - select: { - color: '#C0C0C0', - borderColor: '#000000', - shadow: false - } - }, - dataLabels: { - align: null, // auto - verticalAlign: null, // auto - y: null - }, - softThreshold: false, - startFromThreshold: true, // false doesn't work well: http://jsfiddle.net/highcharts/hz8fopan/14/ - stickyTracking: false, - tooltip: { - distance: 6 - }, - threshold: 0 - }); - - /** - * ColumnSeries object - */ - var ColumnSeries = extendClass(Series, { - type: 'column', - pointAttrToOptions: { // mapping between SVG attributes and the corresponding options - stroke: 'borderColor', - fill: 'color', - r: 'borderRadius' - }, - cropShoulder: 0, - directTouch: true, // When tooltip is not shared, this series (and derivatives) requires direct touch/hover. KD-tree does not apply. - trackerGroups: ['group', 'dataLabelsGroup'], - negStacks: true, // use separate negative stacks, unlike area stacks where a negative - // point is substracted from previous (#1910) - - /** - * Initialize the series - */ - init: function () { - Series.prototype.init.apply(this, arguments); - - var series = this, - chart = series.chart; - - // if the series is added dynamically, force redraw of other - // series affected by a new column - if (chart.hasRendered) { - each(chart.series, function (otherSeries) { - if (otherSeries.type === series.type) { - otherSeries.isDirty = true; - } - }); - } - }, - - /** - * Return the width and x offset of the columns adjusted for grouping, groupPadding, pointPadding, - * pointWidth etc. - */ - getColumnMetrics: function () { - - var series = this, - options = series.options, - xAxis = series.xAxis, - yAxis = series.yAxis, - reversedXAxis = xAxis.reversed, - stackKey, - stackGroups = {}, - columnCount = 0; - - // Get the total number of column type series. - // This is called on every series. Consider moving this logic to a - // chart.orderStacks() function and call it on init, addSeries and removeSeries - if (options.grouping === false) { - columnCount = 1; - } else { - each(series.chart.series, function (otherSeries) { - var otherOptions = otherSeries.options, - otherYAxis = otherSeries.yAxis, - columnIndex; - if (otherSeries.type === series.type && otherSeries.visible && - yAxis.len === otherYAxis.len && yAxis.pos === otherYAxis.pos) { // #642, #2086 - if (otherOptions.stacking) { - stackKey = otherSeries.stackKey; - if (stackGroups[stackKey] === UNDEFINED) { - stackGroups[stackKey] = columnCount++; - } - columnIndex = stackGroups[stackKey]; - } else if (otherOptions.grouping !== false) { // #1162 - columnIndex = columnCount++; - } - otherSeries.columnIndex = columnIndex; - } - }); - } - - var categoryWidth = mathMin( - mathAbs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || xAxis.tickInterval || 1), // #2610 - xAxis.len // #1535 - ), - groupPadding = categoryWidth * options.groupPadding, - groupWidth = categoryWidth - 2 * groupPadding, - pointOffsetWidth = groupWidth / columnCount, - pointWidth = mathMin( - options.maxPointWidth || xAxis.len, - pick(options.pointWidth, pointOffsetWidth * (1 - 2 * options.pointPadding)) - ), - pointPadding = (pointOffsetWidth - pointWidth) / 2, - colIndex = (series.columnIndex || 0) + (reversedXAxis ? 1 : 0), // #1251, #3737 - pointXOffset = pointPadding + (groupPadding + colIndex * - pointOffsetWidth - (categoryWidth / 2)) * - (reversedXAxis ? -1 : 1); - - // Save it for reading in linked series (Error bars particularly) - series.columnMetrics = { - width: pointWidth, - offset: pointXOffset - }; - return series.columnMetrics; - - }, - - /** - * Make the columns crisp. The edges are rounded to the nearest full pixel. - */ - crispCol: function (x, y, w, h) { - var chart = this.chart, - borderWidth = this.borderWidth, - xCrisp = -(borderWidth % 2 ? 0.5 : 0), - yCrisp = borderWidth % 2 ? 0.5 : 1, - right, - bottom, - fromTop; - - if (chart.inverted && chart.renderer.isVML) { - yCrisp += 1; - } - - // Horizontal. We need to first compute the exact right edge, then round it - // and compute the width from there. - right = Math.round(x + w) + xCrisp; - x = Math.round(x) + xCrisp; - w = right - x; - - // Vertical - bottom = Math.round(y + h) + yCrisp; - fromTop = mathAbs(y) <= 0.5 && bottom > 0.5; // #4504, #4656 - y = Math.round(y) + yCrisp; - h = bottom - y; - - // Top edges are exceptions - if (fromTop && h) { // #5146 - y -= 1; - h += 1; - } - - return { - x: x, - y: y, - width: w, - height: h - }; - }, - - /** - * Translate each point to the plot area coordinate system and find shape positions - */ - translate: function () { - var series = this, - chart = series.chart, - options = series.options, - borderWidth = series.borderWidth = pick( - options.borderWidth, - series.closestPointRange * series.xAxis.transA < 2 ? 0 : 1 // #3635 - ), - yAxis = series.yAxis, - threshold = options.threshold, - translatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold), - minPointLength = pick(options.minPointLength, 5), - metrics = series.getColumnMetrics(), - pointWidth = metrics.width, - seriesBarW = series.barW = mathMax(pointWidth, 1 + 2 * borderWidth), // postprocessed for border width - pointXOffset = series.pointXOffset = metrics.offset; - - if (chart.inverted) { - translatedThreshold -= 0.5; // #3355 - } - - // When the pointPadding is 0, we want the columns to be packed tightly, so we allow individual - // columns to have individual sizes. When pointPadding is greater, we strive for equal-width - // columns (#2694). - if (options.pointPadding) { - seriesBarW = mathCeil(seriesBarW); - } - - Series.prototype.translate.apply(series); - - // Record the new values - each(series.points, function (point) { - var yBottom = mathMin(pick(point.yBottom, translatedThreshold), 9e4), // #3575 - safeDistance = 999 + mathAbs(yBottom), - plotY = mathMin(mathMax(-safeDistance, point.plotY), yAxis.len + safeDistance), // Don't draw too far outside plot area (#1303, #2241, #4264) - barX = point.plotX + pointXOffset, - barW = seriesBarW, - barY = mathMin(plotY, yBottom), - up, - barH = mathMax(plotY, yBottom) - barY; - - // Handle options.minPointLength - if (mathAbs(barH) < minPointLength) { - if (minPointLength) { - barH = minPointLength; - up = (!yAxis.reversed && !point.negative) || (yAxis.reversed && point.negative); - barY = mathAbs(barY - translatedThreshold) > minPointLength ? // stacked - yBottom - minPointLength : // keep position - translatedThreshold - (up ? minPointLength : 0); // #1485, #4051 - } - } - - // Cache for access in polar - point.barX = barX; - point.pointWidth = pointWidth; - - // Fix the tooltip on center of grouped columns (#1216, #424, #3648) - point.tooltipPos = chart.inverted ? - [yAxis.len + yAxis.pos - chart.plotLeft - plotY, series.xAxis.len - barX - barW / 2, barH] : - [barX + barW / 2, plotY + yAxis.pos - chart.plotTop, barH]; - - // Register shape type and arguments to be used in drawPoints - point.shapeType = 'rect'; - point.shapeArgs = series.crispCol(barX, barY, barW, barH); - }); - - }, - - getSymbol: noop, - - /** - * Use a solid rectangle like the area series types - */ - drawLegendSymbol: LegendSymbolMixin.drawRectangle, - - - /** - * Columns have no graph - */ - drawGraph: noop, - - /** - * Draw the columns. For bars, the series.group is rotated, so the same coordinates - * apply for columns and bars. This method is inherited by scatter series. - * - */ - drawPoints: function () { - var series = this, - chart = this.chart, - options = series.options, - renderer = chart.renderer, - animationLimit = options.animationLimit || 250, - shapeArgs, - pointAttr; - - // draw the columns - each(series.points, function (point) { - var plotY = point.plotY, - graphic = point.graphic, - borderAttr; - - if (isNumber(plotY) && point.y !== null) { - shapeArgs = point.shapeArgs; - - borderAttr = defined(series.borderWidth) ? { - 'stroke-width': series.borderWidth - } : {}; - - pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE] || series.pointAttr[NORMAL_STATE]; - - if (graphic) { // update - stop(graphic); - graphic.attr(borderAttr).attr(pointAttr)[chart.pointCount < animationLimit ? 'animate' : 'attr'](merge(shapeArgs)); // #4267 - - } else { - point.graphic = graphic = renderer[point.shapeType](shapeArgs) - .attr(borderAttr) - .attr(pointAttr) - .add(point.group || series.group) - .shadow(options.shadow, null, options.stacking && !options.borderRadius); - } - - } else if (graphic) { - point.graphic = graphic.destroy(); // #1269 - } - }); - }, - - /** - * Animate the column heights one by one from zero - * @param {Boolean} init Whether to initialize the animation or run it - */ - animate: function (init) { - var series = this, - yAxis = this.yAxis, - options = series.options, - inverted = this.chart.inverted, - attr = {}, - translatedThreshold; - - if (hasSVG) { // VML is too slow anyway - if (init) { - attr.scaleY = 0.001; - translatedThreshold = mathMin(yAxis.pos + yAxis.len, mathMax(yAxis.pos, yAxis.toPixels(options.threshold))); - if (inverted) { - attr.translateX = translatedThreshold - yAxis.len; - } else { - attr.translateY = translatedThreshold; - } - series.group.attr(attr); - - } else { // run the animation - - attr[inverted ? 'translateX' : 'translateY'] = yAxis.pos; - series.group.animate(attr, extend(animObject(series.options.animation), { - // Do the scale synchronously to ensure smooth updating (#5030) - step: function (val, fx) { - series.group.attr({ - scaleY: mathMax(0.001, fx.pos) // #5250 - }); - } - })); - - // delete this function to allow it only once - series.animate = null; - } - } - }, - - /** - * Remove this series from the chart - */ - remove: function () { - var series = this, - chart = series.chart; - - // column and bar series affects other series of the same type - // as they are either stacked or grouped - if (chart.hasRendered) { - each(chart.series, function (otherSeries) { - if (otherSeries.type === series.type) { - otherSeries.isDirty = true; - } - }); - } - - Series.prototype.remove.apply(series, arguments); - } - }); - seriesTypes.column = ColumnSeries; - /** - * Set the default options for bar - */ - defaultPlotOptions.bar = merge(defaultPlotOptions.column); - /** - * The Bar series class - */ - var BarSeries = extendClass(ColumnSeries, { - type: 'bar', - inverted: true - }); - seriesTypes.bar = BarSeries; - - /** - * Set the default options for scatter - */ - defaultPlotOptions.scatter = merge(defaultSeriesOptions, { - lineWidth: 0, - marker: { - enabled: true // Overrides auto-enabling in line series (#3647) - }, - tooltip: { - headerFormat: '\u25CF {series.name}
', - pointFormat: 'x: {point.x}
y: {point.y}
' - } - }); - - /** - * The scatter series class - */ - var ScatterSeries = extendClass(Series, { - type: 'scatter', - sorted: false, - requireSorting: false, - noSharedTooltip: true, - trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'], - takeOrdinalPosition: false, // #2342 - kdDimensions: 2, - drawGraph: function () { - if (this.options.lineWidth) { - Series.prototype.drawGraph.call(this); - } - } - }); - - seriesTypes.scatter = ScatterSeries; - - /** - * Set the default options for pie - */ - defaultPlotOptions.pie = merge(defaultSeriesOptions, { - borderColor: '#FFFFFF', - borderWidth: 1, - center: [null, null], - clip: false, - colorByPoint: true, // always true for pies - dataLabels: { - // align: null, - // connectorWidth: 1, - // connectorColor: point.color, - // connectorPadding: 5, - distance: 30, - enabled: true, - formatter: function () { // #2945 - return this.y === null ? undefined : this.point.name; - }, - // softConnector: true, - x: 0 - // y: 0 - }, - ignoreHiddenPoint: true, - //innerSize: 0, - legendType: 'point', - marker: null, // point options are specified in the base options - size: null, - showInLegend: false, - slicedOffset: 10, - states: { - hover: { - brightness: 0.1, - shadow: false - } - }, - stickyTracking: false, - tooltip: { - followPointer: true - } - }); - - /** - * Extended point object for pies - */ - var PiePoint = extendClass(Point, { - /** - * Initiate the pie slice - */ - init: function () { - - Point.prototype.init.apply(this, arguments); - - var point = this, - toggleSlice; - - point.name = pick(point.name, 'Slice'); - - // add event listener for select - toggleSlice = function (e) { - point.slice(e.type === 'select'); - }; - addEvent(point, 'select', toggleSlice); - addEvent(point, 'unselect', toggleSlice); - - return point; - }, - - /** - * Toggle the visibility of the pie slice - * @param {Boolean} vis Whether to show the slice or not. If undefined, the - * visibility is toggled - */ - setVisible: function (vis, redraw) { - var point = this, - series = point.series, - chart = series.chart, - ignoreHiddenPoint = series.options.ignoreHiddenPoint; - - redraw = pick(redraw, ignoreHiddenPoint); - - if (vis !== point.visible) { - - // If called without an argument, toggle visibility - point.visible = point.options.visible = vis = vis === UNDEFINED ? !point.visible : vis; - series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data - - // Show and hide associated elements. This is performed regardless of redraw or not, - // because chart.redraw only handles full series. - each(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function (key) { - if (point[key]) { - point[key][vis ? 'show' : 'hide'](true); - } - }); - - if (point.legendItem) { - chart.legend.colorizeItem(point, vis); - } - - // #4170, hide halo after hiding point - if (!vis && point.state === 'hover') { - point.setState(''); - } - - // Handle ignore hidden slices - if (ignoreHiddenPoint) { - series.isDirty = true; - } - - if (redraw) { - chart.redraw(); - } - } - }, - - /** - * Set or toggle whether the slice is cut out from the pie - * @param {Boolean} sliced When undefined, the slice state is toggled - * @param {Boolean} redraw Whether to redraw the chart. True by default. - */ - slice: function (sliced, redraw, animation) { - var point = this, - series = point.series, - chart = series.chart, - translation; - - setAnimation(animation, chart); - - // redraw is true by default - redraw = pick(redraw, true); - - // if called without an argument, toggle - point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced; - series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data - - translation = sliced ? point.slicedTranslation : { - translateX: 0, - translateY: 0 - }; - - point.graphic.animate(translation); - - if (point.shadowGroup) { - point.shadowGroup.animate(translation); - } - - }, - - haloPath: function (size) { - var shapeArgs = this.shapeArgs, - chart = this.series.chart; - - return this.sliced || !this.visible ? [] : this.series.chart.renderer.symbols.arc(chart.plotLeft + shapeArgs.x, chart.plotTop + shapeArgs.y, shapeArgs.r + size, shapeArgs.r + size, { - innerR: this.shapeArgs.r, - start: shapeArgs.start, - end: shapeArgs.end - }); - } - }); - - /** - * The Pie series class - */ - var PieSeries = { - type: 'pie', - isCartesian: false, - pointClass: PiePoint, - requireSorting: false, - directTouch: true, - noSharedTooltip: true, - trackerGroups: ['group', 'dataLabelsGroup'], - axisTypes: [], - pointAttrToOptions: { // mapping between SVG attributes and the corresponding options - stroke: 'borderColor', - 'stroke-width': 'borderWidth', - fill: 'color' - }, - - /** - * Animate the pies in - */ - animate: function (init) { - var series = this, - points = series.points, - startAngleRad = series.startAngleRad; - - if (!init) { - each(points, function (point) { - var graphic = point.graphic, - args = point.shapeArgs; - - if (graphic) { - // start values - graphic.attr({ - r: point.startR || (series.center[3] / 2), // animate from inner radius (#779) - start: startAngleRad, - end: startAngleRad - }); - - // animate - graphic.animate({ - r: args.r, - start: args.start, - end: args.end - }, series.options.animation); - } - }); - - // delete this function to allow it only once - series.animate = null; - } - }, - - /** - * Recompute total chart sum and update percentages of points. - */ - updateTotals: function () { - var i, - total = 0, - points = this.points, - len = points.length, - point, - ignoreHiddenPoint = this.options.ignoreHiddenPoint; - - // Get the total sum - for (i = 0; i < len; i++) { - point = points[i]; - total += (ignoreHiddenPoint && !point.visible) ? 0 : point.y; - } - this.total = total; - - // Set each point's properties - for (i = 0; i < len; i++) { - point = points[i]; - point.percentage = (total > 0 && (point.visible || !ignoreHiddenPoint)) ? point.y / total * 100 : 0; - point.total = total; - } - }, - - /** - * Extend the generatePoints method by adding total and percentage properties to each point - */ - generatePoints: function () { - Series.prototype.generatePoints.call(this); - this.updateTotals(); - }, - - /** - * Do translation for pie slices - */ - translate: function (positions) { - this.generatePoints(); - - var series = this, - cumulative = 0, - precision = 1000, // issue #172 - options = series.options, - slicedOffset = options.slicedOffset, - connectorOffset = slicedOffset + options.borderWidth, - start, - end, - angle, - startAngle = options.startAngle || 0, - startAngleRad = series.startAngleRad = mathPI / 180 * (startAngle - 90), - endAngleRad = series.endAngleRad = mathPI / 180 * ((pick(options.endAngle, startAngle + 360)) - 90), - circ = endAngleRad - startAngleRad, //2 * mathPI, - points = series.points, - radiusX, // the x component of the radius vector for a given point - radiusY, - labelDistance = options.dataLabels.distance, - ignoreHiddenPoint = options.ignoreHiddenPoint, - i, - len = points.length, - point; - - // Get positions - either an integer or a percentage string must be given. - // If positions are passed as a parameter, we're in a recursive loop for adjusting - // space for data labels. - if (!positions) { - series.center = positions = series.getCenter(); - } - - // utility for getting the x value from a given y, used for anticollision logic in data labels - series.getX = function (y, left) { - - angle = math.asin(mathMin((y - positions[1]) / (positions[2] / 2 + labelDistance), 1)); - - return positions[0] + - (left ? -1 : 1) * - (mathCos(angle) * (positions[2] / 2 + labelDistance)); - }; - - // Calculate the geometry for each point - for (i = 0; i < len; i++) { - - point = points[i]; - - // set start and end angle - start = startAngleRad + (cumulative * circ); - if (!ignoreHiddenPoint || point.visible) { - cumulative += point.percentage / 100; - } - end = startAngleRad + (cumulative * circ); - - // set the shape - point.shapeType = 'arc'; - point.shapeArgs = { - x: positions[0], - y: positions[1], - r: positions[2] / 2, - innerR: positions[3] / 2, - start: mathRound(start * precision) / precision, - end: mathRound(end * precision) / precision - }; - - // The angle must stay within -90 and 270 (#2645) - angle = (end + start) / 2; - if (angle > 1.5 * mathPI) { - angle -= 2 * mathPI; - } else if (angle < -mathPI / 2) { - angle += 2 * mathPI; - } - - // Center for the sliced out slice - point.slicedTranslation = { - translateX: mathRound(mathCos(angle) * slicedOffset), - translateY: mathRound(mathSin(angle) * slicedOffset) - }; - - // set the anchor point for tooltips - radiusX = mathCos(angle) * positions[2] / 2; - radiusY = mathSin(angle) * positions[2] / 2; - point.tooltipPos = [ - positions[0] + radiusX * 0.7, - positions[1] + radiusY * 0.7 - ]; - - point.half = angle < -mathPI / 2 || angle > mathPI / 2 ? 1 : 0; - point.angle = angle; - - // set the anchor point for data labels - connectorOffset = mathMin(connectorOffset, labelDistance / 2); // #1678 - point.labelPos = [ - positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector - positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a - positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie - positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a - positions[0] + radiusX, // landing point for connector - positions[1] + radiusY, // a/a - labelDistance < 0 ? // alignment - 'center' : - point.half ? 'right' : 'left', // alignment - angle // center angle - ]; - - } - }, - - drawGraph: null, - - /** - * Draw the data points - */ - drawPoints: function () { - var series = this, - chart = series.chart, - renderer = chart.renderer, - groupTranslation, - //center, - graphic, - //group, - shadow = series.options.shadow, - shadowGroup, - pointAttr, - shapeArgs, - attr; - - if (shadow && !series.shadowGroup) { - series.shadowGroup = renderer.g('shadow') - .add(series.group); - } - - // draw the slices - each(series.points, function (point) { - if (point.y !== null) { - graphic = point.graphic; - shapeArgs = point.shapeArgs; - shadowGroup = point.shadowGroup; - pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE]; - if (!pointAttr.stroke) { - pointAttr.stroke = pointAttr.fill; - } - - // put the shadow behind all points - if (shadow && !shadowGroup) { - shadowGroup = point.shadowGroup = renderer.g('shadow') - .add(series.shadowGroup); - } - - // if the point is sliced, use special translation, else use plot area traslation - groupTranslation = point.sliced ? point.slicedTranslation : { - translateX: 0, - translateY: 0 - }; - - //group.translate(groupTranslation[0], groupTranslation[1]); - if (shadowGroup) { - shadowGroup.attr(groupTranslation); - } - - // draw the slice - if (graphic) { - graphic - .setRadialReference(series.center) - .attr(pointAttr) - .animate(extend(shapeArgs, groupTranslation)); - } else { - attr = { 'stroke-linejoin': 'round' }; - if (!point.visible) { - attr.visibility = 'hidden'; - } - - point.graphic = graphic = renderer[point.shapeType](shapeArgs) - .setRadialReference(series.center) - .attr(pointAttr) - .attr(attr) - .attr(groupTranslation) - .add(series.group) - .shadow(shadow, shadowGroup); - } - } - }); - - }, - - - searchPoint: noop, - - /** - * Utility for sorting data labels - */ - sortByAngle: function (points, sign) { - points.sort(function (a, b) { - return a.angle !== undefined && (b.angle - a.angle) * sign; - }); - }, - - /** - * Use a simple symbol from LegendSymbolMixin - */ - drawLegendSymbol: LegendSymbolMixin.drawRectangle, - - /** - * Use the getCenter method from drawLegendSymbol - */ - getCenter: CenteredSeriesMixin.getCenter, - - /** - * Pies don't have point marker symbols - */ - getSymbol: noop - - }; - PieSeries = extendClass(Series, PieSeries); - seriesTypes.pie = PieSeries; - - /** - * Draw the data labels - */ - Series.prototype.drawDataLabels = function () { - - var series = this, - seriesOptions = series.options, - cursor = seriesOptions.cursor, - options = seriesOptions.dataLabels, - points = series.points, - pointOptions, - generalOptions, - hasRendered = series.hasRendered || 0, - str, - dataLabelsGroup, - defer = pick(options.defer, true), - renderer = series.chart.renderer; - - if (options.enabled || series._hasPointLabels) { - - // Process default alignment of data labels for columns - if (series.dlProcessOptions) { - series.dlProcessOptions(options); - } - - // Create a separate group for the data labels to avoid rotation - dataLabelsGroup = series.plotGroup( - 'dataLabelsGroup', - 'data-labels', - defer && !hasRendered ? 'hidden' : 'visible', // #5133 - options.zIndex || 6 - ); - - if (defer) { - dataLabelsGroup.attr({ opacity: +hasRendered }); // #3300 - if (!hasRendered) { - addEvent(series, 'afterAnimate', function () { - if (series.visible) { // #3023, #3024 - dataLabelsGroup.show(); - } - dataLabelsGroup[seriesOptions.animation ? 'animate' : 'attr']({ opacity: 1 }, { duration: 200 }); - }); - } - } - - // Make the labels for each point - generalOptions = options; - each(points, function (point) { - - var enabled, - dataLabel = point.dataLabel, - labelConfig, - attr, - name, - rotation, - connector = point.connector, - isNew = true, - style, - moreStyle = {}; - - // Determine if each data label is enabled - pointOptions = point.dlOptions || (point.options && point.options.dataLabels); // dlOptions is used in treemaps - enabled = pick(pointOptions && pointOptions.enabled, generalOptions.enabled) && point.y !== null; // #2282, #4641 - - - // If the point is outside the plot area, destroy it. #678, #820 - if (dataLabel && !enabled) { - point.dataLabel = dataLabel.destroy(); - - // Individual labels are disabled if the are explicitly disabled - // in the point options, or if they fall outside the plot area. - } else if (enabled) { - - // Create individual options structure that can be extended without - // affecting others - options = merge(generalOptions, pointOptions); - style = options.style; - - rotation = options.rotation; - - // Get the string - labelConfig = point.getLabelConfig(); - str = options.format ? - format(options.format, labelConfig) : - options.formatter.call(labelConfig, options); - - // Determine the color - style.color = pick(options.color, style.color, series.color, 'black'); - - - // update existing label - if (dataLabel) { - - if (defined(str)) { - dataLabel - .attr({ - text: str - }); - isNew = false; - - } else { // #1437 - the label is shown conditionally - point.dataLabel = dataLabel = dataLabel.destroy(); - if (connector) { - point.connector = connector.destroy(); - } - } - - // create new label - } else if (defined(str)) { - attr = { - //align: align, - fill: options.backgroundColor, - stroke: options.borderColor, - 'stroke-width': options.borderWidth, - r: options.borderRadius || 0, - rotation: rotation, - padding: options.padding, - zIndex: 1 - }; - - // Get automated contrast color - if (style.color === 'contrast') { - moreStyle.color = options.inside || options.distance < 0 || !!seriesOptions.stacking ? - renderer.getContrast(point.color || series.color) : - '#000000'; - } - if (cursor) { - moreStyle.cursor = cursor; - } - - - // Remove unused attributes (#947) - for (name in attr) { - if (attr[name] === UNDEFINED) { - delete attr[name]; - } - } - - dataLabel = point.dataLabel = renderer[rotation ? 'text' : 'label']( // labels don't support rotation - str, - 0, - -9999, - options.shape, - null, - null, - options.useHTML - ) - .attr(attr) - .css(extend(style, moreStyle)) - .add(dataLabelsGroup) - .shadow(options.shadow); - - } - - if (dataLabel) { - // Now the data label is created and placed at 0,0, so we need to align it - series.alignDataLabel(point, dataLabel, options, null, isNew); - } - } - }); - } - }; - - /** - * Align each individual data label - */ - Series.prototype.alignDataLabel = function (point, dataLabel, options, alignTo, isNew) { - var chart = this.chart, - inverted = chart.inverted, - plotX = pick(point.plotX, -9999), - plotY = pick(point.plotY, -9999), - bBox = dataLabel.getBBox(), - baseline = chart.renderer.fontMetrics(options.style.fontSize).b, - rotation = options.rotation, - normRotation, - negRotation, - align = options.align, - rotCorr, // rotation correction - // Math.round for rounding errors (#2683), alignTo to allow column labels (#2700) - visible = this.visible && (point.series.forceDL || chart.isInsidePlot(plotX, mathRound(plotY), inverted) || - (alignTo && chart.isInsidePlot(plotX, inverted ? alignTo.x + 1 : alignTo.y + alignTo.height - 1, inverted))), - alignAttr, // the final position; - justify = pick(options.overflow, 'justify') === 'justify'; - - if (visible) { - - // The alignment box is a singular point - alignTo = extend({ - x: inverted ? chart.plotWidth - plotY : plotX, - y: mathRound(inverted ? chart.plotHeight - plotX : plotY), - width: 0, - height: 0 - }, alignTo); - - // Add the text size for alignment calculation - extend(options, { - width: bBox.width, - height: bBox.height - }); - - // Allow a hook for changing alignment in the last moment, then do the alignment - if (rotation) { - justify = false; // Not supported for rotated text - rotCorr = chart.renderer.rotCorr(baseline, rotation); // #3723 - alignAttr = { - x: alignTo.x + options.x + alignTo.width / 2 + rotCorr.x, - y: alignTo.y + options.y + { top: 0, middle: 0.5, bottom: 1 }[options.verticalAlign] * alignTo.height - }; - dataLabel[isNew ? 'attr' : 'animate'](alignAttr) - .attr({ // #3003 - align: align - }); - - // Compensate for the rotated label sticking out on the sides - normRotation = (rotation + 720) % 360; - negRotation = normRotation > 180 && normRotation < 360; - - if (align === 'left') { - alignAttr.y -= negRotation ? bBox.height : 0; - } else if (align === 'center') { - alignAttr.x -= bBox.width / 2; - alignAttr.y -= bBox.height / 2; - } else if (align === 'right') { - alignAttr.x -= bBox.width; - alignAttr.y -= negRotation ? 0 : bBox.height; - } - - - } else { - dataLabel.align(options, null, alignTo); - alignAttr = dataLabel.alignAttr; - } - - // Handle justify or crop - if (justify) { - this.justifyDataLabel(dataLabel, options, alignAttr, bBox, alignTo, isNew); - - // Now check that the data label is within the plot area - } else if (pick(options.crop, true)) { - visible = chart.isInsidePlot(alignAttr.x, alignAttr.y) && chart.isInsidePlot(alignAttr.x + bBox.width, alignAttr.y + bBox.height); - } - - // When we're using a shape, make it possible with a connector or an arrow pointing to thie point - if (options.shape && !rotation) { - dataLabel.attr({ - anchorX: point.plotX, - anchorY: point.plotY - }); - } - } - - // Show or hide based on the final aligned position - if (!visible) { - stop(dataLabel); - dataLabel.attr({ y: -9999 }); - dataLabel.placed = false; // don't animate back in - } - - }; - - /** - * If data labels fall partly outside the plot area, align them back in, in a way that - * doesn't hide the point. - */ - Series.prototype.justifyDataLabel = function (dataLabel, options, alignAttr, bBox, alignTo, isNew) { - var chart = this.chart, - align = options.align, - verticalAlign = options.verticalAlign, - off, - justified, - padding = dataLabel.box ? 0 : (dataLabel.padding || 0); - - // Off left - off = alignAttr.x + padding; - if (off < 0) { - if (align === 'right') { - options.align = 'left'; - } else { - options.x = -off; - } - justified = true; - } - - // Off right - off = alignAttr.x + bBox.width - padding; - if (off > chart.plotWidth) { - if (align === 'left') { - options.align = 'right'; - } else { - options.x = chart.plotWidth - off; - } - justified = true; - } - - // Off top - off = alignAttr.y + padding; - if (off < 0) { - if (verticalAlign === 'bottom') { - options.verticalAlign = 'top'; - } else { - options.y = -off; - } - justified = true; - } - - // Off bottom - off = alignAttr.y + bBox.height - padding; - if (off > chart.plotHeight) { - if (verticalAlign === 'top') { - options.verticalAlign = 'bottom'; - } else { - options.y = chart.plotHeight - off; - } - justified = true; - } - - if (justified) { - dataLabel.placed = !isNew; - dataLabel.align(options, null, alignTo); - } - }; - - /** - * Override the base drawDataLabels method by pie specific functionality - */ - if (seriesTypes.pie) { - seriesTypes.pie.prototype.drawDataLabels = function () { - var series = this, - data = series.data, - point, - chart = series.chart, - options = series.options.dataLabels, - connectorPadding = pick(options.connectorPadding, 10), - connectorWidth = pick(options.connectorWidth, 1), - plotWidth = chart.plotWidth, - plotHeight = chart.plotHeight, - connector, - connectorPath, - softConnector = pick(options.softConnector, true), - distanceOption = options.distance, - seriesCenter = series.center, - radius = seriesCenter[2] / 2, - centerY = seriesCenter[1], - outside = distanceOption > 0, - dataLabel, - dataLabelWidth, - labelPos, - labelHeight, - halves = [// divide the points into right and left halves for anti collision - [], // right - [] // left - ], - x, - y, - visibility, - rankArr, - i, - j, - overflow = [0, 0, 0, 0], // top, right, bottom, left - sort = function (a, b) { - return b.y - a.y; - }; - - // get out if not enabled - if (!series.visible || (!options.enabled && !series._hasPointLabels)) { - return; - } - - // run parent method - Series.prototype.drawDataLabels.apply(series); - - each(data, function (point) { - if (point.dataLabel && point.visible) { // #407, #2510 - - // Arrange points for detection collision - halves[point.half].push(point); - - // Reset positions (#4905) - point.dataLabel._pos = null; - } - }); - - /* Loop over the points in each half, starting from the top and bottom - * of the pie to detect overlapping labels. - */ - i = 2; - while (i--) { - - var slots = [], - slotsLength, - usedSlots = [], - points = halves[i], - pos, - bottom, - length = points.length, - slotIndex; - - if (!length) { - continue; - } - - // Sort by angle - series.sortByAngle(points, i - 0.5); - - // Assume equal label heights on either hemisphere (#2630) - j = labelHeight = 0; - while (!labelHeight && points[j]) { // #1569 - labelHeight = points[j] && points[j].dataLabel && (points[j].dataLabel.getBBox().height || 21); // 21 is for #968 - j++; - } - - // Only do anti-collision when we are outside the pie and have connectors (#856) - if (distanceOption > 0) { - - // Build the slots - bottom = mathMin(centerY + radius + distanceOption, chart.plotHeight); - for (pos = mathMax(0, centerY - radius - distanceOption); pos <= bottom; pos += labelHeight) { - slots.push(pos); - } - slotsLength = slots.length; - - - /* Visualize the slots - if (!series.slotElements) { - series.slotElements = []; - } - if (i === 1) { - series.slotElements.forEach(function (elem) { - elem.destroy(); - }); - series.slotElements.length = 0; - } - - slots.forEach(function (pos, no) { - var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0), - slotY = pos + chart.plotTop; - - if (isNumber(slotX)) { - series.slotElements.push(chart.renderer.rect(slotX, slotY - 7, 100, labelHeight, 1) - .attr({ - 'stroke-width': 1, - stroke: 'silver', - fill: 'rgba(0,0,255,0.1)' - }) - .add()); - series.slotElements.push(chart.renderer.text('Slot '+ no, slotX, slotY + 4) - .attr({ - fill: 'silver' - }).add()); - } - }); - // */ - - // if there are more values than available slots, remove lowest values - if (length > slotsLength) { - // create an array for sorting and ranking the points within each quarter - rankArr = [].concat(points); - rankArr.sort(sort); - j = length; - while (j--) { - rankArr[j].rank = j; - } - j = length; - while (j--) { - if (points[j].rank >= slotsLength) { - points.splice(j, 1); - } - } - length = points.length; - } - - // The label goes to the nearest open slot, but not closer to the edge than - // the label's index. - for (j = 0; j < length; j++) { - - point = points[j]; - labelPos = point.labelPos; - - var closest = 9999, - distance, - slotI; - - // find the closest slot index - for (slotI = 0; slotI < slotsLength; slotI++) { - distance = mathAbs(slots[slotI] - labelPos[1]); - if (distance < closest) { - closest = distance; - slotIndex = slotI; - } - } - - // if that slot index is closer to the edges of the slots, move it - // to the closest appropriate slot - if (slotIndex < j && slots[j] !== null) { // cluster at the top - slotIndex = j; - } else if (slotsLength < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom - slotIndex = slotsLength - length + j; - while (slots[slotIndex] === null) { // make sure it is not taken - slotIndex++; - } - } else { - // Slot is taken, find next free slot below. In the next run, the next slice will find the - // slot above these, because it is the closest one - while (slots[slotIndex] === null) { // make sure it is not taken - slotIndex++; - } - } - - usedSlots.push({ i: slotIndex, y: slots[slotIndex] }); - slots[slotIndex] = null; // mark as taken - } - // sort them in order to fill in from the top - usedSlots.sort(sort); - } - - // now the used slots are sorted, fill them up sequentially - for (j = 0; j < length; j++) { - - var slot, naturalY; - - point = points[j]; - labelPos = point.labelPos; - dataLabel = point.dataLabel; - visibility = point.visible === false ? HIDDEN : 'inherit'; - naturalY = labelPos[1]; - - if (distanceOption > 0) { - slot = usedSlots.pop(); - slotIndex = slot.i; - - // if the slot next to currrent slot is free, the y value is allowed - // to fall back to the natural position - y = slot.y; - if ((naturalY > y && slots[slotIndex + 1] !== null) || - (naturalY < y && slots[slotIndex - 1] !== null)) { - y = mathMin(mathMax(0, naturalY), chart.plotHeight); - } - - } else { - y = naturalY; - } - - // get the x - use the natural x position for first and last slot, to prevent the top - // and botton slice connectors from touching each other on either side - x = options.justify ? - seriesCenter[0] + (i ? -1 : 1) * (radius + distanceOption) : - series.getX(y === centerY - radius - distanceOption || y === centerY + radius + distanceOption ? naturalY : y, i); - - - // Record the placement and visibility - dataLabel._attr = { - visibility: visibility, - align: labelPos[6] - }; - dataLabel._pos = { - x: x + options.x + - ({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0), - y: y + options.y - 10 // 10 is for the baseline (label vs text) - }; - dataLabel.connX = x; - dataLabel.connY = y; - - - // Detect overflowing data labels - if (this.options.size === null) { - dataLabelWidth = dataLabel.width; - // Overflow left - if (x - dataLabelWidth < connectorPadding) { - overflow[3] = mathMax(mathRound(dataLabelWidth - x + connectorPadding), overflow[3]); - - // Overflow right - } else if (x + dataLabelWidth > plotWidth - connectorPadding) { - overflow[1] = mathMax(mathRound(x + dataLabelWidth - plotWidth + connectorPadding), overflow[1]); - } - - // Overflow top - if (y - labelHeight / 2 < 0) { - overflow[0] = mathMax(mathRound(-y + labelHeight / 2), overflow[0]); - - // Overflow left - } else if (y + labelHeight / 2 > plotHeight) { - overflow[2] = mathMax(mathRound(y + labelHeight / 2 - plotHeight), overflow[2]); - } - } - } // for each point - } // for each half - - // Do not apply the final placement and draw the connectors until we have verified - // that labels are not spilling over. - if (arrayMax(overflow) === 0 || this.verifyDataLabelOverflow(overflow)) { - - // Place the labels in the final position - this.placeDataLabels(); - - // Draw the connectors - if (outside && connectorWidth) { - each(this.points, function (point) { - connector = point.connector; - labelPos = point.labelPos; - dataLabel = point.dataLabel; - - if (dataLabel && dataLabel._pos && point.visible) { - visibility = dataLabel._attr.visibility; - x = dataLabel.connX; - y = dataLabel.connY; - connectorPath = softConnector ? [ - M, - x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label - 'C', - x, y, // first break, next to the label - 2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5], - labelPos[2], labelPos[3], // second break - L, - labelPos[4], labelPos[5] // base - ] : [ - M, - x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label - L, - labelPos[2], labelPos[3], // second break - L, - labelPos[4], labelPos[5] // base - ]; - - if (connector) { - connector.animate({ d: connectorPath }); - connector.attr('visibility', visibility); - - } else { - point.connector = connector = series.chart.renderer.path(connectorPath).attr({ - 'stroke-width': connectorWidth, - stroke: options.connectorColor || point.color || '#606060', - visibility: visibility - //zIndex: 0 // #2722 (reversed) - }) - .add(series.dataLabelsGroup); - } - } else if (connector) { - point.connector = connector.destroy(); - } - }); - } - } - }; - /** - * Perform the final placement of the data labels after we have verified that they - * fall within the plot area. - */ - seriesTypes.pie.prototype.placeDataLabels = function () { - each(this.points, function (point) { - var dataLabel = point.dataLabel, - _pos; - - if (dataLabel && point.visible) { - _pos = dataLabel._pos; - if (_pos) { - dataLabel.attr(dataLabel._attr); - dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos); - dataLabel.moved = true; - } else if (dataLabel) { - dataLabel.attr({ y: -9999 }); - } - } - }); - }; - - seriesTypes.pie.prototype.alignDataLabel = noop; - - /** - * Verify whether the data labels are allowed to draw, or we should run more translation and data - * label positioning to keep them inside the plot area. Returns true when data labels are ready - * to draw. - */ - seriesTypes.pie.prototype.verifyDataLabelOverflow = function (overflow) { - - var center = this.center, - options = this.options, - centerOption = options.center, - minSize = options.minSize || 80, - newSize = minSize, - ret; - - // Handle horizontal size and center - if (centerOption[0] !== null) { // Fixed center - newSize = mathMax(center[2] - mathMax(overflow[1], overflow[3]), minSize); - - } else { // Auto center - newSize = mathMax( - center[2] - overflow[1] - overflow[3], // horizontal overflow - minSize - ); - center[0] += (overflow[3] - overflow[1]) / 2; // horizontal center - } - - // Handle vertical size and center - if (centerOption[1] !== null) { // Fixed center - newSize = mathMax(mathMin(newSize, center[2] - mathMax(overflow[0], overflow[2])), minSize); - - } else { // Auto center - newSize = mathMax( - mathMin( - newSize, - center[2] - overflow[0] - overflow[2] // vertical overflow - ), - minSize - ); - center[1] += (overflow[0] - overflow[2]) / 2; // vertical center - } - - // If the size must be decreased, we need to run translate and drawDataLabels again - if (newSize < center[2]) { - center[2] = newSize; - center[3] = Math.min(relativeLength(options.innerSize || 0, newSize), newSize); // #3632 - this.translate(center); - - if (this.drawDataLabels) { - this.drawDataLabels(); - } - // Else, return true to indicate that the pie and its labels is within the plot area - } else { - ret = true; - } - return ret; - }; - } - - if (seriesTypes.column) { - - /** - * Override the basic data label alignment by adjusting for the position of the column - */ - seriesTypes.column.prototype.alignDataLabel = function (point, dataLabel, options, alignTo, isNew) { - var inverted = this.chart.inverted, - series = point.series, - dlBox = point.dlBox || point.shapeArgs, // data label box for alignment - below = pick(point.below, point.plotY > pick(this.translatedThreshold, series.yAxis.len)), // point.below is used in range series - inside = pick(options.inside, !!this.options.stacking), // draw it inside the box? - overshoot; - - // Align to the column itself, or the top of it - if (dlBox) { // Area range uses this method but not alignTo - alignTo = merge(dlBox); - - if (alignTo.y < 0) { - alignTo.height += alignTo.y; - alignTo.y = 0; - } - overshoot = alignTo.y + alignTo.height - series.yAxis.len; - if (overshoot > 0) { - alignTo.height -= overshoot; - } - - if (inverted) { - alignTo = { - x: series.yAxis.len - alignTo.y - alignTo.height, - y: series.xAxis.len - alignTo.x - alignTo.width, - width: alignTo.height, - height: alignTo.width - }; - } - - // Compute the alignment box - if (!inside) { - if (inverted) { - alignTo.x += below ? 0 : alignTo.width; - alignTo.width = 0; - } else { - alignTo.y += below ? alignTo.height : 0; - alignTo.height = 0; - } - } - } - - - // When alignment is undefined (typically columns and bars), display the individual - // point below or above the point depending on the threshold - options.align = pick( - options.align, - !inverted || inside ? 'center' : below ? 'right' : 'left' - ); - options.verticalAlign = pick( - options.verticalAlign, - inverted || inside ? 'middle' : below ? 'top' : 'bottom' - ); - - // Call the parent method - Series.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew); - }; - } - - - - /** - * Highcharts module to hide overlapping data labels. This module is included in Highcharts. - */ - (function (H) { - var Chart = H.Chart, - each = H.each, - pick = H.pick, - addEvent = H.addEvent; - - // Collect potensial overlapping data labels. Stack labels probably don't need to be - // considered because they are usually accompanied by data labels that lie inside the columns. - Chart.prototype.callbacks.push(function (chart) { - function collectAndHide() { - var labels = []; - - each(chart.series, function (series) { - var dlOptions = series.options.dataLabels, - collections = series.dataLabelCollections || ['dataLabel']; // Range series have two collections - if ((dlOptions.enabled || series._hasPointLabels) && !dlOptions.allowOverlap && series.visible) { // #3866 - each(collections, function (coll) { - each(series.points, function (point) { - if (point[coll]) { - point[coll].labelrank = pick(point.labelrank, point.shapeArgs && point.shapeArgs.height); // #4118 - labels.push(point[coll]); - } - }); - }); - } - }); - chart.hideOverlappingLabels(labels); - } - - // Do it now ... - collectAndHide(); - - // ... and after each chart redraw - addEvent(chart, 'redraw', collectAndHide); - - }); - - /** - * Hide overlapping labels. Labels are moved and faded in and out on zoom to provide a smooth - * visual imression. - */ - Chart.prototype.hideOverlappingLabels = function (labels) { - - var len = labels.length, - label, - i, - j, - label1, - label2, - isIntersecting, - pos1, - pos2, - parent1, - parent2, - padding, - intersectRect = function (x1, y1, w1, h1, x2, y2, w2, h2) { - return !( - x2 > x1 + w1 || - x2 + w2 < x1 || - y2 > y1 + h1 || - y2 + h2 < y1 - ); - }; - - // Mark with initial opacity - for (i = 0; i < len; i++) { - label = labels[i]; - if (label) { - label.oldOpacity = label.opacity; - label.newOpacity = 1; - } - } - - // Prevent a situation in a gradually rising slope, that each label - // will hide the previous one because the previous one always has - // lower rank. - labels.sort(function (a, b) { - return (b.labelrank || 0) - (a.labelrank || 0); - }); - - // Detect overlapping labels - for (i = 0; i < len; i++) { - label1 = labels[i]; - - for (j = i + 1; j < len; ++j) { - label2 = labels[j]; - if (label1 && label2 && label1.placed && label2.placed && label1.newOpacity !== 0 && label2.newOpacity !== 0) { - pos1 = label1.alignAttr; - pos2 = label2.alignAttr; - parent1 = label1.parentGroup; // Different panes have different positions - parent2 = label2.parentGroup; - padding = 2 * (label1.box ? 0 : label1.padding); // Substract the padding if no background or border (#4333) - isIntersecting = intersectRect( - pos1.x + parent1.translateX, - pos1.y + parent1.translateY, - label1.width - padding, - label1.height - padding, - pos2.x + parent2.translateX, - pos2.y + parent2.translateY, - label2.width - padding, - label2.height - padding - ); - - if (isIntersecting) { - (label1.labelrank < label2.labelrank ? label1 : label2).newOpacity = 0; - } - } - } - } - - // Hide or show - each(labels, function (label) { - var complete, - newOpacity; - - if (label) { - newOpacity = label.newOpacity; - - if (label.oldOpacity !== newOpacity && label.placed) { - - // Make sure the label is completely hidden to avoid catching clicks (#4362) - if (newOpacity) { - label.show(true); - } else { - complete = function () { - label.hide(); - }; - } - - // Animate or set the opacity - label.alignAttr.opacity = newOpacity; - label[label.isOld ? 'animate' : 'attr'](label.alignAttr, null, complete); - - } - label.isOld = true; - } - }); - }; - }(Highcharts)); - /** - * TrackerMixin for points and graphs - */ - - var TrackerMixin = Highcharts.TrackerMixin = { - - drawTrackerPoint: function () { - var series = this, - chart = series.chart, - pointer = chart.pointer, - cursor = series.options.cursor, - css = cursor && { cursor: cursor }, - onMouseOver = function (e) { - var target = e.target, - point; - - while (target && !point) { - point = target.point; - target = target.parentNode; - } - - if (point !== UNDEFINED && point !== chart.hoverPoint) { // undefined on graph in scatterchart - point.onMouseOver(e); - } - }; - - // Add reference to the point - each(series.points, function (point) { - if (point.graphic) { - point.graphic.element.point = point; - } - if (point.dataLabel) { - point.dataLabel.element.point = point; - } - }); - - // Add the event listeners, we need to do this only once - if (!series._hasTracking) { - each(series.trackerGroups, function (key) { - if (series[key]) { // we don't always have dataLabelsGroup - series[key] - .addClass(PREFIX + 'tracker') - .on('mouseover', onMouseOver) - .on('mouseout', function (e) { - pointer.onTrackerMouseOut(e); - }) - .css(css); - if (hasTouch) { - series[key].on('touchstart', onMouseOver); - } - } - }); - series._hasTracking = true; - } - }, - - /** - * Draw the tracker object that sits above all data labels and markers to - * track mouse events on the graph or points. For the line type charts - * the tracker uses the same graphPath, but with a greater stroke width - * for better control. - */ - drawTrackerGraph: function () { - var series = this, - options = series.options, - trackByArea = options.trackByArea, - trackerPath = [].concat(trackByArea ? series.areaPath : 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 }, - i, - onMouseOver = function () { - if (chart.hoverSeries !== series) { - series.onMouseOver(); - } - }, - /* - * Empirical lowest possible opacities for TRACKER_FILL for an element to stay invisible but clickable - * IE6: 0.002 - * IE7: 0.002 - * IE8: 0.002 - * IE9: 0.00000000001 (unlimited) - * IE10: 0.0001 (exporting only) - * FF: 0.00000000001 (unlimited) - * Chrome: 0.000001 - * Safari: 0.000001 - * Opera: 0.00000000001 (unlimited) - */ - TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.0001 : 0.002) + ')'; - - // 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]; - 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); - } - }); - } - } - }; - /* End TrackerMixin */ - - - /** - * Add tracking event listener to the series group, so the point graphics - * themselves act as trackers - */ - - if (seriesTypes.column) { - ColumnSeries.prototype.drawTracker = TrackerMixin.drawTrackerPoint; - } - - if (seriesTypes.pie) { - seriesTypes.pie.prototype.drawTracker = TrackerMixin.drawTrackerPoint; - } - - if (seriesTypes.scatter) { - ScatterSeries.prototype.drawTracker = TrackerMixin.drawTrackerPoint; - } - - /* - * Extend Legend for item events - */ - extend(Legend.prototype, { - - setItemEvents: function (item, legendItem, useHTML, itemStyle, itemHiddenStyle) { - var legend = this; - // Set the events on the item group, or in case of useHTML, the item itself (#1249) - (useHTML ? legendItem : item.legendGroup).on('mouseover', function () { - item.setState(HOVER_STATE); - legendItem.css(legend.options.itemHoverStyle); - }) - .on('mouseout', function () { - legendItem.css(item.visible ? itemStyle : itemHiddenStyle); - item.setState(); - }) - .on('click', function (event) { - var strLegendItemClick = 'legendItemClick', - fnLegendItemClick = function () { - if (item.setVisible) { - item.setVisible(); - } - }; - - // Pass over the click/touch event. #4. - event = { - browserEvent: event - }; - - // click the name or symbol - if (item.firePointEvent) { // point - item.firePointEvent(strLegendItemClick, event, fnLegendItemClick); - } else { - fireEvent(item, strLegendItemClick, event, fnLegendItemClick); - } - }); - }, - - createCheckboxForItem: function (item) { - var legend = this; - - item.checkbox = createElement('input', { - type: 'checkbox', - checked: item.selected, - defaultChecked: item.selected // required by IE7 - }, legend.options.itemCheckboxStyle, legend.chart.container); - - addEvent(item.checkbox, 'click', function (event) { - var target = event.target; - fireEvent( - item.series || item, - 'checkboxClick', - { // #3712 - checked: target.checked, - item: item - }, - function () { - item.select(); - } - ); - }); - } - }); - - /* - * Add pointer cursor to legend itemstyle in defaultOptions - */ - defaultOptions.legend.itemStyle.cursor = 'pointer'; - - - /* - * Extend the Chart object with interaction - */ - - extend(Chart.prototype, { - /** - * Display the zoom button - */ - showResetZoom: function () { - var chart = this, - lang = defaultOptions.lang, - btnOptions = chart.options.chart.resetZoomButton, - theme = btnOptions.theme, - states = theme.states, - alignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox'; - - function zoomOut() { - chart.zoomOut(); - } - - this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, zoomOut, theme, states && states.hover) - .attr({ - align: btnOptions.position.align, - title: lang.resetZoomTitle - }) - .add() - .align(btnOptions.position, false, alignTo); - - }, - - /** - * Zoom out to 1:1 - */ - zoomOut: function () { - var chart = this; - fireEvent(chart, 'selection', { resetSelection: true }, function () { - chart.zoom(); - }); - }, - - /** - * Zoom into a given portion of the chart given by axis coordinates - * @param {Object} event - */ - zoom: function (event) { - var chart = this, - hasZoomed, - pointer = chart.pointer, - displayButton = false, - resetZoomButton; - - // If zoom is called with no arguments, reset the axes - if (!event || event.resetSelection) { - each(chart.axes, function (axis) { - hasZoomed = axis.zoom(); - }); - } else { // else, zoom in on all axes - each(event.xAxis.concat(event.yAxis), function (axisData) { - var axis = axisData.axis, - isXAxis = axis.isXAxis; - - // don't zoom more than minRange - if (pointer[isXAxis ? 'zoomX' : 'zoomY'] || pointer[isXAxis ? 'pinchX' : 'pinchY']) { - hasZoomed = axis.zoom(axisData.min, axisData.max); - if (axis.displayBtn) { - displayButton = true; - } - } - }); - } - - // Show or hide the Reset zoom button - resetZoomButton = chart.resetZoomButton; - if (displayButton && !resetZoomButton) { - chart.showResetZoom(); - } else if (!displayButton && isObject(resetZoomButton)) { - chart.resetZoomButton = resetZoomButton.destroy(); - } - - - // Redraw - if (hasZoomed) { - chart.redraw( - pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation - ); - } - }, - - /** - * Pan the chart by dragging the mouse across the pane. This function is called - * on mouse move, and the distance to pan is computed from chartX compared to - * the first chartX position in the dragging operation. - */ - pan: function (e, panning) { - - var chart = this, - hoverPoints = chart.hoverPoints, - doRedraw; - - // remove active points for shared tooltip - if (hoverPoints) { - each(hoverPoints, function (point) { - point.setState(); - }); - } - - each(panning === 'xy' ? [1, 0] : [1], function (isX) { // xy is used in maps - var axis = chart[isX ? 'xAxis' : 'yAxis'][0], - horiz = axis.horiz, - mousePos = e[horiz ? 'chartX' : 'chartY'], - mouseDown = horiz ? 'mouseDownX' : 'mouseDownY', - startPos = chart[mouseDown], - halfPointRange = (axis.pointRange || 0) / 2, - extremes = axis.getExtremes(), - newMin = axis.toValue(startPos - mousePos, true) + halfPointRange, - newMax = axis.toValue(startPos + axis.len - mousePos, true) - halfPointRange, - goingLeft = startPos > mousePos; // #3613 - - if (axis.series.length && - (goingLeft || newMin > mathMin(extremes.dataMin, extremes.min)) && - (!goingLeft || newMax < mathMax(extremes.dataMax, extremes.max))) { - axis.setExtremes(newMin, newMax, false, false, { trigger: 'pan' }); - doRedraw = true; - } - - chart[mouseDown] = mousePos; // set new reference for next run - }); - - if (doRedraw) { - chart.redraw(false); - } - css(chart.container, { cursor: 'move' }); - } - }); - - /* - * Extend the Point object with interaction - */ - extend(Point.prototype, { - /** - * Toggle the selection status of a point - * @param {Boolean} selected Whether to select or unselect the point. - * @param {Boolean} accumulate Whether to add to the previous selection. By default, - * this happens if the control key (Cmd on Mac) was pressed during clicking. - */ - select: function (selected, accumulate) { - var point = this, - series = point.series, - chart = series.chart; - - selected = pick(selected, !point.selected); - - // fire the event with the default handler - point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () { - point.selected = point.options.selected = selected; - series.options.data[inArray(point, series.data)] = point.options; - - point.setState(selected && SELECT_STATE); - - // unselect all other points unless Ctrl or Cmd + click - if (!accumulate) { - each(chart.getSelectedPoints(), function (loopPoint) { - if (loopPoint.selected && loopPoint !== point) { - loopPoint.selected = loopPoint.options.selected = false; - series.options.data[inArray(loopPoint, series.data)] = loopPoint.options; - loopPoint.setState(NORMAL_STATE); - loopPoint.firePointEvent('unselect'); - } - }); - } - }); - }, - - /** - * Runs on mouse over the point - * - * @param {Object} e The event arguments - * @param {Boolean} byProximity Falsy for kd points that are closest to the mouse, or to - * actually hovered points. True for other points in shared tooltip. - */ - onMouseOver: function (e, byProximity) { - var point = this, - series = point.series, - chart = series.chart, - tooltip = chart.tooltip, - hoverPoint = chart.hoverPoint; - - if (chart.hoverSeries !== series) { - series.onMouseOver(); - } - - // set normal state to previous series - if (hoverPoint && hoverPoint !== point) { - hoverPoint.onMouseOut(); - } - - if (point.series) { // It may have been destroyed, #4130 - - // trigger the event - point.firePointEvent('mouseOver'); - - // update the tooltip - if (tooltip && (!tooltip.shared || series.noSharedTooltip)) { - tooltip.refresh(point, e); - } - - // hover this - point.setState(HOVER_STATE); - if (!byProximity) { - chart.hoverPoint = point; - } - } - }, - - /** - * Runs on mouse out from the point - */ - onMouseOut: function () { - var chart = this.series.chart, - hoverPoints = chart.hoverPoints; - - this.firePointEvent('mouseOut'); - - if (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887, #2240 - this.setState(); - chart.hoverPoint = null; - } - }, - - /** - * Import events from the series' and point's options. Only do it on - * demand, to save processing time on hovering. - */ - importEvents: function () { - if (!this.hasImportedEvents) { - var point = this, - options = merge(point.series.options.point, point.options), - events = options.events, - eventType; - - point.events = events; - - for (eventType in events) { - addEvent(point, eventType, events[eventType]); - } - this.hasImportedEvents = true; - - } - }, - - /** - * Set the point's state - * @param {String} state - */ - setState: function (state, move) { - var point = this, - plotX = mathFloor(point.plotX), // #4586 - plotY = point.plotY, - series = point.series, - stateOptions = series.options.states, - markerOptions = defaultPlotOptions[series.type].marker && series.options.marker, - normalDisabled = markerOptions && !markerOptions.enabled, - markerStateOptions = markerOptions && markerOptions.states[state], - stateDisabled = markerStateOptions && markerStateOptions.enabled === false, - stateMarkerGraphic = series.stateMarkerGraphic, - pointMarker = point.marker || {}, - chart = series.chart, - radius, - halo = series.halo, - haloOptions, - newSymbol, - pointAttr; - - state = state || NORMAL_STATE; // empty string - pointAttr = point.pointAttr[state] || series.pointAttr[state]; - - if ( - // already has this state - (state === point.state && !move) || - // selected points don't respond to hover - (point.selected && state !== SELECT_STATE) || - // series' state options is disabled - (stateOptions[state] && stateOptions[state].enabled === false) || - // general point marker's state options is disabled - (state && (stateDisabled || (normalDisabled && markerStateOptions.enabled === false))) || - // individual point marker's state options is disabled - (state && pointMarker.states && pointMarker.states[state] && pointMarker.states[state].enabled === false) // #1610 - - ) { - return; - } - - // apply hover styles to the existing point - if (point.graphic) { - radius = markerOptions && point.graphic.symbolName && pointAttr.r; - point.graphic.attr(merge( - pointAttr, - radius ? { // new symbol attributes (#507, #612) - x: plotX - radius, - y: plotY - radius, - width: 2 * radius, - height: 2 * radius - } : {} - )); - - // Zooming in from a range with no markers to a range with markers - if (stateMarkerGraphic) { - stateMarkerGraphic.hide(); - } - } else { - // if a graphic is not applied to each point in the normal state, create a shared - // graphic for the hover state - if (state && markerStateOptions) { - radius = markerStateOptions.radius; - newSymbol = pointMarker.symbol || series.symbol; - - // If the point has another symbol than the previous one, throw away the - // state marker graphic and force a new one (#1459) - if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) { - stateMarkerGraphic = stateMarkerGraphic.destroy(); - } - - // Add a new state marker graphic - if (!stateMarkerGraphic) { - if (newSymbol) { - series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol( - newSymbol, - plotX - radius, - plotY - radius, - 2 * radius, - 2 * radius - ) - .attr(pointAttr) - .add(series.markerGroup); - stateMarkerGraphic.currentSymbol = newSymbol; - } - - // Move the existing graphic - } else { - stateMarkerGraphic[move ? 'animate' : 'attr']({ // #1054 - x: plotX - radius, - y: plotY - radius - }); - } - } - - if (stateMarkerGraphic) { - stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY, chart.inverted) ? 'show' : 'hide'](); // #2450 - stateMarkerGraphic.element.point = point; // #4310 - } - } - - // Show me your halo - haloOptions = stateOptions[state] && stateOptions[state].halo; - if (haloOptions && haloOptions.size) { - if (!halo) { - series.halo = halo = chart.renderer.path() - .add(chart.seriesGroup); - } - halo.attr(extend({ - 'fill': point.color || series.color, - 'fill-opacity': haloOptions.opacity, - 'zIndex': -1 // #4929, IE8 added halo above everything - }, - haloOptions.attributes))[move ? 'animate' : 'attr']({ - d: point.haloPath(haloOptions.size) - }); - } else if (halo) { - halo.attr({ d: [] }); - } - - point.state = state; - }, - - /** - * Get the circular path definition for the halo - * @param {Number} size The radius of the circular halo - * @returns {Array} The path definition - */ - haloPath: function (size) { - var series = this.series, - chart = series.chart, - plotBox = series.getPlotBox(), - inverted = chart.inverted, - plotX = Math.floor(this.plotX); - - return chart.renderer.symbols.circle( - plotBox.translateX + (inverted ? series.yAxis.len - this.plotY : plotX) - size, - plotBox.translateY + (inverted ? series.xAxis.len - plotX : this.plotY) - size, - size * 2, - size * 2 - ); - } - }); - - /* - * Extend the Series object with interaction - */ - - extend(Series.prototype, { - /** - * Series mouse over handler - */ - onMouseOver: function () { - var series = this, - chart = series.chart, - hoverSeries = chart.hoverSeries; - - // set normal state to previous series - if (hoverSeries && hoverSeries !== series) { - hoverSeries.onMouseOut(); - } - - // trigger the event, but to save processing time, - // only if defined - if (series.options.events.mouseOver) { - fireEvent(series, 'mouseOver'); - } - - // hover this - series.setState(HOVER_STATE); - chart.hoverSeries = series; - }, - - /** - * Series mouse out handler - */ - onMouseOut: function () { - // trigger the event only if listeners exist - var series = this, - options = series.options, - chart = series.chart, - tooltip = chart.tooltip, - hoverPoint = chart.hoverPoint; - - chart.hoverSeries = null; // #182, set to null before the mouseOut event fires - - // trigger mouse out on the point, which must be in this series - if (hoverPoint) { - hoverPoint.onMouseOut(); - } - - // fire the mouse out event - if (series && options.events.mouseOut) { - fireEvent(series, 'mouseOut'); - } - - - // hide the tooltip - if (tooltip && !options.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) { - tooltip.hide(); - } - - // set normal state - series.setState(); - }, - - /** - * Set the state of the graph - */ - setState: function (state) { - var series = this, - options = series.options, - graph = series.graph, - stateOptions = options.states, - lineWidth = options.lineWidth, - attribs, - i = 0; - - 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 + (stateOptions[state].lineWidthPlus || 0); // #4035 - } - - 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 - graph.attr(attribs); - while (series['zoneGraph' + i]) { - series['zoneGraph' + i].attr(attribs); - i = i + 1; - } - } - } - }, - - /** - * Set the visibility of the graph - * - * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED, - * the visibility is toggled. - */ - setVisible: function (vis, redraw) { - var series = this, - chart = series.chart, - legendItem = series.legendItem, - showOrHide, - ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries, - oldVisibility = series.visible; - - // if called without an argument, toggle visibility - series.visible = vis = series.userOptions.visible = vis === UNDEFINED ? !oldVisibility : vis; - showOrHide = vis ? 'show' : 'hide'; - - // show or hide elements - each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker'], function (key) { - if (series[key]) { - series[key][showOrHide](); - } - }); - - - // hide tooltip (#1361) - if (chart.hoverSeries === series || (chart.hoverPoint && chart.hoverPoint.series) === series) { - series.onMouseOut(); - } - - - if (legendItem) { - chart.legend.colorizeItem(series, vis); - } - - - // rescale or adapt to resized chart - series.isDirty = true; - // in a stack, all other series are affected - if (series.options.stacking) { - each(chart.series, function (otherSeries) { - if (otherSeries.options.stacking && otherSeries.visible) { - otherSeries.isDirty = true; - } - }); - } - - // show or hide linked series - each(series.linkedSeries, function (otherSeries) { - otherSeries.setVisible(vis, false); - }); - - if (ignoreHiddenSeries) { - chart.isDirtyBox = true; - } - if (redraw !== false) { - chart.redraw(); - } - - fireEvent(series, showOrHide); - }, - - /** - * Show the graph - */ - show: function () { - this.setVisible(true); - }, - - /** - * Hide the graph - */ - hide: function () { - this.setVisible(false); - }, - - - /** - * Set the selected state of the graph - * - * @param selected {Boolean} True to select the series, false to unselect. If - * UNDEFINED, the selection state is toggled. - */ - select: function (selected) { - var series = this; - // if called without an argument, toggle - series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected; - - if (series.checkbox) { - series.checkbox.checked = selected; - } - - fireEvent(series, selected ? 'select' : 'unselect'); - }, - - drawTracker: TrackerMixin.drawTrackerGraph - }); - - // global variables - extend(Highcharts, { - - // Constructors - Color: Color, - Point: Point, - Tick: Tick, - Renderer: Renderer, - SVGElement: SVGElement, - SVGRenderer: SVGRenderer, - - // Various - arrayMin: arrayMin, - arrayMax: arrayMax, - charts: charts, - correctFloat: correctFloat, - dateFormat: dateFormat, - error: error, - format: format, - pathAnim: pathAnim, - getOptions: getOptions, - hasBidiBug: hasBidiBug, - isTouchDevice: isTouchDevice, - setOptions: setOptions, - addEvent: addEvent, - removeEvent: removeEvent, - createElement: createElement, - discardElement: discardElement, - css: css, - each: each, - map: map, - merge: merge, - splat: splat, - stableSort: stableSort, - extendClass: extendClass, - pInt: pInt, - svg: hasSVG, - canvas: useCanVG, - vml: !hasSVG && !useCanVG, - product: PRODUCT, - version: VERSION - }); - - return Highcharts; -})); diff --git a/public/vendor/highcharts-4.2.5/highstock-all.js b/public/vendor/highcharts-4.2.5/highstock-all.js deleted file mode 100644 index 47b2493332..0000000000 --- a/public/vendor/highcharts-4.2.5/highstock-all.js +++ /dev/null @@ -1,633 +0,0 @@ -/* - Highstock JS v4.2.5 (2016-05-06) - - (c) 2009-2016 Torstein Honsi - - License: www.highcharts.com/license -*/ -(function(L,ga){typeof module==="object"&&module.exports?module.exports=L.document?ga(L):ga:L.Highcharts=ga(L)})(typeof window!=="undefined"?window:this,function(L){function ga(a,b){var c="Highcharts error #"+a+": www.highcharts.com/errors/"+a;if(b)throw Error(c);L.console&&console.log(c)}function xb(a,b,c){this.options=b;this.elem=a;this.prop=c}function z(){var a,b=arguments,c,d={},e=function(a,b){var c,d;typeof a!=="object"&&(a={});for(d in b)b.hasOwnProperty(d)&&(c=b[d],a[d]=c&&typeof c==="object"&& -Object.prototype.toString.call(c)!=="[object Array]"&&d!=="renderTo"&&typeof c.nodeType!=="number"?e(a[d]||{},c):b[d]);return a};b[0]===!0&&(d=b[1],b=Array.prototype.slice.call(b,2));c=b.length;for(a=0;a-1?h.thousandsSep:""))):e=na(f,e)}j.push(e);a=a.slice(c+1);c=(d=!d)?"}":"{"}j.push(a);return j.join("")}function zb(a){return Y.pow(10,V(Y.log(a)/Y.LN10))}function Ab(a,b,c,d,e){var f,g=a,c=q(c,1);f=a/c;b||(b=[1,2,2.5,5,10],d===!1&&(c===1?b=[1,2,5,10]:c<=0.1&&(b=[1/c])));for(d=0;d=a||!e&&f<=(b[d]+(b[d+1]||b[d]))/2)break;g*=c;return g}function nb(a,b){var c=a.length,d,e;for(e=0;ec&&(c=a[b]);return c}function Pa(a,b){for(var c in a)a[c]&&a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Ua(a){ob||(ob=fa(Va));a&&ob.appendChild(a);ob.innerHTML=""}function ka(a,b){return parseFloat(a.toPrecision(b||14))}function $a(a,b){b.renderer.globalAnimation=q(a,b.animation)}function gb(a){return ea(a)? -z(a):{duration:a?500:0}}function Ob(){var a=R.global,b=a.useUTC,c=b?"getUTC":"get",d=b?"setUTC":"set";ba=a.Date||L.Date;yb=b&&a.timezoneOffset;fb=b&&a.getTimezoneOffset;pb=function(a,c,d,h,i,j){var k;b?(k=ba.UTC.apply(0,arguments),k+=eb(k)):k=(new ba(a,c,q(d,1),q(h,0),q(i,0),q(j,0))).getTime();return k};Bb=c+"Minutes";Cb=c+"Hours";Db=c+"Day";ab=c+"Date";hb=c+"Month";ib=c+"FullYear";Pb=d+"Milliseconds";Qb=d+"Seconds";Rb=d+"Minutes";Sb=d+"Hours";qb=d+"Date";Eb=d+"Month";Fb=d+"FullYear"}function va(a){if(!(this instanceof -va))return new va(a);this.init(a)}function Z(){}function bb(a,b,c,d){this.axis=a;this.pos=b;this.type=c||"";this.isNew=!0;!c&&!d&&this.addLabel()}function Tb(a,b,c,d,e){var f=a.chart.inverted;this.axis=a;this.isNegative=c;this.options=b;this.x=d;this.total=null;this.points={};this.stack=e;this.rightCliff=this.leftCliff=0;this.alignOptions={align:b.align||(f?c?"left":"right":"center"),verticalAlign:b.verticalAlign||(f?"middle":c?"bottom":"top"),y:q(b.y,f?4:c?14:-6),x:q(b.x,f?c?-6:6:0)};this.textAlign= -b.textAlign||(f?c?"right":"left":"center")}function Gb(a){var b=a.options,c=b.navigator,d=c.enabled,b=b.scrollbar,e=b.enabled,f=d?c.height:0,g=e?b.height:0;this.handles=[];this.scrollbarButtons=[];this.elementsToDestroy=[];this.chart=a;this.setBaseSeries();this.height=f;this.scrollbarHeight=g;this.scrollbarEnabled=e;this.navigatorEnabled=d;this.navigatorOptions=c;this.scrollbarOptions=b;this.outlineHeight=f+g;this.init()}function Hb(a){this.init(a)}var t,H=L.document,Y=Math,y=Y.round,V=Y.floor,Ea= -Y.ceil,w=Y.max,G=Y.min,S=Y.abs,ca=Y.cos,la=Y.sin,Aa=Y.PI,pa=Aa*2/360,Na=L.navigator&&L.navigator.userAgent||"",Ub=L.opera,Ka=/(msie|trident|edge)/i.test(Na)&&!Ub,rb=H&&H.documentMode===8,sb=!Ka&&/AppleWebKit/.test(Na),Wa=/Firefox/.test(Na),jb=/(Mobile|Android|Windows Phone)/.test(Na),Qa="http://www.w3.org/2000/svg",ja=H&&H.createElementNS&&!!H.createElementNS(Qa,"svg").createSVGRect,Zb=Wa&&parseInt(Na.split("Firefox/")[1],10)<4,qa=H&&!ja&&!Ka&&!!H.createElement("canvas").getContext,Xa,cb,Vb={},Ib= -0,ob,R,na,M,ra=function(){},$=[],kb=0,Va="div",$b=/^[0-9]+$/,tb=["plotTop","marginRight","marginBottom","plotLeft"],ba,pb,yb,fb,Bb,Cb,Db,ab,hb,ib,Pb,Qb,Rb,Sb,qb,Eb,Fb,I={},B;B=L.Highcharts?ga(16,!0):{win:L};B.seriesTypes=I;var Ra=[],wa,sa,o,Fa,Jb,ta,E,T,O,db,Sa;xb.prototype={dSetter:function(){var a=this.paths[0],b=this.paths[1],c=[],d=this.now,e=a.length,f;if(d===1)c=this.toD;else if(e===b.length&&d<1)for(;e--;)f=parseFloat(a[e]),c[e]=isNaN(f)?a[e]:d*parseFloat(b[e]-f)+f;else c=b;this.elem.attr("d", -c)},update:function(){var a=this.elem,b=this.prop,c=this.now,d=this.options.step;if(this[b+"Setter"])this[b+"Setter"]();else a.attr?a.element&&a.attr(b,c):a.style[b]=c+this.unit;d&&d.call(a,c,this)},run:function(a,b,c){var d=this,e=function(a){return e.stopped?!1:d.step(a)},f;this.startTime=+new ba;this.start=a;this.end=b;this.unit=c;this.now=this.start;this.pos=0;e.elem=this.elem;if(e()&&Ra.push(e)===1)e.timerId=setInterval(function(){for(f=0;f=f+this.startTime){this.now=this.end;this.pos=1;this.update();a=g[this.prop]=!0;for(h in g)g[h]!==!0&&(a=!1);a&&e&&e.call(c);c=!1}else this.pos=d.easing((b-this.startTime)/f),this.now=this.start+(this.end-this.start)*this.pos,this.update(),c=!0;return c},initPath:function(a,b,c){var b=b||"",d=a.shift,e=b.indexOf("C")>-1,f=e? -7:3,g,b=b.split(" "),c=[].concat(c),h=a.isArea,i=h?2:1,j=function(a){for(g=a.length;g--;)(a[g]==="M"||a[g]==="L")&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&(j(b),j(c));if(d<=c.length/f&&b.length===c.length)for(;d--;)c=c.slice(0,f).concat(c),h&&(c=c.concat(c.slice(c.length-f)));a.shift=0;if(b.length)for(a=c.length;b.length3?g.length%3:0;c=q(c,e.decimalPoint);d=q(d,e.thousandsSep);a=a<0?"-":"";a+=h?g.substr(0,h)+d:"";a+=g.substr(h).replace(/(\d{3})(?=\d)/g,"$1"+d);b&&(d=Math.abs(i-g+Math.pow(10,-Math.max(b,f)-1)),a+=c+d.toFixed(b).slice(2));return a};Math.easeInOutSine=function(a){return-0.5* -(Math.cos(Math.PI*a)-1)};wa=function(a,b){var c;if(b==="width")return Math.min(a.offsetWidth,a.scrollWidth)-wa(a,"padding-left")-wa(a,"padding-right");else if(b==="height")return Math.min(a.offsetHeight,a.scrollHeight)-wa(a,"padding-top")-wa(a,"padding-bottom");return(c=L.getComputedStyle(a,void 0))&&K(c.getPropertyValue(b))};sa=function(a,b){return b.indexOf?b.indexOf(a):[].indexOf.call(b,a)};Fa=function(a,b){return[].filter.call(a,b)};ta=function(a,b){for(var c=[],d=0,e=a.length;d-1&&(f.splice(h,1),g[b]=f),d(b,c)):(e(),g[b]=[])):(e(),a.hcEvents={})};O=function(a,b,c,d){var e; -e=a.hcEvents;var f,g,c=c||{};if(H.createEvent&&(a.dispatchEvent||a.fireEvent))e=H.createEvent("Events"),e.initEvent(b,!0,!0),e.target=a,A(e,c),a.dispatchEvent?a.dispatchEvent(e):a.fireEvent(b,e);else if(e){e=e[b]||[];f=e.length;if(!c.preventDefault)c.preventDefault=function(){c.defaultPrevented=!0};c.target=a;if(!c.type)c.type=b;for(b=0;b{point.key}
',pointFormat:'\u25cf {series.name}: {point.y}
',shadow:!0,snap:jb?25:10,style:{color:"#333333",cursor:"default",fontSize:"12px", -padding:"8px",pointerEvents:"none",whiteSpace:"nowrap"}},credits:{enabled:!0,text:"Highcharts.com",href:"http://www.highcharts.com",position:{align:"right",x:-10,verticalAlign:"bottom",y:-5},style:{cursor:"pointer",color:"#909090",fontSize:"9px"}}};var W=R.plotOptions,da=W.line;Ob();va.prototype={parsers:[{regex:/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,parse:function(a){return[K(a[1]),K(a[2]),K(a[3]),parseFloat(a[4],10)]}},{regex:/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/, -parse:function(a){return[K(a[1],16),K(a[2],16),K(a[3],16),1]}},{regex:/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,parse:function(a){return[K(a[1]),K(a[2]),K(a[3]),1]}}],init:function(a){var b,c,d,e;if((this.input=a)&&a.stops)this.stops=ta(a.stops,function(a){return new va(a[1])});else for(d=this.parsers.length;d--&&!c;)e=this.parsers[d],(b=e.regex.exec(a))&&(c=e.parse(b));this.rgba=c||[]},get:function(a){var b=this.input,c=this.rgba,d;this.stops?(d=z(b),d.stops=[].concat(d.stops), -o(this.stops,function(b,c){d.stops[c]=[d.stops[c][0],b.get(a)]})):d=c&&C(c[0])?a==="rgb"||!a&&c[3]===1?"rgb("+c[0]+","+c[1]+","+c[2]+")":a==="a"?c[3]:"rgba("+c.join(",")+")":b;return d},brighten:function(a){var b,c=this.rgba;if(this.stops)o(this.stops,function(b){b.brighten(a)});else if(C(a)&&a!==0)for(b=0;b<3;b++)c[b]+=K(a*255),c[b]<0&&(c[b]=0),c[b]>255&&(c[b]=255);return this},setOpacity:function(a){this.rgba[3]=a;return this}};Z.prototype={opacity:1,textProps:"direction,fontSize,fontWeight,fontFamily,fontStyle,color,lineHeight,width,textDecoration,textOverflow,textShadow".split(","), -init:function(a,b){this.element=b==="span"?fa(b):H.createElementNS(Qa,b);this.renderer=a},animate:function(a,b,c){b=q(b,this.renderer.globalAnimation,!0);Sa(this);if(b){if(c)b.complete=c;db(this,a,b)}else this.attr(a,null,c);return this},colorGradient:function(a,b,c){var d=this.renderer,e,f,g,h,i,j,k,l,m,n,p,r=[],s;a.linearGradient?f="linearGradient":a.radialGradient&&(f="radialGradient");if(f){g=a[f];i=d.gradients;k=a.stops;n=c.radialReference;Ja(g)&&(a[f]=g={x1:g[0],y1:g[1],x2:g[2],y2:g[3],gradientUnits:"userSpaceOnUse"}); -f==="radialGradient"&&n&&!v(g.gradientUnits)&&(h=g,g=z(g,d.getRadialAttr(n,h),{gradientUnits:"userSpaceOnUse"}));for(p in g)p!=="id"&&r.push(p,g[p]);for(p in k)r.push(k[p]);r=r.join(",");i[r]?n=i[r].attr("id"):(g.id=n="highcharts-"+Ib++,i[r]=j=d.createElement(f).attr(g).add(d.defs),j.radAttr=h,j.stops=[],o(k,function(a){a[1].indexOf("rgba")===0?(e=va(a[1]),l=e.get("rgb"),m=e.get("a")):(l=a[1],m=1);a=d.createElement("stop").attr({offset:a[0],"stop-color":l,"stop-opacity":m}).add(j);j.stops.push(a)})); -s="url("+d.url+"#"+n+")";c.setAttribute(b,s);c.gradient=r;a.toString=function(){return s}}},applyTextShadow:function(a){var b=this.element,c,d=a.indexOf("contrast")!==-1,e={},f=this.renderer.forExport,g=f||b.style.textShadow!==t&&!Ka;if(d)e.textShadow=a=a.replace(/contrast/g,this.renderer.getContrast(b.style.fill));if(sb||f)e.textRendering="geometricPrecision";g?this.css(e):(this.fakeTS=!0,this.ySetter=this.xSetter,c=[].slice.call(b.getElementsByTagName("tspan")),o(a.split(/\s?,\s?/g),function(a){var d= -b.firstChild,e,f,a=a.split(" ");e=a[a.length-1];(f=a[a.length-2])&&o(c,function(a,c){var g;c===0&&(a.setAttribute("x",b.getAttribute("x")),c=b.getAttribute("y"),a.setAttribute("y",c||0),c===null&&b.setAttribute("y",0));g=a.cloneNode(1);X(g,{"class":"highcharts-text-shadow",fill:e,stroke:e,"stroke-opacity":1/w(K(f),3),"stroke-width":f,"stroke-linejoin":"round"});b.insertBefore(g,d)})}))},attr:function(a,b,c){var d,e=this.element,f,g=this,h;typeof a==="string"&&b!==t&&(d=a,a={},a[d]=b);if(typeof a=== -"string")g=(this[a+"Getter"]||this._defaultGetter).call(this,a,e);else{for(d in a){b=a[d];h=!1;this.symbolName&&/^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(d)&&(f||(this.symbolAttr(a),f=!0),h=!0);if(this.rotation&&(d==="x"||d==="y"))this.doTransform=!0;h||(h=this[d+"Setter"]||this._defaultSetter,h.call(this,b,d,e),this.shadows&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(d)&&this.updateShadows(d,b,h))}if(this.doTransform)this.updateTransform(),this.doTransform=!1}c&& -c();return g},updateShadows:function(a,b,c){for(var d=this.shadows,e=d.length;e--;)c.call(d[e],a==="height"?Math.max(b-(d[e].cutHeight||0),0):a==="d"?this.d:b,a,d[e])},addClass:function(a){var b=this.element,c=X(b,"class")||"";c.indexOf(a)===-1&&X(b,"class",c+" "+a);return this},symbolAttr:function(a){var b=this;o("x,y,r,start,end,width,height,innerR,anchorX,anchorY".split(","),function(c){b[c]=q(a[c],b[c])});b.attr({d:b.renderer.symbols[b.symbolName](b.x,b.y,b.width,b.height,b)})},clip:function(a){return this.attr("clip-path", -a?"url("+this.renderer.url+"#"+a.id+")":"none")},crisp:function(a){var b,c={},d,e=this.strokeWidth||0;d=y(e)%2/2;a.x=V(a.x||this.x||0)+d;a.y=V(a.y||this.y||0)+d;a.width=V((a.width||this.width||0)-2*d);a.height=V((a.height||this.height||0)-2*d);a.strokeWidth=e;for(b in a)this[b]!==a[b]&&(this[b]=c[b]=a[b]);return c},css:function(a){var b=this.styles,c={},d=this.element,e,f,g="";e=!b;if(a&&a.color)a.fill=a.color;if(b)for(f in a)a[f]!==b[f]&&(c[f]=a[f],e=!0);if(e){e=this.textWidth=a&&a.width&&d.nodeName.toLowerCase()=== -"text"&&K(a.width)||this.textWidth;b&&(a=A(b,c));this.styles=a;e&&(qa||!ja&&this.renderer.forExport)&&delete a.width;if(Ka&&!ja)N(this.element,a);else{b=function(a,b){return"-"+b.toLowerCase()};for(f in a)g+=f.replace(/([A-Z])/g,b)+":"+a[f]+";";X(d,"style",g)}e&&this.added&&this.renderer.buildText(this)}return this},on:function(a,b){var c=this,d=c.element;cb&&a==="click"?(d.ontouchstart=function(a){c.touchEventFired=ba.now();a.preventDefault();b.call(d,a)},d.onclick=function(a){(Na.indexOf("Android")=== --1||ba.now()-(c.touchEventFired||0)>1100)&&b.call(d,a)}):d["on"+a]=b;return this},setRadialReference:function(a){var b=this.renderer.gradients[this.element.gradient];this.element.radialReference=a;b&&b.radAttr&&b.animate(this.renderer.getRadialAttr(a,b.radAttr));return this},translate:function(a,b){return this.attr({translateX:a,translateY:b})},invert:function(){this.inverted=!0;this.updateTransform();return this},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=this.scaleX, -d=this.scaleY,e=this.inverted,f=this.rotation,g=this.element;e&&(a+=this.attr("width"),b+=this.attr("height"));a=["translate("+a+","+b+")"];e?a.push("rotate(90) scale(-1,1)"):f&&a.push("rotate("+f+" "+(g.getAttribute("x")||0)+" "+(g.getAttribute("y")||0)+")");(v(c)||v(d))&&a.push("scale("+q(c,1)+" "+q(d,1)+")");a.length&&g.setAttribute("transform",a.join(" "))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);return this},align:function(a,b,c){var d,e,f,g,h={};e=this.renderer;f=e.alignedObjects; -if(a){if(this.alignOptions=a,this.alignByTranslate=b,!c||Ca(c))this.alignTo=d=c||"renderer",za(f,this),f.push(this),c=null}else a=this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=q(c,e[d],e);d=a.align;e=a.verticalAlign;f=(c.x||0)+(a.x||0);g=(c.y||0)+(a.y||0);if(d==="right"||d==="center")f+=(c.width-(a.width||0))/{right:1,center:2}[d];h[b?"translateX":"x"]=y(f);if(e==="bottom"||e==="middle")g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]||1);h[b?"translateY":"y"]=y(g);this[this.placed? -"animate":"attr"](h);this.placed=!0;this.alignAttr=h;return this},getBBox:function(a,b){var c,d=this.renderer,e,f,g,h=this.element,i=this.styles;e=this.textStr;var j,k=h.style,l,m=d.cache,n=d.cacheKeys,p;f=q(b,this.rotation);g=f*pa;e!==t&&(p=["",f||0,i&&i.fontSize,h.style.width].join(","),p=e===""||$b.test(e)?"num:"+e.toString().length+p:e+p);p&&!a&&(c=m[p]);if(!c){if(h.namespaceURI===Qa||d.forExport){try{l=this.fakeTS&&function(a){o(h.querySelectorAll(".highcharts-text-shadow"),function(b){b.style.display= -a})},Wa&&k.textShadow?(j=k.textShadow,k.textShadow=""):l&&l("none"),c=h.getBBox?A({},h.getBBox()):{width:h.offsetWidth,height:h.offsetHeight},j?k.textShadow=j:l&&l("")}catch(r){}if(!c||c.width<0)c={width:0,height:0}}else c=this.htmlGetBBox();if(d.isSVG){d=c.width;e=c.height;if(Ka&&i&&i.fontSize==="11px"&&e.toPrecision(3)==="16.9")c.height=e=14;if(f)c.width=S(e*la(g))+S(d*ca(g)),c.height=S(e*ca(g))+S(d*la(g))}if(p){for(;n.length>250;)delete m[n.shift()];m[p]||n.push(p);m[p]=c}}return c},show:function(a){return this.attr({visibility:a? -"inherit":"visible"})},hide:function(){return this.attr({visibility:"hidden"})},fadeOut:function(a){var b=this;b.animate({opacity:0},{duration:a||150,complete:function(){b.attr({y:-9999})}})},add:function(a){var b=this.renderer,c=this.element,d;if(a)this.parentGroup=a;this.parentInverted=a&&a.inverted;this.textStr!==void 0&&b.buildText(this);this.added=!0;if(!a||a.handleZ||this.zIndex)d=this.zIndexSetter();d||(a?a.element:b.box).appendChild(c);if(this.onAdd)this.onAdd();return this},safeRemoveChild:function(a){var b= -a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||{},c=a.shadows,d=a.renderer.isSVG&&b.nodeName==="SPAN"&&a.parentGroup,e,f;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=b.point=null;Sa(a);if(a.clipPath)a.clipPath=a.clipPath.destroy();if(a.stops){for(f=0;f]*>/g,"")))},textSetter:function(a){if(a!==this.textStr)delete this.bBox,this.textStr=a,this.added&&this.renderer.buildText(this)},fillSetter:function(a,b,c){typeof a==="string"?c.setAttribute(b,a): -a&&this.colorGradient(a,b,c)},visibilitySetter:function(a,b,c){a==="inherit"?c.removeAttribute(b):c.setAttribute(b,a)},zIndexSetter:function(a,b){var c=this.renderer,d=this.parentGroup,c=(d||c).element||c.box,e,f,g=this.element,h;e=this.added;var i;if(v(a))g.zIndex=a,a=+a,this[b]===a&&(e=!1),this[b]=a;if(e){if((a=this.zIndex)&&d)d.handleZ=!0;d=c.childNodes;for(i=0;ia||!v(a)&&v(f)))c.insertBefore(g,e),h=!0;h||c.appendChild(g)}return h},_defaultSetter:function(a, -b,c){c.setAttribute(b,a)}};Z.prototype.yGetter=Z.prototype.xGetter;Z.prototype.translateXSetter=Z.prototype.translateYSetter=Z.prototype.rotationSetter=Z.prototype.verticalAlignSetter=Z.prototype.scaleXSetter=Z.prototype.scaleYSetter=function(a,b){this[b]=a;this.doTransform=!0};Z.prototype["stroke-widthSetter"]=Z.prototype.strokeSetter=function(a,b,c){this[b]=a;if(this.stroke&&this["stroke-width"])this.strokeWidth=this["stroke-width"],Z.prototype.fillSetter.call(this,this.stroke,"stroke",c),c.setAttribute("stroke-width", -this["stroke-width"]),this.hasStroke=!0;else if(b==="stroke-width"&&a===0&&this.hasStroke)c.removeAttribute("stroke"),this.hasStroke=!1};var xa=function(){this.init.apply(this,arguments)};xa.prototype={Element:Z,init:function(a,b,c,d,e,f){var g,d=this.createElement("svg").attr({version:"1.1"}).css(this.getStyle(d));g=d.element;a.appendChild(g);a.innerHTML.indexOf("xmlns")===-1&&X(g,"xmlns",Qa);this.isSVG=!0;this.box=g;this.boxWrapper=d;this.alignedObjects=[];this.url=(Wa||sb)&&H.getElementsByTagName("base").length? -L.location.href.replace(/#.*?$/,"").replace(/([\('\)])/g,"\\$1").replace(/ /g,"%20"):"";this.createElement("desc").add().element.appendChild(H.createTextNode("Created with Highstock 4.2.5"));this.defs=this.createElement("defs").add();this.allowHTML=f;this.forExport=e;this.gradients={};this.cache={};this.cacheKeys=[];this.imgCount=0;this.setSize(b,c,!1);var h;if(Wa&&a.getBoundingClientRect)this.subPixelFix=b=function(){N(a,{left:0,top:0});h=a.getBoundingClientRect();N(a,{left:Ea(h.left)-h.left+"px", -top:Ea(h.top)-h.top+"px"})},b(),E(L,"resize",b)},getStyle:function(a){return this.style=A({fontFamily:'"Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif',fontSize:"12px"},a)},isHidden:function(){return!this.boxWrapper.getBBox().width},destroy:function(){var a=this.defs;this.box=null;this.boxWrapper=this.boxWrapper.destroy();Pa(this.gradients||{});this.gradients=null;if(a)this.defs=a.destroy();this.subPixelFix&&T(L,"resize",this.subPixelFix);return this.alignedObjects=null},createElement:function(a){var b= -new this.Element;b.init(this,a);return b},draw:function(){},getRadialAttr:function(a,b){return{cx:a[0]-a[2]/2+b.cx*a[2],cy:a[1]-a[2]/2+b.cy*a[2],r:b.r*a[2]}},buildText:function(a){for(var b=a.element,c=this,d=c.forExport,e=q(a.textStr,"").toString(),f=e.indexOf("<")!==-1,g=b.childNodes,h,i,j,k=X(b,"x"),l=a.styles,m=a.textWidth,n=l&&l.lineHeight,p=l&&l.textShadow,r=l&&l.textOverflow==="ellipsis",s=g.length,F=m&&!a.added&&this.box,u=function(a){return n?K(n):c.fontMetrics(/(px|em)$/.test(a&&a.style.fontSize)? -a.style.fontSize:l&&l.fontSize||c.style.fontSize||12,a).h},x=function(a){return a.replace(/</g,"<").replace(/>/g,">")};s--;)b.removeChild(g[s]);!f&&!p&&!r&&e.indexOf(" ")===-1?b.appendChild(H.createTextNode(x(e))):(h=/<.*style="([^"]+)".*>/,i=/<.*href="(http[^"]+)".*>/,F&&F.appendChild(b),e=f?e.replace(/<(b|strong)>/g,'').replace(/<(i|em)>/g,'').replace(/
/g,"").split(//g): -[e],e=Fa(e,function(a){return a!==""}),o(e,function(e,f){var g,n=0,e=e.replace(/^\s+|\s+$/g,"").replace(//g,"|||");g=e.split("|||");o(g,function(e){if(e!==""||g.length===1){var p={},s=H.createElementNS(Qa,"tspan"),F;h.test(e)&&(F=e.match(h)[1].replace(/(;| |^)color([ :])/,"$1fill$2"),X(s,"style",F));i.test(e)&&!d&&(X(s,"onclick",'location.href="'+e.match(i)[1]+'"'),N(s,{cursor:"pointer"}));e=x(e.replace(/<(.|\n)*?>/g,"")||" ");if(e!==" "){s.appendChild(H.createTextNode(e)); -if(n)p.dx=0;else if(f&&k!==null)p.x=k;X(s,p);b.appendChild(s);!n&&f&&(!ja&&d&&N(s,{display:"block"}),X(s,"dy",u(s)));if(m){for(var p=e.replace(/([^\^])-/g,"$1- ").split(" "),q=g.length>1||f||p.length>1&&l.whiteSpace!=="nowrap",o,D,v=[],w=u(s),t=1,y=a.rotation,A=e,B=A.length;(q||r)&&(p.length||v.length);)a.rotation=0,o=a.getBBox(!0),D=o.width,!ja&&c.forExport&&(D=c.measureSpanWidth(s.firstChild.data,a.styles)),o=D>m,j===void 0&&(j=o),r&&j?(B/=2,A===""||!o&&B<0.5?p=[]:(A=e.substring(0,A.length+(o?-1: -1)*Ea(B)),p=[A+(m>3?"\u2026":"")],s.removeChild(s.firstChild))):!o||p.length===1?(p=v,v=[],p.length&&(t++,s=H.createElementNS(Qa,"tspan"),X(s,{dy:w,x:k}),F&&X(s,"style",F),b.appendChild(s)),D>m&&(m=D)):(s.removeChild(s.firstChild),v.unshift(p.pop())),p.length&&s.appendChild(H.createTextNode(p.join(" ").replace(/- /g,"-")));a.rotation=y}n++}}})}),j&&a.attr("title",a.textStr),F&&F.removeChild(b),p&&a.applyTextShadow&&a.applyTextShadow(p))},getContrast:function(a){a=va(a).rgba;return a[0]+a[1]+a[2]> -384?"#000000":"#FFFFFF"},button:function(a,b,c,d,e,f,g,h,i){var j=this.label(a,b,c,i,null,null,null,null,"button"),k=0,l,m,n,p,r,s,a={x1:0,y1:0,x2:0,y2:1},e=z({"stroke-width":1,stroke:"#CCCCCC",fill:{linearGradient:a,stops:[[0,"#FEFEFE"],[1,"#F6F6F6"]]},r:2,padding:5,style:{color:"black"}},e);n=e.style;delete e.style;f=z(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#FFF"],[1,"#ACF"]]}},f);p=f.style;delete f.style;g=z(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#9BD"],[1,"#CDF"]]}},g); -r=g.style;delete g.style;h=z(e,{style:{color:"#CCC"}},h);s=h.style;delete h.style;E(j.element,Ka?"mouseover":"mouseenter",function(){k!==3&&j.attr(f).css(p)});E(j.element,Ka?"mouseout":"mouseleave",function(){k!==3&&(l=[e,f,g][k],m=[n,p,r][k],j.attr(l).css(m))});j.setState=function(a){(j.state=k=a)?a===2?j.attr(g).css(r):a===3&&j.attr(h).css(s):j.attr(e).css(n)};return j.on("click",function(a){k!==3&&d.call(j,a)}).attr(e).css(A({cursor:"default"},n))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]= -y(a[1])-b%2/2);a[2]===a[5]&&(a[2]=a[5]=y(a[2])+b%2/2);return a},path:function(a){var b={fill:"none"};Ja(a)?b.d=a:ea(a)&&A(b,a);return this.createElement("path").attr(b)},circle:function(a,b,c){a=ea(a)?a:{x:a,y:b,r:c};b=this.createElement("circle");b.xSetter=b.ySetter=function(a,b,c){c.setAttribute("c"+b,a)};return b.attr(a)},arc:function(a,b,c,d,e,f){if(ea(a))b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x;a=this.symbol("arc",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||0});a.r=c;return a},rect:function(a, -b,c,d,e,f){var e=ea(a)?a.r:e,g=this.createElement("rect"),a=ea(a)?a:a===t?{}:{x:a,y:b,width:w(c,0),height:w(d,0)};if(f!==t)g.strokeWidth=f,a=g.crisp(a);if(e)a.r=e;g.rSetter=function(a,b,c){X(c,{rx:a,ry:a})};return g.attr(a)},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length;this.width=a;this.height=b;for(this.boxWrapper[q(c,!0)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return v(a)?b.attr({"class":"highcharts-"+a}):b},image:function(a, -b,c,d,e){var f={preserveAspectRatio:"none"};arguments.length>1&&A(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f);f.element.setAttributeNS?f.element.setAttributeNS("http://www.w3.org/1999/xlink","href",a):f.element.setAttribute("hc-svg-href",a);return f},symbol:function(a,b,c,d,e,f){var g=this,h,i=this.symbols[a],i=i&&i(y(b),y(c),d,e,f),j=/^url\((.*?)\)$/,k,l;if(i)h=this.path(i),A(h,{symbolName:a,x:b,y:c,width:d,height:e}),f&&A(h,f);else if(j.test(a))l=function(a,b){a.element&& -(a.attr({width:b[0],height:b[1]}),a.alignByTranslate||a.translate(y((d-b[0])/2),y((e-b[1])/2)))},k=a.match(j)[1],a=Vb[k]||f&&f.width&&f.height&&[f.width,f.height],h=this.image(k).attr({x:b,y:c}),h.isImg=!0,a?l(h,a):(h.attr({width:0,height:0}),fa("img",{onload:function(){this.width===0&&(N(this,{position:"absolute",top:"-999em"}),H.body.appendChild(this));l(h,Vb[k]=[this.width,this.height]);this.parentNode&&this.parentNode.removeChild(this);g.imgCount--;if(!g.imgCount&&$[g.chartIndex].onload)$[g.chartIndex].onload()}, -src:k}),this.imgCount++);return h},symbols:{circle:function(a,b,c,d){var e=0.166*c;return["M",a+c/2,b,"C",a+c+e,b,a+c+e,b+d,a+c/2,b+d,"C",a-e,b+d,a-e,b,a+c/2,b,"Z"]},square:function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c,b+d,a,b+d,"Z"]},triangle:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d,a,b+d,"Z"]},"triangle-down":function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c/2,b+d,"Z"]},diamond:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d/2,a+c/2,b+d,a,b+d/2,"Z"]},arc:function(a,b,c,d,e){var f=e.start, -c=e.r||c||d,g=e.end-0.001,d=e.innerR,h=e.open,i=ca(f),j=la(f),k=ca(g),g=la(g),e=e.end-fc&&e>b+g&&eb+g&&ed&&h>a+g&&ha+g&&hk&&/[ \-]/.test(b.textContent||b.innerText))N(b,{width:k+"px",display:"block",whiteSpace:l||"normal"}),this.hasTextWidth=!0;else if(this.hasTextWidth)N(b,{width:"",display:"",whiteSpace:l|| -"nowrap"}),this.hasTextWidth=!1;this.getSpanCorrection(this.hasTextWidth?k:b.offsetWidth,j,h,i,g)}N(b,{left:e+(this.xCorr||0)+"px",top:f+(this.yCorr||0)+"px"});if(sb)j=b.offsetHeight;this.cTT=m}}else this.alignOnAdd=!0},setSpanRotation:function(a,b,c){var d={},e=Ka?"-ms-transform":sb?"-webkit-transform":Wa?"MozTransform":Ub?"-o-transform":"";d[e]=d.transform="rotate("+a+"deg)";d[e+(Wa?"Origin":"-origin")]=d.transformOrigin=b*100+"% "+c+"px";N(this.element,d)},getSpanCorrection:function(a,b,c){this.xCorr= --a*c;this.yCorr=-b}});A(xa.prototype,{html:function(a,b,c){var d=this.createElement("span"),e=d.element,f=d.renderer,g=f.isSVG,h=function(a,b){o(["opacity","visibility"],function(c){U(a,c+"Setter",function(a,c,d,e){a.call(this,c,d,e);b[d]=c})})};d.textSetter=function(a){a!==e.innerHTML&&delete this.bBox;e.innerHTML=this.textStr=a;d.htmlUpdateTransform()};g&&h(d,d.element.style);d.xSetter=d.ySetter=d.alignSetter=d.rotationSetter=function(a,b){b==="align"&&(b="textAlign");d[b]=a;d.htmlUpdateTransform()}; -d.attr({text:a,x:y(b),y:y(c)}).css({position:"absolute",fontFamily:this.style.fontFamily,fontSize:this.style.fontSize});e.style.whiteSpace="nowrap";d.css=d.htmlCss;if(g)d.add=function(a){var b,c=f.box.parentNode,g=[];if(this.parentGroup=a){if(b=a.div,!b){for(;a;)g.push(a),a=a.parentGroup;o(g.reverse(),function(a){var d,e=X(a.element,"class");e&&(e={className:e});b=a.div=a.div||fa(Va,e,{position:"absolute",left:(a.translateX||0)+"px",top:(a.translateY||0)+"px",opacity:a.opacity},b||c);d=b.style;A(a, -{translateXSetter:function(b,c){d.left=b+"px";a[c]=b;a.doTransform=!0},translateYSetter:function(b,c){d.top=b+"px";a[c]=b;a.doTransform=!0}});h(a,d)})}}else b=c;b.appendChild(e);d.added=!0;d.alignOnAdd&&d.htmlUpdateTransform();return d};return d}});var lb,aa;if(!ja&&!qa)aa={init:function(a,b){var c=["<",b,' filled="f" stroked="f"'],d=["position: ","absolute",";"],e=b===Va;(b==="shape"||e)&&d.push("left:0;top:0;width:1px;height:1px;");d.push("visibility: ",e?"hidden":"visible");c.push(' style="',d.join(""), -'"/>');if(b)c=e||b==="span"||b==="img"?c.join(""):a.prepVML(c),this.element=fa(c);this.renderer=a},add:function(a){var b=this.renderer,c=this.element,d=b.box,e=a&&a.inverted,d=a?a.element||a:d;if(a)this.parentGroup=a;e&&b.invertChild(c,d);d.appendChild(c);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform();if(this.onAdd)this.onAdd();return this},updateTransform:Z.prototype.htmlUpdateTransform,setSpanRotation:function(){var a=this.rotation,b=ca(a*pa),c=la(a*pa);N(this.element, -{filter:a?["progid:DXImageTransform.Microsoft.Matrix(M11=",b,", M12=",-c,", M21=",c,", M22=",b,", sizingMethod='auto expand')"].join(""):"none"})},getSpanCorrection:function(a,b,c,d,e){var f=d?ca(d*pa):1,g=d?la(d*pa):0,h=q(this.elemHeight,this.element.offsetHeight),i;this.xCorr=f<0&&-a;this.yCorr=g<0&&-h;i=f*g<0;this.xCorr+=g*b*(i?1-c:c);this.yCorr-=f*b*(d?i?c:1-c:1);e&&e!=="left"&&(this.xCorr-=a*c*(f<0?-1:1),d&&(this.yCorr-=h*c*(g<0?-1:1)),N(this.element,{textAlign:e}))},pathToVML:function(a){for(var b= -a.length,c=[];b--;)if(C(a[b]))c[b]=y(a[b]*10)-5;else if(a[b]==="Z")c[b]="x";else if(c[b]=a[b],a.isArc&&(a[b]==="wa"||a[b]==="at"))c[b+5]===c[b+7]&&(c[b+7]+=a[b+7]>a[b+5]?1:-1),c[b+6]===c[b+8]&&(c[b+8]+=a[b+8]>a[b+6]?1:-1);return c.join(" ")||"x"},clip:function(a){var b=this,c;a?(c=a.members,za(c,b),c.push(b),b.destroyClip=function(){za(c,b)},a=a.getCSS(b)):(b.destroyClip&&b.destroyClip(),a={clip:rb?"inherit":"rect(auto)"});return b.css(a)},css:Z.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&& -Ua(a)},destroy:function(){this.destroyClip&&this.destroyClip();return Z.prototype.destroy.apply(this)},on:function(a,b){this.element["on"+a]=function(){var a=L.event;a.target=a.srcElement;b(a)};return this},cutOffPath:function(a,b){var c,a=a.split(/[ ,]/);c=a.length;if(c===9||c===11)a[c-4]=a[c-2]=K(a[c-2])-10*b;return a.join(" ")},shadow:function(a,b,c){var d=[],e,f=this.element,g=this.renderer,h,i=f.style,j,k=f.path,l,m,n,p;k&&typeof k.value!=="string"&&(k="x");m=k;if(a){n=q(a.width,3);p=(a.opacity|| -0.15)/n;for(e=1;e<=3;e++){l=n*2+1-2*e;c&&(m=this.cutOffPath(k.value,l+0.5));j=[''];h=fa(g.prepVML(j),null,{left:K(i.left)+q(a.offsetX,1),top:K(i.top)+q(a.offsetY,1)});if(c)h.cutOff=l+1;j=[''];fa(g.prepVML(j),null,null,h);b?b.element.appendChild(h):f.parentNode.insertBefore(h,f);d.push(h)}this.shadows=d}return this},updateShadows:ra, -setAttr:function(a,b){rb?this.element[a]=b:this.element.setAttribute(a,b)},classSetter:function(a){this.element.className=a},dashstyleSetter:function(a,b,c){(c.getElementsByTagName("stroke")[0]||fa(this.renderer.prepVML([""]),null,null,c))[b]=a||"solid";this[b]=a},dSetter:function(a,b,c){var d=this.shadows,a=a||[];this.d=a.join&&a.join(" ");c.path=a=this.pathToVML(a);if(d)for(c=d.length;c--;)d[c].path=d[c].cutOff?this.cutOffPath(a,d[c].cutOff):a;this.setAttr(b,a)},fillSetter:function(a,b, -c){var d=c.nodeName;if(d==="SPAN")c.style.color=a;else if(d!=="IMG")c.filled=a!=="none",this.setAttr("fillcolor",this.renderer.color(a,c,b,this))},"fill-opacitySetter":function(a,b,c){fa(this.renderer.prepVML(["<",b.split("-")[0],' opacity="',a,'"/>']),null,null,c)},opacitySetter:ra,rotationSetter:function(a,b,c){c=c.style;this[b]=c[b]=a;c.left=-y(la(a*pa)+1)+"px";c.top=y(ca(a*pa))+"px"},strokeSetter:function(a,b,c){this.setAttr("strokecolor",this.renderer.color(a,c,b,this))},"stroke-widthSetter":function(a, -b,c){c.stroked=!!a;this[b]=a;C(a)&&(a+="px");this.setAttr("strokeweight",a)},titleSetter:function(a,b){this.setAttr(b,a)},visibilitySetter:function(a,b,c){a==="inherit"&&(a="visible");this.shadows&&o(this.shadows,function(c){c.style[b]=a});c.nodeName==="DIV"&&(a=a==="hidden"?"-999em":0,rb||(c.style[b]=a?"visible":"hidden"),b="top");c.style[b]=a},xSetter:function(a,b,c){this[b]=a;b==="x"?b="left":b==="y"&&(b="top");this.updateClipping?(this[b]=a,this.updateClipping()):c.style[b]=a},zIndexSetter:function(a, -b,c){c.style[b]=a}},aa["stroke-opacitySetter"]=aa["fill-opacitySetter"],B.VMLElement=aa=ma(Z,aa),aa.prototype.ySetter=aa.prototype.widthSetter=aa.prototype.heightSetter=aa.prototype.xSetter,aa={Element:aa,isIE8:Na.indexOf("MSIE 8.0")>-1,init:function(a,b,c,d){var e;this.alignedObjects=[];d=this.createElement(Va).css(A(this.getStyle(d),{position:"relative"}));e=d.element;a.appendChild(d.element);this.isVML=!0;this.box=e;this.boxWrapper=d;this.gradients={};this.cache={};this.cacheKeys=[];this.imgCount= -0;this.setSize(b,c,!1);if(!H.namespaces.hcv){H.namespaces.add("hcv","urn:schemas-microsoft-com:vml");try{H.createStyleSheet().cssText="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}catch(f){H.styleSheets[0].cssText+="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}}},isHidden:function(){return!this.box.offsetWidth},clipRect:function(a,b,c,d){var e=this.createElement(),f=ea(a);return A(e, -{members:[],count:0,left:(f?a.x:a)+1,top:(f?a.y:b)+1,width:(f?a.width:c)-1,height:(f?a.height:d)-1,getCSS:function(a){var b=a.element,c=b.nodeName,a=a.inverted,d=this.top-(c==="shape"?b.offsetTop:0),e=this.left,b=e+this.width,f=d+this.height,d={clip:"rect("+y(a?e:d)+"px,"+y(a?f:b)+"px,"+y(a?b:f)+"px,"+y(a?d:e)+"px)"};!a&&rb&&c==="DIV"&&A(d,{width:b+"px",height:f+"px"});return d},updateClipping:function(){o(e.members,function(a){a.element&&a.css(e.getCSS(a))})}})},color:function(a,b,c,d){var e=this, -f,g=/^rgba/,h,i,j="none";a&&a.linearGradient?i="gradient":a&&a.radialGradient&&(i="pattern");if(i){var k,l,m=a.linearGradient||a.radialGradient,n,p,r,s,q,u="",a=a.stops,x,D=[],Q=function(){h=[''];fa(e.prepVML(h),null,null,b)};n=a[0];x=a[a.length-1];n[0]>0&&a.unshift([0,n[1]]);x[0]<1&&a.push([1,x[1]]);o(a,function(a,b){g.test(a[1])?(f=va(a[1]),k=f.get("rgb"),l=f.get("a")):(k=a[1],l=1);D.push(a[0]* -100+"% "+k);b?(r=l,s=k):(p=l,q=k)});if(c==="fill")if(i==="gradient")c=m.x1||m[0]||0,a=m.y1||m[1]||0,n=m.x2||m[2]||0,m=m.y2||m[3]||0,u='angle="'+(90-Y.atan((m-a)/(n-c))*180/Aa)+'"',Q();else{var j=m.r,ha=j*2,v=j*2,w=m.cx,t=m.cy,y=b.radialReference,A,j=function(){y&&(A=d.getBBox(),w+=(y[0]-A.x)/A.width-0.5,t+=(y[1]-A.y)/A.height-0.5,ha*=y[2]/A.width,v*=y[2]/A.height);u='src="'+R.global.VMLRadialGradientURL+'" size="'+ha+","+v+'" origin="0.5,0.5" position="'+w+","+t+'" color2="'+q+'" ';Q()};d.added?j(): -d.onAdd=j;j=s}else j=k}else if(g.test(a)&&b.tagName!=="IMG")f=va(a),d[c+"-opacitySetter"](f.get("a"),c,b),j=f.get("rgb");else{j=b.getElementsByTagName(c);if(j.length)j[0].opacity=1,j[0].type="solid";j=a}return j},prepVML:function(a){var b=this.isIE8,a=a.join("");b?(a=a.replace("/>",' xmlns="urn:schemas-microsoft-com:vml" />'),a=a.indexOf('style="')===-1?a.replace("/>",' style="display:inline-block;behavior:url(#default#VML);" />'):a.replace('style="','style="display:inline-block;behavior:url(#default#VML);')): -a=a.replace("<"," -1&&f.attr({x:b,y:c,width:d,height:e});return f},createElement:function(a){return a==="rect"?this.symbol(a):xa.prototype.createElement.call(this,a)},invertChild:function(a,b){var c=this,d=b.style,e=a.tagName==="IMG"&&a.style;N(a,{flip:"x",left:K(d.width)-(e?K(e.top):1),top:K(d.height)-(e?K(e.left):1),rotation:-90});o(a.childNodes,function(b){c.invertChild(b,a)})},symbols:{arc:function(a,b,c,d,e){var f=e.start,g=e.end,h=e.r||c||d,c=e.innerR,d=ca(f),i=la(f),j=ca(g),k=la(g);if(g-f===0)return["x"];f=["wa", -a-h,b-h,a+h,b+h,a+h*d,b+h*i,a+h*j,b+h*k];e.open&&!c&&f.push("e","M",a,b);f.push("at",a-c,b-c,a+c,b+c,a+c*j,b+c*k,a+c*d,b+c*i,"x","e");f.isArc=!0;return f},circle:function(a,b,c,d,e){e&&(c=d=2*e.r);e&&e.isCircle&&(a-=c/2,b-=d/2);return["wa",a,b,a+c,b+d,a+c,b+d/2,a+c,b+d/2,"e"]},rect:function(a,b,c,d,e){return xa.prototype.symbols[!v(e)||!e.r?"square":"callout"].call(0,a,b,c,d,e)}}},B.VMLRenderer=lb=function(){this.init.apply(this,arguments)},lb.prototype=z(xa.prototype,aa),Xa=lb;xa.prototype.measureSpanWidth= -function(a,b){var c=H.createElement("span"),d;d=H.createTextNode(a);c.appendChild(d);N(c,b);this.box.appendChild(c);d=c.offsetWidth;Ua(c);return d};var Wb;if(qa)B.CanVGRenderer=aa=function(){Qa="http://www.w3.org/1999/xhtml"},aa.prototype.symbols={},Wb=function(){function a(){var a=b.length,d;for(d=0;d0&&c+i*j>e&&(n=y((d-c)/ca(h*pa)));else if(d=c+(1-i)*j,c-i*je&&(l=e-a.x+l*i,m=-1),l=G(k,l),ll||b.autoRotation&&g.styles.width)n=l;if(n){p.width=n;if(!b.options.labels.style.textOverflow)p.textOverflow="ellipsis";g.css(p)}},getPosition:function(a,b,c,d){var e=this.axis,f=e.chart,g=d&&f.oldChartHeight||f.chartHeight;return{x:a?e.translate(b+c,null,null,d)+e.transB:e.left+e.offset+ -(e.opposite?(d&&f.oldChartWidth||f.chartWidth)-e.right-e.left:0),y:a?g-e.bottom+e.offset-(e.opposite?e.height:0):g-e.translate(b+c,null,null,d)-e.transB}},getLabelPosition:function(a,b,c,d,e,f,g,h){var i=this.axis,j=i.transA,k=i.reversed,l=i.staggerLines,m=i.tickRotCorr||{x:0,y:0},n=e.y;v(n)||(n=i.side===0?c.rotation?-8:-c.getBBox().height:i.side===2?m.y+8:ca(c.rotation*pa)*(m.y-c.getBBox(!1,0).height/2));a=a+e.x+m.x-(f&&d?f*j*(k?-1:1):0);b=b+n-(f&&!d?f*j*(k?1:-1):0);l&&(c=g/(h||1)%l,i.opposite&& -(c=l-c-1),b+=c*(i.labelOffset/l));return{x:a,y:y(b)}},getMarkPath:function(a,b,c,d,e,f){return f.crispLine(["M",a,b,"L",a+(e?0:-c),b+(e?c:0)],d)},render:function(a,b,c){var d=this.axis,e=d.options,f=d.chart.renderer,g=d.horiz,h=this.type,i=this.label,j=this.pos,k=e.labels,l=this.gridLine,m=h?h+"Grid":"grid",n=h?h+"Tick":"tick",p=e[m+"LineWidth"],r=e[m+"LineColor"],s=e[m+"LineDashStyle"],m=d.tickSize(n),n=e[n+"Color"],F=this.mark,u=k.step,o=!0,D=d.tickmarkOffset,Q=this.getPosition(g,j,D,b),ha=Q.x, -Q=Q.y,v=g&&ha===d.pos+d.len||!g&&Q===d.pos?-1:1,c=q(c,1);this.isActive=!0;if(p){j=d.getPlotLinePath(j+D,p*v,b,!0);if(l===t){l={stroke:r,"stroke-width":p};if(s)l.dashstyle=s;if(!h)l.zIndex=1;if(b)l.opacity=0;this.gridLine=l=p?f.path(j).attr(l).add(d.gridGroup):null}if(!b&&l&&j)l[this.isNew?"attr":"animate"]({d:j,opacity:c})}if(m)d.opposite&&(m[0]=-m[0]),h=this.getMarkPath(ha,Q,m[0],m[1]*v,g,f),F?F.animate({d:h,opacity:c}):this.mark=f.path(h).attr({stroke:n,"stroke-width":m[1],opacity:c}).add(d.axisGroup); -if(i&&C(ha))i.xy=Q=this.getLabelPosition(ha,Q,i,g,k,D,a,u),this.isFirst&&!this.isLast&&!q(e.showFirstLabel,1)||this.isLast&&!this.isFirst&&!q(e.showLastLabel,1)?o=!1:g&&!d.isRadial&&!k.step&&!k.rotation&&!b&&c!==0&&this.handleOverflow(Q),u&&a%u&&(o=!1),o&&C(Q.y)?(Q.opacity=c,i[this.isNew?"attr":"animate"](Q),this.isNew=!1):i.attr("y",-9999)},destroy:function(){Pa(this,this.axis)}};B.PlotLineOrBand=function(a,b){this.axis=a;if(b)this.options=b,this.id=b.id};B.PlotLineOrBand.prototype={render:function(){var a= -this,b=a.axis,c=b.horiz,d=a.options,e=d.label,f=a.label,g=d.width,h=d.to,i=d.from,j=v(i)&&v(h),k=d.value,l=d.dashStyle,m=a.svgElem,n=[],p,r=d.color,s=q(d.zIndex,0),F=d.events,u={},o=b.chart.renderer,n=b.log2lin;b.isLog&&(i=n(i),h=n(h),k=n(k));if(g){if(n=b.getPlotLinePath(k,g),u={stroke:r,"stroke-width":g},l)u.dashstyle=l}else if(j){n=b.getPlotBandPath(i,h,d);if(r)u.fill=r;if(d.borderWidth)u.stroke=d.borderColor,u["stroke-width"]=d.borderWidth}else return;u.zIndex=s;if(m)if(n)m.show(),m.animate({d:n}); -else{if(m.hide(),f)a.label=f=f.destroy()}else if(n&&n.length&&(a.svgElem=m=o.path(n).attr(u).add(),F))for(p in d=function(b){m.on(b,function(c){F[b].apply(a,[c])})},F)d(p);e&&v(e.text)&&n&&n.length&&b.width>0&&b.height>0&&!n.flat?(e=z({align:c&&j&&"center",x:c?!j&&4:10,verticalAlign:!c&&j&&"middle",y:c?j?16:10:j?6:-4,rotation:c&&!j&&90},e),this.renderLabel(e,n,j,s)):f&&f.hide();return a},renderLabel:function(a,b,c,d){var e=this.label,f=this.axis.chart.renderer;if(!e)e={align:a.textAlign||a.align, -rotation:a.rotation},e.zIndex=d,this.label=e=f.text(a.text,0,0,a.useHTML).attr(e).css(a.style).add();d=[b[1],b[4],c?b[6]:b[1]];b=[b[2],b[5],c?b[7]:b[2]];c=Ma(d);f=Ma(b);e.align(a,!1,{x:c,y:f,width:Da(d)-c,height:Da(b)-f});e.show()},destroy:function(){za(this.axis.plotLinesAndBands,this);delete this.axis;Pa(this)}};var J=B.Axis=function(){this.init.apply(this,arguments)};J.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:"%H:%M:%S.%L",second:"%H:%M:%S",minute:"%H:%M",hour:"%H:%M",day:"%e. %b", -week:"%e. %b",month:"%b '%y",year:"%Y"},endOnTick:!1,gridLineColor:"#D8D8D8",labels:{enabled:!0,style:{color:"#606060",cursor:"default",fontSize:"11px"},x:0},lineColor:"#C0D0E0",lineWidth:1,minPadding:0.01,maxPadding:0.01,minorGridLineColor:"#E0E0E0",minorGridLineWidth:1,minorTickColor:"#A0A0A0",minorTickLength:2,minorTickPosition:"outside",startOfWeek:1,startOnTick:!1,tickColor:"#C0D0E0",tickLength:10,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",title:{align:"middle", -style:{color:"#707070"}},type:"linear"},defaultYAxisOptions:{endOnTick:!0,gridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0,title:{rotation:270,text:"Values"},stackLabels:{enabled:!1,formatter:function(){return B.numberFormat(this.total,-1)},style:z(W.line.dataLabels.style,{color:"#000000"})}},defaultLeftAxisOptions:{labels:{x:-15},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:15},title:{rotation:90}},defaultBottomAxisOptions:{labels:{autoRotation:[-45], -x:0},title:{rotation:0}},defaultTopAxisOptions:{labels:{autoRotation:[-45],x:0},title:{rotation:0}},init:function(a,b){var c=b.isX;this.chart=a;this.horiz=a.inverted?!c:c;this.coll=(this.isXAxis=c)?"xAxis":"yAxis";this.opposite=b.opposite;this.side=b.side||(this.horiz?this.opposite?0:2:this.opposite?1:3);this.setOptions(b);var d=this.options,e=d.type;this.labelFormatter=d.labels.formatter||this.defaultLabelFormatter;this.userOptions=b;this.minPixelPadding=0;this.reversed=d.reversed;this.visible=d.visible!== -!1;this.zoomEnabled=d.zoomEnabled!==!1;this.categories=d.categories||e==="category";this.names=this.names||[];this.isLog=e==="logarithmic";this.isDatetimeAxis=e==="datetime";this.isLinked=v(d.linkedTo);this.ticks={};this.labelEdge=[];this.minorTicks={};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange=this.userMinRange=d.minRange||d.maxZoom;this.range=d.range;this.offset=d.offset||0;this.stacks={};this.oldStacks={};this.stacksTouched=0;this.min=this.max=null;this.crosshair= -q(d.crosshair,ua(a.options.tooltip.crosshairs)[c?0:1],!1);var f,d=this.options.events;sa(this,a.axes)===-1&&(c&&!this.isColorAxis?a.axes.splice(a.xAxis.length,0,this):a.axes.push(this),a[this.coll].push(this));this.series=this.series||[];if(a.inverted&&c&&this.reversed===t)this.reversed=!0;this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;for(f in d)E(this,f,d[f]);if(this.isLog)this.val2lin=this.log2lin,this.lin2val=this.lin2log},setOptions:function(a){this.options=z(this.defaultOptions, -this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],z(R[this.coll],a))},defaultLabelFormatter:function(){var a=this.axis,b=this.value,c=a.categories,d=this.dateTimeLabelFormat,e=R.lang.numericSymbols,f=e&&e.length,g,h=a.options.labels.format,a=a.isLog?b:a.tickInterval;if(h)g=La(h,this);else if(c)g=b;else if(d)g=na(d,b);else if(f&&a>=1E3)for(;f--&&g===t;)c=Math.pow(1E3,f+1),a>=c&&b*10% -c===0&&e[f]!==null&&(g=B.numberFormat(b/c,-1)+e[f]);g===t&&(g=S(b)>=1E4?B.numberFormat(b,-1):B.numberFormat(b,-1,t,""));return g},getSeriesExtremes:function(){var a=this,b=a.chart;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=a.threshold=null;a.softThreshold=!a.isXAxis;a.buildStacks&&a.buildStacks();o(a.series,function(c){if(c.visible||!b.options.chart.ignoreHiddenSeries){var d=c.options,e=d.threshold,f;a.hasVisibleSeries=!0;a.isLog&&e<=0&&(e=null);if(a.isXAxis){if(d=c.xData,d.length)c=Ma(d),!C(c)&&!(c instanceof -ba)&&(d=Fa(d,function(a){return C(a)}),c=Ma(d)),a.dataMin=G(q(a.dataMin,d[0]),c),a.dataMax=w(q(a.dataMax,d[0]),Da(d))}else{c.getExtremes();f=c.dataMax;c=c.dataMin;if(v(c)&&v(f))a.dataMin=G(q(a.dataMin,c),c),a.dataMax=w(q(a.dataMax,f),f);if(v(e))a.threshold=e;if(!d.softThreshold||a.isLog)a.softThreshold=!1}}})},translate:function(a,b,c,d,e,f){var g=this.linkedParent||this,h=1,i=0,j=d?g.oldTransA:g.transA,d=d?g.oldMin:g.min,k=g.minPixelPadding,e=(g.isOrdinal||g.isBroken||g.isLog&&e)&&g.lin2val;if(!j)j= -g.transA;if(c)h*=-1,i=g.len;g.reversed&&(h*=-1,i-=h*(g.sector||g.len));b?(a=a*h+i,a-=k,a=a/j+d,e&&(a=g.lin2val(a))):(e&&(a=g.val2lin(a)),f==="between"&&(f=0.5),a=h*(a-d)*j+i+h*k+(C(f)?j*f*g.pointRange:0));return a},toPixels:function(a,b){return this.translate(a,!1,!this.horiz,null,!0)+(b?0:this.pos)},toValue:function(a,b){return this.translate(a-(b?0:this.pos),!0,!this.horiz,null,!0)},getPlotLinePath:function(a,b,c,d,e){var f=this.chart,g=this.left,h=this.top,i,j,k=c&&f.oldChartHeight||f.chartHeight, -l=c&&f.oldChartWidth||f.chartWidth,m;i=this.transB;var n=function(a,b,c){if(ac)d?a=G(w(b,a),c):m=!0;return a},e=q(e,this.translate(a,null,null,c)),a=c=y(e+i);i=j=y(k-e-i);C(e)?this.horiz?(i=h,j=k-this.bottom,a=c=n(a,g,g+this.width)):(a=g,c=l-this.right,i=j=n(i,h,h+this.height)):m=!0;return m&&!d?null:f.renderer.crispLine(["M",a,i,"L",c,j],b||1)},getLinearTickPositions:function(a,b,c){var d,e=ka(V(b/a)*a),f=ka(Ea(c/a)*a),g=[];if(b===c&&C(b))return[b];for(b=e;b<=f;){g.push(b);b=ka(b+a);if(b=== -d)break;d=b}return g},getMinorTickPositions:function(){var a=this.options,b=this.tickPositions,c=this.minorTickInterval,d=[],e,f=this.pointRangePadding||0;e=this.min-f;var f=this.max+f,g=f-e;if(g&&g/c=this.minRange,f,g,h,i,j,k;if(this.isXAxis&&this.minRange===t&&!this.isLog)v(a.min)||v(a.max)?this.minRange=null:(o(this.series,function(a){i=a.xData;for(g=j=a.xIncrement?1:i.length-1;g>0;g--)if(h=i[g]-i[g-1],f===t||h=p?(s=p,k=0):b.dataMax<= -p&&(F=p,j=0)),b.min=q(u,s,b.dataMin),b.max=q(x,F,b.dataMax));if(e)!a&&G(b.min,q(b.dataMin,b.min))<=0&&ga(10,1),b.min=ka(f(b.min),15),b.max=ka(f(b.max),15);if(b.range&&v(b.max))b.userMin=b.min=u=w(b.min,b.minFromRange()),b.userMax=x=b.max,b.range=null;O(b,"foundExtremes");b.beforePadding&&b.beforePadding();b.adjustForMinRange();if(!n&&!b.axisPointRange&&!b.usePercentage&&!i&&v(b.min)&&v(b.max)&&(f=b.max-b.min))!v(u)&&k&&(b.min-=f*k),!v(x)&&j&&(b.max+=f*j);if(C(d.floor))b.min=w(b.min,d.floor);if(C(d.ceiling))b.max= -G(b.max,d.ceiling);if(r&&v(b.dataMin))if(p=p||0,!v(u)&&b.min=p)b.min=p;else if(!v(x)&&b.max>p&&b.dataMax<=p)b.max=p;b.tickInterval=b.min===b.max||b.min===void 0||b.max===void 0?1:i&&!l&&m===b.linkedParent.options.tickPixelInterval?l=b.linkedParent.tickInterval:q(l,this.tickAmount?(b.max-b.min)/w(this.tickAmount-1,1):void 0,n?1:(b.max-b.min)*m/w(b.len,m));h&&!a&&o(b.series,function(a){a.processData(b.min!==b.oldMin||b.max!==b.oldMax)});b.setAxisTranslation(!0);b.beforeSetTickPositions&& -b.beforeSetTickPositions();if(b.postProcessTickInterval)b.tickInterval=b.postProcessTickInterval(b.tickInterval);if(b.pointRange&&!l)b.tickInterval=w(b.pointRange,b.tickInterval);a=q(d.minTickInterval,b.isDatetimeAxis&&b.closestPointRange);if(!l&&b.tickInterval0.5&&b.tickInterval<5&&b.max>1E3&&b.max<9999)),!!this.tickAmount);if(!this.tickAmount&&this.len)b.tickInterval=b.unsquish(); -this.setTickPositions()},setTickPositions:function(){var a=this.options,b,c=a.tickPositions,d=a.tickPositioner,e=a.startOnTick,f=a.endOnTick,g;this.tickmarkOffset=this.categories&&a.tickmarkPlacement==="between"&&this.tickInterval===1?0.5:0;this.minorTickInterval=a.minorTickInterval==="auto"&&this.tickInterval?this.tickInterval/5:a.minorTickInterval;this.tickPositions=b=c&&c.slice();if(!b&&(b=this.isDatetimeAxis?this.getTimeTicks(this.normalizeTimeTickInterval(this.tickInterval,a.units),this.min, -this.max,a.startOfWeek,this.ordinalPositions,this.closestPointRange,!0):this.isLog?this.getLogTickPositions(this.tickInterval,this.min,this.max):this.getLinearTickPositions(this.tickInterval,this.min,this.max),b.length>this.len&&(b=[b[0],b.pop()]),this.tickPositions=b,d&&(d=d.apply(this,[this.min,this.max]))))this.tickPositions=b=d;if(!this.isLinked)this.trimTicks(b,e,f),this.min===this.max&&v(this.min)&&!this.tickAmount&&(g=!0,this.min-=0.5,this.max+=0.5),this.single=g,!c&&!d&&this.adjustTickAmount()}, -trimTicks:function(a,b,c){var d=a[0],e=a[a.length-1],f=this.minPointOffset||0;if(b)this.min=d;else for(;this.min-f>a[0];)a.shift();if(c)this.max=e;else for(;this.max+fc&&(this.tickInterval*=2,this.setTickPositions()); -if(v(d)){for(a=c=b.length;a--;)(d===3&&a%2===1||d<=2&&a>0&&a=e&&(b=e));this.displayBtn=a!==t||b!==t;this.setExtremes(a,b,!1,t,{trigger:"zoom"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,d=this.horiz,e=q(b.width,a.plotWidth-c+(b.offsetRight||0)),f=q(b.height,a.plotHeight),g=q(b.top,a.plotTop),b=q(b.left,a.plotLeft+c),c=/%$/;c.test(f)&&(f=Math.round(parseFloat(f)/100*a.plotHeight));c.test(g)&&(g=Math.round(parseFloat(g)/100*a.plotHeight+ -a.plotTop));this.left=b;this.top=g;this.width=e;this.height=f;this.bottom=a.chartHeight-f-g;this.right=a.chartWidth-e-b;this.len=w(d?e:f,0);this.pos=d?b:g},getExtremes:function(){var a=this.isLog,b=this.lin2log;return{min:a?ka(b(this.min)):this.min,max:a?ka(b(this.max)):this.max,dataMin:this.dataMin,dataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}},getThreshold:function(a){var b=this.isLog,c=this.lin2log,d=b?c(this.min):this.min,b=b?c(this.max):this.max;a===null?a=b<0?b:d:d>a?a=d:b< -a&&(a=b);return this.translate(a,0,1,0,1)},autoLabelAlign:function(a){a=(q(a,0)-this.side*90+720)%360;return a>15&&a<165?"right":a>195&&a<345?"left":"center"},tickSize:function(a){var b=this.options,c=b[a+"Length"],d=q(b[a+"Width"],a==="tick"&&this.isXAxis?1:0);if(d&&c)return b[a+"Position"]==="inside"&&(c=-c),[c,d]},labelMetrics:function(){return this.chart.renderer.fontMetrics(this.options.labels.style.fontSize,this.ticks[0]&&this.ticks[0].label)},unsquish:function(){var a=this.options.labels,b= -this.horiz,c=this.tickInterval,d=c,e=this.len/(((this.categories?1:0)+this.max-this.min)/c),f,g=a.rotation,h=this.labelMetrics(),i,j=Number.MAX_VALUE,k,l=function(a){a/=e||1;a=a>1?Ea(a):1;return a*c};b?(k=!a.staggerLines&&!a.step&&(v(g)?[g]:e=-90&&a<=90)i=l(S(h.h/la(pa*a))),b=i+S(a/360),bm)m=a.labelLength}),m>h&&m>j.h?i.rotation=this.labelRotation:this.labelRotation=0;else if(g&&(l={width:h+"px"},!k)){l.textOverflow="clip";for(n=c.length;!f&&n--;)if(p=c[n],h=d[p].label)if(h.styles.textOverflow==="ellipsis"?h.css({textOverflow:"clip"}):d[p].labelLength>g&&h.css({width:g+"px"}),h.getBBox().height>this.len/c.length-(j.h-j.f))h.specCss={textOverflow:"ellipsis"}}if(i.rotation&&(l={width:(m>a.chartHeight*0.5?a.chartHeight* -0.33:a.chartHeight)+"px"},!k))l.textOverflow="ellipsis";if(this.labelAlign=e.align||this.autoLabelAlign(this.labelRotation))i.align=this.labelAlign;o(c,function(a){var b=(a=d[a])&&a.label;if(b)b.attr(i),l&&b.css(z(l,b.specCss)),delete b.specCss,a.rotation=i.rotation});this.tickRotCorr=b.rotCorr(j.b,this.labelRotation||0,this.side!==0)},hasData:function(){return this.hasVisibleSeries||v(this.min)&&v(this.max)&&!!this.tickPositions},getOffset:function(){var a=this,b=a.chart,c=b.renderer,d=a.options, -e=a.tickPositions,f=a.ticks,g=a.horiz,h=a.side,i=b.inverted?[1,0,3,2][h]:h,j,k,l=0,m,n=0,p=d.title,r=d.labels,s=0,F=a.opposite,u=b.axisOffset,b=b.clipOffset,x=[-1,1,1,-1][h],D,Q=a.axisParent,ha=this.tickSize("tick");j=a.hasData();a.showAxis=k=j||q(d.showEmpty,!0);a.staggerLines=a.horiz&&r.staggerLines;if(!a.axisGroup)a.gridGroup=c.g("grid").attr({zIndex:d.gridZIndex||1}).add(Q),a.axisGroup=c.g("axis").attr({zIndex:d.zIndex||2}).add(Q),a.labelGroup=c.g("axis-labels").attr({zIndex:r.zIndex||7}).addClass("highcharts-"+ -a.coll.toLowerCase()+"-labels").add(Q);if(j||a.isLinked){if(o(e,function(b){f[b]?f[b].addLabel():f[b]=new bb(a,b)}),a.renderUnsquish(),r.reserveSpace!==!1&&(h===0||h===2||{1:"left",3:"right"}[h]===a.labelAlign||a.labelAlign==="center")&&o(e,function(a){s=w(f[a].getLabelSize(),s)}),a.staggerLines)s*=a.staggerLines,a.labelOffset=s*(a.opposite?-1:1)}else for(D in f)f[D].destroy(),delete f[D];if(p&&p.text&&p.enabled!==!1){if(!a.axisTitle)(D=p.textAlign)||(D=(g?{low:"left",middle:"center",high:"right"}: -{low:F?"right":"left",middle:"center",high:F?"left":"right"})[p.align]),a.axisTitle=c.text(p.text,0,0,p.useHTML).attr({zIndex:7,rotation:p.rotation||0,align:D}).addClass("highcharts-"+this.coll.toLowerCase()+"-title").css(p.style).add(a.axisGroup),a.axisTitle.isNew=!0;if(k)l=a.axisTitle.getBBox()[g?"height":"width"],m=p.offset,n=v(m)?0:q(p.margin,g?5:10);a.axisTitle[k?"show":"hide"](!0)}a.offset=x*q(d.offset,u[h]);a.tickRotCorr=a.tickRotCorr||{x:0,y:0};c=h===0?-a.labelMetrics().h:h===2?a.tickRotCorr.y: -0;n=Math.abs(s)+n;s&&(n-=c,n+=x*(g?q(r.y,a.tickRotCorr.y+x*8):r.x));a.axisTitleMargin=q(m,n);u[h]=w(u[h],a.axisTitleMargin+l+x*a.offset,n,j&&e.length&&ha?ha[0]:0);d=d.offset?0:V(d.lineWidth/2)*2;b[i]=w(b[i],d)},getLinePath:function(a){var b=this.chart,c=this.opposite,d=this.offset,e=this.horiz,f=this.left+(c?this.width:0)+d,d=b.chartHeight-this.bottom-(c?this.height:0)+d;c&&(a*=-1);return b.renderer.crispLine(["M",e?this.left:f,e?d:this.top,"L",e?b.chartWidth-this.right:f,e?d:b.chartHeight-this.bottom], -a)},getTitlePosition:function(){var a=this.horiz,b=this.left,c=this.top,d=this.len,e=this.options.title,f=a?b:c,g=this.opposite,h=this.offset,i=e.x||0,j=e.y||0,k=K(e.style.fontSize||12),d={low:f+(a?0:d),middle:f+d/2,high:f+(a?d:0)}[e.align],b=(a?c+this.height:b)+(a?1:-1)*(g?-1:1)*this.axisTitleMargin+(this.side===2?k:0);return{x:a?d+i:b+(g?this.width:0)+h+i,y:a?b+j-(g?this.height:0)+h:d+j}},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.options,e=a.isLog,f=a.lin2log,g=a.isLinked,h=a.tickPositions, -i=a.axisTitle,j=a.ticks,k=a.minorTicks,l=a.alternateBands,m=d.stackLabels,n=d.alternateGridColor,p=a.tickmarkOffset,r=d.lineWidth,s,q=b.hasRendered&&C(a.oldMin),u=a.showAxis,x=gb(c.globalAnimation),D,Q;a.labelEdge.length=0;a.overlap=!1;o([j,k,l],function(a){for(var b in a)a[b].isActive=!1});if(a.hasData()||g){a.minorTickInterval&&!a.categories&&o(a.getMinorTickPositions(),function(b){k[b]||(k[b]=new bb(a,b,"minor"));q&&k[b].isNew&&k[b].render(null,!0);k[b].render(null,!1,1)});if(h.length&&(o(h,function(b, -c){if(!g||b>=a.min&&b<=a.max)j[b]||(j[b]=new bb(a,b)),q&&j[b].isNew&&j[b].render(c,!0,0.1),j[b].render(c)}),p&&(a.min===0||a.single)))j[-1]||(j[-1]=new bb(a,-1,null,!0)),j[-1].render(-1);n&&o(h,function(c,d){Q=h[d+1]!==t?h[d+1]+p:a.max-p;if(d%2===0&&c=M.second?0:k*V(i.getMilliseconds()/k));if(j>=M.second)i[Qb](j>=M.minute?0:k*V(i.getSeconds()/k));if(j>=M.minute)i[Rb](j>=M.hour?0:k*V(i[Bb]()/k));if(j>=M.hour)i[Sb](j>=M.day?0:k*V(i[Cb]()/k)); -if(j>=M.day)i[qb](j>=M.month?1:k*V(i[ab]()/k));j>=M.month&&(i[Eb](j>=M.year?0:k*V(i[hb]()/k)),h=i[ib]());j>=M.year&&(h-=h%k,i[Fb](h));if(j===M.week)i[qb](i[ab]()-i[Db]()+q(d,1));b=1;if(yb||fb)i=i.getTime(),i=new ba(i+eb(i));h=i[ib]();for(var d=i.getTime(),l=i[hb](),m=i[ab](),n=!g||!!fb,p=(M.day+(g?eb(i):i.getTimezoneOffset()*6E4))%M.day;d=0.5)a=y(a),i=this.getLinearTickPositions(a,b,c);else if(a>=0.08)for(var f=V(b),j,k,l,m,n,e=a>0.3?[1,2,4]:a>0.15?[1,2,4,6,8]:[1,2,3,4,5,6,7,8,9];fb&&(!d||m<=c)&&m!==t&&i.push(m),m>c&& -(n=!0),m=l}else if(b=g(b),c=g(c),a=e[d?"minorTickInterval":"tickInterval"],a=q(a==="auto"?null:a,this._minorAutoInterval,(c-b)*(e.tickPixelInterval/(d?5:1))/((d?f/this.tickPositions.length:f)||1)),a=Ab(a,null,zb(a)),i=ta(this.getLinearTickPositions(a,b,c),h),!d)this._minorAutoInterval=a/5;if(!d)this.tickInterval=a;return i};J.prototype.log2lin=function(a){return Y.log(a)/Y.LN10};J.prototype.lin2log=function(a){return Y.pow(10,a)};var Lb=B.Tooltip=function(){this.init.apply(this,arguments)};Lb.prototype= -{init:function(a,b){var c=b.borderWidth,d=b.style,e=K(d.padding);this.chart=a;this.options=b;this.crosshairs=[];this.now={x:0,y:0};this.isHidden=!0;this.label=a.renderer.label("",0,0,b.shape||"callout",null,null,b.useHTML,null,"tooltip").attr({padding:e,fill:b.backgroundColor,"stroke-width":c,r:b.borderRadius,zIndex:8}).css(d).css({padding:0}).add().attr({y:-9999});qa||this.label.shadow(b.shadow);this.shared=b.shared},destroy:function(){if(this.label)this.label=this.label.destroy();clearTimeout(this.hideTimer); -clearTimeout(this.tooltipTimeout)},move:function(a,b,c,d){var e=this,f=e.now,g=e.options.animation!==!1&&!e.isHidden&&(S(a-f.x)>1||S(b-f.y)>1),h=e.followPointer||e.len>1;A(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:h?t:g?(2*f.anchorX+c)/3:c,anchorY:h?t:g?(f.anchorY+d)/2:d});e.label.attr(f);if(g)clearTimeout(this.tooltipTimeout),this.tooltipTimeout=setTimeout(function(){e&&e.move(a,b,c,d)},32)},hide:function(a){var b=this;clearTimeout(this.hideTimer);a=q(a,this.options.hideDelay,500);if(!this.isHidden)this.hideTimer= -Za(function(){b.label[a?"fadeOut":"hide"]();b.isHidden=!0},a)},getAnchor:function(a,b){var c,d=this.chart,e=d.inverted,f=d.plotTop,g=d.plotLeft,h=0,i=0,j,k,a=ua(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===t&&(b=d.pointer.normalize(b)),c=[b.chartX-d.plotLeft,b.chartY-f]);c||(o(a,function(a){j=a.series.yAxis;k=a.series.xAxis;h+=a.plotX+(!e&&k?k.left-g:0);i+=(a.plotLow?(a.plotLow+a.plotHigh)/2:a.plotY)+(!e&&j?j.top-f:0)}),h/=a.length,i/=a.length,c=[e?d.plotWidth-i:h,this.shared&&!e&&a.length> -1&&b?b.chartY-f:e?d.plotHeight-h:i]);return ta(c,y)},getPosition:function(a,b,c){var d=this.chart,e=this.distance,f={},g=c.h||0,h,i=["y",d.chartHeight,b,c.plotY+d.plotTop,d.plotTop,d.plotTop+d.plotHeight],j=["x",d.chartWidth,a,c.plotX+d.plotLeft,d.plotLeft,d.plotLeft+d.plotWidth],k=!this.followPointer&&q(c.ttBelow,!d.inverted===!!c.negative),l=function(a,b,c,d,h,i){var j=cb?d: -d+g);else return!1},m=function(a,b,c,d){var g;db-e?g=!1:f[a]=db-c/2?b-c-2:d-c/2;return g},n=function(a){var b=i;i=j;j=b;h=a},p=function(){l.apply(0,i)!==!1?m.apply(0,j)===!1&&!h&&(n(!0),p()):h?f.x=f.y=0:(n(!0),p())};(d.inverted||this.len>1)&&n();p();return f},defaultFormatter:function(a){var b=this.points||ua(this),c;c=[a.tooltipFooterHeaderFormatter(b[0])];c=c.concat(a.bodyFormatter(b));c.push(a.tooltipFooterHeaderFormatter(b[0],!0));return c.join("")},refresh:function(a,b){var c= -this.chart,d=this.label,e=this.options,f,g,h,i={},j,k=[];j=e.formatter||this.defaultFormatter;var i=c.hoverPoints,l,m=this.shared;clearTimeout(this.hideTimer);this.followPointer=ua(a)[0].series.tooltipOptions.followPointer;h=this.getAnchor(a,b);f=h[0];g=h[1];m&&(!a.series||!a.series.noSharedTooltip)?(c.hoverPoints=a,i&&o(i,function(a){a.setState()}),o(a,function(a){a.setState("hover");k.push(a.getLabelConfig())}),i={x:a[0].category,y:a[0].y},i.points=k,this.len=k.length,a=a[0]):i=a.getLabelConfig(); -j=j.call(i,this);i=a.series;this.distance=q(i.tooltipOptions.distance,16);j===!1?this.hide():(this.isHidden&&(Sa(d),d.attr("opacity",1).show()),d.attr({text:j}),l=e.borderColor||a.color||i.color||"#606060",d.attr({stroke:l}),this.updatePosition({plotX:f,plotY:g,negative:a.negative,ttBelow:a.ttBelow,h:h[2]||0}),this.isHidden=!1);O(c,"tooltipRefresh",{text:j,x:f+c.plotLeft,y:g+c.plotTop,borderColor:l})},updatePosition:function(a){var b=this.chart,c=this.label,c=(this.options.positioner||this.getPosition).call(this, -c.width,c.height,a);this.move(y(c.x),y(c.y||0),a.plotX+b.plotLeft,a.plotY+b.plotTop)},getXDateFormat:function(a,b,c){var d,b=b.dateTimeLabelFormats,e=c&&c.closestPointRange,f,g={millisecond:15,second:12,minute:9,hour:6,day:3},h,i="millisecond";if(e){h=na("%m-%d %H:%M:%S.%L",a.x);for(f in M){if(e===M.week&&+na("%w",a.x)===c.options.startOfWeek&&h.substr(6)==="00:00:00.000"){f="week";break}if(M[f]>e){f=i;break}if(g[f]&&h.substr(g[f])!=="01-01 00:00:00.000".substr(g[f]))break;f!=="week"&&(i=f)}f&&(d= -b[f])}else d=b.day;return d||b.year},tooltipFooterHeaderFormatter:function(a,b){var c=b?"footer":"header",d=a.series,e=d.tooltipOptions,f=e.xDateFormat,g=d.xAxis,h=g&&g.options.type==="datetime"&&C(a.key),c=e[c+"Format"];h&&!f&&(f=this.getXDateFormat(a,e,g));h&&f&&(c=c.replace("{point.key}","{point.key:"+f+"}"));return La(c,{point:a,series:d})},bodyFormatter:function(a){return ta(a,function(a){var c=a.series.tooltipOptions;return(c.pointFormatter||a.point.tooltipFormatter).call(a.point,c.pointFormat)})}}; -var oa;cb=H&&H.documentElement.ontouchstart!==t;var Ya=B.Pointer=function(a,b){this.init(a,b)};Ya.prototype={init:function(a,b){var c=b.chart,d=c.events,e=qa?"":c.zoomType,c=a.inverted,f;this.options=b;this.chart=a;this.zoomX=f=/x/.test(e);this.zoomY=e=/y/.test(e);this.zoomHor=f&&!c||e&&c;this.zoomVert=e&&!c||f&&c;this.hasZoom=f||e;this.runChartClick=d&&!!d.click;this.pinchDown=[];this.lastValidTouch={};if(B.Tooltip&&b.tooltip.enabled)a.tooltip=new Lb(a,b.tooltip),this.followTouchMove=q(b.tooltip.followTouchMove, -!0);this.setDOMEvents()},normalize:function(a,b){var c,d,a=a||L.event;if(!a.target)a.target=a.srcElement;d=a.touches?a.touches.length?a.touches.item(0):a.changedTouches[0]:a;if(!b)this.chartPosition=b=Jb(this.chart.container);d.pageX===t?(c=w(a.x,a.clientX-b.left),d=a.y):(c=d.pageX-b.left,d=d.pageY-b.top);return A(a,{chartX:y(c),chartY:y(d)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};o(this.chart.axes,function(c){b[c.isXAxis?"xAxis":"yAxis"].push({axis:c,value:c.toValue(a[c.horiz?"chartX": -"chartY"])})});return b},runPointActions:function(a){var b=this.chart,c=b.series,d=b.tooltip,e=d?d.shared:!1,f=b.hoverPoint,g=b.hoverSeries,h,i=[Number.MAX_VALUE,Number.MAX_VALUE],j,k,l=[],m=[],n;if(!e&&!g)for(h=0;h=m[c].series.group.zIndex;if(a[b]h+j&&(d=h+j),ei+k&&(e=i+k),this.hasDragged=Math.sqrt(Math.pow(n-d,2)+Math.pow(p-e,2)),this.hasDragged>10){l=b.isInsidePlot(n-h,p-i);if(b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&l&&!r&&!m)this.selectionMarker=m=b.renderer.rect(h,i, -f?1:j,g?1:k,0).attr({fill:c.selectionMarkerFill||"rgba(69,114,167,0.25)",zIndex:7}).add();m&&f&&(d-=n,m.attr({width:S(d),x:(d>0?0:d)+n}));m&&g&&(d=e-p,m.attr({height:S(d),y:(d>0?0:d)+p}));l&&!m&&c.panning&&b.pan(a,c.panning)}},drop:function(a){var b=this,c=this.chart,d=this.hasPinched;if(this.selectionMarker){var e={originalEvent:a,xAxis:[],yAxis:[]},f=this.selectionMarker,g=f.attr?f.attr("x"):f.x,h=f.attr?f.attr("y"):f.y,i=f.attr?f.attr("width"):f.width,j=f.attr?f.attr("height"):f.height,k;if(this.hasDragged|| -d)o(c.axes,function(c){if(c.zoomEnabled&&v(c.min)&&(d||b[{xAxis:"zoomX",yAxis:"zoomY"}[c.coll]])){var f=c.horiz,n=a.type==="touchend"?c.minPixelPadding:0,p=c.toValue((f?g:h)+n),f=c.toValue((f?g+i:h+j)-n);e[c.coll].push({axis:c,min:G(p,f),max:w(p,f)});k=!0}}),k&&O(c,"selection",e,function(a){c.zoom(A(a,d?{animation:!1}:null))});this.selectionMarker=this.selectionMarker.destroy();d&&this.scaleGroups()}if(c)N(c.container,{cursor:c._cursor}),c.cancelClick=this.hasDragged>10,c.mouseIsDown=this.hasDragged= -this.hasPinched=!1,this.pinchDown=[]},onContainerMouseDown:function(a){a=this.normalize(a);a.preventDefault&&a.preventDefault();this.dragStart(a)},onDocumentMouseUp:function(a){$[oa]&&$[oa].pointer.drop(a)},onDocumentMouseMove:function(a){var b=this.chart,c=this.chartPosition,a=this.normalize(a,c);c&&!this.inClass(a.target,"highcharts-tracker")&&!b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)&&this.reset()},onContainerMouseLeave:function(a){var b=$[oa];if(b&&(a.relatedTarget||a.toElement))b.pointer.reset(), -b.pointer.chartPosition=null},onContainerMouseMove:function(a){var b=this.chart;if(!v(oa)||!$[oa]||!$[oa].mouseIsDown)oa=b.index;a=this.normalize(a);a.returnValue=!1;b.mouseIsDown==="mousedown"&&this.drag(a);(this.inClass(a.target,"highcharts-tracker")||b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop))&&!b.openMenu&&this.runPointActions(a)},inClass:function(a,b){for(var c;a;){if(c=X(a,"class")){if(c.indexOf(b)!==-1)return!0;if(c.indexOf("highcharts-container")!==-1)return!1}a=a.parentNode}}, -onTrackerMouseOut:function(a){var b=this.chart.hoverSeries,a=a.relatedTarget||a.toElement;if(b&&a&&!b.options.stickyTracking&&!this.inClass(a,"highcharts-tooltip")&&!this.inClass(a,"highcharts-series-"+b.index))b.onMouseOut()},onContainerClick:function(a){var b=this.chart,c=b.hoverPoint,d=b.plotLeft,e=b.plotTop,a=this.normalize(a);b.cancelClick||(c&&this.inClass(a.target,"highcharts-tracker")?(O(c.series,"click",A(a,{point:c})),b.hoverPoint&&c.firePointEvent("click",a)):(A(a,this.getCoordinates(a)), -b.isInsidePlot(a.chartX-d,a.chartY-e)&&O(b,"click",a)))},setDOMEvents:function(){var a=this,b=a.chart.container;b.onmousedown=function(b){a.onContainerMouseDown(b)};b.onmousemove=function(b){a.onContainerMouseMove(b)};b.onclick=function(b){a.onContainerClick(b)};E(b,"mouseleave",a.onContainerMouseLeave);kb===1&&E(H,"mouseup",a.onDocumentMouseUp);if(cb)b.ontouchstart=function(b){a.onContainerTouchStart(b)},b.ontouchmove=function(b){a.onContainerTouchMove(b)},kb===1&&E(H,"touchend",a.onDocumentTouchEnd)}, -destroy:function(){var a;T(this.chart.container,"mouseleave",this.onContainerMouseLeave);kb||(T(H,"mouseup",this.onDocumentMouseUp),T(H,"touchend",this.onDocumentTouchEnd));clearInterval(this.tooltipTimeout);for(a in this)this[a]=null}};A(B.Pointer.prototype,{pinchTranslate:function(a,b,c,d,e,f){(this.zoomHor||this.pinchHor)&&this.pinchTranslateDirection(!0,a,b,c,d,e,f);(this.zoomVert||this.pinchVert)&&this.pinchTranslateDirection(!1,a,b,c,d,e,f)},pinchTranslateDirection:function(a,b,c,d,e,f,g,h){var i= -this.chart,j=a?"x":"y",k=a?"X":"Y",l="chart"+k,m=a?"width":"height",n=i["plot"+(a?"Left":"Top")],p,r,s=h||1,q=i.inverted,o=i.bounds[a?"h":"v"],x=b.length===1,D=b[0][l],v=c[0][l],w=!x&&b[1][l],y=!x&&c[1][l],t,c=function(){!x&&S(D-w)>20&&(s=h||S(v-y)/S(D-w));r=(n-v)/s+D;p=i["plot"+(a?"Width":"Height")]/s};c();b=r;bo.max&&(b=o.max-p,t=!0);t?(v-=0.8*(v-g[j][0]),x||(y-=0.8*(y-g[j][1])),c()):g[j]=[v,y];q||(f[j]=r-n,f[m]=p);f=q?1/s:s;e[m]=p;e[j]=b;d[q?a?"scaleY":"scaleX":"scale"+ -k]=s;d["translate"+k]=f*n+(v-f*D)},pinch:function(a){var b=this,c=b.chart,d=b.pinchDown,e=a.touches,f=e.length,g=b.lastValidTouch,h=b.hasZoom,i=b.selectionMarker,j={},k=f===1&&(b.inClass(a.target,"highcharts-tracker")&&c.runTrackerClick||b.runChartClick),l={};if(f>1)b.initiated=!0;h&&b.initiated&&!k&&a.preventDefault();ta(e,function(a){return b.normalize(a)});if(a.type==="touchstart")o(e,function(a,b){d[b]={chartX:a.chartX,chartY:a.chartY}}),g.x=[d[0].chartX,d[1]&&d[1].chartX],g.y=[d[0].chartY,d[1]&& -d[1].chartY],o(c.axes,function(a){if(a.zoomEnabled){var b=c.bounds[a.horiz?"h":"v"],d=a.minPixelPadding,e=a.toPixels(q(a.options.min,a.dataMin)),f=a.toPixels(q(a.options.max,a.dataMax)),g=G(e,f),e=w(e,f);b.min=G(a.pos,g-d);b.max=w(a.pos+a.len,e+d)}}),b.res=!0;else if(d.length){if(!i)b.selectionMarker=i=A({destroy:ra,touch:!0},c.plotBox);b.pinchTranslate(d,e,j,i,l,g);b.hasPinched=h;b.scaleGroups(j,l);if(!h&&b.followTouchMove&&f===1)this.runPointActions(b.normalize(a));else if(b.res)b.res=!1,this.reset(!1, -0)}},touch:function(a,b){var c=this.chart,d;oa=c.index;if(a.touches.length===1)if(a=this.normalize(a),c.isInsidePlot(a.chartX-c.plotLeft,a.chartY-c.plotTop)&&!c.openMenu){b&&this.runPointActions(a);if(a.type==="touchmove")c=this.pinchDown,d=c[0]?Math.sqrt(Math.pow(c[0].chartX-a.chartX,2)+Math.pow(c[0].chartY-a.chartY,2))>=4:!1;q(d,!0)&&this.pinch(a)}else b&&this.reset();else a.touches.length===2&&this.pinch(a)},onContainerTouchStart:function(a){this.touch(a,!0)},onContainerTouchMove:function(a){this.touch(a)}, -onDocumentTouchEnd:function(a){$[oa]&&$[oa].pointer.drop(a)}});if(L.PointerEvent||L.MSPointerEvent){var Ga={},Mb=!!L.PointerEvent,ac=function(){var a,b=[];b.item=function(a){return this[a]};for(a in Ga)Ga.hasOwnProperty(a)&&b.push({pageX:Ga[a].pageX,pageY:Ga[a].pageY,target:Ga[a].target});return b},Nb=function(a,b,c,d){if((a.pointerType==="touch"||a.pointerType===a.MSPOINTER_TYPE_TOUCH)&&$[oa])d(a),d=$[oa].pointer,d[b]({type:c,target:a.currentTarget,preventDefault:ra,touches:ac()})};A(Ya.prototype, -{onContainerPointerDown:function(a){Nb(a,"onContainerTouchStart","touchstart",function(a){Ga[a.pointerId]={pageX:a.pageX,pageY:a.pageY,target:a.currentTarget}})},onContainerPointerMove:function(a){Nb(a,"onContainerTouchMove","touchmove",function(a){Ga[a.pointerId]={pageX:a.pageX,pageY:a.pageY};if(!Ga[a.pointerId].target)Ga[a.pointerId].target=a.currentTarget})},onDocumentPointerUp:function(a){Nb(a,"onDocumentTouchEnd","touchend",function(a){delete Ga[a.pointerId]})},batchMSEvents:function(a){a(this.chart.container, -Mb?"pointerdown":"MSPointerDown",this.onContainerPointerDown);a(this.chart.container,Mb?"pointermove":"MSPointerMove",this.onContainerPointerMove);a(H,Mb?"pointerup":"MSPointerUp",this.onDocumentPointerUp)}});U(Ya.prototype,"init",function(a,b,c){a.call(this,b,c);this.hasZoom&&N(b.container,{"-ms-touch-action":"none","touch-action":"none"})});U(Ya.prototype,"setDOMEvents",function(a){a.apply(this);(this.hasZoom||this.followTouchMove)&&this.batchMSEvents(E)});U(Ya.prototype,"destroy",function(a){this.batchMSEvents(T); -a.call(this)})}var ub=B.Legend=function(a,b){this.init(a,b)};ub.prototype={init:function(a,b){var c=this,d=b.itemStyle,e=b.itemMarginTop||0;this.options=b;if(b.enabled)c.itemStyle=d,c.itemHiddenStyle=z(d,b.itemHiddenStyle),c.itemMarginTop=e,c.padding=d=q(b.padding,8),c.initialItemX=d,c.initialItemY=d-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.symbolWidth=q(b.symbolWidth,16),c.pages=[],c.render(),E(c.chart,"endResize",function(){c.positionCheckboxes()})},colorizeItem:function(a,b){var c=this.options, -d=a.legendItem,e=a.legendLine,f=a.legendSymbol,g=this.itemHiddenStyle.color,c=b?c.itemStyle.color:g,h=b?a.legendColor||a.color||"#CCC":g,g=a.options&&a.options.marker,i={fill:h},j;d&&d.css({fill:c,color:c});e&&e.attr({stroke:h});if(f){if(g&&f.isMarker)for(j in i.stroke=h,g=a.convertAttribs(g),g)d=g[j],d!==t&&(i[j]=d);f.attr(i)}},positionItem:function(a){var b=this.options,c=b.symbolPadding,b=!b.rtl,d=a._legendItemPos,e=d[0],d=d[1],f=a.checkbox;(a=a.legendGroup)&&a.element&&a.translate(b?e:this.legendWidth- -e-2*c-4,d);if(f)f.x=e,f.y=d},destroyItem:function(a){var b=a.checkbox;o(["legendItem","legendLine","legendSymbol","legendGroup"],function(b){a[b]&&(a[b]=a[b].destroy())});b&&Ua(a.checkbox)},destroy:function(){var a=this.group,b=this.box;if(b)this.box=b.destroy();if(a)this.group=a.destroy()},positionCheckboxes:function(a){var b=this.group.alignAttr,c,d=this.clipHeight||this.legendHeight,e=this.titleHeight;if(b)c=b.translateY,o(this.allItems,function(f){var g=f.checkbox,h;g&&(h=c+e+g.y+(a||0)+3,N(g, -{left:b.translateX+f.checkboxOffset+g.x-20+"px",top:h+"px",display:h>c-6&&h(m||b.chartWidth-2*j-r-d.x))this.itemX=r,this.itemY+=p+this.lastLineHeight+n,this.lastLineHeight=0;this.maxItemWidth=w(this.maxItemWidth,f);this.lastItemY=p+this.itemY+n;this.lastLineHeight=w(g,this.lastLineHeight);a._legendItemPos=[this.itemX,this.itemY];e?this.itemX+=f:(this.itemY+=p+g+n,this.lastLineHeight=g);this.offsetWidth=m||w((e?this.itemX-r-k:f)+j,this.offsetWidth)}, -getAllItems:function(){var a=[];o(this.chart.series,function(b){var c=b.options;if(q(c.showInLegend,!v(c.linkedTo)?t:!1,!0))a=a.concat(b.legendItems||(c.legendType==="point"?b.data:b))});return a},adjustMargins:function(a,b){var c=this.chart,d=this.options,e=d.align.charAt(0)+d.verticalAlign.charAt(0)+d.layout.charAt(0);this.display&&!d.floating&&o([/(lth|ct|rth)/,/(rtv|rm|rbv)/,/(rbh|cb|lbh)/,/(lbv|lm|ltv)/],function(f,g){f.test(e)&&!v(a[g])&&(c[tb[g]]=w(c[tb[g]],c.legend[(g+1)%2?"legendHeight": -"legendWidth"]+[1,-1,-1,1][g]*d[g%2?"x":"y"]+q(d.margin,12)+b[g]))})},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,e,f,g,h,i=a.box,j=a.options,k=a.padding,l=j.borderWidth,m=j.backgroundColor;a.itemX=a.initialItemX;a.itemY=a.initialItemY;a.offsetWidth=0;a.lastItemY=0;if(!d)a.group=d=c.g("legend").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),a.scrollGroup=c.g().add(a.contentGroup);a.renderTitle();e=a.getAllItems();nb(e,function(a,b){return(a.options&&a.options.legendIndex|| -0)-(b.options&&b.options.legendIndex||0)});j.reversed&&e.reverse();a.allItems=e;a.display=f=!!e.length;a.lastLineHeight=0;o(e,function(b){a.renderItem(b)});g=(j.width||a.offsetWidth)+k;h=a.lastItemY+a.lastLineHeight+a.titleHeight;h=a.handleOverflow(h);h+=k;if(l||m){if(i){if(g>0&&h>0)i[i.isNew?"attr":"animate"](i.crisp({width:g,height:h})),i.isNew=!1}else a.box=i=c.rect(0,0,g,h,j.borderRadius,l||0).attr({stroke:j.borderColor,"stroke-width":l||0,fill:m||"none"}).add(d).shadow(j.shadow),i.isNew=!0;i[f? -"show":"hide"]()}a.legendWidth=g;a.legendHeight=h;o(e,function(b){a.positionItem(b)});f&&d.align(A({width:g,height:h},j),!0,"spacingBox");b.isResizing||this.positionCheckboxes()},handleOverflow:function(a){var b=this,c=this.chart,d=c.renderer,e=this.options,f=e.y,f=c.spacingBox.height+(e.verticalAlign==="top"?-f:f)-this.padding,g=e.maxHeight,h,i=this.clipRect,j=e.navigation,k=q(j.animation,!0),l=j.arrowSize||12,m=this.nav,n=this.pages,p=this.padding,r,s=this.allItems,F=function(a){i.attr({height:a}); -if(b.contentGroup.div)b.contentGroup.div.style.clip="rect("+p+"px,9999px,"+(p+a)+"px,0)"};e.layout==="horizontal"&&(f/=2);g&&(f=G(f,g));n.length=0;if(a>f&&j.enabled!==!1){this.clipHeight=h=w(f-20-this.titleHeight-p,0);this.currentPage=q(this.currentPage,1);this.fullHeight=a;o(s,function(a,b){var c=a._legendItemPos[1],d=y(a.legendItem.getBBox().height),e=n.length;if(!e||c-n[e-1]>h&&(r||c)!==n[e-1])n.push(r||c),e++;b===s.length-1&&c+d-n[e-1]>h&&n.push(c);c!==r&&(r=c)});if(!i)i=b.clipRect=d.clipRect(0, -p,9999,0),b.contentGroup.clip(i);F(h);if(!m)this.nav=m=d.g().attr({zIndex:1}).add(this.group),this.up=d.symbol("triangle",0,0,l,l).on("click",function(){b.scroll(-1,k)}).add(m),this.pager=d.text("",15,10).css(j.style).add(m),this.down=d.symbol("triangle-down",0,0,l,l).on("click",function(){b.scroll(1,k)}).add(m);b.scroll(0);a=f}else if(m)F(c.chartHeight),m.hide(),this.scrollGroup.attr({translateY:1}),this.clipHeight=0;return a},scroll:function(a,b){var c=this.pages,d=c.length,e=this.currentPage+a, -f=this.clipHeight,g=this.options.navigation,h=g.activeColor,g=g.inactiveColor,i=this.pager,j=this.padding;e>d&&(e=d);if(e>0)b!==t&&$a(b,this.chart),this.nav.attr({translateX:j,translateY:f+this.padding+7+this.titleHeight,visibility:"visible"}),this.up.attr({fill:e===1?g:h}).css({cursor:e===1?"default":"pointer"}),i.attr({text:e+"/"+d}),this.down.attr({x:18+this.pager.getBBox().width,fill:e===d?g:h}).css({cursor:e===d?"default":"pointer"}),c=-c[e-1]+this.initialItemY,this.scrollGroup.animate({translateY:c}), -this.currentPage=e,this.positionCheckboxes(c)}};aa=B.LegendSymbolMixin={drawRectangle:function(a,b){var c=a.options.symbolHeight||a.fontMetrics.f;b.legendSymbol=this.chart.renderer.rect(0,a.baseline-c+1,a.symbolWidth,c,a.options.symbolRadius||0).attr({zIndex:3}).add(b.legendGroup)},drawLineMarker:function(a){var b=this.options,c=b.marker,d=a.symbolWidth,e=this.chart.renderer,f=this.legendGroup,a=a.baseline-y(a.fontMetrics.b*0.3),g;if(b.lineWidth){g={"stroke-width":b.lineWidth};if(b.dashStyle)g.dashstyle= -b.dashStyle;this.legendLine=e.path(["M",0,a,"L",d,a]).attr(g).add(f)}if(c&&c.enabled!==!1)b=c.radius,this.legendSymbol=c=e.symbol(this.symbol,d/2-b,a-b,2*b,2*b,c).add(f),c.isMarker=!0}};(/Trident\/7\.0/.test(Na)||Wa)&&U(ub.prototype,"positionItem",function(a,b){var c=this,d=function(){b._legendItemPos&&a.call(c,b)};d();setTimeout(d)});var Ba=B.Chart=function(){this.getArgs.apply(this,arguments)};B.chart=function(a,b,c){return new Ba(a,b,c)};Ba.prototype={callbacks:[],getArgs:function(){var a=[].slice.call(arguments); -if(Ca(a[0])||a[0].nodeName)this.renderTo=a.shift();this.init(a[0],a[1])},init:function(a,b){var c,d=a.series;a.series=null;c=z(R,a);c.series=a.series=d;this.userOptions=a;d=c.chart;this.margin=this.splashArray("margin",d);this.spacing=this.splashArray("spacing",d);var e=d.events;this.bounds={h:{},v:{}};this.callback=b;this.isResizing=0;this.options=c;this.axes=[];this.series=[];this.hasCartesianSeries=d.showAxes;var f=this,g;f.index=$.length;$.push(f);kb++;d.reflow!==!1&&E(f,"load",function(){f.initReflow()}); -if(e)for(g in e)E(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=qa?!1:q(d.animation,!0);f.pointCount=f.colorCounter=f.symbolCounter=0;f.firstRender()},initSeries:function(a){var b=this.options.chart;(b=I[a.type||b.type||b.defaultSeriesType])||ga(17,!0);b=new b;b.init(this,a);return b},isInsidePlot:function(a,b,c){var d=c?b:a,a=c?a:b;return d>=0&&d<=this.plotWidth&&a>=0&&a<=this.plotHeight},redraw:function(a){var b=this.axes,c=this.series,d=this.pointer,e=this.legend,f=this.isDirtyLegend,g,h,i=this.hasCartesianSeries, -j=this.isDirtyBox,k=c.length,l=k,m=this.renderer,n=m.isHidden(),p=[];$a(a,this);n&&this.cloneRenderTo();for(this.layOutTitles();l--;)if(a=c[l],a.options.stacking&&(g=!0,a.isDirty)){h=!0;break}if(h)for(l=k;l--;)if(a=c[l],a.options.stacking)a.isDirty=!0;o(c,function(a){a.isDirty&&a.options.legendType==="point"&&(a.updateTotals&&a.updateTotals(),f=!0);a.isDirtyData&&O(a,"updatedData")});if(f&&e.options.enabled)e.render(),this.isDirtyLegend=!1;g&&this.getStacks();if(i&&!this.isResizing)this.maxTicks= -null,o(b,function(a){a.setScale()});this.getMargins();i&&(o(b,function(a){a.isDirty&&(j=!0)}),o(b,function(a){var b=a.min+","+a.max;if(a.extKey!==b)a.extKey=b,p.push(function(){O(a,"afterSetExtremes",A(a.eventArgs,a.getExtremes()));delete a.eventArgs});(j||g)&&a.redraw()}));j&&this.drawChartBox();o(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});d&&d.reset(!0);m.draw();O(this,"redraw");n&&this.cloneRenderTo(!0);o(p,function(a){a.call()})},get:function(a){var b=this.axes, -c=this.series,d,e;for(d=0;d19?this.containerHeight:400))},cloneRenderTo:function(a){var b= -this.renderToClone,c=this.container;a?b&&(this.renderTo.appendChild(c),Ua(b),delete this.renderToClone):(c&&c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=b=this.renderTo.cloneNode(0),N(b,{position:"absolute",top:"-9999px",display:"block"}),b.style.setProperty&&b.style.setProperty("display","block","important"),H.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options,c=b.chart,d,e;a=this.renderTo;var f="highcharts-"+Ib++;if(!a)this.renderTo= -a=c.renderTo;if(Ca(a))this.renderTo=a=H.getElementById(a);a||ga(13,!0);d=K(X(a,"data-highcharts-chart"));C(d)&&$[d]&&$[d].hasRendered&&$[d].destroy();X(a,"data-highcharts-chart",this.index);a.innerHTML="";!c.skipClone&&!a.offsetWidth&&this.cloneRenderTo();this.getChartSize();d=this.chartWidth;e=this.chartHeight;this.container=a=fa(Va,{className:"highcharts-container"+(c.className?" "+c.className:""),id:f},A({position:"relative",overflow:"hidden",width:d+"px",height:e+"px",textAlign:"left",lineHeight:"normal", -zIndex:0,"-webkit-tap-highlight-color":"rgba(0,0,0,0)"},c.style),this.renderToClone||a);this._cursor=a.style.cursor;this.renderer=new (B[c.renderer]||Xa)(a,d,e,c.style,c.forExport,b.exporting&&b.exporting.allowHTML);qa&&this.renderer.create(this,a,d,e);this.renderer.chartIndex=this.index},getMargins:function(a){var b=this.spacing,c=this.margin,d=this.titleOffset;this.resetMargins();if(d&&!v(c[0]))this.plotTop=w(this.plotTop,d+this.options.title.margin+b[0]);this.legend.adjustMargins(c,b);this.extraBottomMargin&& -(this.marginBottom+=this.extraBottomMargin);this.extraTopMargin&&(this.plotTop+=this.extraTopMargin);a||this.getAxisMargins()},getAxisMargins:function(){var a=this,b=a.axisOffset=[0,0,0,0],c=a.margin;a.hasCartesianSeries&&o(a.axes,function(a){a.visible&&a.getOffset()});o(tb,function(d,e){v(c[e])||(a[d]+=b[e])});a.setChartSize()},reflow:function(a){var b=this,c=b.options.chart,d=b.renderTo,e=c.width||wa(d,"width"),f=c.height||wa(d,"height"),c=a?a.target:L;if(!b.hasUserSize&&!b.isPrinting&&e&&f&&(c=== -L||c===H)){if(e!==b.containerWidth||f!==b.containerHeight)clearTimeout(b.reflowTimeout),b.reflowTimeout=Za(function(){if(b.container)b.setSize(e,f,!1),b.hasUserSize=null},a?100:0);b.containerWidth=e;b.containerHeight=f}},initReflow:function(){var a=this,b=function(b){a.reflow(b)};E(L,"resize",b);E(a,"destroy",function(){T(L,"resize",b)})},setSize:function(a,b,c){var d=this,e,f,g=d.renderer;d.isResizing+=1;$a(c,d);d.oldChartHeight=d.chartHeight;d.oldChartWidth=d.chartWidth;if(v(a))d.chartWidth=e=w(0, -y(a)),d.hasUserSize=!!e;if(v(b))d.chartHeight=f=w(0,y(b));a=g.globalAnimation;(a?db:N)(d.container,{width:e+"px",height:f+"px"},a);d.setChartSize(!0);g.setSize(e,f,c);d.maxTicks=null;o(d.axes,function(a){a.isDirty=!0;a.setScale()});o(d.series,function(a){a.isDirty=!0});d.isDirtyLegend=!0;d.isDirtyBox=!0;d.layOutTitles();d.getMargins();d.redraw(c);d.oldChartHeight=null;O(d,"resize");Za(function(){d&&O(d,"endResize",null,function(){d.isResizing-=1})},gb(a).duration)},setChartSize:function(a){var b= -this.inverted,c=this.renderer,d=this.chartWidth,e=this.chartHeight,f=this.options.chart,g=this.spacing,h=this.clipOffset,i,j,k,l;this.plotLeft=i=y(this.plotLeft);this.plotTop=j=y(this.plotTop);this.plotWidth=k=w(0,y(d-i-this.marginRight));this.plotHeight=l=w(0,y(e-j-this.marginBottom));this.plotSizeX=b?l:k;this.plotSizeY=b?k:l;this.plotBorderWidth=f.plotBorderWidth||0;this.spacingBox=c.spacingBox={x:g[3],y:g[0],width:d-g[3]-g[1],height:e-g[0]-g[2]};this.plotBox=c.plotBox={x:i,y:j,width:k,height:l}; -d=2*V(this.plotBorderWidth/2);b=Ea(w(d,h[3])/2);c=Ea(w(d,h[0])/2);this.clipBox={x:b,y:c,width:V(this.plotSizeX-w(d,h[1])/2-b),height:w(0,V(this.plotSizeY-w(d,h[2])/2-c))};a||o(this.axes,function(a){a.setAxisSize();a.setAxisTranslation()})},resetMargins:function(){var a=this;o(tb,function(b,c){a[b]=q(a.margin[c],a.spacing[c])});a.axisOffset=[0,0,0,0];a.clipOffset=[0,0,0,0]},drawChartBox:function(){var a=this.options.chart,b=this.renderer,c=this.chartWidth,d=this.chartHeight,e=this.chartBackground, -f=this.plotBackground,g=this.plotBorder,h=this.plotBGImage,i=a.borderWidth||0,j=a.backgroundColor,k=a.plotBackgroundColor,l=a.plotBackgroundImage,m=a.plotBorderWidth||0,n,p=this.plotLeft,r=this.plotTop,s=this.plotWidth,q=this.plotHeight,o=this.plotBox,x=this.clipRect,D=this.clipBox;n=i+(a.shadow?8:0);if(i||j)if(e)e.animate(e.crisp({width:c-n,height:d-n}));else{e={fill:j||"none"};if(i)e.stroke=a.borderColor,e["stroke-width"]=i;this.chartBackground=b.rect(n/2,n/2,c-n,d-n,a.borderRadius,i).attr(e).addClass("highcharts-background").add().shadow(a.shadow)}if(k)f? -f.animate(o):this.plotBackground=b.rect(p,r,s,q,0).attr({fill:k}).add().shadow(a.plotShadow);if(l)h?h.animate(o):this.plotBGImage=b.image(l,p,r,s,q).add();x?x.animate({width:D.width,height:D.height}):this.clipRect=b.clipRect(D);if(m)g?(g.strokeWidth=-m,g.animate(g.crisp({x:p,y:r,width:s,height:q}))):this.plotBorder=b.rect(p,r,s,q,0,-m).attr({stroke:a.plotBorderColor,"stroke-width":m,fill:"none",zIndex:1}).add();this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,c,d=a.options.series, -e,f;o(["inverted","angular","polar"],function(g){c=I[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&&e--;)(c=I[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},linkSeries:function(){var a=this,b=a.series;o(b,function(a){a.linkedSeries.length=0});o(b,function(b){var d=b.options.linkedTo;if(Ca(d)&&(d=d===":previous"?a.series[b.index-1]:a.get(d)))d.linkedSeries.push(b),b.linkedParent=d,b.visible=q(b.options.visible,d.options.visible,b.visible)})},renderSeries:function(){o(this.series, -function(a){a.translate();a.render()})},renderLabels:function(){var a=this,b=a.options.labels;b.items&&o(b.items,function(c){var d=A(b.style,c.style),e=K(d.left)+a.plotLeft,f=K(d.top)+a.plotTop+12;delete d.left;delete d.top;a.renderer.text(c.html,e,f).attr({zIndex:2}).css(d).add()})},render:function(){var a=this.axes,b=this.renderer,c=this.options,d,e,f,g;this.setTitle();this.legend=new ub(this,c.legend);this.getStacks&&this.getStacks();this.getMargins(!0);this.setChartSize();d=this.plotWidth;e=this.plotHeight-= -21;o(a,function(a){a.setScale()});this.getAxisMargins();f=d/this.plotWidth>1.1;g=e/this.plotHeight>1.05;if(f||g)this.maxTicks=null,o(a,function(a){(a.horiz&&f||!a.horiz&&g)&&a.setTickInterval(!0)}),this.getMargins();this.drawChartBox();this.hasCartesianSeries&&o(a,function(a){a.visible&&a.render()});if(!this.seriesGroup)this.seriesGroup=b.g("series-group").attr({zIndex:3}).add();this.renderSeries();this.renderLabels();this.showCredits(c.credits);this.hasRendered=!0},showCredits:function(a){if(a.enabled&& -!this.credits)this.credits=this.renderer.text(a.text,0,0).on("click",function(){if(a.href)L.location.href=a.href}).attr({align:a.position.align,zIndex:8}).css(a.style).add().align(a.position)},destroy:function(){var a=this,b=a.axes,c=a.series,d=a.container,e,f=d&&d.parentNode;O(a,"destroy");$[a.index]=t;kb--;a.renderTo.removeAttribute("data-highcharts-chart");T(a);for(e=b.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=c[e].destroy();o("title,subtitle,chartBackground,plotBackground,plotBGImage,plotBorder,seriesGroup,clipRect,credits,pointer,scroller,rangeSelector,legend,resetZoomButton,tooltip,renderer".split(","), -function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});if(d)d.innerHTML="",T(d),f&&Ua(d);for(e in a)delete a[e]},isReadyToRender:function(){var a=this;return!ja&&L==L.top&&H.readyState!=="complete"||qa&&!L.canvg?(qa?Wb.push(function(){a.firstRender()},a.options.global.canvasToolsURL):H.attachEvent("onreadystatechange",function(){H.detachEvent("onreadystatechange",a.firstRender);H.readyState==="complete"&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options;if(a.isReadyToRender()){a.getContainer(); -O(a,"init");a.resetMargins();a.setChartSize();a.propFromSeries();a.getAxes();o(b.series||[],function(b){a.initSeries(b)});a.linkSeries();O(a,"beforeRender");if(B.Pointer)a.pointer=new Ya(a,b);a.render();a.renderer.draw();if(!a.renderer.imgCount&&a.onload)a.onload();a.cloneRenderTo(!0)}},onload:function(){var a=this;o([this.callback].concat(this.callbacks),function(b){b&&a.index!==void 0&&b.apply(a,[a])});O(a,"load");this.onload=null},splashArray:function(a,b){var c=b[a],c=ea(c)?c:[c,c,c,c];return[q(b[a+ -"Top"],c[0]),q(b[a+"Right"],c[1]),q(b[a+"Bottom"],c[2]),q(b[a+"Left"],c[3])]}};var bc=B.CenteredSeriesMixin={getCenter:function(){var a=this.options,b=this.chart,c=2*(a.slicedOffset||0),d=b.plotWidth-2*c,b=b.plotHeight-2*c,e=a.center,e=[q(e[0],"50%"),q(e[1],"50%"),a.size||"100%",a.innerSize||0],f=G(d,b),g,h;for(g=0;g<4;++g)h=e[g],a=g<2||g===2&&/%$/.test(h),e[g]=(/%$/.test(h)?[d,b,f,e[2]][g]*parseFloat(h)/100:parseFloat(h))+(a?c:0);e[3]>e[2]&&(e[3]=e[2]);return e}},Ha=function(){};Ha.prototype={init:function(a, -b,c){this.series=a;this.color=a.color;this.applyOptions(b,c);this.pointAttr={};if(a.options.colorByPoint&&(b=a.options.colors||a.chart.options.colors,this.color=this.color||b[a.colorCounter++],a.colorCounter===b.length))a.colorCounter=0;a.chart.pointCount++;return this},applyOptions:function(a,b){var c=this.series,d=c.options.pointValKey||c.pointValKey,a=Ha.prototype.optionsToObject.call(this,a);A(this,a);this.options=this.options?A(this.options,a):a;if(d)this.y=this[d];this.isNull=this.x===null|| -this.y===null;if(this.x===void 0&&c)this.x=b===void 0?c.autoIncrement():b;return this},optionsToObject:function(a){var b={},c=this.series,d=c.options.keys,e=d||c.pointArrayMap||["y"],f=e.length,g=0,h=0;if(C(a)||a===null)b[e[0]]=a;else if(Ja(a)){if(!d&&a.length>f){c=typeof a[0];if(c==="string")b.name=a[0];else if(c==="number")b.x=a[0];g++}for(;hn){for(c=0;k===null&&ci||this.forceCrop))if(b[d-1]p)b=[],c=[];else if(b[0]p)e=this.cropData(this.xData,this.yData,n,p),b=e.xData,c=e.yData,e=e.start,f=!0;for(i=b.length||1;--i;)d=m?j(b[i])-j(b[i-1]):b[i]-b[i-1],d>0&&(g===t||d=c){f=w(0,i-h);break}for(c=i;cd){g=c+h;break}return{xData:a.slice(f,g),yData:b.slice(f,g),start:f,end:g}},generatePoints:function(){var a=this.options.data,b=this.data,c,d=this.processedXData,e=this.processedYData,f=this.pointClass,g=d.length,h=this.cropStart||0,i,j=this.hasGroupedData,k,l=[],m;if(!b&&!j)b=[],b.length=a.length,b=this.data=b;for(m=0;m0),j=this.getExtremesFromAll||this.options.getExtremesFromAll||this.cropped||(c[l+1]||j)>=g&&(c[l-1]||j)<=h,i&&j)if(i=k.length)for(;i--;)k[i]!==null&&(e[f++]=k[i]);else e[f++]=k;this.dataMin=Ma(e);this.dataMax=Da(e)},translate:function(){this.processedXData||this.processData();this.generatePoints();for(var a=this.options,b=a.stacking,c=this.xAxis,d=c.categories,e=this.yAxis,f=this.points,g=f.length,h=!!this.modifyValue,i=a.pointPlacement, -j=i==="between"||C(i),k=a.threshold,l=a.startFromThreshold?k:0,m,n,p,r,s=Number.MAX_VALUE,a=0;a=0&&n<=e.len&&m>=0&&m<=c.len;o.clientX=j?c.translate(u,0,0,0,1):m;o.negative=o.y<(k||0);o.category=d&&d[o.x]!==t?d[o.x]:o.x;o.isNull||(p!==void 0&&(s=G(s,S(m-p))),p=m)}this.closestPointRangePx=s},getValidPoints:function(a, -b){var c=this.chart;return Fa(a||this.points||[],function(a){return b&&!c.isInsidePlot(a.plotX,a.plotY,c.inverted)?!1:!a.isNull})},setClip:function(a){var b=this.chart,c=this.options,d=b.renderer,e=b.inverted,f=this.clipBox,g=f||b.clipBox,h=this.sharedClipKey||["_sharedClip",a&&a.duration,a&&a.easing,g.height,c.xAxis,c.yAxis].join(","),i=b[h],j=b[h+"m"];if(!i){if(a)g.width=0,b[h+"m"]=j=d.clipRect(-99,e?-b.plotLeft:-b.plotTop,99,e?b.chartWidth:b.chartHeight);b[h]=i=d.clipRect(g)}a&&(i.count+=1);if(c.clip!== -!1)this.group.clip(a||f?i:b.clipRect),this.markerGroup.clip(j),this.sharedClipKey=h;a||(i.count-=1,i.count<=0&&h&&b[h]&&(f||(b[h]=b[h].destroy()),b[h+"m"]&&(b[h+"m"]=b[h+"m"].destroy())))},animate:function(a){var b=this.chart,c=this.options.animation,d;if(c&&!ea(c))c=W[this.type].animation;a?this.setClip(c):(d=this.sharedClipKey,(a=b[d])&&a.animate({width:b.plotSizeX},c),b[d+"m"]&&b[d+"m"].animate({width:b.plotSizeX+99},c),this.animate=null)},afterAnimate:function(){this.setClip();O(this,"afterAnimate")}, -drawPoints:function(){var a,b=this.points,c=this.chart,d,e,f,g,h,i,j,k,l=this.options.marker,m=this.pointAttr[""],n,p,r,s=this.markerGroup,o=q(l.enabled,this.xAxis.isRadial,this.closestPointRangePx>2*l.radius);if(l.enabled!==!1||this._hasPointMarkers)for(f=b.length;f--;)if(g=b[f],d=V(g.plotX),e=g.plotY,k=g.graphic,n=g.marker||{},p=!!g.marker,a=o&&n.enabled===t||n.enabled,r=g.isInside,a&&C(e)&&g.y!==null)if(a=g.pointAttr[g.selected?"select":""]||m,h=a.r,i=q(n.symbol,this.symbol),j=i.indexOf("url")=== -0,k)k[r?"show":"hide"](!0).attr(a).animate(A({x:d-h,y:e-h},k.symbolName?{width:2*h,height:2*h}:{}));else{if(r&&(h>0||j))g.graphic=c.renderer.symbol(i,d-h,e-h,2*h,2*h,p?n:l).attr(a).add(s)}else if(k)g.graphic=k.destroy()},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,h={},a=a||{},b=b||{},c=c||{},d=d||{};for(f in e)g=e[f],h[f]=q(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=a.options,c=W[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color,h=a.options.negativeColor, -i={stroke:g,fill:g},j=a.points||[],k,l=[],m,n=a.pointAttrToOptions;f=a.hasPointSpecificOptions;var p=c.lineColor,r=c.fillColor;k=b.turboThreshold;var s=a.zones,F=a.zoneAxis||"y",u,x;b.marker?(e.radius=e.radius||c.radius+e.radiusPlus,e.lineWidth=e.lineWidth||c.lineWidth+e.lineWidthPlus):(e.color=e.color||va(e.color||g).brighten(e.brightness).get(),e.negativeColor=e.negativeColor||va(e.negativeColor||h).brighten(e.brightness).get());l[""]=a.convertAttribs(c,i);o(["hover","select"],function(b){l[b]= -a.convertAttribs(d[b],l[""])});a.pointAttr=l;g=j.length;if(!k||g=i.value;)i=s[++f];k.color=k.fillColor=i=q(i.color,a.color)}f=b.colorByPoint||k.color;if(k.options)for(x in n)v(c[n[x]])&&(f=!0);if(f){c=c||{};m=[];d=c.states||{};f=d.hover=d.hover||{};if(!b.marker||k.negative&&!f.fillColor&&!e.fillColor)f[a.pointAttrToOptions.fill]=f.color||!k.options.color&&e[k.negative&& -h?"negativeColor":"color"]||va(k.color).brighten(f.brightness||e.brightness).get();u={color:k.color};if(!r)u.fillColor=k.color;if(!p)u.lineColor=k.color;c.hasOwnProperty("color")&&!c.color&&delete c.color;if(i&&!e.fillColor)f.fillColor=i;m[""]=a.convertAttribs(A(u,c),l[""]);m.hover=a.convertAttribs(d.hover,l.hover,m[""]);m.select=a.convertAttribs(d.select,l.select,m[""])}else m=l;k.pointAttr=m}},destroy:function(){var a=this,b=a.chart,c=/AppleWebKit\/533/.test(Na),d,e=a.data||[],f,g,h;O(a,"destroy"); -T(a);o(a.axisTypes||[],function(b){if(h=a[b])za(h.series,a),h.isDirty=h.forceRedraw=!0});a.legendItem&&a.chart.legend.destroyItem(a);for(d=e.length;d--;)(f=e[d])&&f.destroy&&f.destroy();a.points=null;clearTimeout(a.animationTimeout);for(g in a)a[g]instanceof Z&&!a[g].survive&&(d=c&&g==="group"?"hide":"destroy",a[g][d]());if(b.hoverSeries===a)b.hoverSeries=null;za(b.series,a);for(g in a)delete a[g]},getGraphPath:function(a,b,c){var d=this,e=d.options,f=e.step,g,h=[],i,a=a||d.points;(g=a.reversed)&& -a.reverse();(f={right:1,center:2}[f]||f&&3)&&g&&(f=4-f);e.connectNulls&&!b&&!c&&(a=this.getValidPoints(a));o(a,function(g,k){var l=g.plotX,m=g.plotY,n=a[k-1];if((g.leftCliff||n&&n.rightCliff)&&!c)i=!0;g.isNull&&!v(b)&&k>0?i=!e.connectNulls:g.isNull&&!b?i=!0:(k===0||i?n=["M",g.plotX,g.plotY]:d.getPointSpline?n=d.getPointSpline(a,g,k):f?(n=f===1?["L",n.plotX,m]:f===2?["L",(n.plotX+l)/2,n.plotY,"L",(n.plotX+l)/2,m]:["L",l,n.plotY],n.push("L",l,m)):n=["L",l,m],h.push.apply(h,n),i=!1)});return d.graphPath= -h},drawGraph:function(){var a=this,b=this.options,c=[["graph",b.lineColor||this.color,b.dashStyle]],d=b.lineWidth,e=b.linecap!=="square",f=(this.gappedPath||this.getGraphPath).call(this),g=this.fillGraph&&this.color||"none";o(this.zones,function(d,e){c.push(["zoneGraph"+e,d.color||a.color,d.dashStyle||b.dashStyle])});o(c,function(c,i){var j=c[0],k=a[j];if(k)k.animate({d:f});else if((d||g)&&f.length)k={stroke:c[1],"stroke-width":d,fill:g,zIndex:1},c[2]?k.dashstyle=c[2]:e&&(k["stroke-linecap"]=k["stroke-linejoin"]= -"round"),a[j]=a.chart.renderer.path(f).attr(k).add(a.group).shadow(i<2&&b.shadow)})},applyZones:function(){var a=this,b=this.chart,c=b.renderer,d=this.zones,e,f,g=this.clips||[],h,i=this.graph,j=this.area,k=w(b.chartWidth,b.chartHeight),l=this[(this.zoneAxis||"y")+"Axis"],m,n=l.reversed,p=b.inverted,r=l.horiz,s,F,u,x=!1;if(d.length&&(i||j)&&l.min!==t)i&&i.hide(),j&&j.hide(),m=l.getExtremes(),o(d,function(d,o){e=n?r?b.plotWidth:0:r?0:l.toPixels(m.min);e=G(w(q(f,e),0),k);f=G(w(y(l.toPixels(q(d.value, -m.max),!0)),0),k);x&&(e=f=l.toPixels(m.max));s=Math.abs(e-f);F=G(e,f);u=w(e,f);if(l.isXAxis){if(h={x:p?u:F,y:0,width:s,height:k},!r)h.x=b.plotHeight-h.x}else if(h={x:0,y:p?u:F,width:k,height:s},r)h.y=b.plotWidth-h.y;b.inverted&&c.isVML&&(h=l.isXAxis?{x:0,y:n?F:u,height:h.width,width:b.chartWidth}:{x:h.y-b.plotLeft-b.spacingBox.x,y:0,width:h.height,height:b.chartHeight});g[o]?g[o].animate(h):(g[o]=c.clipRect(h),i&&a["zoneGraph"+o].clip(g[o]),j&&a["zoneArea"+o].clip(g[o]));x=d.value>m.max}),this.clips= -g},invertGroups:function(){function a(){var a={width:b.yAxis.len,height:b.xAxis.len};o(["group","markerGroup"],function(c){b[c]&&b[c].attr(a).invert()})}var b=this,c=b.chart;if(b.xAxis)E(c,"resize",a),E(b,"destroy",function(){T(c,"resize",a)}),a(),b.invertGroups=a},plotGroup:function(a,b,c,d,e){var f=this[a],g=!f;g&&(this[a]=f=this.chart.renderer.g(b).attr({zIndex:d||0.1}).add(e),f.addClass("highcharts-series-"+this.index));f.attr({visibility:c})[g?"attr":"animate"](this.getPlotBox());return f},getPlotBox:function(){var a= -this.chart,b=this.xAxis,c=this.yAxis;if(a.inverted)b=c,c=this.xAxis;return{translateX:b?b.left:a.plotLeft,translateY:c?c.top:a.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this,b=a.chart,c,d=a.options,e=!!a.animate&&b.renderer.isSVG&&gb(d.animation).duration,f=a.visible?"inherit":"hidden",g=d.zIndex,h=a.hasRendered,i=b.seriesGroup;c=a.plotGroup("group","series",f,g,i);a.markerGroup=a.plotGroup("markerGroup","markers",f,g,i);e&&a.animate(!0);a.getAttribs();c.inverted=a.isCartesian?b.inverted: -!1;a.drawGraph&&(a.drawGraph(),a.applyZones());o(a.points,function(a){a.redraw&&a.redraw()});a.drawDataLabels&&a.drawDataLabels();a.visible&&a.drawPoints();a.drawTracker&&a.options.enableMouseTracking!==!1&&a.drawTracker();b.inverted&&a.invertGroups();d.clip!==!1&&!a.sharedClipKey&&!h&&c.clip(b.clipRect);e&&a.animate();if(!h)a.animationTimeout=Za(function(){a.afterAnimate()},e);a.isDirty=a.isDirtyData=!1;a.hasRendered=!0},redraw:function(){var a=this.chart,b=this.isDirty||this.isDirtyData,c=this.group, -d=this.xAxis,e=this.yAxis;c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:q(d&&d.left,a.plotLeft),translateY:q(e&&e.top,a.plotTop)}));this.translate();this.render();b&&delete this.kdTree},kdDimensions:1,kdAxisArray:["clientX","plotY"],searchPoint:function(a,b){var c=this.xAxis,d=this.yAxis,e=this.chart.inverted;return this.searchKDTree({clientX:e?c.len-a.chartY+c.pos:a.chartX-c.pos,plotY:e?d.len-a.chartX+d.pos:a.chartY-d.pos},b)},buildKDTree:function(){function a(c, -e,f){var g,h;if(h=c&&c.length)return g=b.kdAxisArray[e%f],c.sort(function(a,b){return a[g]-b[g]}),h=Math.floor(h/2),{point:c[h],left:a(c.slice(0,h),e+1,f),right:a(c.slice(h+1),e+1,f)}}var b=this,c=b.kdDimensions;delete b.kdTree;Za(function(){b.kdTree=a(b.getValidPoints(null,!b.directTouch),c,c)},b.options.kdNow?0:1)},searchKDTree:function(a,b){function c(a,b,j,k){var l=b.point,m=d.kdAxisArray[j%k],n,p,r=l;p=v(a[e])&&v(l[e])?Math.pow(a[e]-l[e],2):null;n=v(a[f])&&v(l[f])?Math.pow(a[f]-l[f],2):null; -n=(p||0)+(n||0);l.dist=v(n)?Math.sqrt(n):Number.MAX_VALUE;l.distX=v(p)?Math.sqrt(p):Number.MAX_VALUE;m=a[m]-l[m];n=m<0?"left":"right";p=m<0?"right":"left";b[n]&&(n=c(a,b[n],j+1,k),r=n[g]0&&this.singleStacks===!1&&(s.points[o][0]=s.points[this.index+","+x+",0"][0]);e==="percent"?(r=r?i:j,k&&m[r]&&m[r][x]?(r=m[r][x],s.total=r.total=w(r.total,s.total)+S(D)||0):s.total=ka(s.total+(S(D)||0))):s.total=ka(s.total+(D||0));s.cum=q(s.cum,g)+(D||0);if(D!==null)s.points[o].push(s.cum),c[u]=s.cum}if(e==="percent")l.usePercentage=!0;this.stackedYData=c;l.oldStacks= -{}}};P.prototype.setPercentStacks=function(){var a=this,b=a.stackKey,c=a.yAxis.stacks,d=a.processedXData,e;o([b,"-"+b],function(b){var f;for(var g=d.length,h,i;g--;)if(h=d[g],e=a.getStackIndicator(e,h,a.index),f=(i=c[b]&&c[b][h])&&i.points[e.key],h=f)i=i.total?100/i.total:0,h[0]=ka(h[0]*i),h[1]=ka(h[1]*i),a.stackedYData[g]=h[1]})};P.prototype.getStackIndicator=function(a,b,c){!v(a)||a.x!==b?a={x:b,index:0}:a.index++;a.key=[c,b,a.index].join(",");return a};A(Ba.prototype,{addSeries:function(a,b,c){var d, -e=this;a&&(b=q(b,!0),O(e,"addSeries",{options:a},function(){d=e.initSeries(a);e.isDirtyLegend=!0;e.linkSeries();b&&e.redraw(c)}));return d},addAxis:function(a,b,c,d){var e=b?"xAxis":"yAxis",f=this.options,a=z(a,{index:this[e].length,isX:b});new J(this,a);f[e]=ua(f[e]||{});f[e].push(a);q(c,!0)&&this.redraw(d)},showLoading:function(a){var b=this,c=b.options,d=b.loadingDiv,e=c.loading,f=function(){d&&N(d,{left:b.plotLeft+"px",top:b.plotTop+"px",width:b.plotWidth+"px",height:b.plotHeight+"px"})};if(!d)b.loadingDiv= -d=fa(Va,{className:"highcharts-loading"},A(e.style,{zIndex:10,display:"none"}),b.container),b.loadingSpan=fa("span",null,e.labelStyle,d),E(b,"redraw",f);b.loadingSpan.innerHTML=a||c.lang.loading;if(!b.loadingShown)N(d,{opacity:0,display:""}),db(d,{opacity:e.style.opacity},{duration:e.showDuration||0}),b.loadingShown=!0;f()},hideLoading:function(){var a=this.options,b=this.loadingDiv;b&&db(b,{opacity:0},{duration:a.loading.hideDuration||100,complete:function(){N(b,{display:"none"})}});this.loadingShown= -!1}});A(Ha.prototype,{update:function(a,b,c,d){function e(){f.applyOptions(a);if(f.y===null&&h)f.graphic=h.destroy();if(ea(a)&&!Ja(a))f.redraw=function(){if(h&&h.element&&a&&a.marker&&a.marker.symbol)f.graphic=h.destroy();if(a&&a.dataLabels&&f.dataLabel)f.dataLabel=f.dataLabel.destroy();f.redraw=null};i=f.index;g.updateParallelArrays(f,i);if(l&&f.name)l[f.x]=f.name;k.data[i]=ea(k.data[i])&&!Ja(k.data[i])?f.options:a;g.isDirty=g.isDirtyData=!0;if(!g.fixedBox&&g.hasCartesianSeries)j.isDirtyBox=!0;if(k.legendType=== -"point")j.isDirtyLegend=!0;b&&j.redraw(c)}var f=this,g=f.series,h=f.graphic,i,j=g.chart,k=g.options,l=g.xAxis&&g.xAxis.names,b=q(b,!0);d===!1?e():f.firePointEvent("update",{options:a},e)},remove:function(a,b){this.series.removePoint(sa(this,this.series.data),a,b)}});A(P.prototype,{addPoint:function(a,b,c,d){var e=this,f=e.options,g=e.data,h=e.graph,i=e.area,j=e.chart,k=e.xAxis&&e.xAxis.names,l=h&&h.shift||0,m=["graph","area"],h=f.data,n,p=e.xData;$a(d,j);if(c){for(d=e.zones.length;d--;)m.push("zoneGraph"+ -d,"zoneArea"+d);o(m,function(a){if(e[a])e[a].shift=l+(f.step?2:1)})}if(i)i.isArea=!0;b=q(b,!0);i={series:e};e.pointClass.prototype.applyOptions.apply(i,[a]);m=i.x;d=p.length;if(e.requireSorting&&mm;)d--;e.updateParallelArrays(i,"splice",d,0,0);e.updateParallelArrays(i,d);if(k&&i.name)k[m]=i.name;h.splice(d,0,a);n&&(e.data.splice(d,0,null),e.processData());f.legendType==="point"&&e.generatePoints();c&&(g[0]&&g[0].remove?g[0].remove(!1):(g.shift(),e.updateParallelArrays(i, -"shift"),h.shift()));e.isDirty=!0;e.isDirtyData=!0;b&&(e.getAttribs(),j.redraw())},removePoint:function(a,b,c){var d=this,e=d.data,f=e[a],g=d.points,h=d.chart,i=function(){g&&g.length===e.length&&g.splice(a,1);e.splice(a,1);d.options.data.splice(a,1);d.updateParallelArrays(f||{series:d},"splice",a,1);f&&f.destroy();d.isDirty=!0;d.isDirtyData=!0;b&&h.redraw()};$a(c,h);b=q(b,!0);f?f.firePointEvent("remove",null,i):i()},remove:function(a,b){var c=this,d=c.chart;O(c,"remove",null,function(){c.destroy(); -d.isDirtyLegend=d.isDirtyBox=!0;d.linkSeries();q(a,!0)&&d.redraw(b)})},update:function(a,b){var c=this,d=this.chart,e=this.userOptions,f=this.type,g=I[f].prototype,h=["group","markerGroup","dataLabelsGroup"],i;if(a.type&&a.type!==f||a.zIndex!==void 0)h.length=0;o(h,function(a){h[a]=c[a];delete c[a]});a=z(e,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data},a);this.remove(!1);for(i in g)this[i]=t;A(this,I[a.type||f].prototype);o(h,function(a){c[a]=h[a]});this.init(d, -a);d.linkSeries();q(b,!0)&&d.redraw(!1)}});A(J.prototype,{update:function(a,b){var c=this.chart,a=c.options[this.coll][this.options.index]=z(this.userOptions,a);this.destroy(!0);this._addedPlotLB=this.chart._labelPanes=t;this.init(c,A(a,{events:t}));c.isDirtyBox=!0;q(b,!0)&&c.redraw()},remove:function(a){for(var b=this.chart,c=this.coll,d=this.series,e=d.length;e--;)d[e]&&d[e].remove(!1);za(b.axes,this);za(b[c],this);b.options[c].splice(this.options.index,1);o(b[c],function(a,b){a.options.index=b}); -this.destroy();b.isDirtyBox=!0;q(a,!0)&&b.redraw()},setTitle:function(a,b){this.update({title:a},b)},setCategories:function(a,b){this.update({categories:a},b)}});var Ia=ma(P);I.line=Ia;W.area=z(da,{softThreshold:!1,threshold:0});var ya=ma(P,{type:"area",singleStacks:!1,getStackPoints:function(){var a=[],b=[],c=this.xAxis,d=this.yAxis,e=d.stacks[this.stackKey],f={},g=this.points,h=this.index,i=d.series,j=i.length,k,l=q(d.options.reversedStacks,!0)?1:-1,m,n;if(this.options.stacking){for(m=0;m=0&&m=0&&ma&&h>e?(h=w(a,e),j=2*e-h):hc&&j>e?(j=w(c,e),h=2*e-j):j0.5;b=Math.round(b)+f;d-=b;g&&d&&(b-=1,d+=1);return{x:a,y:b,width:c,height:d}},translate:function(){var a=this,b=a.chart,c=a.options,d=a.borderWidth=q(c.borderWidth,a.closestPointRange*a.xAxis.transA<2?0:1),e=a.yAxis,f=a.translatedThreshold=e.getThreshold(c.threshold), -g=q(c.minPointLength,5),h=a.getColumnMetrics(),i=h.width,j=a.barW=w(i,1+2*d),k=a.pointXOffset=h.offset;b.inverted&&(f-=0.5);c.pointPadding&&(j=Ea(j));P.prototype.translate.apply(a);o(a.points,function(c){var d=G(q(c.yBottom,f),9E4),h=999+S(d),h=G(w(-h,c.plotY),e.len+h),p=c.plotX+k,r=j,o=G(h,d),F,u=w(h,d)-o;S(u)g?d-g:f-(F?g:0));c.barX=p;c.pointWidth=i;c.tooltipPos=b.inverted?[e.len+e.pos-b.plotLeft-h,a.xAxis.len-p-r/2,u]:[p+r/ -2,h+e.pos-b.plotTop,u];c.shapeType="rect";c.shapeArgs=a.crispCol(p,o,r,u)})},getSymbol:ra,drawLegendSymbol:aa.drawRectangle,drawGraph:ra,drawPoints:function(){var a=this,b=this.chart,c=a.options,d=b.renderer,e=c.animationLimit||250,f,g;o(a.points,function(h){var i=h.graphic,j;if(C(h.plotY)&&h.y!==null)f=h.shapeArgs,j=v(a.borderWidth)?{"stroke-width":a.borderWidth}:{},g=h.pointAttr[h.selected?"select":""]||a.pointAttr[""],i?(Sa(i),i.attr(j).attr(g)[b.pointCount\u25cf {series.name}
',pointFormat:"x: {point.x}
y: {point.y}
"}});ya=ma(P,{type:"scatter", -sorted:!1,requireSorting:!1,noSharedTooltip:!0,trackerGroups:["group","markerGroup","dataLabelsGroup"],takeOrdinalPosition:!1,kdDimensions:2,drawGraph:function(){this.options.lineWidth&&P.prototype.drawGraph.call(this)}});I.scatter=ya;W.pie=z(da,{borderColor:"#FFFFFF",borderWidth:1,center:[null,null],clip:!1,colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return this.y===null?void 0:this.point.name},x:0},ignoreHiddenPoint:!0,legendType:"point",marker:null,size:null,showInLegend:!1, -slicedOffset:10,states:{hover:{brightness:0.1,shadow:!1}},stickyTracking:!1,tooltip:{followPointer:!0}});da={type:"pie",isCartesian:!1,pointClass:ma(Ha,{init:function(){Ha.prototype.init.apply(this,arguments);var a=this,b;a.name=q(a.name,"Slice");b=function(b){a.slice(b.type==="select")};E(a,"select",b);E(a,"unselect",b);return a},setVisible:function(a,b){var c=this,d=c.series,e=d.chart,f=d.options.ignoreHiddenPoint,b=q(b,f);if(a!==c.visible){c.visible=c.options.visible=a=a===t?!c.visible:a;d.options.data[sa(c, -d.data)]=c.options;o(["graphic","dataLabel","connector","shadowGroup"],function(b){if(c[b])c[b][a?"show":"hide"](!0)});c.legendItem&&e.legend.colorizeItem(c,a);!a&&c.state==="hover"&&c.setState("");if(f)d.isDirty=!0;b&&e.redraw()}},slice:function(a,b,c){var d=this.series;$a(c,d.chart);q(b,!0);this.sliced=this.options.sliced=a=v(a)?a:!this.sliced;d.options.data[sa(this,d.data)]=this.options;a=a?this.slicedTranslation:{translateX:0,translateY:0};this.graphic.animate(a);this.shadowGroup&&this.shadowGroup.animate(a)}, -haloPath:function(a){var b=this.shapeArgs,c=this.series.chart;return this.sliced||!this.visible?[]:this.series.chart.renderer.symbols.arc(c.plotLeft+b.x,c.plotTop+b.y,b.r+a,b.r+a,{innerR:this.shapeArgs.r,start:b.start,end:b.end})}}),requireSorting:!1,directTouch:!0,noSharedTooltip:!0,trackerGroups:["group","dataLabelsGroup"],axisTypes:[],pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color"},animate:function(a){var b=this,c=b.points,d=b.startAngleRad;if(!a)o(c,function(a){var c= -a.graphic,g=a.shapeArgs;c&&(c.attr({r:a.startR||b.center[3]/2,start:d,end:d}),c.animate({r:g.r,start:g.start,end:g.end},b.options.animation))}),b.animate=null},updateTotals:function(){var a,b=0,c=this.points,d=c.length,e,f=this.options.ignoreHiddenPoint;for(a=0;a0&&(e.visible||!f)?e.y/b*100:0,e.total=b},generatePoints:function(){P.prototype.generatePoints.call(this);this.updateTotals()},translate:function(a){this.generatePoints(); -var b=0,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f,g,h,i=c.startAngle||0,j=this.startAngleRad=Aa/180*(i-90),i=(this.endAngleRad=Aa/180*(q(c.endAngle,i+360)-90))-j,k=this.points,l=c.dataLabels.distance,c=c.ignoreHiddenPoint,m,n=k.length,p;if(!a)this.center=a=this.getCenter();this.getX=function(b,c){h=Y.asin(G((b-a[1])/(a[2]/2+l),1));return a[0]+(c?-1:1)*ca(h)*(a[2]/2+l)};for(m=0;m1.5*Aa?h-=2*Aa:h<-Aa/2&&(h+=2*Aa);p.slicedTranslation={translateX:y(ca(h)*d),translateY:y(la(h)*d)};f=ca(h)*a[2]/2;g=la(h)*a[2]/2;p.tooltipPos=[a[0]+f*0.7,a[1]+g*0.7];p.half=h<-Aa/2||h>Aa/2?1:0;p.angle=h;e=G(e,l/2);p.labelPos=[a[0]+f+ca(h)*l,a[1]+g+la(h)*l,a[0]+f+ca(h)*e,a[1]+g+la(h)*e,a[0]+f,a[1]+g,l<0?"center":p.half?"right":"left",h]}},drawGraph:null,drawPoints:function(){var a=this,b=a.chart.renderer,c,d,e=a.options.shadow, -f,g,h,i;if(e&&!a.shadowGroup)a.shadowGroup=b.g("shadow").add(a.group);o(a.points,function(j){if(j.y!==null){d=j.graphic;h=j.shapeArgs;f=j.shadowGroup;g=j.pointAttr[j.selected?"select":""];if(!g.stroke)g.stroke=g.fill;if(e&&!f)f=j.shadowGroup=b.g("shadow").add(a.shadowGroup);c=j.sliced?j.slicedTranslation:{translateX:0,translateY:0};f&&f.attr(c);if(d)d.setRadialReference(a.center).attr(g).animate(A(h,c));else{i={"stroke-linejoin":"round"};if(!j.visible)i.visibility="hidden";j.graphic=d=b[j.shapeType](h).setRadialReference(a.center).attr(g).attr(i).attr(c).add(a.group).shadow(e, -f)}}})},searchPoint:ra,sortByAngle:function(a,b){a.sort(function(a,d){return a.angle!==void 0&&(d.angle-a.angle)*b})},drawLegendSymbol:aa.drawRectangle,getCenter:bc.getCenter,getSymbol:ra};da=ma(P,da);I.pie=da;P.prototype.drawDataLabels=function(){var a=this,b=a.options,c=b.cursor,d=b.dataLabels,e=a.points,f,g,h=a.hasRendered||0,i,j,k=q(d.defer,!0),l=a.chart.renderer;if(d.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(d),j=a.plotGroup("dataLabelsGroup","data-labels",k&&!h?"hidden": -"visible",d.zIndex||6),k&&(j.attr({opacity:+h}),h||E(a,"afterAnimate",function(){a.visible&&j.show();j[b.animation?"animate":"attr"]({opacity:1},{duration:200})})),g=d,o(e,function(e){var h,k=e.dataLabel,r,o,F=e.connector,u=!0,x,D={};f=e.dlOptions||e.options&&e.options.dataLabels;h=q(f&&f.enabled,g.enabled)&&e.y!==null;if(k&&!h)e.dataLabel=k.destroy();else if(h){d=z(g,f);x=d.style;h=d.rotation;r=e.getLabelConfig();i=d.format?La(d.format,r):d.formatter.call(r,d);x.color=q(d.color,x.color,a.color,"black"); -if(k)if(v(i))k.attr({text:i}),u=!1;else{if(e.dataLabel=k=k.destroy(),F)e.connector=F.destroy()}else if(v(i)){k={fill:d.backgroundColor,stroke:d.borderColor,"stroke-width":d.borderWidth,r:d.borderRadius||0,rotation:h,padding:d.padding,zIndex:1};if(x.color==="contrast")D.color=d.inside||d.distance<0||b.stacking?l.getContrast(e.color||a.color):"#000000";if(c)D.cursor=c;for(o in k)k[o]===t&&delete k[o];k=e.dataLabel=l[h?"text":"label"](i,0,-9999,d.shape,null,null,d.useHTML).attr(k).css(A(x,D)).add(j).shadow(d.shadow)}k&& -a.alignDataLabel(e,k,d,null,u)}})};P.prototype.alignDataLabel=function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=q(a.plotX,-9999),i=q(a.plotY,-9999),j=b.getBBox(),k=f.renderer.fontMetrics(c.style.fontSize).b,l=c.rotation,m=c.align,n=this.visible&&(a.series.forceDL||f.isInsidePlot(h,y(i),g)||d&&f.isInsidePlot(h,g?d.x+1:d.y+d.height-1,g)),p=q(c.overflow,"justify")==="justify";if(n)d=A({x:g?f.plotWidth-i:h,y:y(g?f.plotHeight-h:i),width:0,height:0},d),A(c,{width:j.width,height:j.height}),l?(p=!1,g=f.renderer.rotCorr(k, -l),g={x:d.x+c.x+d.width/2+g.x,y:d.y+c.y+{top:0,middle:0.5,bottom:1}[c.verticalAlign]*d.height},b[e?"attr":"animate"](g).attr({align:m}),h=(l+720)%360,h=h>180&&h<360,m==="left"?g.y-=h?j.height:0:m==="center"?(g.x-=j.width/2,g.y-=j.height/2):m==="right"&&(g.x-=j.width,g.y-=h?0:j.height)):(b.align(c,null,d),g=b.alignAttr),p?this.justifyDataLabel(b,c,g,j,d,e):q(c.crop,!0)&&(n=f.isInsidePlot(g.x,g.y)&&f.isInsidePlot(g.x+j.width,g.y+j.height)),c.shape&&!l&&b.attr({anchorX:a.plotX,anchorY:a.plotY});if(!n)Sa(b), -b.attr({y:-9999}),b.placed=!1};P.prototype.justifyDataLabel=function(a,b,c,d,e,f){var g=this.chart,h=b.align,i=b.verticalAlign,j,k,l=a.box?0:a.padding||0;j=c.x+l;if(j<0)h==="right"?b.align="left":b.x=-j,k=!0;j=c.x+d.width-l;if(j>g.plotWidth)h==="left"?b.align="right":b.x=g.plotWidth-j,k=!0;j=c.y+l;if(j<0)i==="bottom"?b.verticalAlign="top":b.y=-j,k=!0;j=c.y+d.height-l;if(j>g.plotHeight)i==="top"?b.verticalAlign="bottom":b.y=g.plotHeight-j,k=!0;if(k)a.placed=!f,a.align(b,null,e)};if(I.pie)I.pie.prototype.drawDataLabels= -function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=q(e.connectorPadding,10),g=q(e.connectorWidth,1),h=d.plotWidth,i=d.plotHeight,j,k,l=q(e.softConnector,!0),m=e.distance,n=a.center,p=n[2]/2,r=n[1],s=m>0,F,u,x,D=[[],[]],v,t,A,B,z,C=[0,0,0,0],H=function(a,b){return b.y-a.y};if(a.visible&&(e.enabled||a._hasPointLabels)){P.prototype.drawDataLabels.apply(a);o(b,function(a){if(a.dataLabel&&a.visible)D[a.half].push(a),a.dataLabel._pos=null});for(B=2;B--;){var E=[],L=[],K=D[B],I=K.length, -J;if(I){a.sortByAngle(K,B-0.5);for(z=b=0;!b&&K[z];)b=K[z]&&K[z].dataLabel&&(K[z].dataLabel.getBBox().height||21),z++;if(m>0){u=G(r+p+m,d.plotHeight);for(z=w(0,r-p-m);z<=u;z+=b)E.push(z);u=E.length;if(I>u){c=[].concat(K);c.sort(H);for(z=I;z--;)c[z].rank=z;for(z=I;z--;)K[z].rank>=u&&K.splice(z,1);I=K.length}for(z=0;z0){if(u=L.pop(),J=u.i,t=u.y,c>t&&E[J+1]!==null||ch-f&&(C[1]=w(y(v+u-h+f),C[1])),t- -b/2<0?C[0]=w(y(-t+b/2),C[0]):t+b/2>i&&(C[2]=w(y(t+b/2-i),C[2]))}}}if(Da(C)===0||this.verifyDataLabelOverflow(C))this.placeDataLabels(),s&&g&&o(this.points,function(b){j=b.connector;x=b.labelPos;if((F=b.dataLabel)&&F._pos&&b.visible)A=F._attr.visibility,v=F.connX,t=F.connY,k=l?["M",v+(x[6]==="left"?5:-5),t,"C",v,t,2*x[2]-x[4],2*x[3]-x[5],x[2],x[3],"L",x[4],x[5]]:["M",v+(x[6]==="left"?5:-5),t,"L",x[2],x[3],"L",x[4],x[5]],j?(j.animate({d:k}),j.attr("visibility",A)):b.connector=j=a.chart.renderer.path(k).attr({"stroke-width":g, -stroke:e.connectorColor||b.color||"#606060",visibility:A}).add(a.dataLabelsGroup);else if(j)b.connector=j.destroy()})}},I.pie.prototype.placeDataLabels=function(){o(this.points,function(a){var b=a.dataLabel;if(b&&a.visible)(a=b._pos)?(b.attr(b._attr),b[b.moved?"animate":"attr"](a),b.moved=!0):b&&b.attr({y:-9999})})},I.pie.prototype.alignDataLabel=ra,I.pie.prototype.verifyDataLabelOverflow=function(a){var b=this.center,c=this.options,d=c.center,e=c.minSize||80,f=e,g;d[0]!==null?f=w(b[2]-w(a[1],a[3]), -e):(f=w(b[2]-a[1]-a[3],e),b[0]+=(a[3]-a[1])/2);d[1]!==null?f=w(G(f,b[2]-w(a[0],a[2])),e):(f=w(G(f,b[2]-a[0]-a[2]),e),b[1]+=(a[0]-a[2])/2);fq(this.translatedThreshold, -g.yAxis.len)),j=q(c.inside,!!this.options.stacking);if(h){d=z(h);if(d.y<0)d.height+=d.y,d.y=0;h=d.y+d.height-g.yAxis.len;h>0&&(d.height-=h);f&&(d={x:g.yAxis.len-d.y-d.height,y:g.xAxis.len-d.x-d.width,width:d.height,height:d.width});if(!j)f?(d.x+=i?0:d.width,d.width=0):(d.y+=i?d.height:0,d.height=0)}c.align=q(c.align,!f||j?"center":i?"right":"left");c.verticalAlign=q(c.verticalAlign,f||j?"middle":i?"top":"bottom");P.prototype.alignDataLabel.call(this,a,b,c,d,e)};(function(a){var b=a.Chart,c=a.each, -d=a.pick,e=a.addEvent;b.prototype.callbacks.push(function(a){function b(){var e=[];c(a.series,function(a){var b=a.options.dataLabels,f=a.dataLabelCollections||["dataLabel"];(b.enabled||a._hasPointLabels)&&!b.allowOverlap&&a.visible&&c(f,function(b){c(a.points,function(a){if(a[b])a[b].labelrank=d(a.labelrank,a.shapeArgs&&a.shapeArgs.height),e.push(a[b])})})});a.hideOverlappingLabels(e)}b();e(a,"redraw",b)});b.prototype.hideOverlappingLabels=function(a){var b=a.length,d,e,j,k,l,m,n,p,r;for(e=0;el.x+n.translateX+(j.width-r)||m.x+p.translateX+(k.width-r)l.y+n.translateY+(j.height-r)||m.y+p.translateY+(k.height-r)h;if(b.series.length&&(i||l>G(k.dataMin,k.min))&&(!i||jd;f[j]g*5||p){if(f[j]>q){for(k=a.call(this,b,f[i],f[j],e);k.length&&k[0]<=q;)k.shift();k.length&&(q=k[k.length-1]);o=o.concat(k)}i=j+1}if(p)break}a=k.info;if(h&&a.unitRange<=M.hour){j=o.length-1;for(i=1;id?a-1:a;for(u=void 0;h--;)i=j[h],d=u-i,u&&d2){d=b[1]-b[0];for(h=a-1;h--&&!c;)b[h+1]-b[h]!==d&&(c=!0);if(!this.options.keepOrdinalPadding&&(b[0]-f>d||g-b[b.length-1]>d))c=!0}c?(this.ordinalPositions=b,d=this.val2lin(w(f,b[0]),!0),h=w(this.val2lin(G(g,b[b.length-1]),!0),1),this.ordinalSlope=g=(g-f)/(h-d),this.ordinalOffset=f-d*g):this.ordinalPositions=this.ordinalSlope=this.ordinalOffset=t}this.isOrdinal=e&&c;this.groupIntervalFactor= -null},val2lin:function(a,b){var c=this.ordinalPositions,d;if(c){var e=c.length,f;for(d=e;d--;)if(c[d]===a){f=d;break}for(d=e-1;d--;)if(a>c[d]||d===0){c=(a-c[d])/(c[d+1]-c[d]);f=d+c;break}d=b?f:this.ordinalSlope*(f||0)+this.ordinalOffset}else d=a;return d},lin2val:function(a,b){var c=this.ordinalPositions;if(c){var d=this.ordinalSlope,e=this.ordinalOffset,f=c.length-1,g,h;if(b)a<0?a=c[0]:a>f?a=c[f]:(f=V(a),h=a-f);else for(;f--;)if(g=d*f+e,a>=g){d=d*(f+1)+e;h=(a-g)/(d-g);break}c=h!==t&&c[f]!==t?c[f]+ -(h?h*(c[f+1]-c[f]):0):a}else c=a;return c},getExtendedPositions:function(){var a=this.chart,b=this.series[0].currentDataGrouping,c=this.ordinalIndex,d=b?b.count+b.unitName:"raw",e=this.getExtremes(),f,g;if(!c)c=this.ordinalIndex={};if(!c[d])f={series:[],getExtremes:function(){return{min:e.dataMin,max:e.dataMax}},options:{ordinal:!0},val2lin:J.prototype.val2lin},o(this.series,function(c){g={xAxis:f,xData:c.xData,chart:a,destroyGroupedData:ra};g.options={dataGrouping:b?{enabled:!0,forced:!0,approximation:"open", -units:[[b.unitName,[b.count]]]}:{enabled:!1}};c.processData.apply(g);f.series.push(g)}),this.beforeSetTickPositions.apply(f),c[d]=f.ordinalPositions;return c[d]},getGroupIntervalFactor:function(a,b,c){var d,c=c.processedXData,e=c.length,f=[];d=this.groupIntervalFactor;if(!d){for(d=0;d1)k&&o(k,function(a){a.setState()}),f<0?(k=m,p=c.ordinalPositions?c:m):(k= -c.ordinalPositions?c:m,p=m),m=p.ordinalPositions,h>m[m.length-1]&&m.push(h),this.fixedRange=j-i,f=c.toFixedRange(null,null,l.apply(k,[n.apply(k,[i,!0])+f,!0]),l.apply(p,[n.apply(p,[j,!0])+f,!0])),f.min>=G(g.dataMin,i)&&f.max<=w(h,j)&&c.setExtremes(f.min,f.max,!0,!1,{trigger:"pan"}),this.mouseDownX=d,N(this.container,{cursor:"move"})}else e=!0}else e=!0;e&&a.apply(this,Array.prototype.slice.call(arguments,1))});P.prototype.gappedPath=function(){var a=this.options.gapSize,b=this.points.slice(),c=b.length- -1;if(a&&c>0)for(;c--;)b[c+1].x-b[c].x>this.closestPointRange*a&&b.splice(c+1,0,{isNull:!0});return this.getGraphPath(b)};(function(a){a(B)})(function(a){function b(){return Array.prototype.slice.call(arguments,1)}function c(a){a.apply(this);this.drawBreaks(this.xAxis,["x"]);this.drawBreaks(this.yAxis,d(this.pointArrayMap,["y"]))}var d=a.pick,e=a.wrap,f=a.each,g=a.extend,h=a.fireEvent,i=a.Axis,j=a.Series;g(i.prototype,{isInBreak:function(a,b){var c=a.repeat||Infinity,d=a.from,e=a.to-a.from,c=b>=d? -(b-d)%c:c-(d-b)%c;return a.inclusive?c<=e:c=a)break;else if(d.isInBreak(c,a)){b-=a-c.from;break}return b};this.lin2val=function(a){var b,c;for(c=0;c=a)break;else b.tok;)l-=f;for(;lb.to||i>b.from&&ob.from&&ob.from&&o>b.to&&o=c[0])break;for(;q<=j;q++){for(;c[1]!==t&&a[q]>=c[1]||q===j;)if(k=c.shift(),l=d.apply(0,n),l!==t&&(g.push(k),h.push(l),i.push({start:v,length:n[0].length})),v=q,n[0]=[],n[1]=[],n[2]=[],n[3]=[],q===j)break;if(q===j)break;if(p){k=this.cropStart+q;k=e&&e[k]||this.pointClass.prototype.applyOptions.apply({series:this}, -[f[k]]);var u;for(l=0;l0;)f[b]+= -g/2;f[0]=Math.max(f[0],c)}this.currentDataGrouping=i.info;this.closestPointRange=i.info.totalRange;this.groupMap=e[2];if(v(f[0])&&f[0]this.chart.plotSizeX/d||b&&f.forced)e=!0;return e?d:0};J.prototype.setDataGrouping=function(a,b){var c,b=q(b,!0);a||(a={forced:!1,units:null});if(this instanceof J)for(c=this.series.length;c--;)this.series[c].update({dataGrouping:a}, -!1);else o(this.chart.options.series,function(b){b.dataGrouping=a},!1);b&&this.chart.redraw()};W.ohlc=z(W.column,{lineWidth:1,tooltip:{pointFormat:'\u25cf {series.name}
Open: {point.open}
High: {point.high}
Low: {point.low}
Close: {point.close}
'},states:{hover:{lineWidth:3}},threshold:null});da=ma(I.column,{type:"ohlc",pointArrayMap:["open","high","low","close"],toYData:function(a){return[a.open,a.high,a.low,a.close]},pointValKey:"high", -pointAttrToOptions:{stroke:"color","stroke-width":"lineWidth"},upColorProp:"stroke",getAttribs:function(){I.column.prototype.getAttribs.apply(this,arguments);var a=this.options,b=a.states,a=a.upColor||this.color,c=z(this.pointAttr),d=this.upColorProp;c[""][d]=a;c.hover[d]=b.hover.upColor||a;c.select[d]=b.select.upColor||a;o(this.points,function(a){if(a.open"},threshold:null,y:-30});I.flags=ma(I.column,{type:"flags",sorted:!1,noSharedTooltip:!0,allowDG:!1,takeOrdinalPosition:!1,trackerGroups:["markerGroup"],forceCrop:!0,init:P.prototype.init,pointAttrToOptions:{fill:"fillColor",stroke:"color","stroke-width":"lineWidth",r:"radius"},translate:function(){I.column.prototype.translate.apply(this);var a=this.options,b=this.chart,c=this.points,d=c.length-1,e,f,g=a.onSeries;e=g&&b.get(g); -var a=a.onKey||"y",g=e&&e.options.step,h=e&&e.points,i=h&&h.length,j=this.xAxis,k=j.getExtremes(),l,m,n;if(e&&e.visible&&i){e=e.currentDataGrouping;m=h[i-1].x+(e?e.totalRange:0);c.sort(function(a,b){return a.x-b.x});for(a="plot"+a[0].toUpperCase()+a.substr(1);i--&&c[d];)if(e=c[d],l=h[i],l.x<=e.x&&l[a]!==void 0){if(e.x<=m)e.plotY=l[a],l.x=k.min&&a.x<=k.max?a.plotY= -b.chartHeight-j.bottom-(j.opposite?j.height:0)+j.offset-b.plotTop:a.shapeArgs={};if((f=c[d-1])&&f.plotX===a.plotX){if(f.stackIndex===t)f.stackIndex=0;e=f.stackIndex+1}a.stackIndex=e})},drawPoints:function(){var a,b=this.pointAttr[""],c=this.points,d=this.chart,e=d.renderer,f,g,h=this.options,i=h.y,j,k,l,m,n,p,o=this.yAxis;for(k=c.length;k--;)if(l=c[k],a=l.plotX>this.xAxis.len,f=l.plotX,f>0&&(f-=q(l.lineWidth,h.lineWidth)%2),m=l.stackIndex,j=l.options.shape||h.shape,g=l.plotY,g!==t&&(g=l.plotY+i-(m!== -t&&m*h.stackDistance)),n=m?t:l.plotX,p=m?t:l.plotY,m=l.graphic,g!==t&&f>=0&&!a)a=l.pointAttr[l.selected?"select":""]||b,m?m.attr({x:f,y:g,r:a.r,anchorX:n,anchorY:p}):l.graphic=e.label(l.options.title||h.title||"A",f,g,j,n,p,h.useHTML).css(z(h.style,l.style)).attr(a).attr({align:j==="flag"?"left":"center",width:h.width,height:h.height}).add(this.markerGroup).shadow(h.shadow),l.tooltipPos=d.inverted?[o.len+o.pos-d.plotLeft-g,this.xAxis.len-f]:[f,g];else if(m)l.graphic=m.destroy()},drawTracker:function(){var a= -this.points;mb.drawTrackerPoint.apply(this);o(a,function(b){var c=b.graphic;c&&E(c.element,"mouseover",function(){if(b.stackIndex>0&&!b.raised)b._y=c.y,c.attr({y:b._y-8}),b.raised=!0;o(a,function(a){if(a!==b&&a.raised&&a.graphic)a.graphic.attr({y:a._y}),a.raised=!1})})})},animate:ra,buildKDTree:ra,setClip:ra});vb.flag=function(a,b,c,d,e){return["M",e&&e.anchorX||a,e&&e.anchorY||b,"L",a,b+d,a,b,a+c,b,a+c,b+d,a,b+d,"Z"]};o(["circle","square"],function(a){vb[a+"pin"]=function(b,c,d,e,f){var g=f&&f.anchorX, -f=f&&f.anchorY;a==="circle"&&e>d&&(b-=y((e-d)/2),d=e);b=vb[a](b,c,d,e);g&&f&&b.push("M",g,c>f?c:c+e,"L",g,f);return b}});Xa===B.VMLRenderer&&o(["flag","circlepin","squarepin"],function(a){lb.prototype.symbols[a]=vb[a]});var da=[].concat(Yb),wb=function(a){var b=Fa(arguments,function(a){return C(a)});if(b.length)return Math[a].apply(0,b)};da[4]=["day",[1,2,3,4]];da[5]=["week",[1,2,3]];A(R,{navigator:{handles:{backgroundColor:"#ebe7e8",borderColor:"#b2b1b6"},height:40,margin:25,maskFill:"rgba(128,179,236,0.3)", -maskInside:!0,outlineColor:"#b2b1b6",outlineWidth:1,series:{type:I.areaspline===t?"line":"areaspline",color:"#4572A7",compare:null,fillOpacity:0.05,dataGrouping:{approximation:"average",enabled:!0,groupPixelWidth:2,smoothed:!0,units:da},dataLabels:{enabled:!1,zIndex:2},id:"highcharts-navigator-series",lineColor:null,lineWidth:1,marker:{enabled:!1},pointRange:0,shadow:!1,threshold:null},xAxis:{tickWidth:0,lineWidth:0,gridLineColor:"#EEE",gridLineWidth:1,tickPixelInterval:200,labels:{align:"left",style:{color:"#888"}, -x:3,y:-4},crosshair:!1},yAxis:{gridLineWidth:0,startOnTick:!1,endOnTick:!1,minPadding:0.1,maxPadding:0.1,labels:{enabled:!1},crosshair:!1,title:{text:null},tickWidth:0}},scrollbar:{height:jb?20:14,barBackgroundColor:"#bfc8d1",barBorderRadius:0,barBorderWidth:1,barBorderColor:"#bfc8d1",buttonArrowColor:"#666",buttonBackgroundColor:"#ebe7e8",buttonBorderColor:"#bbb",buttonBorderRadius:0,buttonBorderWidth:1,minWidth:6,rifleColor:"#666",trackBackgroundColor:"#eeeeee",trackBorderColor:"#eeeeee",trackBorderWidth:1, -liveRedraw:ja&&!jb}});Gb.prototype={drawHandle:function(a,b){var c=this.chart.renderer,d=this.elementsToDestroy,e=this.handles,f=this.navigatorOptions.handles,f={fill:f.backgroundColor,stroke:f.borderColor,"stroke-width":1},g;this.rendered||(e[b]=c.g("navigator-handle-"+["left","right"][b]).css({cursor:"ew-resize"}).attr({zIndex:10-b}).add(),g=c.rect(-4.5,0,9,16,0,1).attr(f).add(e[b]),d.push(g),g=c.path(["M",-1.5,4,"L",-1.5,12,"M",0.5,4,"L",0.5,12]).attr(f).add(e[b]),d.push(g));e[b][this.rendered? -"animate":"attr"]({translateX:this.scrollerLeft+this.scrollbarHeight+parseInt(a,10),translateY:this.top+this.height/2-8})},drawScrollbarButton:function(a){var b=this.chart.renderer,c=this.elementsToDestroy,d=this.scrollbarButtons,e=this.scrollbarHeight,f=this.scrollbarOptions,g;this.rendered||(d[a]=b.g().add(this.scrollbarGroup),g=b.rect(-0.5,-0.5,e+1,e+1,f.buttonBorderRadius,f.buttonBorderWidth).attr({stroke:f.buttonBorderColor,"stroke-width":f.buttonBorderWidth,fill:f.buttonBackgroundColor}).add(d[a]), -c.push(g),g=b.path(["M",e/2+(a?-1:1),e/2-3,"L",e/2+(a?-1:1),e/2+3,e/2+(a?2:-2),e/2]).attr({fill:f.buttonArrowColor}).add(d[a]),c.push(g));a&&d[a].attr({translateX:this.scrollerWidth-e})},render:function(a,b,c,d){var e=this.chart,f=e.renderer,g,h,i,j,k=this.scrollbarGroup,l=this.navigatorGroup,m=this.scrollbar,l=this.xAxis,n=this.scrollbarTrack,p=this.scrollbarHeight,o=this.scrollbarEnabled,s=this.navigatorOptions,t=this.scrollbarOptions,u=t.minWidth,x=this.height,D=this.top,z=this.navigatorEnabled, -A=s.outlineWidth,B=A/2,E=0,H=this.outlineHeight,K=t.barBorderRadius,J=t.barBorderWidth,I=D+B,L=this.rendered;if(C(a)&&C(b)&&(!this.hasDragged||v(c))){this.navigatorLeft=g=q(l.left,e.plotLeft+p);this.navigatorWidth=h=q(l.len,e.plotWidth-2*p);this.scrollerLeft=i=g-p;this.scrollerWidth=j=j=h+2*p;c=q(c,l.translate(a));d=q(d,l.translate(b));if(!C(c)||S(c)===Infinity)c=0,d=j;if(!(l.translate(d,!0)-l.translate(c,!0)12?"visible":"hidden"})[f]({d:["M", -u-3,p/4,"L",u-3,2*p/3,"M",u,p/4,"L",u,2*p/3,"M",u+3,p/4,"L",u+3,2*p/3]});this.scrollbarPad=E;this.rendered=!0}}},addEvents:function(){var a=this.chart,b=a.container,c=this.mouseDownHandler,d=this.mouseMoveHandler,e=this.mouseUpHandler,f;f=[[b,"mousedown",c],[b,"mousemove",d],[H,"mouseup",e]];cb&&f.push([b,"touchstart",c],[b,"touchmove",d],[H,"touchend",e]);o(f,function(a){E.apply(null,a)});this._events=f;this.series&&E(this.series.xAxis,"foundExtremes",function(){a.scroller.modifyNavigatorAxisExtremes()}); -E(a,"redraw",function(){var a=this.scroller,b;if(a)(b=a.baseSeries.xAxis)&&a.render(b.min,b.max)})},removeEvents:function(){o(this._events,function(a){T.apply(null,a)});this._events=t;this.navigatorEnabled&&this.baseSeries&&T(this.baseSeries,"updatedData",this.updatedDataHandler)},init:function(){var a=this,b=a.chart,c,d,e=a.scrollbarHeight,f=a.navigatorOptions,g=a.height,h=a.top,i,j=a.baseSeries;a.mouseDownHandler=function(d){var d=b.pointer.normalize(d),e=a.zoomedMin,f=a.zoomedMax,h=a.top,j=a.scrollbarHeight, -k=a.scrollerLeft,l=a.scrollerWidth,o=a.navigatorLeft,q=a.navigatorWidth,v=a.scrollbarPad,t=a.range,w=d.chartX,y=d.chartY,d=b.xAxis[0],z,A=jb?10:7;if(y>h&&yo+e-v&&wk&&wk+ -l-j?e+t*0.2:w=q)f=q-t,z=a.getUnionExtremes().dataMax;if(f!==e)a.fixedWidth=t,e=c.toFixedRange(f,f+t,null,z),d.setExtremes(e.min,e.max,!0,!1,{trigger:"navigator"})}};a.mouseMoveHandler=function(c){var d=a.scrollbarHeight,e=a.navigatorLeft,f=a.navigatorWidth,g=a.scrollerLeft,h=a.scrollerWidth,j=a.range,k,l;if(!c.touches||c.touches[0].pageX!==0)c=b.pointer.normalize(c),k=c.chartX,kg+h-d&&(k=g+h-d),a.grabbedLeft?(l=!0,a.render(0,0,k-e,a.otherHandlePos)):a.grabbedRight? -(l=!0,a.render(0,0,a.otherHandlePos,k-e)):a.grabbedCenter&&(l=!0,kf+i-j&&(k=f+i-j),a.render(0,0,k-i,k-i+j)),l&&a.scrollbarOptions.liveRedraw&&setTimeout(function(){a.mouseUpHandler(c)},0),a.hasDragged=l};a.mouseUpHandler=function(d){var e,f;if(a.hasDragged){if(a.zoomedMin===a.otherHandlePos)e=a.fixedExtreme;else if(a.zoomedMax===a.otherHandlePos)f=a.fixedExtreme;if(a.zoomedMax===a.navigatorWidth)f=a.getUnionExtremes().dataMax;e=c.toFixedRange(a.zoomedMin,a.zoomedMax,e,f);v(e.min)&&b.xAxis[0].setExtremes(e.min, -e.max,!0,!1,{trigger:"navigator",triggerOp:"navigator-drag",DOMEvent:d})}if(d.type!=="mousemove")a.grabbedLeft=a.grabbedRight=a.grabbedCenter=a.fixedWidth=a.fixedExtreme=a.otherHandlePos=a.hasDragged=i=null};var k=b.xAxis.length,l=b.yAxis.length;b.extraBottomMargin=a.outlineHeight+f.margin;a.navigatorEnabled?(a.xAxis=c=new J(b,z({breaks:j&&j.xAxis.options.breaks,ordinal:j&&j.xAxis.options.ordinal},f.xAxis,{id:"navigator-x-axis",isX:!0,type:"datetime",index:k,height:g,offset:0,offsetLeft:e,offsetRight:-e, -keepOrdinalPadding:!0,startOnTick:!1,endOnTick:!1,minPadding:0,maxPadding:0,zoomEnabled:!1})),a.yAxis=d=new J(b,z(f.yAxis,{id:"navigator-y-axis",alignTicks:!1,height:g,offset:0,index:l,zoomEnabled:!1})),j||f.series.data?a.addBaseSeries():b.series.length===0&&U(b,"redraw",function(c,d){if(b.series.length>0&&!a.series)a.setBaseSeries(),b.redraw=c;c.call(b,d)})):a.xAxis=c={translate:function(a,c){var d=b.xAxis[0],f=d.getExtremes(),g=b.plotWidth-2*e,h=wb("min",d.options.min,f.dataMin),d=wb("max",d.options.max, -f.dataMax)-h;return c?a*d/g+h:g*(a-h)/d},toFixedRange:J.prototype.toFixedRange};if(j&&j.xAxis&&this.navigatorOptions.adaptToUpdatedData!==!1)E(j,"updatedData",this.updatedDataHandler),E(j.xAxis,"foundExtremes",function(){j.xAxis&&this.chart.scroller.modifyBaseAxisExtremes()}),j.userOptions.events=A(j.userOptions.event,{updatedData:this.updatedDataHandler});U(b,"getMargins",function(b){var e=this.legend,f=e.options;b.apply(this,[].slice.call(arguments,1));a.top=h=a.navigatorOptions.top||this.chartHeight- -a.height-a.scrollbarHeight-this.spacing[2]-(f.verticalAlign==="bottom"&&f.enabled&&!f.floating?e.legendHeight+q(f.margin,10):0);if(c&&d)c.options.top=d.options.top=h,c.setAxisSize(),d.setAxisSize()});a.addEvents()},getUnionExtremes:function(a){var b=this.chart.xAxis[0],c=this.xAxis,d=c.options,e=b.options,f;if(!a||b.dataMin!==null)f={dataMin:q(d&&d.min,wb("min",e.min,b.dataMin,c.dataMin,c.min)),dataMax:q(d&&d.max,wb("max",e.max,b.dataMax,c.dataMax,c.max))};return f},setBaseSeries:function(a){var b= -this.chart,a=a||b.options.navigator.baseSeries;this.series&&this.series.remove();this.baseSeries=b.series[a]||typeof a==="string"&&b.get(a)||b.series[0];this.xAxis&&this.addBaseSeries()},addBaseSeries:function(){var a=this.baseSeries,b=a?a.options:{},a=b.data,c=this.navigatorOptions.series,d;d=c.data;this.hasNavigatorData=!!d;b=z(b,c,{enableMouseTracking:!1,group:"nav",padXAxis:!1,xAxis:"navigator-x-axis",yAxis:"navigator-y-axis",name:"Navigator",showInLegend:!1,stacking:!1,isInternal:!0,visible:!0}); -b.data=d||a.slice(0);this.series=this.chart.initSeries(b)},modifyNavigatorAxisExtremes:function(){var a=this.xAxis,b;if(a.getExtremes&&(b=this.getUnionExtremes(!0))&&(b.dataMin!==a.min||b.dataMax!==a.max))a.min=b.dataMin,a.max=b.dataMax},modifyBaseAxisExtremes:function(){var a=this.baseSeries.xAxis,b=a.getExtremes(),c=b.dataMin,d=b.dataMax,b=b.max-b.min,e=this.stickToMin,f=this.stickToMax,g,h,i=this.series,j=!!a.setExtremes;e&&(h=c,g=h+b);f&&(g=d,e||(h=w(g-b,i?i.xData[0]:-Number.MAX_VALUE)));if(j&& -(e||f)&&C(h))a.min=a.userMin=h,a.max=a.userMax=g;this.stickToMin=this.stickToMax=null},updatedDataHandler:function(){var a=this.chart.scroller,b=a.baseSeries,c=a.series;a.stickToMin=b.xAxis.min<=b.xData[0];a.stickToMax=a.zoomedMax>=a.navigatorWidth;if(c&&!a.hasNavigatorData&&(c.options.pointStart=b.xData[0],c.setData(b.options.data,!1),c.graph&&b.graph))c.graph.shift=b.graph.shift},destroy:function(){this.removeEvents();o([this.xAxis,this.yAxis,this.leftShade,this.rightShade,this.outline,this.scrollbarTrack, -this.scrollbarRifles,this.scrollbarGroup,this.scrollbar],function(a){a&&a.destroy&&a.destroy()});this.xAxis=this.yAxis=this.leftShade=this.rightShade=this.outline=this.scrollbarTrack=this.scrollbarRifles=this.scrollbarGroup=this.scrollbar=null;o([this.scrollbarButtons,this.handles,this.elementsToDestroy],function(a){Pa(a)})}};B.Scroller=Gb;U(J.prototype,"zoom",function(a,b,c){var d=this.chart,e=d.options,f=e.chart.zoomType,g=e.navigator,e=e.rangeSelector,h;if(this.isXAxis&&(g&&g.enabled||e&&e.enabled))if(f=== -"x")d.resetZoomButton="blocked";else if(f==="y")h=!1;else if(f==="xy")d=this.previousZoom,v(b)?this.previousZoom=[this.min,this.max]:d&&(b=d[0],c=d[1],delete this.previousZoom);return h!==t?h:a.call(this,b,c)});U(Ba.prototype,"init",function(a,b,c){E(this,"beforeRender",function(){var a=this.options;if(a.navigator.enabled||a.scrollbar.enabled)this.scroller=new Gb(this)});a.call(this,b,c)});U(P.prototype,"addPoint",function(a,b,c,d,e){var f=this.options.turboThreshold;f&&this.xData.length>f&&ea(b)&& -!Ja(b)&&this.chart.scroller&&ga(20,!0);a.call(this,b,c,d,e)});A(R,{rangeSelector:{buttonTheme:{width:28,height:18,fill:"#f7f7f7",padding:2,r:0,"stroke-width":0,style:{color:"#444",cursor:"pointer",fontWeight:"normal"},zIndex:7,states:{hover:{fill:"#e7e7e7"},select:{fill:"#e7f0f9",style:{color:"black",fontWeight:"bold"}}}},height:35,inputPosition:{align:"right"},labelStyle:{color:"#666"}}});R.lang=z(R.lang,{rangeSelectorZoom:"Zoom",rangeSelectorFrom:"From",rangeSelectorTo:"To"});Hb.prototype={clickButton:function(a, -b){var c=this,d=c.selected,e=c.chart,f=c.buttons,g=c.buttonOptions[a],h=e.xAxis[0],i=e.scroller&&e.scroller.getUnionExtremes()||h||{},j=i.dataMin,k=i.dataMax,l,m=h&&y(G(h.max,q(k,h.max))),n=g.type,p,i=g._range,r,s,v,u=g.dataGrouping;if(!(j===null||k===null||a===c.selected)){e.fixedRange=i;if(u)this.forcedDataGrouping=!0,J.prototype.setDataGrouping.call(h||{chart:this.chart},u,!1);if(n==="month"||n==="year")if(h){if(n={range:g,max:m,dataMin:j,dataMax:k},l=h.minFromRange.call(n),C(n.newMax))m=n.newMax}else i= -g;else if(i)l=w(m-i,j),m=G(l+i,k);else if(n==="ytd")if(h){if(k===t)j=Number.MAX_VALUE,k=Number.MIN_VALUE,o(e.series,function(a){a=a.xData;j=G(a[0],j);k=w(a[a.length-1],k)}),b=!1;m=new ba(k);l=m.getFullYear();l=r=w(j||0,ba.UTC(l,0,1));m=m.getTime();m=G(k||m,m)}else{E(e,"beforeRender",function(){c.clickButton(a)});return}else n==="all"&&h&&(l=j,m=k);f[d]&&f[d].setState(0);if(f[a])f[a].setState(2),c.lastSelected=a;h?(h.setExtremes(l,m,q(b,1),0,{trigger:"rangeSelectorButton",rangeSelectorButton:g}),c.setSelected(a)): -(p=e.options.xAxis[0],v=p.range,p.range=i,s=p.min,p.min=r,c.setSelected(a),E(e,"load",function(){p.range=v;p.min=s}))}},setSelected:function(a){this.selected=this.options.selected=a},defaultButtons:[{type:"month",count:1,text:"1m"},{type:"month",count:3,text:"3m"},{type:"month",count:6,text:"6m"},{type:"ytd",text:"YTD"},{type:"year",count:1,text:"1y"},{type:"all",text:"All"}],init:function(a){var b=this,c=a.options.rangeSelector,d=c.buttons||[].concat(b.defaultButtons),e=c.selected,f=b.blurInputs= -function(){var a=b.minInput,c=b.maxInput;a&&a.blur&&O(a,"blur");c&&c.blur&&O(c,"blur")};b.chart=a;b.options=c;b.buttons=[];a.extraTopMargin=c.height;b.buttonOptions=d;E(a.container,"mousedown",f);E(a,"resize",f);o(d,b.computeButtonRange);e!==t&&d[e]&&this.clickButton(e,!1);E(a,"load",function(){E(a.xAxis[0],"setExtremes",function(c){this.max-this.min!==a.fixedRange&&c.trigger!=="rangeSelectorButton"&&c.trigger!=="updatedData"&&b.forcedDataGrouping&&this.setDataGrouping(!1,!1)});E(a.xAxis[0],"afterSetExtremes", -function(){b.updateButtonStates(!0)})})},updateButtonStates:function(a){var b=this,c=this.chart,d=c.xAxis[0],e=c.scroller&&c.scroller.getUnionExtremes()||d,f=e.dataMin,g=e.dataMax,h=b.selected,i=b.options.allButtonsEnabled,j=b.buttons;a&&c.fixedRange!==y(d.max-d.min)&&(j[h]&&j[h].setState(0),b.setSelected(null));o(b.buttonOptions,function(a,e){var m=y(d.max-d.min),n=a._range,o=a.type,q=a.count||1,s=n>g-f,t=n=g-f&&j[e].state!==2,v=a.type==="ytd"&&na("%Y",f)=== -na("%Y",g),w=c.renderer.forExport&&e===h,n=n===m,z=!d.hasVisibleSeries;if((o==="month"||o==="year")&&m>={month:28,year:365}[o]*864E5*q&&m<={month:31,year:366}[o]*864E5*q)n=!0;w||n&&e!==h&&e===b.lastSelected?(b.setSelected(e),j[e].setState(2)):!i&&(s||t||u||v||z)?j[e].setState(3):j[e].state===3&&j[e].setState(0)})},computeButtonRange:function(a){var b=a.type,c=a.count||1,d={millisecond:1,second:1E3,minute:6E4,hour:36E5,day:864E5,week:6048E5};if(d[b])a._range=d[b]*c;else if(b==="month"||b==="year")a._range= -{month:30,year:365}[b]*864E5*c},setInputValue:function(a,b){var c=this.chart.options.rangeSelector;if(v(b))this[a+"Input"].HCTime=b;this[a+"Input"].value=na(c.inputEditDateFormat||"%Y-%m-%d",this[a+"Input"].HCTime);this[a+"DateBox"].attr({text:na(c.inputDateFormat||"%b %e, %Y",this[a+"Input"].HCTime)})},showInput:function(a){var b=this.inputGroup,c=this[a+"DateBox"];N(this[a+"Input"],{left:b.translateX+c.x+"px",top:b.translateY+"px",width:c.width-2+"px",height:c.height-2+"px",border:"2px solid silver"})}, -hideInput:function(a){N(this[a+"Input"],{border:0,width:"1px",height:"1px"});this.setInputValue(a)},drawInput:function(a){function b(){var a=j.value,b=(g.inputDateParser||ba.parse)(a),e=d.xAxis[0],f=e.dataMin,h=e.dataMax;if(b!==j.previousValue)j.previousValue=b,C(b)||(b=a.split("-"),b=ba.UTC(K(b[0]),K(b[1])-1,K(b[2]))),C(b)&&(R.global.useUTC||(b+=(new ba).getTimezoneOffset()*6E4),i?b>c.maxInput.HCTime?b=t:bh&&(b=h),b!==t&&d.xAxis[0].setExtremes(i?b:e.min,i?e.max: -b,t,t,{trigger:"rangeSelectorInput"}))}var c=this,d=c.chart,e=d.renderer.style,f=d.renderer,g=d.options.rangeSelector,h=c.div,i=a==="min",j,k,l=this.inputGroup;this[a+"Label"]=k=f.label(R.lang[i?"rangeSelectorFrom":"rangeSelectorTo"],this.inputGroup.offset).attr({padding:2}).css(z(e,g.labelStyle)).add(l);l.offset+=k.width+5;this[a+"DateBox"]=f=f.label("",l.offset).attr({padding:2,width:g.inputBoxWidth||90,height:g.inputBoxHeight||17,stroke:g.inputBoxBorderColor||"silver","stroke-width":1}).css(z({textAlign:"center", -color:"#444"},e,g.inputStyle)).on("click",function(){c.showInput(a);c[a+"Input"].focus()}).add(l);l.offset+=f.width+(i?10:0);this[a+"Input"]=j=fa("input",{name:a,className:"highcharts-range-selector",type:"text"},A({position:"absolute",border:0,width:"1px",height:"1px",padding:0,textAlign:"center",fontSize:e.fontSize,fontFamily:e.fontFamily,left:"-9em",top:d.plotTop+"px"},g.inputStyle),h);j.onfocus=function(){c.showInput(a)};j.onblur=function(){c.hideInput(a)};j.onchange=b;j.onkeypress=function(a){a.keyCode=== -13&&b()}},getPosition:function(){var a=this.chart,b=a.options.rangeSelector,a=q((b.buttonPosition||{}).y,a.plotTop-a.axisOffset[0]-b.height);return{buttonTop:a,inputTop:a-10}},render:function(a,b){var c=this,d=c.chart,e=d.renderer,f=d.container,g=d.options,h=g.exporting&&g.exporting.enabled!==!1&&g.navigation&&g.navigation.buttonOptions,i=g.rangeSelector,j=c.buttons,g=R.lang,k=c.div,k=c.inputGroup,l=i.buttonTheme,m=i.buttonPosition||{},n=i.inputEnabled,p=l&&l.states,r=d.plotLeft,s,t=this.getPosition(), -u=c.group,w=c.rendered;if(!w&&(c.group=u=e.g("range-selector-buttons").add(),c.zoomText=e.text(g.rangeSelectorZoom,q(m.x,r),15).css(i.labelStyle).add(u),s=q(m.x,r)+c.zoomText.getBBox().width+5,o(c.buttonOptions,function(a,b){j[b]=e.button(a.text,s,0,function(){c.clickButton(b);c.isActive=!0},l,p&&p.hover,p&&p.select,p&&p.disabled).css({textAlign:"center"}).add(u);s+=j[b].width+q(i.buttonSpacing,5);c.selected===b&&j[b].setState(2)}),c.updateButtonStates(),n!==!1))c.div=k=fa("div",null,{position:"relative", -height:0,zIndex:1}),f.parentNode.insertBefore(k,f),c.inputGroup=k=e.g("input-group").add(),k.offset=0,c.drawInput("min"),c.drawInput("max");u[w?"animate":"attr"]({translateY:t.buttonTop});n!==!1&&(k.align(A({y:t.inputTop,width:k.offset,x:h&&t.inputTop<(h.y||0)+h.height-d.spacing[0]?-40:0},i.inputPosition),!0,d.spacingBox),v(n)||(d=u.getBBox(),k[k.translateX0.7&&c<1.3&&(d?a=b-e:b=a+e);C(a)||(a=b=void 0);return{min:a, -max:b}};J.prototype.minFromRange=function(){var a=this.range,b={month:"Month",year:"FullYear"}[a.type],c,d=this.max,e,f,g=function(a,c){var d=new ba(a);d["set"+b](d["get"+b]()+c);return d.getTime()-a};C(a)?(c=this.max-a,f=a):c=d+g(d,-a.count);e=q(this.dataMin,Number.MIN_VALUE);C(c)||(c=e);if(c<=e)c=e,f===void 0&&(f=g(c,a.count)),this.newMax=G(c+f,this.dataMax);C(d)||(c=void 0);return c};U(Ba.prototype,"init",function(a,b,c){E(this,"init",function(){if(this.options.rangeSelector.enabled)this.rangeSelector= -new Hb(this)});a.call(this,b,c)});B.RangeSelector=Hb;Ba.prototype.callbacks.push(function(a){function b(){d=a.xAxis[0].getExtremes();C(d.min)&&f.render(d.min,d.max)}function c(a){f.render(a.min,a.max)}var d,e=a.scroller,f=a.rangeSelector;e&&(d=a.xAxis[0].getExtremes(),e.render(d.min,d.max));f&&(E(a.xAxis[0],"afterSetExtremes",c),E(a,"resize",b),b());E(a,"destroy",function(){f&&(T(a,"resize",b),T(a.xAxis[0],"afterSetExtremes",c))})});B.StockChart=B.stockChart=function(a,b,c){var d=Ca(a)||a.nodeName, -e=arguments[d?1:0],f=e.series,g,h=q(e.navigator&&e.navigator.enabled,!0)?{startOnTick:!1,endOnTick:!1}:null,i={marker:{enabled:!1,radius:2}},j={shadow:!1,borderWidth:0};e.xAxis=ta(ua(e.xAxis||{}),function(a){return z({minPadding:0,maxPadding:0,ordinal:!0,title:{text:null},labels:{overflow:"justify"},showLastLabel:!0},a,{type:"datetime",categories:null},h)});e.yAxis=ta(ua(e.yAxis||{}),function(a){g=q(a.opposite,!0);return z({labels:{y:-2},opposite:g,showLastLabel:!1,title:{text:null}},a)});e.series= -null;e=z({chart:{panning:!0,pinchType:"x"},navigator:{enabled:!0},scrollbar:{enabled:!0},rangeSelector:{enabled:!0},title:{text:null,style:{fontSize:"16px"}},tooltip:{shared:!0,crosshairs:!0},legend:{enabled:!1},plotOptions:{line:i,spline:i,area:i,areaspline:i,arearange:i,areasplinerange:i,column:j,columnrange:j,candlestick:j,ohlc:j}},e,{_stock:!0,chart:{inverted:!1}});e.series=f;return d?new Ba(a,e,c):new Ba(e,b)};U(Ya.prototype,"init",function(a,b,c){var d=c.chart.pinchType||"";a.call(this,b,c); -this.pinchX=this.pinchHor=d.indexOf("x")!==-1;this.pinchY=this.pinchVert=d.indexOf("y")!==-1;this.hasZoom=this.hasZoom||this.pinchHor||this.pinchVert});U(J.prototype,"autoLabelAlign",function(a){var b=this.chart,c=this.options,b=b._labelPanes=b._labelPanes||{},d=this.options.labels;if(this.chart.options._stock&&this.coll==="yAxis"&&(c=c.top+","+c.height,!b[c]&&d.enabled)){if(d.x===15)d.x=0;if(d.align===void 0)d.align="right";b[c]=1;return"right"}return a.call(this,[].slice.call(arguments,1))});U(J.prototype, -"getPlotLinePath",function(a,b,c,d,e,f){var g=this,h=this.isLinked&&!this.series?this.linkedParent.series:this.series,i=g.chart,j=i.renderer,k=g.left,l=g.top,m,n,p,r,s=[],t=[],u,x;if(g.coll==="colorAxis")return a.apply(this,[].slice.call(arguments,1));t=g.isXAxis?v(g.options.yAxis)?[i.yAxis[g.options.yAxis]]:ta(h,function(a){return a.yAxis}):v(g.options.xAxis)?[i.xAxis[g.options.xAxis]]:ta(h,function(a){return a.xAxis});o(g.isXAxis?i.yAxis:i.xAxis,function(a){if(v(a.options.id)?a.options.id.indexOf("navigator")=== --1:1){var b=a.isXAxis?"yAxis":"xAxis",b=v(a.options[b])?i[b][a.options[b]]:i[b][0];g===b&&t.push(a)}});u=t.length?[]:[g.isXAxis?i.yAxis[0]:i.xAxis[0]];o(t,function(a){sa(a,u)===-1&&u.push(a)});x=q(f,g.translate(b,null,null,d));C(x)&&(g.horiz?o(u,function(a){var b;n=a.pos;r=n+a.len;m=p=y(x+g.transB);if(mk+g.width)e?m=p=G(w(k,m),k+g.width):b=!0;b||s.push("M",m,n,"L",p,r)}):o(u,function(a){var b;m=a.pos;p=m+a.len;n=r=y(l+g.height-x);if(nl+g.height)e?n=r=G(w(l,n),g.top+g.height):b=!0;b||s.push("M", -m,n,"L",p,r)}));return s.length>0?j.crispPolyLine(s,c||1):null});J.prototype.getPlotBandPath=function(a,b){var c=this.getPlotLinePath(b,null,null,!0),d=this.getPlotLinePath(a,null,null,!0),e=[],f;if(d&&c&&d.toString()!==c.toString())for(f=0;f=e&&(l-=k.translateX+b.width-e);k.attr({x:l,y:j,visibility:"visible"})}});var gc=ia.init,hc=ia.processData,ic=Ha.prototype.tooltipFormatter;ia.init=function(){gc.apply(this,arguments);this.setCompare(this.options.compare)};ia.setCompare=function(a){this.modifyValue=a==="value"||a==="percent"?function(b,c){var d=this.compareValue;if(b!==t&&(b=a==="value"? -b-d:b=100*(b/d)-100,c))c.change=b;return b}:null;if(this.chart.hasRendered)this.isDirty=!0};ia.processData=function(){var a,b=-1,c,d,e,f;hc.apply(this,arguments);if(this.xAxis&&this.processedYData){c=this.processedXData;d=this.processedYData;e=d.length;this.pointArrayMap&&(b=sa(this.pointValKey||"y",this.pointArrayMap));for(a=0;a-1?d[a][b]:d[a],C(f)&&c[a]>=this.xAxis.min&&f!==0){this.compareValue=f;break}}};U(ia,"getExtremes",function(a){var b;a.apply(this,[].slice.call(arguments,1)); -if(this.modifyValue)b=[this.modifyValue(this.dataMin),this.modifyValue(this.dataMax)],this.dataMin=Ma(b),this.dataMax=Da(b)});J.prototype.setCompare=function(a,b){this.isXAxis||(o(this.series,function(b){b.setCompare(a)}),q(b,!0)&&this.chart.redraw())};Ha.prototype.tooltipFormatter=function(a){a=a.replace("{point.change}",(this.change>0?"+":"")+B.numberFormat(this.change,q(this.series.tooltipOptions.changeDecimals,2)));return ic.apply(this,[a])};U(P.prototype,"render",function(a){if(this.chart.options._stock&& -this.xAxis)!this.clipBox&&this.animate?(this.clipBox=z(this.chart.clipBox),this.clipBox.width=this.xAxis.len,this.clipBox.height=this.yAxis.len):this.chart[this.sharedClipKey]&&(Sa(this.chart[this.sharedClipKey]),this.chart[this.sharedClipKey].attr({width:this.xAxis.len,height:this.yAxis.len}));a.call(this)});A(B,{Color:va,Point:Ha,Tick:bb,Renderer:Xa,SVGElement:Z,SVGRenderer:xa,arrayMin:Ma,arrayMax:Da,charts:$,correctFloat:ka,dateFormat:na,error:ga,format:La,pathAnim:void 0,getOptions:function(){return R}, -hasBidiBug:Zb,isTouchDevice:jb,setOptions:function(a){R=z(!0,R,a);Ob();return R},addEvent:E,removeEvent:T,createElement:fa,discardElement:Ua,css:N,each:o,map:ta,merge:z,splat:ua,stableSort:nb,extendClass:ma,pInt:K,svg:ja,canvas:qa,vml:!ja&&!qa,product:"Highstock",version:"4.2.5"});return B}); -/* - Highcharts JS v4.2.5 (2016-05-06) - - (c) 2009-2016 Torstein Honsi - - License: www.highcharts.com/license -*/ -(function(m){typeof module==="object"&&module.exports?module.exports=m:m(Highcharts)})(function(m){function M(a,b,c){this.init(a,b,c)}var R=m.arrayMin,S=m.arrayMax,t=m.each,H=m.extend,I=m.isNumber,u=m.merge,T=m.map,o=m.pick,B=m.pInt,G=m.correctFloat,p=m.getOptions().plotOptions,i=m.seriesTypes,v=m.extendClass,N=m.splat,w=m.wrap,O=m.Axis,z=m.Tick,J=m.Point,U=m.Pointer,V=m.CenteredSeriesMixin,C=m.TrackerMixin,x=m.Series,y=Math,F=y.round,D=y.floor,P=y.max,W=m.Color,r=function(){};H(M.prototype,{init:function(a, -b,c){var d=this,g=d.defaultOptions;d.chart=b;d.options=a=u(g,b.angular?{background:{}}:void 0,a);(a=a.background)&&t([].concat(N(a)).reverse(),function(a){var b=a.backgroundColor,g=c.userOptions,a=u(d.defaultBackgroundOptions,a);if(b)a.backgroundColor=b;a.color=a.backgroundColor;c.options.plotBands.unshift(a);g.plotBands=g.plotBands||[];g.plotBands!==c.options.plotBands&&g.plotBands.unshift(a)})},defaultOptions:{center:["50%","50%"],size:"85%",startAngle:0},defaultBackgroundOptions:{shape:"circle", -borderWidth:1,borderColor:"silver",backgroundColor:{linearGradient:{x1:0,y1:0,x2:0,y2:1},stops:[[0,"#FFF"],[1,"#DDD"]]},from:-Number.MAX_VALUE,innerRadius:0,to:Number.MAX_VALUE,outerRadius:"105%"}});var A=O.prototype,z=z.prototype,X={getOffset:r,redraw:function(){this.isDirty=!1},render:function(){this.isDirty=!1},setScale:r,setCategories:r,setTitle:r},Q={isRadial:!0,defaultRadialGaugeOptions:{labels:{align:"center",x:0,y:null},minorGridLineWidth:0,minorTickInterval:"auto",minorTickLength:10,minorTickPosition:"inside", -minorTickWidth:1,tickLength:10,tickPosition:"inside",tickWidth:2,title:{rotation:0},zIndex:2},defaultRadialXOptions:{gridLineWidth:1,labels:{align:null,distance:15,x:0,y:null},maxPadding:0,minPadding:0,showLastLabel:!1,tickLength:0},defaultRadialYOptions:{gridLineInterpolation:"circle",labels:{align:"right",x:-3,y:-2},showLastLabel:!1,title:{x:4,text:null,rotation:90}},setOptions:function(a){a=this.options=u(this.defaultOptions,this.defaultRadialOptions,a);if(!a.plotBands)a.plotBands=[]},getOffset:function(){A.getOffset.call(this); -this.chart.axisOffset[this.side]=0;this.center=this.pane.center=V.getCenter.call(this.pane)},getLinePath:function(a,b){var c=this.center,b=o(b,c[2]/2-this.offset);return this.chart.renderer.symbols.arc(this.left+c[0],this.top+c[1],b,b,{start:this.startAngleRad,end:this.endAngleRad,open:!0,innerR:0})},setAxisTranslation:function(){A.setAxisTranslation.call(this);if(this.center)this.transA=this.isCircular?(this.endAngleRad-this.startAngleRad)/(this.max-this.min||1):this.center[2]/2/(this.max-this.min|| -1),this.minPixelPadding=this.isXAxis?this.transA*this.minPointOffset:0},beforeSetTickPositions:function(){this.autoConnect&&(this.max+=this.categories&&1||this.pointRange||this.closestPointRange||0)},setAxisSize:function(){A.setAxisSize.call(this);if(this.isRadial){this.center=this.pane.center=m.CenteredSeriesMixin.getCenter.call(this.pane);if(this.isCircular)this.sector=this.endAngleRad-this.startAngleRad;this.len=this.width=this.height=this.center[2]*o(this.sector,1)/2}},getPosition:function(a, -b){return this.postTranslate(this.isCircular?this.translate(a):0,o(this.isCircular?b:this.translate(a),this.center[2]/2)-this.offset)},postTranslate:function(a,b){var c=this.chart,d=this.center,a=this.startAngleRad+a;return{x:c.plotLeft+d[0]+Math.cos(a)*b,y:c.plotTop+d[1]+Math.sin(a)*b}},getPlotBandPath:function(a,b,c){var d=this.center,g=this.startAngleRad,e=d[2]/2,j=[o(c.outerRadius,"100%"),c.innerRadius,o(c.thickness,10)],l=/%$/,h,f=this.isCircular;this.options.gridLineInterpolation==="polygon"? -d=this.getPlotLinePath(a).concat(this.getPlotLinePath(b,!0)):(a=Math.max(a,this.min),b=Math.min(b,this.max),f||(j[0]=this.translate(a),j[1]=this.translate(b)),j=T(j,function(a){l.test(a)&&(a=B(a,10)*e/100);return a}),c.shape==="circle"||!f?(a=-Math.PI/2,b=Math.PI*1.5,h=!0):(a=g+this.translate(a),b=g+this.translate(b)),d=this.chart.renderer.symbols.arc(this.left+d[0],this.top+d[1],j[0],j[0],{start:Math.min(a,b),end:Math.max(a,b),innerR:o(j[1],j[0]-j[2]),open:h}));return d},getPlotLinePath:function(a, -b){var c=this,d=c.center,g=c.chart,e=c.getPosition(a),j,l,h;c.isCircular?h=["M",d[0]+g.plotLeft,d[1]+g.plotTop,"L",e.x,e.y]:c.options.gridLineInterpolation==="circle"?(a=c.translate(a))&&(h=c.getLinePath(0,a)):(t(g.xAxis,function(a){a.pane===c.pane&&(j=a)}),h=[],a=c.translate(a),d=j.tickPositions,j.autoConnect&&(d=d.concat([d[0]])),b&&(d=[].concat(d).reverse()),t(d,function(e,b){l=j.getPosition(e,a);h.push(b?"L":"M",l.x,l.y)}));return h},getTitlePosition:function(){var a=this.center,b=this.chart, -c=this.options.title;return{x:b.plotLeft+a[0]+(c.x||0),y:b.plotTop+a[1]-{high:0.5,middle:0.25,low:0}[c.align]*a[2]+(c.y||0)}}};w(A,"init",function(a,b,c){var k;var d=b.angular,g=b.polar,e=c.isX,j=d&&e,l,h;h=b.options;var f=c.pane||0;if(d){if(H(this,j?X:Q),l=!e)this.defaultRadialOptions=this.defaultRadialGaugeOptions}else if(g)H(this,Q),this.defaultRadialOptions=(l=e)?this.defaultRadialXOptions:u(this.defaultYAxisOptions,this.defaultRadialYOptions);if(d||g)b.inverted=!1,h.chart.zoomType=null;a.call(this, -b,c);if(!j&&(d||g)){a=this.options;if(!b.panes)b.panes=[];this.pane=(k=b.panes[f]=b.panes[f]||new M(N(h.pane)[f],b,this),b=k);h=b.options;this.startAngleRad=b=(h.startAngle-90)*Math.PI/180;this.endAngleRad=h=(o(h.endAngle,h.startAngle+360)-90)*Math.PI/180;this.offset=a.offset||0;if((this.isCircular=l)&&c.max===void 0&&h-b===2*Math.PI)this.autoConnect=!0}});w(A,"autoLabelAlign",function(a){if(!this.isRadial)return a.apply(this,[].slice.call(arguments,1))});w(z,"getPosition",function(a,b,c,d,g){var e= -this.axis;return e.getPosition?e.getPosition(c):a.call(this,b,c,d,g)});w(z,"getLabelPosition",function(a,b,c,d,g,e,j,l,h){var f=this.axis,k=e.y,n=20,s=e.align,i=(f.translate(this.pos)+f.startAngleRad+Math.PI/2)/Math.PI*180%360;f.isRadial?(a=f.getPosition(this.pos,f.center[2]/2+o(e.distance,-25)),e.rotation==="auto"?d.attr({rotation:i}):k===null&&(k=f.chart.renderer.fontMetrics(d.styles.fontSize).b-d.getBBox().height/2),s===null&&(f.isCircular?(this.label.getBBox().width>f.len*f.tickInterval/(f.max- -f.min)&&(n=0),s=i>n&&i<180-n?"left":i>180+n&&i<360-n?"right":"center"):s="center",d.attr({align:s})),a.x+=e.x,a.y+=k):a=a.call(this,b,c,d,g,e,j,l,h);return a});w(z,"getMarkPath",function(a,b,c,d,g,e,j){var l=this.axis;l.isRadial?(a=l.getPosition(this.pos,l.center[2]/2+d),b=["M",b,c,"L",a.x,a.y]):b=a.call(this,b,c,d,g,e,j);return b});p.arearange=u(p.area,{lineWidth:1,marker:null,threshold:null,tooltip:{pointFormat:'\u25cf {series.name}: {point.low} - {point.high}
'}, -trackByArea:!0,dataLabels:{align:null,verticalAlign:null,xLow:0,xHigh:0,yLow:0,yHigh:0},states:{hover:{halo:!1}}});i.arearange=v(i.area,{type:"arearange",pointArrayMap:["low","high"],dataLabelCollections:["dataLabel","dataLabelUpper"],toYData:function(a){return[a.low,a.high]},pointValKey:"low",deferTranslatePolar:!0,highToXY:function(a){var b=this.chart,c=this.xAxis.postTranslate(a.rectPlotX,this.yAxis.len-a.plotHigh);a.plotHighX=c.x-b.plotLeft;a.plotHigh=c.y-b.plotTop},translate:function(){var a= -this,b=a.yAxis;i.area.prototype.translate.apply(a);t(a.points,function(a){var d=a.low,g=a.high,e=a.plotY;g===null||d===null?a.isNull=!0:(a.plotLow=e,a.plotHigh=b.translate(g,0,1,0,1))});this.chart.polar&&t(this.points,function(b){a.highToXY(b)})},getGraphPath:function(){var a=this.points,b=[],c=[],d=a.length,g=x.prototype.getGraphPath,e,j,l;l=this.options;for(var h=l.step,d=a.length;d--;)e=a[d],!e.isNull&&(!a[d+1]||a[d+1].isNull)&&c.push({plotX:e.plotX,plotY:e.plotLow}),j={plotX:e.plotX,plotY:e.plotHigh, -isNull:e.isNull},c.push(j),b.push(j),!e.isNull&&(!a[d-1]||a[d-1].isNull)&&c.push({plotX:e.plotX,plotY:e.plotLow});a=g.call(this,a);if(h)h===!0&&(h="left"),l.step={left:"right",center:"center",right:"left"}[h];b=g.call(this,b);c=g.call(this,c);l.step=h;l=[].concat(a,b);!this.chart.polar&&c[0]==="M"&&(c[0]="L");this.areaPath=this.areaPath.concat(a,c);return l},drawDataLabels:function(){var a=this.data,b=a.length,c,d=[],g=x.prototype,e=this.options.dataLabels,j=e.align,l=e.verticalAlign,h=e.inside,f, -k,n=this.chart.inverted;if(e.enabled||this._hasPointLabels){for(c=b;c--;)if(f=a[c]){k=h?f.plotHighf.plotLow;f.y=f.high;f._plotY=f.plotY;f.plotY=f.plotHigh;d[c]=f.dataLabel;f.dataLabel=f.dataLabelUpper;f.below=k;if(n){if(!j)e.align=k?"right":"left"}else if(!l)e.verticalAlign=k?"top":"bottom";e.x=e.xHigh;e.y=e.yHigh}g.drawDataLabels&&g.drawDataLabels.apply(this,arguments);for(c=b;c--;)if(f=a[c]){k=h?f.plotHighf.plotLow;f.dataLabelUpper=f.dataLabel;f.dataLabel= -d[c];f.y=f.low;f.plotY=f._plotY;f.below=!k;if(n){if(!j)e.align=k?"left":"right"}else if(!l)e.verticalAlign=k?"bottom":"top";e.x=e.xLow;e.y=e.yLow}g.drawDataLabels&&g.drawDataLabels.apply(this,arguments)}e.align=j;e.verticalAlign=l},alignDataLabel:function(){i.column.prototype.alignDataLabel.apply(this,arguments)},setStackedPoints:r,getSymbol:r,drawPoints:r});p.areasplinerange=u(p.arearange);i.areasplinerange=v(i.arearange,{type:"areasplinerange",getPointSpline:i.spline.prototype.getPointSpline}); -(function(){var a=i.column.prototype;p.columnrange=u(p.column,p.arearange,{lineWidth:1,pointRange:null});i.columnrange=v(i.arearange,{type:"columnrange",translate:function(){var b=this,c=b.yAxis,d=b.xAxis,g=d.startAngleRad,e,j=b.chart,l=b.xAxis.isRadial,h;a.translate.apply(b);t(b.points,function(a){var k=a.shapeArgs,n=b.options.minPointLength,s,i;a.plotHigh=h=c.translate(a.high,0,1,0,1);a.plotLow=a.plotY;i=h;s=o(a.rectPlotY,a.plotY)-h;Math.abs(s)\u25cf {series.name}
Maximum: {point.high}
Upper quartile: {point.q3}
Median: {point.median}
Lower quartile: {point.q1}
Minimum: {point.low}
'}, -whiskerLength:"50%",whiskerWidth:2});i.boxplot=v(i.column,{type:"boxplot",pointArrayMap:["low","q1","median","q3","high"],toYData:function(a){return[a.low,a.q1,a.median,a.q3,a.high]},pointValKey:"high",pointAttrToOptions:{fill:"fillColor",stroke:"color","stroke-width":"lineWidth"},drawDataLabels:r,translate:function(){var a=this.yAxis,b=this.pointArrayMap;i.column.prototype.translate.apply(this);t(this.points,function(c){t(b,function(b){c[b]!==null&&(c[b+"Plot"]=a.translate(c[b],0,1,0,1))})})},drawPoints:function(){var a= -this,b=a.options,c=a.chart.renderer,d,g,e,j,l,h,f,k,n,i,m,K,L,p,u,r,w,v,x,y,C,B,z=a.doQuartiles!==!1,A,E=a.options.whiskerLength;t(a.points,function(q){n=q.graphic;C=q.shapeArgs;m={};p={};r={};B=q.color||a.color;if(q.plotY!==void 0)if(d=q.pointAttr[q.selected?"selected":""],w=C.width,v=D(C.x),x=v+w,y=F(w/2),g=D(z?q.q1Plot:q.lowPlot),e=D(z?q.q3Plot:q.lowPlot),j=D(q.highPlot),l=D(q.lowPlot),m.stroke=q.stemColor||b.stemColor||B,m["stroke-width"]=o(q.stemWidth,b.stemWidth,b.lineWidth),m.dashstyle=q.stemDashStyle|| -b.stemDashStyle,p.stroke=q.whiskerColor||b.whiskerColor||B,p["stroke-width"]=o(q.whiskerWidth,b.whiskerWidth,b.lineWidth),r.stroke=q.medianColor||b.medianColor||B,r["stroke-width"]=o(q.medianWidth,b.medianWidth,b.lineWidth),f=m["stroke-width"]%2/2,k=v+y+f,i=["M",k,e,"L",k,j,"M",k,g,"L",k,l],z&&(f=d["stroke-width"]%2/2,k=D(k)+f,g=D(g)+f,e=D(e)+f,v+=f,x+=f,K=["M",v,e,"L",v,g,"L",x,g,"L",x,e,"L",v,e,"z"]),E&&(f=p["stroke-width"]%2/2,j+=f,l+=f,A=/%$/.test(E)?y*parseFloat(E)/100:E/2,L=["M",k-A,j,"L",k+ -A,j,"M",k-A,l,"L",k+A,l]),f=r["stroke-width"]%2/2,h=F(q.medianPlot)+f,u=["M",v,h,"L",x,h],n)q.stem.animate({d:i}),E&&q.whiskers.animate({d:L}),z&&q.box.animate({d:K}),q.medianShape.animate({d:u});else{q.graphic=n=c.g().add(a.group);q.stem=c.path(i).attr(m).add(n);if(E)q.whiskers=c.path(L).attr(p).add(n);if(z)q.box=c.path(K).attr(d).add(n);q.medianShape=c.path(u).attr(r).add(n)}})},setStackedPoints:r});p.errorbar=u(p.boxplot,{color:"#000000",grouping:!1,linkedTo:":previous",tooltip:{pointFormat:'\u25cf {series.name}: {point.low} - {point.high}
'}, -whiskerWidth:null});i.errorbar=v(i.boxplot,{type:"errorbar",pointArrayMap:["low","high"],toYData:function(a){return[a.low,a.high]},pointValKey:"high",doQuartiles:!1,drawDataLabels:i.arearange?i.arearange.prototype.drawDataLabels:r,getColumnMetrics:function(){return this.linkedParent&&this.linkedParent.columnMetrics||i.column.prototype.getColumnMetrics.call(this)}});p.waterfall=u(p.column,{lineWidth:1,lineColor:"#333",dashStyle:"dot",borderColor:"#333",dataLabels:{inside:!0},states:{hover:{lineWidthPlus:0}}}); -i.waterfall=v(i.column,{type:"waterfall",upColorProp:"fill",pointValKey:"y",translate:function(){var a=this.options,b=this.yAxis,c,d,g,e,j,l,h,f,k,n=o(a.minPointLength,5),s=a.threshold,m=a.stacking;i.column.prototype.translate.apply(this);this.minPointLengthOffset=0;h=f=s;d=this.points;for(c=0,a=d.length;c0?b.translate(h,0,1)-e.y:b.translate(h,0,1)-b.translate(h-l,0,1);h+=l}e.height<0&&(e.y+=e.height,e.height*=-1);g.plotY=e.y=F(e.y)-this.borderWidth%2/2;e.height=P(F(e.height),0.001); -g.yBottom=e.y+e.height;if(e.height<=n)e.height=n,this.minPointLengthOffset+=n;e.y-=this.minPointLengthOffset;e=g.plotY+(g.negative?e.height:0)-this.minPointLengthOffset;this.chart.inverted?g.tooltipPos[0]=b.len-e:g.tooltipPos[1]=e}},processData:function(a){var b=this.yData,c=this.options.data,d,g=b.length,e,j,l,h,f,k;j=e=l=h=this.options.threshold||0;for(k=0;k -0?(e.pointAttr=g,e.color=d):e.pointAttr=a.pointAttr})},getGraphPath:function(){var a=this.data,b=a.length,c=F(this.options.lineWidth+this.borderWidth)%2/2,d=[],g,e,j;for(j=1;j0?(j-a)/i:0.5,k&&j>=0&&(j=Math.sqrt(j)),j=y.ceil(c+j*(d-c))/2),h.push(j);this.radii=h},animate:function(a){var b=this.options.animation;if(!a)t(this.points, -function(a){var d=a.graphic,a=a.shapeArgs;d&&a&&(d.attr("r",1),d.animate({r:a.r},b))}),this.animate=null},translate:function(){var a,b=this.data,c,d,g=this.radii;i.scatter.prototype.translate.call(this);for(a=b.length;a--;)c=b[a],d=g?g[a]:0,I(d)&&d>=this.minPxSize/2?(c.shapeType="circle",c.shapeArgs={x:c.plotX,y:c.plotY,r:d},c.dlBox={x:c.plotX-d,y:c.plotY-d,width:2*d,height:2*d}):c.shapeArgs=c.plotY=c.dlBox=void 0},drawLegendSymbol:function(a,b){var c=this.chart.renderer,d=c.fontMetrics(a.itemStyle.fontSize).f/ -2;b.legendSymbol=c.circle(d,a.baseline-d,d).attr({zIndex:3}).add(b.legendGroup);b.legendSymbol.isMarker=!0},drawPoints:i.column.prototype.drawPoints,alignDataLabel:i.column.prototype.alignDataLabel,buildKDTree:r,applyZones:r});O.prototype.beforePadding=function(){var a=this,b=this.len,c=this.chart,d=0,g=b,e=this.isXAxis,j=e?"xData":"yData",l=this.min,h={},f=y.min(c.plotWidth,c.plotHeight),k=Number.MAX_VALUE,n=-Number.MAX_VALUE,i=this.max-l,m=b/i,p=[];t(this.series,function(b){var g=b.options;if(b.bubblePadding&& -(b.visible||!c.options.chart.ignoreHiddenSeries))if(a.allowZoomOutside=!0,p.push(b),e)t(["minSize","maxSize"],function(a){var b=g[a],e=/%$/.test(b),b=B(b);h[a]=e?f*b/100:b}),b.minPxSize=h.minSize,b.maxPxSize=h.maxSize,b=b.zData,b.length&&(k=o(g.zMin,y.min(k,y.max(R(b),g.displayNegative===!1?g.zThreshold:-Number.MAX_VALUE))),n=o(g.zMax,y.max(n,S(b))))});t(p,function(b){var c=b[j],f=c.length,h;e&&b.getRadii(k,n,b.minPxSize,b.maxPxSize);if(i>0)for(;f--;)I(c[f])&&a.dataMin<=c[f]&&c[f]<=a.dataMax&&(h= -b.radii[f],d=Math.min((c[f]-l)*m-h,d),g=Math.max((c[f]-l)*m+h,g))});p.length&&i>0&&!this.isLog&&(g-=b,m*=(b+d-g)/b,t([["min","userMin",d],["max","userMax",g]],function(b){o(a.options[b[0]],a[b[1]])===void 0&&(a[b[0]]+=b[2]/m)}))};(function(){function a(a,b){var c=this.chart,d=this.options.animation,h=this.group,f=this.markerGroup,k=this.xAxis.center,i=c.plotLeft,m=c.plotTop;if(c.polar){if(c.renderer.isSVG)d===!0&&(d={}),b?(c={translateX:k[0]+i,translateY:k[1]+m,scaleX:0.001,scaleY:0.001},h.attr(c), -f&&f.attr(c)):(c={translateX:i,translateY:m,scaleX:1,scaleY:1},h.animate(c,d),f&&f.animate(c,d),this.animate=null)}else a.call(this,b)}var b=x.prototype,c=U.prototype,d;b.searchPointByAngle=function(a){var b=this.chart,c=this.xAxis.pane.center;return this.searchKDTree({clientX:180+Math.atan2(a.chartX-c[0]-b.plotLeft,a.chartY-c[1]-b.plotTop)*(-180/Math.PI)})};w(b,"buildKDTree",function(a){if(this.chart.polar)this.kdByAngle?this.searchPoint=this.searchPointByAngle:this.kdDimensions=2;a.apply(this)}); -b.toXY=function(a){var b,c=this.chart,d=a.plotX;b=a.plotY;a.rectPlotX=d;a.rectPlotY=b;b=this.xAxis.postTranslate(a.plotX,this.yAxis.len-b);a.plotX=a.polarPlotX=b.x-c.plotLeft;a.plotY=a.polarPlotY=b.y-c.plotTop;this.kdByAngle?(c=(d/Math.PI*180+this.xAxis.pane.options.startAngle)%360,c<0&&(c+=360),a.clientX=c):a.clientX=a.plotX};i.spline&&w(i.spline.prototype,"getPointSpline",function(a,b,c,d){var h,f,k,i,m,p,o;if(this.chart.polar){h=c.plotX;f=c.plotY;a=b[d-1];k=b[d+1];this.connectEnds&&(a||(a=b[b.length- -2]),k||(k=b[1]));if(a&&k)i=a.plotX,m=a.plotY,b=k.plotX,p=k.plotY,i=(1.5*h+i)/2.5,m=(1.5*f+m)/2.5,k=(1.5*h+b)/2.5,o=(1.5*f+p)/2.5,b=Math.sqrt(Math.pow(i-h,2)+Math.pow(m-f,2)),p=Math.sqrt(Math.pow(k-h,2)+Math.pow(o-f,2)),i=Math.atan2(m-f,i-h),m=Math.atan2(o-f,k-h),o=Math.PI/2+(i+m)/2,Math.abs(i-o)>Math.PI/2&&(o-=Math.PI),i=h+Math.cos(o)*b,m=f+Math.sin(o)*b,k=h+Math.cos(Math.PI+o)*p,o=f+Math.sin(Math.PI+o)*p,c.rightContX=k,c.rightContY=o;d?(c=["C",a.rightContX||a.plotX,a.rightContY||a.plotY,i||h,m|| -f,h,f],a.rightContX=a.rightContY=null):c=["M",h,f]}else c=a.call(this,b,c,d);return c});w(b,"translate",function(a){var b=this.chart;a.call(this);if(b.polar&&(this.kdByAngle=b.tooltip&&b.tooltip.shared,!this.preventPostTranslate)){a=this.points;for(b=a.length;b--;)this.toXY(a[b])}});w(b,"getGraphPath",function(a,b){var c=this;if(this.chart.polar){b=b||this.points;if(this.options.connectEnds!==!1&&b[0]&&b[0].y!==null)this.connectEnds=!0,b.splice(b.length,0,b[0]);t(b,function(a){a.polarPlotY===void 0&& -c.toXY(a)})}return a.apply(this,[].slice.call(arguments,1))});w(b,"animate",a);if(i.column)d=i.column.prototype,d.polarArc=function(a,b,c,d){var h=this.xAxis.center,f=this.yAxis.len;return this.chart.renderer.symbols.arc(h[0],h[1],f-b,null,{start:c,end:d,innerR:f-o(a,f)})},w(d,"animate",a),w(d,"translate",function(a){var b=this.xAxis,c=b.startAngleRad,d,h,f;this.preventPostTranslate=!0;a.call(this);if(b.isRadial){d=this.points;for(f=d.length;f--;)h=d[f],a=h.barX+c,h.shapeType="path",h.shapeArgs={d:this.polarArc(h.yBottom, -h.plotY,a,a+h.pointWidth)},this.toXY(h),h.tooltipPos=[h.plotX,h.plotY],h.ttBelow=h.plotY>b.center[1]}}),w(d,"alignDataLabel",function(a,c,d,i,h,f){if(this.chart.polar){a=c.rectPlotX/Math.PI*180;if(i.align===null)i.align=a>20&&a<160?"left":a>200&&a<340?"right":"center";if(i.verticalAlign===null)i.verticalAlign=a<45||a>315?"bottom":a>135&&a<225?"top":"middle";b.alignDataLabel.call(this,c,d,i,h,f)}else a.call(this,c,d,i,h,f)});w(c,"getCoordinates",function(a,b){var c=this.chart,d={xAxis:[],yAxis:[]}; -c.polar?t(c.axes,function(a){var f=a.isXAxis,g=a.center,i=b.chartX-g[0]-c.plotLeft,g=b.chartY-g[1]-c.plotTop;d[f?"xAxis":"yAxis"].push({axis:a,value:a.translate(f?Math.PI-Math.atan2(i,g):Math.sqrt(Math.pow(i,2)+Math.pow(g,2)),!0)})}):d=a.call(this,b);return d})})()}); -/* - Highcharts JS v4.2.5 (2016-05-06) - - 3D features for Highcharts JS - - @license: www.highcharts.com/license -*/ -(function(d){typeof module==="object"&&module.exports?module.exports=d:d(Highcharts)})(function(d){function o(c,b,a){var e,f,g=b.options.chart.options3d,d=!1,j=b.scale3d||1;a?(d=b.inverted,a=b.plotWidth/2,b=b.plotHeight/2,e=g.depth/2,f=s(g.depth,1)*s(g.viewDistance,0)):(a=b.plotLeft+b.plotWidth/2,b=b.plotTop+b.plotHeight/2,e=g.depth/2,f=s(g.depth,1)*s(g.viewDistance,0));var k=[],i=a,m=b,x=e,y=f,a=B*(d?g.beta:-g.beta),g=B*(d?-g.alpha:g.alpha),q=r(a),p=l(a),n=r(g),u=l(g),t,z,v,w,C,o;A(c,function(a){t= -(d?a.y:a.x)-i;z=(d?a.x:a.y)-m;v=(a.z||0)-x;w=p*t-q*v;C=-q*n*t+u*z-p*n*v;o=q*u*t+n*z+p*u*v;y>0&&yf&&g-f>n/2+ -1.0E-4?(k=k.concat(u(c,b,a,e,f,f+n/2,d,j)),k=k.concat(u(c,b,a,e,f+n/2,g,d,j))):gn/2+1.0E-4?(k=k.concat(u(c,b,a,e,f,f-n/2,d,j)),k=k.concat(u(c,b,a,e,f-n/2,g,d,j))):(k=g-f,k=["C",c+a*l(f)-a*F*k*r(f)+d,b+e*r(f)+e*F*k*l(f)+j,c+a*l(g)+a*F*k*r(g)+d,b+e*r(g)-e*F*k*l(g)+j,c+a*l(g)+d,b+e*r(g)+j]);return k}function J(c){if(this.chart.is3d()){var b=this.chart.options.plotOptions.column.grouping;if(b!==void 0&&!b&&this.group.zIndex!==void 0&&!this.zIndexSet)this.group.attr({zIndex:this.group.zIndex*10}), -this.zIndexSet=!0;var a=this.options,e=this.options.states;this.borderWidth=a.borderWidth=D(a.edgeWidth)?a.edgeWidth:1;d.each(this.data,function(b){if(b.y!==null)b=b.pointAttr,this.borderColor=d.pick(a.edgeColor,b[""].fill),b[""].stroke=this.borderColor,b.hover.stroke=d.pick(e.hover.edgeColor,this.borderColor),b.select.stroke=d.pick(e.select.edgeColor,this.borderColor)})}c.apply(this,[].slice.call(arguments,1))}var M=d.animObject,A=d.each,N=d.extend,O=d.inArray,G=d.merge,s=d.pick,K=d.wrap,n=Math.PI, -B=n/180,r=Math.sin,l=Math.cos,L=Math.round;d.perspective=o;var F=4*(Math.sqrt(2)-1)/3/(n/2);d.SVGRenderer.prototype.toLinePath=function(c,b){var a=[];d.each(c,function(b){a.push("L",b.x,b.y)});c.length&&(a[0]="M",b&&a.push("Z"));return a};d.SVGRenderer.prototype.cuboid=function(c){var b=this.g(),c=this.cuboidPath(c);b.front=this.path(c[0]).attr({zIndex:c[3],"stroke-linejoin":"round"}).add(b);b.top=this.path(c[1]).attr({zIndex:c[4],"stroke-linejoin":"round"}).add(b);b.side=this.path(c[2]).attr({zIndex:c[5], -"stroke-linejoin":"round"}).add(b);b.fillSetter=function(a){var b=d.Color(a).brighten(0.1).get(),c=d.Color(a).brighten(-0.1).get();this.front.attr({fill:a});this.top.attr({fill:b});this.side.attr({fill:c});this.color=a;return this};b.opacitySetter=function(a){this.front.attr({opacity:a});this.top.attr({opacity:a});this.side.attr({opacity:a});return this};b.attr=function(a){if(a.shapeArgs||D(a.x))a=this.renderer.cuboidPath(a.shapeArgs||a),this.front.attr({d:a[0],zIndex:a[3]}),this.top.attr({d:a[1], -zIndex:a[4]}),this.side.attr({d:a[2],zIndex:a[5]});else return d.SVGElement.prototype.attr.call(this,a);return this};b.animate=function(a,b,c){D(a.x)&&D(a.y)?(a=this.renderer.cuboidPath(a),this.front.attr({zIndex:a[3]}).animate({d:a[0]},b,c),this.top.attr({zIndex:a[4]}).animate({d:a[1]},b,c),this.side.attr({zIndex:a[5]}).animate({d:a[2]},b,c),this.attr({zIndex:-a[6]})):a.opacity?(this.front.animate(a,b,c),this.top.animate(a,b,c),this.side.animate(a,b,c)):d.SVGElement.prototype.animate.call(this,a, -b,c);return this};b.destroy=function(){this.front.destroy();this.top.destroy();this.side.destroy();return null};b.attr({zIndex:-c[6]});return b};d.SVGRenderer.prototype.cuboidPath=function(c){function b(a){return i[a]}var a=c.x,e=c.y,f=c.z,g=c.height,h=c.width,j=c.depth,k=d.map,i=[{x:a,y:e,z:f},{x:a+h,y:e,z:f},{x:a+h,y:e+g,z:f},{x:a,y:e+g,z:f},{x:a,y:e+g,z:f+j},{x:a+h,y:e+g,z:f+j},{x:a+h,y:e,z:f+j},{x:a,y:e,z:f+j}],i=o(i,d.charts[this.chartIndex],c.insidePlotArea),f=function(a,c){var e=[],a=k(a,b), -c=k(c,b);I(a)<0?e=a:I(c)<0&&(e=c);return e},c=f([3,2,1,0],[7,6,5,4]),a=[4,5,2,3],e=f([1,6,7,0],a),f=f([1,2,5,6],[0,7,4,3]);return[this.toLinePath(c,!0),this.toLinePath(e,!0),this.toLinePath(f,!0),E(c),E(e),E(f),E(k(a,b))*9E9]};d.SVGRenderer.prototype.arc3d=function(c){function b(a){var b=!1,c={},e;for(e in a)O(e,f)!==-1&&(c[e]=a[e],delete a[e],b=!0);return b?c:!1}var a=this.g(),e=a.renderer,f="x,y,r,innerR,start,end".split(","),c=G(c);c.alpha*=B;c.beta*=B;a.top=e.path();a.side1=e.path();a.side2=e.path(); -a.inn=e.path();a.out=e.path();a.onAdd=function(){var b=a.parentGroup;a.top.add(a);a.out.add(b);a.inn.add(b);a.side1.add(b);a.side2.add(b)};a.setPaths=function(b){var c=a.renderer.arc3dPath(b),e=c.zTop*100;a.attribs=b;a.top.attr({d:c.top,zIndex:c.zTop});a.inn.attr({d:c.inn,zIndex:c.zInn});a.out.attr({d:c.out,zIndex:c.zOut});a.side1.attr({d:c.side1,zIndex:c.zSide1});a.side2.attr({d:c.side2,zIndex:c.zSide2});a.zIndex=e;a.attr({zIndex:e});b.center&&(a.top.setRadialReference(b.center),delete b.center)}; -a.setPaths(c);a.fillSetter=function(a){var b=d.Color(a).brighten(-0.1).get();this.fill=a;this.side1.attr({fill:b});this.side2.attr({fill:b});this.inn.attr({fill:b});this.out.attr({fill:b});this.top.attr({fill:a});return this};A(["opacity","translateX","translateY","visibility"],function(b){a[b+"Setter"]=function(b,c){a[c]=b;A(["out","inn","side1","side2","top"],function(e){a[e].attr(c,b)})}});K(a,"attr",function(c,e,d){var f;if(typeof e==="object"&&(f=b(e)))N(a.attribs,f),a.setPaths(a.attribs);return c.call(this, -e,d)});K(a,"animate",function(a,c,e,d){var f,m=this.attribs,l;delete c.center;delete c.z;delete c.depth;delete c.alpha;delete c.beta;e=M(s(e,this.renderer.globalAnimation));if(e.duration&&(c=G(c),f=b(c)))l=f,e.step=function(a,b){function c(a){return m[a]+(s(l[a],m[a])-m[a])*b.pos}b.elem.setPaths(G(m,{x:c("x"),y:c("y"),r:c("r"),innerR:c("innerR"),start:c("start"),end:c("end")}))};return a.call(this,c,e,d)});a.destroy=function(){this.top.destroy();this.out.destroy();this.inn.destroy();this.side1.destroy(); -this.side2.destroy();d.SVGElement.prototype.destroy.call(this)};a.hide=function(){this.top.hide();this.out.hide();this.inn.hide();this.side1.hide();this.side2.hide()};a.show=function(){this.top.show();this.out.show();this.inn.show();this.side1.show();this.side2.show()};return a};d.SVGRenderer.prototype.arc3dPath=function(c){function b(a){a%=2*n;a>n&&(a=2*n-a);return a}var a=c.x,e=c.y,d=c.start,g=c.end-1.0E-5,h=c.r,j=c.innerR,k=c.depth,i=c.alpha,m=c.beta,x=l(d),y=r(d),c=l(g),q=r(g),p=h*l(m);h*=l(i); -var o=j*l(m),s=j*l(i),j=k*r(m),t=k*r(i),k=["M",a+p*x,e+h*y],k=k.concat(u(a,e,p,h,d,g,0,0)),k=k.concat(["L",a+o*c,e+s*q]),k=k.concat(u(a,e,o,s,g,d,0,0)),k=k.concat(["Z"]),z=m>0?n/2:0,m=i>0?0:n/2,z=d>-z?d:g>-z?-z:d,v=gw&&dn-m&&da&&(n=Math.min(n,1-Math.abs((h+m)/(a+m))%1));jd&&(n=d<0?Math.min(n,(k+l)/(-d+k+l)):Math.min(n,1-(k+l)/(d+l)%1));i0?4:-1}).css({stroke:g.color}).add()):(d={x:m+(b.yAxis[0].opposite?0:-f.size),y:l+(b.xAxis[0].opposite?-g.size:0),z:j,width:i+f.size,height:k+g.size,depth:h.size,insidePlotArea:!1},this.backFrame?this.backFrame.animate(d):this.backFrame=a.cuboid(d).attr({fill:h.color,zIndex:-3}).css({stroke:h.color}).add(),b={x:m+(b.yAxis[0].opposite?i:-f.size),y:l+(b.xAxis[0].opposite?-g.size:0),z:0,width:f.size,height:k+g.size,depth:j,insidePlotArea:!1},this.sideFrame?this.sideFrame.animate(b): -this.sideFrame=a.cuboid(b).attr({fill:f.color,zIndex:-2}).css({stroke:f.color}).add())}});d.wrap(d.Axis.prototype,"getPlotLinePath",function(c){var b=c.apply(this,[].slice.call(arguments,1));if(!this.chart.is3d())return b;if(b===null)return b;var a=this.chart,d=a.options.chart.options3d,a=this.isZAxis?a.plotWidth:d.depth,d=this.opposite;this.horiz&&(d=!d);b=[this.swapZ({x:b[1],y:b[2],z:d?a:0}),this.swapZ({x:b[1],y:b[2],z:a}),this.swapZ({x:b[4],y:b[5],z:a}),this.swapZ({x:b[4],y:b[5],z:d?0:a})];b=o(b, -this.chart,!1);return b=this.chart.renderer.toLinePath(b,!1)});d.wrap(d.Axis.prototype,"getLinePath",function(c){return this.chart.is3d()?[]:c.apply(this,[].slice.call(arguments,1))});d.wrap(d.Axis.prototype,"getPlotBandPath",function(c){if(!this.chart.is3d())return c.apply(this,[].slice.call(arguments,1));var b=arguments,a=b[1],b=this.getPlotLinePath(b[2]);(a=this.getPlotLinePath(a))&&b?a.push("L",b[10],b[11],"L",b[7],b[8],"L",b[4],b[5],"L",b[1],b[2]):a=null;return a});d.wrap(d.Tick.prototype,"getMarkPath", -function(c){var b=c.apply(this,[].slice.call(arguments,1));if(!this.axis.chart.is3d())return b;b=[this.axis.swapZ({x:b[1],y:b[2],z:0}),this.axis.swapZ({x:b[4],y:b[5],z:0})];b=o(b,this.axis.chart,!1);return b=["M",b[0].x,b[0].y,"L",b[1].x,b[1].y]});d.wrap(d.Tick.prototype,"getLabelPosition",function(c){var b=c.apply(this,[].slice.call(arguments,1));if(!this.axis.chart.is3d())return b;var a=o([this.axis.swapZ({x:b.x,y:b.y,z:0})],this.axis.chart,!1)[0];a.x-=!this.axis.horiz&&this.axis.opposite?this.axis.transA: -0;a.old=b;return a});d.wrap(d.Tick.prototype,"handleOverflow",function(c,b){if(this.axis.chart.is3d())b=b.old;return c.call(this,b)});d.wrap(d.Axis.prototype,"getTitlePosition",function(c){var b=this.chart.is3d(),a,d;if(b)d=this.axisTitleMargin,this.axisTitleMargin=0;a=c.apply(this,[].slice.call(arguments,1));if(b)a=o([this.swapZ({x:a.x,y:a.y,z:0})],this.chart,!1)[0],a[this.horiz?"y":"x"]+=(this.horiz?1:-1)*(this.opposite?-1:1)*d,this.axisTitleMargin=d;return a});d.wrap(d.Axis.prototype,"drawCrosshair", -function(c){var b=arguments;this.chart.is3d()&&b[2]&&(b[2]={plotX:b[2].plotXold||b[2].plotX,plotY:b[2].plotYold||b[2].plotY});c.apply(this,[].slice.call(b,1))});d.Axis.prototype.swapZ=function(c,b){if(this.isZAxis){var a=b?0:this.chart.plotLeft,d=this.chart;return{x:a+(d.yAxis[0].opposite?c.z:d.xAxis[0].width-c.z),y:c.y,z:c.x-a}}return c};var H=d.ZAxis=function(){this.isZAxis=!0;this.init.apply(this,arguments)};d.extend(H.prototype,d.Axis.prototype);d.extend(H.prototype,{setOptions:function(c){c= -d.merge({offset:0,lineWidth:0},c);d.Axis.prototype.setOptions.call(this,c);this.coll="zAxis"},setAxisSize:function(){d.Axis.prototype.setAxisSize.call(this);this.width=this.len=this.chart.options.chart.options3d.depth;this.right=this.chart.chartWidth-this.width-this.left},getSeriesExtremes:function(){var c=this,b=c.chart;c.hasVisibleSeries=!1;c.dataMin=c.dataMax=c.ignoreMinPadding=c.ignoreMaxPadding=null;c.buildStacks&&c.buildStacks();d.each(c.series,function(a){if(a.visible||!b.options.chart.ignoreHiddenSeries)if(c.hasVisibleSeries= -!0,a=a.zData,a.length)c.dataMin=Math.min(s(c.dataMin,a[0]),Math.min.apply(null,a)),c.dataMax=Math.max(s(c.dataMax,a[0]),Math.max.apply(null,a))})}});d.wrap(d.Chart.prototype,"getAxes",function(c){var b=this,a=this.options,a=a.zAxis=d.splat(a.zAxis||{});c.call(this);if(b.is3d())this.zAxis=[],d.each(a,function(a,c){a.index=c;a.isX=!0;(new H(b,a)).setScale()})});d.wrap(d.seriesTypes.column.prototype,"translate",function(c){c.apply(this,[].slice.call(arguments,1));if(this.chart.is3d()){var b=this.chart, -a=this.options,e=a.depth||25,f=(a.stacking?a.stack||0:this._i)*(e+(a.groupZPadding||1));a.grouping!==!1&&(f=0);f+=a.groupZPadding||1;d.each(this.data,function(a){if(a.y!==null){var c=a.shapeArgs,d=a.tooltipPos;a.shapeType="cuboid";c.z=f;c.depth=e;c.insidePlotArea=!0;d=o([{x:d[0],y:d[1],z:f}],b,!0)[0];a.tooltipPos=[d.x,d.y]}});this.z=f}});d.wrap(d.seriesTypes.column.prototype,"animate",function(c){if(this.chart.is3d()){var b=arguments[1],a=this.yAxis,e=this,f=this.yAxis.reversed;if(d.svg)b?d.each(e.data, -function(b){if(b.y!==null&&(b.height=b.shapeArgs.height,b.shapey=b.shapeArgs.y,b.shapeArgs.height=1,!f))b.shapeArgs.y=b.stackY?b.plotY+a.translate(b.stackY):b.plotY+(b.negative?-b.height:b.height)}):(d.each(e.data,function(a){if(a.y!==null)a.shapeArgs.height=a.height,a.shapeArgs.y=a.shapey,a.graphic&&a.graphic.animate(a.shapeArgs,e.options.animation)}),this.drawDataLabels(),e.animate=null)}else c.apply(this,[].slice.call(arguments,1))});d.wrap(d.seriesTypes.column.prototype,"init",function(c){c.apply(this, -[].slice.call(arguments,1));if(this.chart.is3d()){var b=this.options,a=b.grouping,d=b.stacking,f=s(this.yAxis.options.reversedStacks,!0),g=0;if(a===void 0||a){a=this.chart.retrieveStacks(d);g=b.stack||0;for(d=0;d=a.min&&g<=a.max:!1,e.push({x:f.plotX,y:f.plotY,z:f.plotZ});b=o(e,b,!0);for(h=0;h{point.x}
y: {point.y}
z: {point.z}
":"x: {point.x}
y: {point.y}
z: {point.z}
";return c});if(d.VMLRenderer)d.setOptions({animate:!1}),d.VMLRenderer.prototype.cuboid=d.SVGRenderer.prototype.cuboid, -d.VMLRenderer.prototype.cuboidPath=d.SVGRenderer.prototype.cuboidPath,d.VMLRenderer.prototype.toLinePath=d.SVGRenderer.prototype.toLinePath,d.VMLRenderer.prototype.createElement3D=d.SVGRenderer.prototype.createElement3D,d.VMLRenderer.prototype.arc3d=function(c){c=d.SVGRenderer.prototype.arc3d.call(this,c);c.css({zIndex:c.zIndex});return c},d.VMLRenderer.prototype.arc3dPath=d.SVGRenderer.prototype.arc3dPath,d.wrap(d.Axis.prototype,"render",function(c){c.apply(this,[].slice.call(arguments,1));this.sideFrame&& -(this.sideFrame.css({zIndex:0}),this.sideFrame.front.attr({fill:this.sideFrame.color}));this.bottomFrame&&(this.bottomFrame.css({zIndex:1}),this.bottomFrame.front.attr({fill:this.bottomFrame.color}));this.backFrame&&(this.backFrame.css({zIndex:0}),this.backFrame.front.attr({fill:this.backFrame.color}))})}); -/* - Highcharts JS v4.2.5 (2016-05-06) - Exporting module - - (c) 2010-2016 Torstein Honsi - - License: www.highcharts.com/license -*/ -(function(f){typeof module==="object"&&module.exports?module.exports=f:f(Highcharts)})(function(f){var t=f.win,k=t.document,C=f.Chart,v=f.addEvent,D=f.removeEvent,E=f.fireEvent,s=f.createElement,u=f.discardElement,x=f.css,l=f.merge,q=f.each,r=f.extend,F=f.splat,G=Math.max,H=f.isTouchDevice,I=f.Renderer.prototype.symbols,A=f.getOptions(),B;r(A.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"});A.navigation={menuStyle:{border:"1px solid #A0A0A0",background:"#FFFFFF",padding:"5px 0"},menuItemStyle:{padding:"0 10px",background:"none",color:"#303030",fontSize:H?"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}}; -A.exporting={type:"image/png",url:"http://export.highcharts.com/",printMaxWidth:780,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(a,b,e){var c,a=s("form",l({method:"post",action:a,enctype:"multipart/form-data"},e),{display:"none"},k.body);for(c in b)s("input",{type:"hidden",name:c,value:b[c]},null,a);a.submit();u(a)};r(C.prototype,{sanitizeSVG:function(a){return a.replace(/zIndex="[^"]+"/g,"").replace(/isShadow="[^"]+"/g,"").replace(/symbolName="[^"]+"/g,"").replace(/jQuery[0-9]+="[^"]+"/g,"").replace(/url\([^#]+#/g,"url(#").replace(/.*?$/,"").replace(/(fill|stroke)="rgba\(([ 0-9]+,[ 0-9]+,[ 0-9]+),([ 0-9\.]+)\)"/g,'$1="rgb($2)" $1-opacity="$3"').replace(/ /g,"\u00a0").replace(/­/g,"\u00ad").replace(//g,"<$1title>").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()})},getChartHTML:function(){return this.container.innerHTML},getSVG:function(a){var b=this,e,c,g,j,h,d=l(b.options,a),m=d.exporting.allowHTML;if(!k.createElementNS)k.createElementNS=function(a,b){return k.createElement(b)};c=s("div",null,{position:"absolute",top:"-9999em",width:b.chartWidth+"px",height:b.chartHeight+"px"},k.body); -g=b.renderTo.style.width;h=b.renderTo.style.height;g=d.exporting.sourceWidth||d.chart.width||/px$/.test(g)&&parseInt(g,10)||600;h=d.exporting.sourceHeight||d.chart.height||/px$/.test(h)&&parseInt(h,10)||400;r(d.chart,{animation:!1,renderTo:c,forExport:!0,renderer:"SVGRenderer",width:g,height:h});d.exporting.enabled=!1;delete d.data;d.series=[];q(b.series,function(a){j=l(a.userOptions,{animation:!1,enableMouseTracking:!1,showCheckbox:!1,visible:a.visible});j.isInternal||d.series.push(j)});a&&q(["xAxis", -"yAxis"],function(b){q(F(a[b]),function(a,c){d[b][c]=l(d[b][c],a)})});e=new f.Chart(d,b.callback);q(["xAxis","yAxis"],function(a){q(b[a],function(b,c){var d=e[a][c],f=b.getExtremes(),g=f.userMin,f=f.userMax;d&&(g!==void 0||f!==void 0)&&d.setExtremes(g,f,!0,!1)})});g=e.getChartHTML();d=null;e.destroy();u(c);if(m&&(c=g.match(/<\/svg>(.*?$)/)))c=''+c[1]+"",g=g.replace("",c+""); -g=this.sanitizeSVG(g);return g=g.replace(/(url\(#highcharts-[0-9]+)"/g,"$1").replace(/"/g,"'")},getSVGForExport:function(a,b){var e=this.options.exporting;return this.getSVG(l({chart:{borderRadius:0}},e.chartOptions,b,{exporting:{sourceWidth:a&&a.sourceWidth||e.sourceWidth,sourceHeight:a&&a.sourceHeight||e.sourceHeight}}))},exportChart:function(a,b){var e=this.getSVGForExport(a,b),a=l(this.options.exporting,a);f.post(a.url,{filename:a.filename||"chart",type:a.type,width:a.width||0,scale:a.scale|| -2,svg:e},a.formAttributes)},print:function(){var a=this,b=a.container,e=[],c=b.parentNode,f=k.body,j=f.childNodes,h=a.options.exporting.printMaxWidth,d,m,n;if(!a.isPrinting){a.isPrinting=!0;a.pointer.reset(null,0);E(a,"beforePrint");if(n=h&&a.chartWidth>h)d=a.hasUserSize,m=[a.chartWidth,a.chartHeight,!1],a.setSize(h,a.chartHeight,!1);q(j,function(a,b){if(a.nodeType===1)e[b]=a.style.display,a.style.display="none"});f.appendChild(b);t.focus();t.print();setTimeout(function(){c.appendChild(b);q(j,function(a, -b){if(a.nodeType===1)a.style.display=e[b]});a.isPrinting=!1;if(n)a.setSize.apply(a,m),a.hasUserSize=d;E(a,"afterPrint")},1E3)}},contextMenu:function(a,b,e,c,f,j,h){var d=this,m=d.options.navigation,n=m.menuItemStyle,o=d.chartWidth,p=d.chartHeight,l="cache-"+a,i=d[l],w=G(f,j),y,z,t,u=function(b){d.pointer.inClass(b.target,a)||z()};if(!i)d[l]=i=s("div",{className:a},{position:"absolute",zIndex:1E3,padding:w+"px"},d.container),y=s("div",null,r({MozBoxShadow:"3px 3px 10px #888",WebkitBoxShadow:"3px 3px 10px #888", -boxShadow:"3px 3px 10px #888"},m.menuStyle),i),z=function(){x(i,{display:"none"});h&&h.setState(0);d.openMenu=!1},v(i,"mouseleave",function(){t=setTimeout(z,500)}),v(i,"mouseenter",function(){clearTimeout(t)}),v(k,"mouseup",u),v(d,"destroy",function(){D(k,"mouseup",u)}),q(b,function(a){if(a){var b=a.separator?s("hr",null,null,y):s("div",{onmouseover:function(){x(this,m.menuItemHoverStyle)},onmouseout:function(){x(this,n)},onclick:function(b){b&&b.stopPropagation();z();a.onclick&&a.onclick.apply(d, -arguments)},innerHTML:a.text||d.options.lang[a.textKey]},r({cursor:"pointer"},n),y);d.exportDivElements.push(b)}}),d.exportDivElements.push(y,i),d.exportMenuWidth=i.offsetWidth,d.exportMenuHeight=i.offsetHeight;b={display:"block"};e+d.exportMenuWidth>o?b.right=o-e-f-w+"px":b.left=e-w+"px";c+j+d.exportMenuHeight>p&&h.alignOptions.verticalAlign!=="top"?b.bottom=p-c-w+"px":b.top=c+j-w+"px";x(i,b);d.openMenu=!0},addButton:function(a){var b=this,e=b.renderer,c=l(b.options.navigation.buttonOptions,a),g= -c.onclick,j=c.menuItems,h,d,m={stroke:c.symbolStroke,fill:c.symbolFill},n=c.symbolSize||12;if(!b.btnCount)b.btnCount=0;if(!b.exportDivElements)b.exportDivElements=[],b.exportSVGElements=[];if(c.enabled!==!1){var o=c.theme,p=o.states,k=p&&p.hover,p=p&&p.select,i;delete o.states;g?i=function(a){a.stopPropagation();g.call(b,a)}:j&&(i=function(){b.contextMenu(d.menuClassName,j,d.translateX,d.translateY,d.width,d.height,d);d.setState(2)});c.text&&c.symbol?o.paddingLeft=f.pick(o.paddingLeft,25):c.text|| -r(o,{width:c.width,height:c.height,padding:0});d=e.button(c.text,0,0,i,o,k,p).attr({title:b.options.lang[c._titleKey],"stroke-linecap":"round",zIndex:3});d.menuClassName=a.menuClassName||"highcharts-menu-"+b.btnCount++;c.symbol&&(h=e.symbol(c.symbol,c.symbolX-n/2,c.symbolY-n/2,n,n).attr(r(m,{"stroke-width":c.symbolStrokeWidth||1,zIndex:1})).add(d));d.add().align(r(c,{width:d.width,x:f.pick(c.x,B)}),!0,"spacingBox");B+=(d.width+c.buttonSpacing)*(c.align==="right"?-1:1);b.exportSVGElements.push(d,h)}}, -destroyExport:function(a){var a=a.target,b,e;for(b=0;b=g)&&(c===void 0||b<=c)){a=f.color;if(e)e.dataClass=h;break}}else{this.isLog&&(b=this.val2lin(b));a=1-(this.max-b)/(this.max-this.min);for(h=c.length;h--;)if(a>c[h][0])break; -g=c[h]||c[h+1];c=c[h+1]||g;a=1-(c[0]-a)/(c[0]-g[0]||1);a=this.tweenColors(g.color,c.color,a)}return a},tweenColors:function(b,a,i){var c;!a.rgba.length||!b.rgba.length?b=a.input||"none":(b=b.rgba,a=a.rgba,c=a[3]!==1||b[3]!==1,b=(c?"rgba(":"rgb(")+Math.round(a[0]+(b[0]-a[0])*(1-i))+","+Math.round(a[1]+(b[1]-a[1])*(1-i))+","+Math.round(a[2]+(b[2]-a[2])*(1-i))+(c?","+(a[3]+(b[3]-a[3])*(1-i)):"")+")");return b}};k(["fill","stroke"],function(b){a.Fx.prototype[b+"Setter"]=function(){this.elem.attr(b,l.tweenColors(a.Color(this.start), -a.Color(this.end),this.pos))}});a.seriesTypes.solidgauge=a.extendClass(a.seriesTypes.gauge,{type:"solidgauge",pointAttrToOptions:{},bindAxes:function(){var b;a.seriesTypes.gauge.prototype.bindAxes.call(this);b=this.yAxis;a.extend(b,l);b.options.dataClasses&&b.initDataClasses(b.options);b.initStops(b.options)},drawPoints:function(){var b=this,e=b.yAxis,i=e.center,c=b.options,g=b.chart.renderer,d=c.overshoot,f=t(d)?d/180*Math.PI:0;a.each(b.points,function(a){var d=a.graphic,j=e.startAngleRad+e.translate(a.y, -null,null,null,!0),k=r(s(a.options.radius,c.radius,100))*i[2]/200,m=r(s(a.options.innerRadius,c.innerRadius,60))*i[2]/200,n=e.toColor(a.y,a),o=Math.min(e.startAngleRad,e.endAngleRad),l=Math.max(e.startAngleRad,e.endAngleRad);n==="none"&&(n=a.color||b.color||"none");if(n!=="none")a.color=n;j=Math.max(o-f,Math.min(l+f,j));c.wrap===!1&&(j=Math.max(o,Math.min(l,j)));o=Math.min(j,e.startAngleRad);j=Math.max(j,e.startAngleRad);j-o>2*Math.PI&&(j=o+2*Math.PI);a.shapeArgs=m={x:i[0],y:i[1],r:k,innerR:m,start:o, -end:j,fill:n};a.startR=k;if(d){if(a=m.d,d.animate(m),a)m.d=a}else d={stroke:c.borderColor||"none","stroke-width":c.borderWidth||0,fill:n,"sweep-flag":0},c.linecap!=="square"&&(d["stroke-linecap"]=d["stroke-linejoin"]="round"),a.graphic=g.arc(m).attr(d).add(b.group)})},animate:function(b){if(!b)this.startAngleRad=this.yAxis.startAngleRad,a.seriesTypes.pie.prototype.animate.call(this,b)}})}); -/* - - Highcharts funnel module - - (c) 2010-2016 Torstein Honsi - - License: www.highcharts.com/license -*/ -(function(b){typeof module==="object"&&module.exports?module.exports=b:b(Highcharts)})(function(b){var q=b.getOptions(),w=q.plotOptions,r=b.seriesTypes,F=b.merge,E=function(){},B=b.each;w.funnel=F(w.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}}});r.funnel=b.extendClass(r.pie,{type:"funnel",animate:E,translate:function(){var a= -function(k,a){return/%$/.test(k)?a*parseInt(k,10)/100:parseInt(k,10)},b=0,d=this.chart,c=this.options,f=c.reversed,g=c.ignoreHiddenPoint,o=d.plotWidth,h=d.plotHeight,q=0,d=c.center,i=a(d[0],o),x=a(d[1],h),r=a(c.width,o),l,s,e=a(c.height,h),t=a(c.neckWidth,o),C=a(c.neckHeight,h),u=x-e/2+e-C,a=this.data,y,z,w=c.dataLabels.position==="left"?1:0,A,m,D,p,j,v,n;this.getWidthAt=s=function(k){var a=x-e/2;return k>u||e===C?t:t+(r-t)*(1-(k-a)/(e-C))};this.getX=function(k,a){return i+(a?-1:1)*(s(f?h-k:k)/2+ -c.dataLabels.distance)};this.center=[i,x,e];this.centerX=i;B(a,function(a){if(!g||a.visible!==!1)b+=a.y});B(a,function(a){n=null;z=b?a.y/b:0;m=x-e/2+q*e;j=m+z*e;l=s(m);A=i-l/2;D=A+l;l=s(j);p=i-l/2;v=p+l;m>u?(A=p=i-t/2,D=v=i+t/2):j>u&&(n=j,l=s(u),p=i-l/2,v=p+l,j=u);f&&(m=e-m,j=e-j,n=n?e-n:null);y=["M",A,m,"L",D,m,v,j];n&&y.push(v,n,p,n);y.push(p,j,"Z");a.shapeType="path";a.shapeArgs={d:y};a.percentage=z*100;a.plotX=i;a.plotY=(m+(n||j))/2;a.tooltipPos=[i,a.plotY];a.slice=E;a.half=w;if(!g||a.visible!== -!1)q+=z})},drawPoints:function(){var a=this,b=a.chart.renderer,d,c,f;B(a.data,function(g){f=g.graphic;c=g.shapeArgs;d=g.pointAttr[g.selected?"select":""];f?f.attr(d).animate(c):g.graphic=b.path(c).attr(d).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,d,c,f,g=a.length,o,h;for(this.center[2]-=2*b;g--;)f=a[g],c=(d=f.half)?1:-1,h=f.plotY,o=this.getX(h,d),f.labelPos=[0,h,o+(b-5)*c,h,o+ -b*c,h,d?"right":"left",0];r.pie.prototype.drawDataLabels.call(this)}});q.plotOptions.pyramid=b.merge(q.plotOptions.funnel,{neckWidth:"0%",neckHeight:"0%",reversed:!0});b.seriesTypes.pyramid=b.extendClass(b.seriesTypes.funnel,{type:"pyramid"})}); -/* - Highcharts JS v4.2.5 (2016-05-06) - Plugin for displaying a message when there is no data visible in chart. - - (c) 2010-2016 Highsoft AS - Author: Oystein Moseng - - License: www.highcharts.com/license -*/ -(function(a){typeof module==="object"&&module.exports?module.exports=a:a(Highcharts)})(function(a){function h(){return!!this.points.length}function d(){this.hasData()?this.hideNoData():this.showNoData()}var e=a.seriesTypes,c=a.Chart.prototype,f=a.getOptions(),g=a.extend,i=a.each;g(f.lang,{noData:"No data to display"});f.noData={position:{x:0,y:0,align:"center",verticalAlign:"middle"},attr:{},style:{fontWeight:"bold",fontSize:"12px",color:"#60606a"}};i(["pie","gauge","waterfall","bubble","treemap"], -function(b){if(e[b])e[b].prototype.hasData=h});a.Series.prototype.hasData=function(){return this.visible&&this.dataMax!==void 0&&this.dataMin!==void 0};c.showNoData=function(b){var a=this.options,b=b||a.lang.noData,a=a.noData;if(!this.noDataLabel)this.noDataLabel=this.renderer.label(b,0,0,null,null,null,a.useHTML,null,"no-data").attr(a.attr).css(a.style).add(),this.noDataLabel.align(g(this.noDataLabel.getBBox(),a.position),!1,"plotBox")};c.hideNoData=function(){if(this.noDataLabel)this.noDataLabel= -this.noDataLabel.destroy()};c.hasData=function(){for(var a=this.series,c=a.length;c--;)if(a[c].hasData()&&!a[c].options.isInternal)return!0;return!1};c.callbacks.push(function(b){a.addEvent(b,"load",d);a.addEvent(b,"redraw",d)})}); -(function(l){var n=l.Chart,m=l.each,o=l.pick,p=l.addEvent;n.prototype.callbacks.push(function(e){function f(){var d=[];m(e.series,function(a){var b=a.options.dataLabels,c=a.dataLabelCollections||["dataLabel"];(b.enabled||a._hasPointLabels)&&!b.allowOverlap&&a.visible&&m(c,function(b){m(a.points,function(a){if(a[b])a[b].labelrank=o(a.labelrank,a.shapeArgs&&a.shapeArgs.height),d.push(a[b])})})});e.hideOverlappingLabels(d)}f();p(e,"redraw",f)});n.prototype.hideOverlappingLabels=function(e){var f=e.length, -d,a,b,c,g,h,i,j,k;for(a=0;ag.x+i.translateX+(b.width-k)||h.x+j.translateX+(c.width-k)g.y+i.translateY+(b.height-k)||h.y+j.translateY+ -(c.height-k)=d&&e<=c&&!x&&g!==""&&(g=a.split(i),j(g,function(b,a){a>= -h&&a<=q&&(f[a-h]||(f[a-h]=[]),f[a-h][t]=b)}),t+=1)}),this.dataFound())},parseTable:function(){var b=this.options,a=b.table,e=this.columns,f=b.startRow||0,d=b.endRow||Number.MAX_VALUE,c=b.startColumn||0,h=b.endColumn||Number.MAX_VALUE;a&&(typeof a==="string"&&(a=u.getElementById(a)),j(a.getElementsByTagName("tr"),function(b,a){a>=f&&a<=d&&j(b.children,function(b,d){if((b.tagName==="TD"||b.tagName==="TH")&&d>=c&&d<=h)e[d-c]||(e[d-c]=[]),e[d-c][a-f]=b.innerHTML})}),this.dataFound())},parseGoogleSpreadsheet:function(){var b= -this,a=this.options,e=a.googleSpreadsheetKey,f=this.columns,d=a.startRow||0,c=a.endRow||Number.MAX_VALUE,h=a.startColumn||0,g=a.endColumn||Number.MAX_VALUE,i,j;e&&jQuery.ajax({dataType:"json",url:"https://spreadsheets.google.com/feeds/cells/"+e+"/"+(a.googleSpreadsheetWorksheet||"od6")+"/public/values?alt=json-in-script&callback=?",error:a.error,success:function(a){var a=a.feed.entry,e,n=a.length,m=0,k=0,l;for(l=0;l= -h&&l<=g)f[l-h]=[],f[l-h].length=Math.min(k,c-d);for(l=0;l=h&&j<=g&&i>=d&&i<=c)f[j-h][i-d]=e.content.$t;b.dataFound()}})},trim:function(b,a){typeof b==="string"&&(b=b.replace(/^\s+|\s+$/g,""),a&&/^[0-9\s]+$/.test(b)&&(b=b.replace(/\s/g,"")),this.decimalRegex&&(b=b.replace(this.decimalRegex,"$1.$2")));return b},parseTypes:function(){for(var b=this.columns,a=b.length;a--;)this.parseColumn(b[a],a)},parseColumn:function(b,a){var e=this.rawColumns, -f=this.columns,d=b.length,c,h,g,i,j=this.firstRowAsNames,k=r(a,this.valueCount.xColumns)!==-1,o=[],n=this.chartOptions,m,p=(this.options.columnTypes||[])[a],n=k&&(n&&n.xAxis&&w(n.xAxis)[0].type==="category"||p==="string");for(e[a]||(e[a]=[]);d--;)if(c=o[d]||b[d],g=this.trim(c),i=this.trim(c,!0),h=parseFloat(i),e[a][d]===void 0&&(e[a][d]=g),n||d===0&&j)b[d]=g;else if(+i===h)b[d]=h,h>31536E6&&p!=="float"?b.isDatetime=!0:b.isNumeric=!0,b[d+1]!==void 0&&(m=h>b[d+1]);else if(h=this.parseDate(c),k&&s(h)&& -p!=="float"){if(o[d]=c,b[d]=h,b.isDatetime=!0,b[d+1]!==void 0){c=h>b[d+1];if(c!==m&&m!==void 0)this.alternativeFormat?(this.dateFormat=this.alternativeFormat,d=b.length,this.alternativeFormat=this.dateFormats[this.dateFormat].alternative):b.unsorted=!0;m=c}}else if(b[d]=g===""?null:g,d!==0&&(b.isDatetime||b.isNumeric))b.mixed=!0;k&&b.mixed&&(f[a]=e[a]);if(k&&m&&this.options.sort)for(a=0;a0;){i=new k;i.addColumnReader(0,"x");c=r(0, -d);c!==-1&&d.splice(c,1);for(c=0;c0&&g[0].readers.length>0&&(i=b[g[0].readers[0].columnIndex],i!==void 0&&(i.isDatetime?a="datetime":i.isNumeric||(a="category")));if(a==="category")for(c=0;c=2&&(d=this.getReferencedColumnIndexes(),d.length>=2))d.shift(),d.sort(),this.name=b[d.shift()].name;return f};k.prototype.addColumnReader=function(b,a){this.readers.push({columnIndex:b,configName:a});if(!(a==="x"||a==="y"||a===void 0))this.pointIsArray=!1};k.prototype.getReferencedColumnIndexes=function(){var b,a=[],e;for(b=0;b-1?h.thousandsSep:""))):e=na(f,e)}j.push(e);a=a.slice(c+1);c=(d=!d)?"}":"{"}j.push(a);return j.join("")}function zb(a){return Y.pow(10,V(Y.log(a)/Y.LN10))}function Ab(a,b,c,d,e){var f,g=a,c=q(c,1);f=a/c;b||(b=[1,2,2.5,5,10],d===!1&&(c===1?b=[1,2,5,10]:c<=0.1&&(b=[1/c])));for(d=0;d=a||!e&&f<=(b[d]+(b[d+1]||b[d]))/2)break;g*=c;return g}function nb(a,b){var c=a.length,d,e;for(e=0;ec&&(c=a[b]);return c}function Pa(a,b){for(var c in a)a[c]&&a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Ua(a){ob||(ob=fa(Va));a&&ob.appendChild(a);ob.innerHTML=""}function ka(a,b){return parseFloat(a.toPrecision(b||14))}function $a(a,b){b.renderer.globalAnimation=q(a,b.animation)}function gb(a){return ea(a)? -z(a):{duration:a?500:0}}function Ob(){var a=R.global,b=a.useUTC,c=b?"getUTC":"get",d=b?"setUTC":"set";ba=a.Date||L.Date;yb=b&&a.timezoneOffset;fb=b&&a.getTimezoneOffset;pb=function(a,c,d,h,i,j){var k;b?(k=ba.UTC.apply(0,arguments),k+=eb(k)):k=(new ba(a,c,q(d,1),q(h,0),q(i,0),q(j,0))).getTime();return k};Bb=c+"Minutes";Cb=c+"Hours";Db=c+"Day";ab=c+"Date";hb=c+"Month";ib=c+"FullYear";Pb=d+"Milliseconds";Qb=d+"Seconds";Rb=d+"Minutes";Sb=d+"Hours";qb=d+"Date";Eb=d+"Month";Fb=d+"FullYear"}function va(a){if(!(this instanceof -va))return new va(a);this.init(a)}function Z(){}function bb(a,b,c,d){this.axis=a;this.pos=b;this.type=c||"";this.isNew=!0;!c&&!d&&this.addLabel()}function Tb(a,b,c,d,e){var f=a.chart.inverted;this.axis=a;this.isNegative=c;this.options=b;this.x=d;this.total=null;this.points={};this.stack=e;this.rightCliff=this.leftCliff=0;this.alignOptions={align:b.align||(f?c?"left":"right":"center"),verticalAlign:b.verticalAlign||(f?"middle":c?"bottom":"top"),y:q(b.y,f?4:c?14:-6),x:q(b.x,f?c?-6:6:0)};this.textAlign= -b.textAlign||(f?c?"right":"left":"center")}function Gb(a){var b=a.options,c=b.navigator,d=c.enabled,b=b.scrollbar,e=b.enabled,f=d?c.height:0,g=e?b.height:0;this.handles=[];this.scrollbarButtons=[];this.elementsToDestroy=[];this.chart=a;this.setBaseSeries();this.height=f;this.scrollbarHeight=g;this.scrollbarEnabled=e;this.navigatorEnabled=d;this.navigatorOptions=c;this.scrollbarOptions=b;this.outlineHeight=f+g;this.init()}function Hb(a){this.init(a)}var t,H=L.document,Y=Math,y=Y.round,V=Y.floor,Ea= -Y.ceil,w=Y.max,G=Y.min,S=Y.abs,ca=Y.cos,la=Y.sin,Aa=Y.PI,pa=Aa*2/360,Na=L.navigator&&L.navigator.userAgent||"",Ub=L.opera,Ka=/(msie|trident|edge)/i.test(Na)&&!Ub,rb=H&&H.documentMode===8,sb=!Ka&&/AppleWebKit/.test(Na),Wa=/Firefox/.test(Na),jb=/(Mobile|Android|Windows Phone)/.test(Na),Qa="http://www.w3.org/2000/svg",ja=H&&H.createElementNS&&!!H.createElementNS(Qa,"svg").createSVGRect,Zb=Wa&&parseInt(Na.split("Firefox/")[1],10)<4,qa=H&&!ja&&!Ka&&!!H.createElement("canvas").getContext,Xa,cb,Vb={},Ib= -0,ob,R,na,M,ra=function(){},$=[],kb=0,Va="div",$b=/^[0-9]+$/,tb=["plotTop","marginRight","marginBottom","plotLeft"],ba,pb,yb,fb,Bb,Cb,Db,ab,hb,ib,Pb,Qb,Rb,Sb,qb,Eb,Fb,I={},B;B=L.Highcharts?ga(16,!0):{win:L};B.seriesTypes=I;var Ra=[],wa,sa,o,Fa,Jb,ta,E,T,O,db,Sa;xb.prototype={dSetter:function(){var a=this.paths[0],b=this.paths[1],c=[],d=this.now,e=a.length,f;if(d===1)c=this.toD;else if(e===b.length&&d<1)for(;e--;)f=parseFloat(a[e]),c[e]=isNaN(f)?a[e]:d*parseFloat(b[e]-f)+f;else c=b;this.elem.attr("d", -c)},update:function(){var a=this.elem,b=this.prop,c=this.now,d=this.options.step;if(this[b+"Setter"])this[b+"Setter"]();else a.attr?a.element&&a.attr(b,c):a.style[b]=c+this.unit;d&&d.call(a,c,this)},run:function(a,b,c){var d=this,e=function(a){return e.stopped?!1:d.step(a)},f;this.startTime=+new ba;this.start=a;this.end=b;this.unit=c;this.now=this.start;this.pos=0;e.elem=this.elem;if(e()&&Ra.push(e)===1)e.timerId=setInterval(function(){for(f=0;f=f+this.startTime){this.now=this.end;this.pos=1;this.update();a=g[this.prop]=!0;for(h in g)g[h]!==!0&&(a=!1);a&&e&&e.call(c);c=!1}else this.pos=d.easing((b-this.startTime)/f),this.now=this.start+(this.end-this.start)*this.pos,this.update(),c=!0;return c},initPath:function(a,b,c){var b=b||"",d=a.shift,e=b.indexOf("C")>-1,f=e? -7:3,g,b=b.split(" "),c=[].concat(c),h=a.isArea,i=h?2:1,j=function(a){for(g=a.length;g--;)(a[g]==="M"||a[g]==="L")&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&(j(b),j(c));if(d<=c.length/f&&b.length===c.length)for(;d--;)c=c.slice(0,f).concat(c),h&&(c=c.concat(c.slice(c.length-f)));a.shift=0;if(b.length)for(a=c.length;b.length3?g.length%3:0;c=q(c,e.decimalPoint);d=q(d,e.thousandsSep);a=a<0?"-":"";a+=h?g.substr(0,h)+d:"";a+=g.substr(h).replace(/(\d{3})(?=\d)/g,"$1"+d);b&&(d=Math.abs(i-g+Math.pow(10,-Math.max(b,f)-1)),a+=c+d.toFixed(b).slice(2));return a};Math.easeInOutSine=function(a){return-0.5* -(Math.cos(Math.PI*a)-1)};wa=function(a,b){var c;if(b==="width")return Math.min(a.offsetWidth,a.scrollWidth)-wa(a,"padding-left")-wa(a,"padding-right");else if(b==="height")return Math.min(a.offsetHeight,a.scrollHeight)-wa(a,"padding-top")-wa(a,"padding-bottom");return(c=L.getComputedStyle(a,void 0))&&K(c.getPropertyValue(b))};sa=function(a,b){return b.indexOf?b.indexOf(a):[].indexOf.call(b,a)};Fa=function(a,b){return[].filter.call(a,b)};ta=function(a,b){for(var c=[],d=0,e=a.length;d-1&&(f.splice(h,1),g[b]=f),d(b,c)):(e(),g[b]=[])):(e(),a.hcEvents={})};O=function(a,b,c,d){var e; -e=a.hcEvents;var f,g,c=c||{};if(H.createEvent&&(a.dispatchEvent||a.fireEvent))e=H.createEvent("Events"),e.initEvent(b,!0,!0),e.target=a,A(e,c),a.dispatchEvent?a.dispatchEvent(e):a.fireEvent(b,e);else if(e){e=e[b]||[];f=e.length;if(!c.preventDefault)c.preventDefault=function(){c.defaultPrevented=!0};c.target=a;if(!c.type)c.type=b;for(b=0;b{point.key}
',pointFormat:'\u25cf {series.name}: {point.y}
',shadow:!0,snap:jb?25:10,style:{color:"#333333",cursor:"default",fontSize:"12px", -padding:"8px",pointerEvents:"none",whiteSpace:"nowrap"}},credits:{enabled:!0,text:"Highcharts.com",href:"http://www.highcharts.com",position:{align:"right",x:-10,verticalAlign:"bottom",y:-5},style:{cursor:"pointer",color:"#909090",fontSize:"9px"}}};var W=R.plotOptions,da=W.line;Ob();va.prototype={parsers:[{regex:/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,parse:function(a){return[K(a[1]),K(a[2]),K(a[3]),parseFloat(a[4],10)]}},{regex:/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/, -parse:function(a){return[K(a[1],16),K(a[2],16),K(a[3],16),1]}},{regex:/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,parse:function(a){return[K(a[1]),K(a[2]),K(a[3]),1]}}],init:function(a){var b,c,d,e;if((this.input=a)&&a.stops)this.stops=ta(a.stops,function(a){return new va(a[1])});else for(d=this.parsers.length;d--&&!c;)e=this.parsers[d],(b=e.regex.exec(a))&&(c=e.parse(b));this.rgba=c||[]},get:function(a){var b=this.input,c=this.rgba,d;this.stops?(d=z(b),d.stops=[].concat(d.stops), -o(this.stops,function(b,c){d.stops[c]=[d.stops[c][0],b.get(a)]})):d=c&&C(c[0])?a==="rgb"||!a&&c[3]===1?"rgb("+c[0]+","+c[1]+","+c[2]+")":a==="a"?c[3]:"rgba("+c.join(",")+")":b;return d},brighten:function(a){var b,c=this.rgba;if(this.stops)o(this.stops,function(b){b.brighten(a)});else if(C(a)&&a!==0)for(b=0;b<3;b++)c[b]+=K(a*255),c[b]<0&&(c[b]=0),c[b]>255&&(c[b]=255);return this},setOpacity:function(a){this.rgba[3]=a;return this}};Z.prototype={opacity:1,textProps:"direction,fontSize,fontWeight,fontFamily,fontStyle,color,lineHeight,width,textDecoration,textOverflow,textShadow".split(","), -init:function(a,b){this.element=b==="span"?fa(b):H.createElementNS(Qa,b);this.renderer=a},animate:function(a,b,c){b=q(b,this.renderer.globalAnimation,!0);Sa(this);if(b){if(c)b.complete=c;db(this,a,b)}else this.attr(a,null,c);return this},colorGradient:function(a,b,c){var d=this.renderer,e,f,g,h,i,j,k,l,m,n,p,r=[],s;a.linearGradient?f="linearGradient":a.radialGradient&&(f="radialGradient");if(f){g=a[f];i=d.gradients;k=a.stops;n=c.radialReference;Ja(g)&&(a[f]=g={x1:g[0],y1:g[1],x2:g[2],y2:g[3],gradientUnits:"userSpaceOnUse"}); -f==="radialGradient"&&n&&!v(g.gradientUnits)&&(h=g,g=z(g,d.getRadialAttr(n,h),{gradientUnits:"userSpaceOnUse"}));for(p in g)p!=="id"&&r.push(p,g[p]);for(p in k)r.push(k[p]);r=r.join(",");i[r]?n=i[r].attr("id"):(g.id=n="highcharts-"+Ib++,i[r]=j=d.createElement(f).attr(g).add(d.defs),j.radAttr=h,j.stops=[],o(k,function(a){a[1].indexOf("rgba")===0?(e=va(a[1]),l=e.get("rgb"),m=e.get("a")):(l=a[1],m=1);a=d.createElement("stop").attr({offset:a[0],"stop-color":l,"stop-opacity":m}).add(j);j.stops.push(a)})); -s="url("+d.url+"#"+n+")";c.setAttribute(b,s);c.gradient=r;a.toString=function(){return s}}},applyTextShadow:function(a){var b=this.element,c,d=a.indexOf("contrast")!==-1,e={},f=this.renderer.forExport,g=f||b.style.textShadow!==t&&!Ka;if(d)e.textShadow=a=a.replace(/contrast/g,this.renderer.getContrast(b.style.fill));if(sb||f)e.textRendering="geometricPrecision";g?this.css(e):(this.fakeTS=!0,this.ySetter=this.xSetter,c=[].slice.call(b.getElementsByTagName("tspan")),o(a.split(/\s?,\s?/g),function(a){var d= -b.firstChild,e,f,a=a.split(" ");e=a[a.length-1];(f=a[a.length-2])&&o(c,function(a,c){var g;c===0&&(a.setAttribute("x",b.getAttribute("x")),c=b.getAttribute("y"),a.setAttribute("y",c||0),c===null&&b.setAttribute("y",0));g=a.cloneNode(1);X(g,{"class":"highcharts-text-shadow",fill:e,stroke:e,"stroke-opacity":1/w(K(f),3),"stroke-width":f,"stroke-linejoin":"round"});b.insertBefore(g,d)})}))},attr:function(a,b,c){var d,e=this.element,f,g=this,h;typeof a==="string"&&b!==t&&(d=a,a={},a[d]=b);if(typeof a=== -"string")g=(this[a+"Getter"]||this._defaultGetter).call(this,a,e);else{for(d in a){b=a[d];h=!1;this.symbolName&&/^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(d)&&(f||(this.symbolAttr(a),f=!0),h=!0);if(this.rotation&&(d==="x"||d==="y"))this.doTransform=!0;h||(h=this[d+"Setter"]||this._defaultSetter,h.call(this,b,d,e),this.shadows&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(d)&&this.updateShadows(d,b,h))}if(this.doTransform)this.updateTransform(),this.doTransform=!1}c&& -c();return g},updateShadows:function(a,b,c){for(var d=this.shadows,e=d.length;e--;)c.call(d[e],a==="height"?Math.max(b-(d[e].cutHeight||0),0):a==="d"?this.d:b,a,d[e])},addClass:function(a){var b=this.element,c=X(b,"class")||"";c.indexOf(a)===-1&&X(b,"class",c+" "+a);return this},symbolAttr:function(a){var b=this;o("x,y,r,start,end,width,height,innerR,anchorX,anchorY".split(","),function(c){b[c]=q(a[c],b[c])});b.attr({d:b.renderer.symbols[b.symbolName](b.x,b.y,b.width,b.height,b)})},clip:function(a){return this.attr("clip-path", -a?"url("+this.renderer.url+"#"+a.id+")":"none")},crisp:function(a){var b,c={},d,e=this.strokeWidth||0;d=y(e)%2/2;a.x=V(a.x||this.x||0)+d;a.y=V(a.y||this.y||0)+d;a.width=V((a.width||this.width||0)-2*d);a.height=V((a.height||this.height||0)-2*d);a.strokeWidth=e;for(b in a)this[b]!==a[b]&&(this[b]=c[b]=a[b]);return c},css:function(a){var b=this.styles,c={},d=this.element,e,f,g="";e=!b;if(a&&a.color)a.fill=a.color;if(b)for(f in a)a[f]!==b[f]&&(c[f]=a[f],e=!0);if(e){e=this.textWidth=a&&a.width&&d.nodeName.toLowerCase()=== -"text"&&K(a.width)||this.textWidth;b&&(a=A(b,c));this.styles=a;e&&(qa||!ja&&this.renderer.forExport)&&delete a.width;if(Ka&&!ja)N(this.element,a);else{b=function(a,b){return"-"+b.toLowerCase()};for(f in a)g+=f.replace(/([A-Z])/g,b)+":"+a[f]+";";X(d,"style",g)}e&&this.added&&this.renderer.buildText(this)}return this},on:function(a,b){var c=this,d=c.element;cb&&a==="click"?(d.ontouchstart=function(a){c.touchEventFired=ba.now();a.preventDefault();b.call(d,a)},d.onclick=function(a){(Na.indexOf("Android")=== --1||ba.now()-(c.touchEventFired||0)>1100)&&b.call(d,a)}):d["on"+a]=b;return this},setRadialReference:function(a){var b=this.renderer.gradients[this.element.gradient];this.element.radialReference=a;b&&b.radAttr&&b.animate(this.renderer.getRadialAttr(a,b.radAttr));return this},translate:function(a,b){return this.attr({translateX:a,translateY:b})},invert:function(){this.inverted=!0;this.updateTransform();return this},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=this.scaleX, -d=this.scaleY,e=this.inverted,f=this.rotation,g=this.element;e&&(a+=this.attr("width"),b+=this.attr("height"));a=["translate("+a+","+b+")"];e?a.push("rotate(90) scale(-1,1)"):f&&a.push("rotate("+f+" "+(g.getAttribute("x")||0)+" "+(g.getAttribute("y")||0)+")");(v(c)||v(d))&&a.push("scale("+q(c,1)+" "+q(d,1)+")");a.length&&g.setAttribute("transform",a.join(" "))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);return this},align:function(a,b,c){var d,e,f,g,h={};e=this.renderer;f=e.alignedObjects; -if(a){if(this.alignOptions=a,this.alignByTranslate=b,!c||Ca(c))this.alignTo=d=c||"renderer",za(f,this),f.push(this),c=null}else a=this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=q(c,e[d],e);d=a.align;e=a.verticalAlign;f=(c.x||0)+(a.x||0);g=(c.y||0)+(a.y||0);if(d==="right"||d==="center")f+=(c.width-(a.width||0))/{right:1,center:2}[d];h[b?"translateX":"x"]=y(f);if(e==="bottom"||e==="middle")g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]||1);h[b?"translateY":"y"]=y(g);this[this.placed? -"animate":"attr"](h);this.placed=!0;this.alignAttr=h;return this},getBBox:function(a,b){var c,d=this.renderer,e,f,g,h=this.element,i=this.styles;e=this.textStr;var j,k=h.style,l,m=d.cache,n=d.cacheKeys,p;f=q(b,this.rotation);g=f*pa;e!==t&&(p=["",f||0,i&&i.fontSize,h.style.width].join(","),p=e===""||$b.test(e)?"num:"+e.toString().length+p:e+p);p&&!a&&(c=m[p]);if(!c){if(h.namespaceURI===Qa||d.forExport){try{l=this.fakeTS&&function(a){o(h.querySelectorAll(".highcharts-text-shadow"),function(b){b.style.display= -a})},Wa&&k.textShadow?(j=k.textShadow,k.textShadow=""):l&&l("none"),c=h.getBBox?A({},h.getBBox()):{width:h.offsetWidth,height:h.offsetHeight},j?k.textShadow=j:l&&l("")}catch(r){}if(!c||c.width<0)c={width:0,height:0}}else c=this.htmlGetBBox();if(d.isSVG){d=c.width;e=c.height;if(Ka&&i&&i.fontSize==="11px"&&e.toPrecision(3)==="16.9")c.height=e=14;if(f)c.width=S(e*la(g))+S(d*ca(g)),c.height=S(e*ca(g))+S(d*la(g))}if(p){for(;n.length>250;)delete m[n.shift()];m[p]||n.push(p);m[p]=c}}return c},show:function(a){return this.attr({visibility:a? -"inherit":"visible"})},hide:function(){return this.attr({visibility:"hidden"})},fadeOut:function(a){var b=this;b.animate({opacity:0},{duration:a||150,complete:function(){b.attr({y:-9999})}})},add:function(a){var b=this.renderer,c=this.element,d;if(a)this.parentGroup=a;this.parentInverted=a&&a.inverted;this.textStr!==void 0&&b.buildText(this);this.added=!0;if(!a||a.handleZ||this.zIndex)d=this.zIndexSetter();d||(a?a.element:b.box).appendChild(c);if(this.onAdd)this.onAdd();return this},safeRemoveChild:function(a){var b= -a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||{},c=a.shadows,d=a.renderer.isSVG&&b.nodeName==="SPAN"&&a.parentGroup,e,f;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=b.point=null;Sa(a);if(a.clipPath)a.clipPath=a.clipPath.destroy();if(a.stops){for(f=0;f]*>/g,"")))},textSetter:function(a){if(a!==this.textStr)delete this.bBox,this.textStr=a,this.added&&this.renderer.buildText(this)},fillSetter:function(a,b,c){typeof a==="string"?c.setAttribute(b,a): -a&&this.colorGradient(a,b,c)},visibilitySetter:function(a,b,c){a==="inherit"?c.removeAttribute(b):c.setAttribute(b,a)},zIndexSetter:function(a,b){var c=this.renderer,d=this.parentGroup,c=(d||c).element||c.box,e,f,g=this.element,h;e=this.added;var i;if(v(a))g.zIndex=a,a=+a,this[b]===a&&(e=!1),this[b]=a;if(e){if((a=this.zIndex)&&d)d.handleZ=!0;d=c.childNodes;for(i=0;ia||!v(a)&&v(f)))c.insertBefore(g,e),h=!0;h||c.appendChild(g)}return h},_defaultSetter:function(a, -b,c){c.setAttribute(b,a)}};Z.prototype.yGetter=Z.prototype.xGetter;Z.prototype.translateXSetter=Z.prototype.translateYSetter=Z.prototype.rotationSetter=Z.prototype.verticalAlignSetter=Z.prototype.scaleXSetter=Z.prototype.scaleYSetter=function(a,b){this[b]=a;this.doTransform=!0};Z.prototype["stroke-widthSetter"]=Z.prototype.strokeSetter=function(a,b,c){this[b]=a;if(this.stroke&&this["stroke-width"])this.strokeWidth=this["stroke-width"],Z.prototype.fillSetter.call(this,this.stroke,"stroke",c),c.setAttribute("stroke-width", -this["stroke-width"]),this.hasStroke=!0;else if(b==="stroke-width"&&a===0&&this.hasStroke)c.removeAttribute("stroke"),this.hasStroke=!1};var xa=function(){this.init.apply(this,arguments)};xa.prototype={Element:Z,init:function(a,b,c,d,e,f){var g,d=this.createElement("svg").attr({version:"1.1"}).css(this.getStyle(d));g=d.element;a.appendChild(g);a.innerHTML.indexOf("xmlns")===-1&&X(g,"xmlns",Qa);this.isSVG=!0;this.box=g;this.boxWrapper=d;this.alignedObjects=[];this.url=(Wa||sb)&&H.getElementsByTagName("base").length? -L.location.href.replace(/#.*?$/,"").replace(/([\('\)])/g,"\\$1").replace(/ /g,"%20"):"";this.createElement("desc").add().element.appendChild(H.createTextNode("Created with Highstock 4.2.5"));this.defs=this.createElement("defs").add();this.allowHTML=f;this.forExport=e;this.gradients={};this.cache={};this.cacheKeys=[];this.imgCount=0;this.setSize(b,c,!1);var h;if(Wa&&a.getBoundingClientRect)this.subPixelFix=b=function(){N(a,{left:0,top:0});h=a.getBoundingClientRect();N(a,{left:Ea(h.left)-h.left+"px", -top:Ea(h.top)-h.top+"px"})},b(),E(L,"resize",b)},getStyle:function(a){return this.style=A({fontFamily:'"Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif',fontSize:"12px"},a)},isHidden:function(){return!this.boxWrapper.getBBox().width},destroy:function(){var a=this.defs;this.box=null;this.boxWrapper=this.boxWrapper.destroy();Pa(this.gradients||{});this.gradients=null;if(a)this.defs=a.destroy();this.subPixelFix&&T(L,"resize",this.subPixelFix);return this.alignedObjects=null},createElement:function(a){var b= -new this.Element;b.init(this,a);return b},draw:function(){},getRadialAttr:function(a,b){return{cx:a[0]-a[2]/2+b.cx*a[2],cy:a[1]-a[2]/2+b.cy*a[2],r:b.r*a[2]}},buildText:function(a){for(var b=a.element,c=this,d=c.forExport,e=q(a.textStr,"").toString(),f=e.indexOf("<")!==-1,g=b.childNodes,h,i,j,k=X(b,"x"),l=a.styles,m=a.textWidth,n=l&&l.lineHeight,p=l&&l.textShadow,r=l&&l.textOverflow==="ellipsis",s=g.length,F=m&&!a.added&&this.box,u=function(a){return n?K(n):c.fontMetrics(/(px|em)$/.test(a&&a.style.fontSize)? -a.style.fontSize:l&&l.fontSize||c.style.fontSize||12,a).h},x=function(a){return a.replace(/</g,"<").replace(/>/g,">")};s--;)b.removeChild(g[s]);!f&&!p&&!r&&e.indexOf(" ")===-1?b.appendChild(H.createTextNode(x(e))):(h=/<.*style="([^"]+)".*>/,i=/<.*href="(http[^"]+)".*>/,F&&F.appendChild(b),e=f?e.replace(/<(b|strong)>/g,'').replace(/<(i|em)>/g,'').replace(/
/g,"").split(//g): -[e],e=Fa(e,function(a){return a!==""}),o(e,function(e,f){var g,n=0,e=e.replace(/^\s+|\s+$/g,"").replace(//g,"|||");g=e.split("|||");o(g,function(e){if(e!==""||g.length===1){var p={},s=H.createElementNS(Qa,"tspan"),F;h.test(e)&&(F=e.match(h)[1].replace(/(;| |^)color([ :])/,"$1fill$2"),X(s,"style",F));i.test(e)&&!d&&(X(s,"onclick",'location.href="'+e.match(i)[1]+'"'),N(s,{cursor:"pointer"}));e=x(e.replace(/<(.|\n)*?>/g,"")||" ");if(e!==" "){s.appendChild(H.createTextNode(e)); -if(n)p.dx=0;else if(f&&k!==null)p.x=k;X(s,p);b.appendChild(s);!n&&f&&(!ja&&d&&N(s,{display:"block"}),X(s,"dy",u(s)));if(m){for(var p=e.replace(/([^\^])-/g,"$1- ").split(" "),q=g.length>1||f||p.length>1&&l.whiteSpace!=="nowrap",o,D,v=[],w=u(s),t=1,y=a.rotation,A=e,B=A.length;(q||r)&&(p.length||v.length);)a.rotation=0,o=a.getBBox(!0),D=o.width,!ja&&c.forExport&&(D=c.measureSpanWidth(s.firstChild.data,a.styles)),o=D>m,j===void 0&&(j=o),r&&j?(B/=2,A===""||!o&&B<0.5?p=[]:(A=e.substring(0,A.length+(o?-1: -1)*Ea(B)),p=[A+(m>3?"\u2026":"")],s.removeChild(s.firstChild))):!o||p.length===1?(p=v,v=[],p.length&&(t++,s=H.createElementNS(Qa,"tspan"),X(s,{dy:w,x:k}),F&&X(s,"style",F),b.appendChild(s)),D>m&&(m=D)):(s.removeChild(s.firstChild),v.unshift(p.pop())),p.length&&s.appendChild(H.createTextNode(p.join(" ").replace(/- /g,"-")));a.rotation=y}n++}}})}),j&&a.attr("title",a.textStr),F&&F.removeChild(b),p&&a.applyTextShadow&&a.applyTextShadow(p))},getContrast:function(a){a=va(a).rgba;return a[0]+a[1]+a[2]> -384?"#000000":"#FFFFFF"},button:function(a,b,c,d,e,f,g,h,i){var j=this.label(a,b,c,i,null,null,null,null,"button"),k=0,l,m,n,p,r,s,a={x1:0,y1:0,x2:0,y2:1},e=z({"stroke-width":1,stroke:"#CCCCCC",fill:{linearGradient:a,stops:[[0,"#FEFEFE"],[1,"#F6F6F6"]]},r:2,padding:5,style:{color:"black"}},e);n=e.style;delete e.style;f=z(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#FFF"],[1,"#ACF"]]}},f);p=f.style;delete f.style;g=z(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#9BD"],[1,"#CDF"]]}},g); -r=g.style;delete g.style;h=z(e,{style:{color:"#CCC"}},h);s=h.style;delete h.style;E(j.element,Ka?"mouseover":"mouseenter",function(){k!==3&&j.attr(f).css(p)});E(j.element,Ka?"mouseout":"mouseleave",function(){k!==3&&(l=[e,f,g][k],m=[n,p,r][k],j.attr(l).css(m))});j.setState=function(a){(j.state=k=a)?a===2?j.attr(g).css(r):a===3&&j.attr(h).css(s):j.attr(e).css(n)};return j.on("click",function(a){k!==3&&d.call(j,a)}).attr(e).css(A({cursor:"default"},n))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]= -y(a[1])-b%2/2);a[2]===a[5]&&(a[2]=a[5]=y(a[2])+b%2/2);return a},path:function(a){var b={fill:"none"};Ja(a)?b.d=a:ea(a)&&A(b,a);return this.createElement("path").attr(b)},circle:function(a,b,c){a=ea(a)?a:{x:a,y:b,r:c};b=this.createElement("circle");b.xSetter=b.ySetter=function(a,b,c){c.setAttribute("c"+b,a)};return b.attr(a)},arc:function(a,b,c,d,e,f){if(ea(a))b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x;a=this.symbol("arc",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||0});a.r=c;return a},rect:function(a, -b,c,d,e,f){var e=ea(a)?a.r:e,g=this.createElement("rect"),a=ea(a)?a:a===t?{}:{x:a,y:b,width:w(c,0),height:w(d,0)};if(f!==t)g.strokeWidth=f,a=g.crisp(a);if(e)a.r=e;g.rSetter=function(a,b,c){X(c,{rx:a,ry:a})};return g.attr(a)},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length;this.width=a;this.height=b;for(this.boxWrapper[q(c,!0)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return v(a)?b.attr({"class":"highcharts-"+a}):b},image:function(a, -b,c,d,e){var f={preserveAspectRatio:"none"};arguments.length>1&&A(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f);f.element.setAttributeNS?f.element.setAttributeNS("http://www.w3.org/1999/xlink","href",a):f.element.setAttribute("hc-svg-href",a);return f},symbol:function(a,b,c,d,e,f){var g=this,h,i=this.symbols[a],i=i&&i(y(b),y(c),d,e,f),j=/^url\((.*?)\)$/,k,l;if(i)h=this.path(i),A(h,{symbolName:a,x:b,y:c,width:d,height:e}),f&&A(h,f);else if(j.test(a))l=function(a,b){a.element&& -(a.attr({width:b[0],height:b[1]}),a.alignByTranslate||a.translate(y((d-b[0])/2),y((e-b[1])/2)))},k=a.match(j)[1],a=Vb[k]||f&&f.width&&f.height&&[f.width,f.height],h=this.image(k).attr({x:b,y:c}),h.isImg=!0,a?l(h,a):(h.attr({width:0,height:0}),fa("img",{onload:function(){this.width===0&&(N(this,{position:"absolute",top:"-999em"}),H.body.appendChild(this));l(h,Vb[k]=[this.width,this.height]);this.parentNode&&this.parentNode.removeChild(this);g.imgCount--;if(!g.imgCount&&$[g.chartIndex].onload)$[g.chartIndex].onload()}, -src:k}),this.imgCount++);return h},symbols:{circle:function(a,b,c,d){var e=0.166*c;return["M",a+c/2,b,"C",a+c+e,b,a+c+e,b+d,a+c/2,b+d,"C",a-e,b+d,a-e,b,a+c/2,b,"Z"]},square:function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c,b+d,a,b+d,"Z"]},triangle:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d,a,b+d,"Z"]},"triangle-down":function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c/2,b+d,"Z"]},diamond:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d/2,a+c/2,b+d,a,b+d/2,"Z"]},arc:function(a,b,c,d,e){var f=e.start, -c=e.r||c||d,g=e.end-0.001,d=e.innerR,h=e.open,i=ca(f),j=la(f),k=ca(g),g=la(g),e=e.end-fc&&e>b+g&&eb+g&&ed&&h>a+g&&ha+g&&hk&&/[ \-]/.test(b.textContent||b.innerText))N(b,{width:k+"px",display:"block",whiteSpace:l||"normal"}),this.hasTextWidth=!0;else if(this.hasTextWidth)N(b,{width:"",display:"",whiteSpace:l|| -"nowrap"}),this.hasTextWidth=!1;this.getSpanCorrection(this.hasTextWidth?k:b.offsetWidth,j,h,i,g)}N(b,{left:e+(this.xCorr||0)+"px",top:f+(this.yCorr||0)+"px"});if(sb)j=b.offsetHeight;this.cTT=m}}else this.alignOnAdd=!0},setSpanRotation:function(a,b,c){var d={},e=Ka?"-ms-transform":sb?"-webkit-transform":Wa?"MozTransform":Ub?"-o-transform":"";d[e]=d.transform="rotate("+a+"deg)";d[e+(Wa?"Origin":"-origin")]=d.transformOrigin=b*100+"% "+c+"px";N(this.element,d)},getSpanCorrection:function(a,b,c){this.xCorr= --a*c;this.yCorr=-b}});A(xa.prototype,{html:function(a,b,c){var d=this.createElement("span"),e=d.element,f=d.renderer,g=f.isSVG,h=function(a,b){o(["opacity","visibility"],function(c){U(a,c+"Setter",function(a,c,d,e){a.call(this,c,d,e);b[d]=c})})};d.textSetter=function(a){a!==e.innerHTML&&delete this.bBox;e.innerHTML=this.textStr=a;d.htmlUpdateTransform()};g&&h(d,d.element.style);d.xSetter=d.ySetter=d.alignSetter=d.rotationSetter=function(a,b){b==="align"&&(b="textAlign");d[b]=a;d.htmlUpdateTransform()}; -d.attr({text:a,x:y(b),y:y(c)}).css({position:"absolute",fontFamily:this.style.fontFamily,fontSize:this.style.fontSize});e.style.whiteSpace="nowrap";d.css=d.htmlCss;if(g)d.add=function(a){var b,c=f.box.parentNode,g=[];if(this.parentGroup=a){if(b=a.div,!b){for(;a;)g.push(a),a=a.parentGroup;o(g.reverse(),function(a){var d,e=X(a.element,"class");e&&(e={className:e});b=a.div=a.div||fa(Va,e,{position:"absolute",left:(a.translateX||0)+"px",top:(a.translateY||0)+"px",opacity:a.opacity},b||c);d=b.style;A(a, -{translateXSetter:function(b,c){d.left=b+"px";a[c]=b;a.doTransform=!0},translateYSetter:function(b,c){d.top=b+"px";a[c]=b;a.doTransform=!0}});h(a,d)})}}else b=c;b.appendChild(e);d.added=!0;d.alignOnAdd&&d.htmlUpdateTransform();return d};return d}});var lb,aa;if(!ja&&!qa)aa={init:function(a,b){var c=["<",b,' filled="f" stroked="f"'],d=["position: ","absolute",";"],e=b===Va;(b==="shape"||e)&&d.push("left:0;top:0;width:1px;height:1px;");d.push("visibility: ",e?"hidden":"visible");c.push(' style="',d.join(""), -'"/>');if(b)c=e||b==="span"||b==="img"?c.join(""):a.prepVML(c),this.element=fa(c);this.renderer=a},add:function(a){var b=this.renderer,c=this.element,d=b.box,e=a&&a.inverted,d=a?a.element||a:d;if(a)this.parentGroup=a;e&&b.invertChild(c,d);d.appendChild(c);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform();if(this.onAdd)this.onAdd();return this},updateTransform:Z.prototype.htmlUpdateTransform,setSpanRotation:function(){var a=this.rotation,b=ca(a*pa),c=la(a*pa);N(this.element, -{filter:a?["progid:DXImageTransform.Microsoft.Matrix(M11=",b,", M12=",-c,", M21=",c,", M22=",b,", sizingMethod='auto expand')"].join(""):"none"})},getSpanCorrection:function(a,b,c,d,e){var f=d?ca(d*pa):1,g=d?la(d*pa):0,h=q(this.elemHeight,this.element.offsetHeight),i;this.xCorr=f<0&&-a;this.yCorr=g<0&&-h;i=f*g<0;this.xCorr+=g*b*(i?1-c:c);this.yCorr-=f*b*(d?i?c:1-c:1);e&&e!=="left"&&(this.xCorr-=a*c*(f<0?-1:1),d&&(this.yCorr-=h*c*(g<0?-1:1)),N(this.element,{textAlign:e}))},pathToVML:function(a){for(var b= -a.length,c=[];b--;)if(C(a[b]))c[b]=y(a[b]*10)-5;else if(a[b]==="Z")c[b]="x";else if(c[b]=a[b],a.isArc&&(a[b]==="wa"||a[b]==="at"))c[b+5]===c[b+7]&&(c[b+7]+=a[b+7]>a[b+5]?1:-1),c[b+6]===c[b+8]&&(c[b+8]+=a[b+8]>a[b+6]?1:-1);return c.join(" ")||"x"},clip:function(a){var b=this,c;a?(c=a.members,za(c,b),c.push(b),b.destroyClip=function(){za(c,b)},a=a.getCSS(b)):(b.destroyClip&&b.destroyClip(),a={clip:rb?"inherit":"rect(auto)"});return b.css(a)},css:Z.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&& -Ua(a)},destroy:function(){this.destroyClip&&this.destroyClip();return Z.prototype.destroy.apply(this)},on:function(a,b){this.element["on"+a]=function(){var a=L.event;a.target=a.srcElement;b(a)};return this},cutOffPath:function(a,b){var c,a=a.split(/[ ,]/);c=a.length;if(c===9||c===11)a[c-4]=a[c-2]=K(a[c-2])-10*b;return a.join(" ")},shadow:function(a,b,c){var d=[],e,f=this.element,g=this.renderer,h,i=f.style,j,k=f.path,l,m,n,p;k&&typeof k.value!=="string"&&(k="x");m=k;if(a){n=q(a.width,3);p=(a.opacity|| -0.15)/n;for(e=1;e<=3;e++){l=n*2+1-2*e;c&&(m=this.cutOffPath(k.value,l+0.5));j=[''];h=fa(g.prepVML(j),null,{left:K(i.left)+q(a.offsetX,1),top:K(i.top)+q(a.offsetY,1)});if(c)h.cutOff=l+1;j=[''];fa(g.prepVML(j),null,null,h);b?b.element.appendChild(h):f.parentNode.insertBefore(h,f);d.push(h)}this.shadows=d}return this},updateShadows:ra, -setAttr:function(a,b){rb?this.element[a]=b:this.element.setAttribute(a,b)},classSetter:function(a){this.element.className=a},dashstyleSetter:function(a,b,c){(c.getElementsByTagName("stroke")[0]||fa(this.renderer.prepVML([""]),null,null,c))[b]=a||"solid";this[b]=a},dSetter:function(a,b,c){var d=this.shadows,a=a||[];this.d=a.join&&a.join(" ");c.path=a=this.pathToVML(a);if(d)for(c=d.length;c--;)d[c].path=d[c].cutOff?this.cutOffPath(a,d[c].cutOff):a;this.setAttr(b,a)},fillSetter:function(a,b, -c){var d=c.nodeName;if(d==="SPAN")c.style.color=a;else if(d!=="IMG")c.filled=a!=="none",this.setAttr("fillcolor",this.renderer.color(a,c,b,this))},"fill-opacitySetter":function(a,b,c){fa(this.renderer.prepVML(["<",b.split("-")[0],' opacity="',a,'"/>']),null,null,c)},opacitySetter:ra,rotationSetter:function(a,b,c){c=c.style;this[b]=c[b]=a;c.left=-y(la(a*pa)+1)+"px";c.top=y(ca(a*pa))+"px"},strokeSetter:function(a,b,c){this.setAttr("strokecolor",this.renderer.color(a,c,b,this))},"stroke-widthSetter":function(a, -b,c){c.stroked=!!a;this[b]=a;C(a)&&(a+="px");this.setAttr("strokeweight",a)},titleSetter:function(a,b){this.setAttr(b,a)},visibilitySetter:function(a,b,c){a==="inherit"&&(a="visible");this.shadows&&o(this.shadows,function(c){c.style[b]=a});c.nodeName==="DIV"&&(a=a==="hidden"?"-999em":0,rb||(c.style[b]=a?"visible":"hidden"),b="top");c.style[b]=a},xSetter:function(a,b,c){this[b]=a;b==="x"?b="left":b==="y"&&(b="top");this.updateClipping?(this[b]=a,this.updateClipping()):c.style[b]=a},zIndexSetter:function(a, -b,c){c.style[b]=a}},aa["stroke-opacitySetter"]=aa["fill-opacitySetter"],B.VMLElement=aa=ma(Z,aa),aa.prototype.ySetter=aa.prototype.widthSetter=aa.prototype.heightSetter=aa.prototype.xSetter,aa={Element:aa,isIE8:Na.indexOf("MSIE 8.0")>-1,init:function(a,b,c,d){var e;this.alignedObjects=[];d=this.createElement(Va).css(A(this.getStyle(d),{position:"relative"}));e=d.element;a.appendChild(d.element);this.isVML=!0;this.box=e;this.boxWrapper=d;this.gradients={};this.cache={};this.cacheKeys=[];this.imgCount= -0;this.setSize(b,c,!1);if(!H.namespaces.hcv){H.namespaces.add("hcv","urn:schemas-microsoft-com:vml");try{H.createStyleSheet().cssText="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}catch(f){H.styleSheets[0].cssText+="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}}},isHidden:function(){return!this.box.offsetWidth},clipRect:function(a,b,c,d){var e=this.createElement(),f=ea(a);return A(e, -{members:[],count:0,left:(f?a.x:a)+1,top:(f?a.y:b)+1,width:(f?a.width:c)-1,height:(f?a.height:d)-1,getCSS:function(a){var b=a.element,c=b.nodeName,a=a.inverted,d=this.top-(c==="shape"?b.offsetTop:0),e=this.left,b=e+this.width,f=d+this.height,d={clip:"rect("+y(a?e:d)+"px,"+y(a?f:b)+"px,"+y(a?b:f)+"px,"+y(a?d:e)+"px)"};!a&&rb&&c==="DIV"&&A(d,{width:b+"px",height:f+"px"});return d},updateClipping:function(){o(e.members,function(a){a.element&&a.css(e.getCSS(a))})}})},color:function(a,b,c,d){var e=this, -f,g=/^rgba/,h,i,j="none";a&&a.linearGradient?i="gradient":a&&a.radialGradient&&(i="pattern");if(i){var k,l,m=a.linearGradient||a.radialGradient,n,p,r,s,q,u="",a=a.stops,x,D=[],Q=function(){h=[''];fa(e.prepVML(h),null,null,b)};n=a[0];x=a[a.length-1];n[0]>0&&a.unshift([0,n[1]]);x[0]<1&&a.push([1,x[1]]);o(a,function(a,b){g.test(a[1])?(f=va(a[1]),k=f.get("rgb"),l=f.get("a")):(k=a[1],l=1);D.push(a[0]* -100+"% "+k);b?(r=l,s=k):(p=l,q=k)});if(c==="fill")if(i==="gradient")c=m.x1||m[0]||0,a=m.y1||m[1]||0,n=m.x2||m[2]||0,m=m.y2||m[3]||0,u='angle="'+(90-Y.atan((m-a)/(n-c))*180/Aa)+'"',Q();else{var j=m.r,ha=j*2,v=j*2,w=m.cx,t=m.cy,y=b.radialReference,A,j=function(){y&&(A=d.getBBox(),w+=(y[0]-A.x)/A.width-0.5,t+=(y[1]-A.y)/A.height-0.5,ha*=y[2]/A.width,v*=y[2]/A.height);u='src="'+R.global.VMLRadialGradientURL+'" size="'+ha+","+v+'" origin="0.5,0.5" position="'+w+","+t+'" color2="'+q+'" ';Q()};d.added?j(): -d.onAdd=j;j=s}else j=k}else if(g.test(a)&&b.tagName!=="IMG")f=va(a),d[c+"-opacitySetter"](f.get("a"),c,b),j=f.get("rgb");else{j=b.getElementsByTagName(c);if(j.length)j[0].opacity=1,j[0].type="solid";j=a}return j},prepVML:function(a){var b=this.isIE8,a=a.join("");b?(a=a.replace("/>",' xmlns="urn:schemas-microsoft-com:vml" />'),a=a.indexOf('style="')===-1?a.replace("/>",' style="display:inline-block;behavior:url(#default#VML);" />'):a.replace('style="','style="display:inline-block;behavior:url(#default#VML);')): -a=a.replace("<"," -1&&f.attr({x:b,y:c,width:d,height:e});return f},createElement:function(a){return a==="rect"?this.symbol(a):xa.prototype.createElement.call(this,a)},invertChild:function(a,b){var c=this,d=b.style,e=a.tagName==="IMG"&&a.style;N(a,{flip:"x",left:K(d.width)-(e?K(e.top):1),top:K(d.height)-(e?K(e.left):1),rotation:-90});o(a.childNodes,function(b){c.invertChild(b,a)})},symbols:{arc:function(a,b,c,d,e){var f=e.start,g=e.end,h=e.r||c||d,c=e.innerR,d=ca(f),i=la(f),j=ca(g),k=la(g);if(g-f===0)return["x"];f=["wa", -a-h,b-h,a+h,b+h,a+h*d,b+h*i,a+h*j,b+h*k];e.open&&!c&&f.push("e","M",a,b);f.push("at",a-c,b-c,a+c,b+c,a+c*j,b+c*k,a+c*d,b+c*i,"x","e");f.isArc=!0;return f},circle:function(a,b,c,d,e){e&&(c=d=2*e.r);e&&e.isCircle&&(a-=c/2,b-=d/2);return["wa",a,b,a+c,b+d,a+c,b+d/2,a+c,b+d/2,"e"]},rect:function(a,b,c,d,e){return xa.prototype.symbols[!v(e)||!e.r?"square":"callout"].call(0,a,b,c,d,e)}}},B.VMLRenderer=lb=function(){this.init.apply(this,arguments)},lb.prototype=z(xa.prototype,aa),Xa=lb;xa.prototype.measureSpanWidth= -function(a,b){var c=H.createElement("span"),d;d=H.createTextNode(a);c.appendChild(d);N(c,b);this.box.appendChild(c);d=c.offsetWidth;Ua(c);return d};var Wb;if(qa)B.CanVGRenderer=aa=function(){Qa="http://www.w3.org/1999/xhtml"},aa.prototype.symbols={},Wb=function(){function a(){var a=b.length,d;for(d=0;d0&&c+i*j>e&&(n=y((d-c)/ca(h*pa)));else if(d=c+(1-i)*j,c-i*je&&(l=e-a.x+l*i,m=-1),l=G(k,l),ll||b.autoRotation&&g.styles.width)n=l;if(n){p.width=n;if(!b.options.labels.style.textOverflow)p.textOverflow="ellipsis";g.css(p)}},getPosition:function(a,b,c,d){var e=this.axis,f=e.chart,g=d&&f.oldChartHeight||f.chartHeight;return{x:a?e.translate(b+c,null,null,d)+e.transB:e.left+e.offset+ -(e.opposite?(d&&f.oldChartWidth||f.chartWidth)-e.right-e.left:0),y:a?g-e.bottom+e.offset-(e.opposite?e.height:0):g-e.translate(b+c,null,null,d)-e.transB}},getLabelPosition:function(a,b,c,d,e,f,g,h){var i=this.axis,j=i.transA,k=i.reversed,l=i.staggerLines,m=i.tickRotCorr||{x:0,y:0},n=e.y;v(n)||(n=i.side===0?c.rotation?-8:-c.getBBox().height:i.side===2?m.y+8:ca(c.rotation*pa)*(m.y-c.getBBox(!1,0).height/2));a=a+e.x+m.x-(f&&d?f*j*(k?-1:1):0);b=b+n-(f&&!d?f*j*(k?1:-1):0);l&&(c=g/(h||1)%l,i.opposite&& -(c=l-c-1),b+=c*(i.labelOffset/l));return{x:a,y:y(b)}},getMarkPath:function(a,b,c,d,e,f){return f.crispLine(["M",a,b,"L",a+(e?0:-c),b+(e?c:0)],d)},render:function(a,b,c){var d=this.axis,e=d.options,f=d.chart.renderer,g=d.horiz,h=this.type,i=this.label,j=this.pos,k=e.labels,l=this.gridLine,m=h?h+"Grid":"grid",n=h?h+"Tick":"tick",p=e[m+"LineWidth"],r=e[m+"LineColor"],s=e[m+"LineDashStyle"],m=d.tickSize(n),n=e[n+"Color"],F=this.mark,u=k.step,o=!0,D=d.tickmarkOffset,Q=this.getPosition(g,j,D,b),ha=Q.x, -Q=Q.y,v=g&&ha===d.pos+d.len||!g&&Q===d.pos?-1:1,c=q(c,1);this.isActive=!0;if(p){j=d.getPlotLinePath(j+D,p*v,b,!0);if(l===t){l={stroke:r,"stroke-width":p};if(s)l.dashstyle=s;if(!h)l.zIndex=1;if(b)l.opacity=0;this.gridLine=l=p?f.path(j).attr(l).add(d.gridGroup):null}if(!b&&l&&j)l[this.isNew?"attr":"animate"]({d:j,opacity:c})}if(m)d.opposite&&(m[0]=-m[0]),h=this.getMarkPath(ha,Q,m[0],m[1]*v,g,f),F?F.animate({d:h,opacity:c}):this.mark=f.path(h).attr({stroke:n,"stroke-width":m[1],opacity:c}).add(d.axisGroup); -if(i&&C(ha))i.xy=Q=this.getLabelPosition(ha,Q,i,g,k,D,a,u),this.isFirst&&!this.isLast&&!q(e.showFirstLabel,1)||this.isLast&&!this.isFirst&&!q(e.showLastLabel,1)?o=!1:g&&!d.isRadial&&!k.step&&!k.rotation&&!b&&c!==0&&this.handleOverflow(Q),u&&a%u&&(o=!1),o&&C(Q.y)?(Q.opacity=c,i[this.isNew?"attr":"animate"](Q),this.isNew=!1):i.attr("y",-9999)},destroy:function(){Pa(this,this.axis)}};B.PlotLineOrBand=function(a,b){this.axis=a;if(b)this.options=b,this.id=b.id};B.PlotLineOrBand.prototype={render:function(){var a= -this,b=a.axis,c=b.horiz,d=a.options,e=d.label,f=a.label,g=d.width,h=d.to,i=d.from,j=v(i)&&v(h),k=d.value,l=d.dashStyle,m=a.svgElem,n=[],p,r=d.color,s=q(d.zIndex,0),F=d.events,u={},o=b.chart.renderer,n=b.log2lin;b.isLog&&(i=n(i),h=n(h),k=n(k));if(g){if(n=b.getPlotLinePath(k,g),u={stroke:r,"stroke-width":g},l)u.dashstyle=l}else if(j){n=b.getPlotBandPath(i,h,d);if(r)u.fill=r;if(d.borderWidth)u.stroke=d.borderColor,u["stroke-width"]=d.borderWidth}else return;u.zIndex=s;if(m)if(n)m.show(),m.animate({d:n}); -else{if(m.hide(),f)a.label=f=f.destroy()}else if(n&&n.length&&(a.svgElem=m=o.path(n).attr(u).add(),F))for(p in d=function(b){m.on(b,function(c){F[b].apply(a,[c])})},F)d(p);e&&v(e.text)&&n&&n.length&&b.width>0&&b.height>0&&!n.flat?(e=z({align:c&&j&&"center",x:c?!j&&4:10,verticalAlign:!c&&j&&"middle",y:c?j?16:10:j?6:-4,rotation:c&&!j&&90},e),this.renderLabel(e,n,j,s)):f&&f.hide();return a},renderLabel:function(a,b,c,d){var e=this.label,f=this.axis.chart.renderer;if(!e)e={align:a.textAlign||a.align, -rotation:a.rotation},e.zIndex=d,this.label=e=f.text(a.text,0,0,a.useHTML).attr(e).css(a.style).add();d=[b[1],b[4],c?b[6]:b[1]];b=[b[2],b[5],c?b[7]:b[2]];c=Ma(d);f=Ma(b);e.align(a,!1,{x:c,y:f,width:Da(d)-c,height:Da(b)-f});e.show()},destroy:function(){za(this.axis.plotLinesAndBands,this);delete this.axis;Pa(this)}};var J=B.Axis=function(){this.init.apply(this,arguments)};J.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:"%H:%M:%S.%L",second:"%H:%M:%S",minute:"%H:%M",hour:"%H:%M",day:"%e. %b", -week:"%e. %b",month:"%b '%y",year:"%Y"},endOnTick:!1,gridLineColor:"#D8D8D8",labels:{enabled:!0,style:{color:"#606060",cursor:"default",fontSize:"11px"},x:0},lineColor:"#C0D0E0",lineWidth:1,minPadding:0.01,maxPadding:0.01,minorGridLineColor:"#E0E0E0",minorGridLineWidth:1,minorTickColor:"#A0A0A0",minorTickLength:2,minorTickPosition:"outside",startOfWeek:1,startOnTick:!1,tickColor:"#C0D0E0",tickLength:10,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",title:{align:"middle", -style:{color:"#707070"}},type:"linear"},defaultYAxisOptions:{endOnTick:!0,gridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0,title:{rotation:270,text:"Values"},stackLabels:{enabled:!1,formatter:function(){return B.numberFormat(this.total,-1)},style:z(W.line.dataLabels.style,{color:"#000000"})}},defaultLeftAxisOptions:{labels:{x:-15},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:15},title:{rotation:90}},defaultBottomAxisOptions:{labels:{autoRotation:[-45], -x:0},title:{rotation:0}},defaultTopAxisOptions:{labels:{autoRotation:[-45],x:0},title:{rotation:0}},init:function(a,b){var c=b.isX;this.chart=a;this.horiz=a.inverted?!c:c;this.coll=(this.isXAxis=c)?"xAxis":"yAxis";this.opposite=b.opposite;this.side=b.side||(this.horiz?this.opposite?0:2:this.opposite?1:3);this.setOptions(b);var d=this.options,e=d.type;this.labelFormatter=d.labels.formatter||this.defaultLabelFormatter;this.userOptions=b;this.minPixelPadding=0;this.reversed=d.reversed;this.visible=d.visible!== -!1;this.zoomEnabled=d.zoomEnabled!==!1;this.categories=d.categories||e==="category";this.names=this.names||[];this.isLog=e==="logarithmic";this.isDatetimeAxis=e==="datetime";this.isLinked=v(d.linkedTo);this.ticks={};this.labelEdge=[];this.minorTicks={};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange=this.userMinRange=d.minRange||d.maxZoom;this.range=d.range;this.offset=d.offset||0;this.stacks={};this.oldStacks={};this.stacksTouched=0;this.min=this.max=null;this.crosshair= -q(d.crosshair,ua(a.options.tooltip.crosshairs)[c?0:1],!1);var f,d=this.options.events;sa(this,a.axes)===-1&&(c&&!this.isColorAxis?a.axes.splice(a.xAxis.length,0,this):a.axes.push(this),a[this.coll].push(this));this.series=this.series||[];if(a.inverted&&c&&this.reversed===t)this.reversed=!0;this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;for(f in d)E(this,f,d[f]);if(this.isLog)this.val2lin=this.log2lin,this.lin2val=this.lin2log},setOptions:function(a){this.options=z(this.defaultOptions, -this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],z(R[this.coll],a))},defaultLabelFormatter:function(){var a=this.axis,b=this.value,c=a.categories,d=this.dateTimeLabelFormat,e=R.lang.numericSymbols,f=e&&e.length,g,h=a.options.labels.format,a=a.isLog?b:a.tickInterval;if(h)g=La(h,this);else if(c)g=b;else if(d)g=na(d,b);else if(f&&a>=1E3)for(;f--&&g===t;)c=Math.pow(1E3,f+1),a>=c&&b*10% -c===0&&e[f]!==null&&(g=B.numberFormat(b/c,-1)+e[f]);g===t&&(g=S(b)>=1E4?B.numberFormat(b,-1):B.numberFormat(b,-1,t,""));return g},getSeriesExtremes:function(){var a=this,b=a.chart;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=a.threshold=null;a.softThreshold=!a.isXAxis;a.buildStacks&&a.buildStacks();o(a.series,function(c){if(c.visible||!b.options.chart.ignoreHiddenSeries){var d=c.options,e=d.threshold,f;a.hasVisibleSeries=!0;a.isLog&&e<=0&&(e=null);if(a.isXAxis){if(d=c.xData,d.length)c=Ma(d),!C(c)&&!(c instanceof -ba)&&(d=Fa(d,function(a){return C(a)}),c=Ma(d)),a.dataMin=G(q(a.dataMin,d[0]),c),a.dataMax=w(q(a.dataMax,d[0]),Da(d))}else{c.getExtremes();f=c.dataMax;c=c.dataMin;if(v(c)&&v(f))a.dataMin=G(q(a.dataMin,c),c),a.dataMax=w(q(a.dataMax,f),f);if(v(e))a.threshold=e;if(!d.softThreshold||a.isLog)a.softThreshold=!1}}})},translate:function(a,b,c,d,e,f){var g=this.linkedParent||this,h=1,i=0,j=d?g.oldTransA:g.transA,d=d?g.oldMin:g.min,k=g.minPixelPadding,e=(g.isOrdinal||g.isBroken||g.isLog&&e)&&g.lin2val;if(!j)j= -g.transA;if(c)h*=-1,i=g.len;g.reversed&&(h*=-1,i-=h*(g.sector||g.len));b?(a=a*h+i,a-=k,a=a/j+d,e&&(a=g.lin2val(a))):(e&&(a=g.val2lin(a)),f==="between"&&(f=0.5),a=h*(a-d)*j+i+h*k+(C(f)?j*f*g.pointRange:0));return a},toPixels:function(a,b){return this.translate(a,!1,!this.horiz,null,!0)+(b?0:this.pos)},toValue:function(a,b){return this.translate(a-(b?0:this.pos),!0,!this.horiz,null,!0)},getPlotLinePath:function(a,b,c,d,e){var f=this.chart,g=this.left,h=this.top,i,j,k=c&&f.oldChartHeight||f.chartHeight, -l=c&&f.oldChartWidth||f.chartWidth,m;i=this.transB;var n=function(a,b,c){if(ac)d?a=G(w(b,a),c):m=!0;return a},e=q(e,this.translate(a,null,null,c)),a=c=y(e+i);i=j=y(k-e-i);C(e)?this.horiz?(i=h,j=k-this.bottom,a=c=n(a,g,g+this.width)):(a=g,c=l-this.right,i=j=n(i,h,h+this.height)):m=!0;return m&&!d?null:f.renderer.crispLine(["M",a,i,"L",c,j],b||1)},getLinearTickPositions:function(a,b,c){var d,e=ka(V(b/a)*a),f=ka(Ea(c/a)*a),g=[];if(b===c&&C(b))return[b];for(b=e;b<=f;){g.push(b);b=ka(b+a);if(b=== -d)break;d=b}return g},getMinorTickPositions:function(){var a=this.options,b=this.tickPositions,c=this.minorTickInterval,d=[],e,f=this.pointRangePadding||0;e=this.min-f;var f=this.max+f,g=f-e;if(g&&g/c=this.minRange,f,g,h,i,j,k;if(this.isXAxis&&this.minRange===t&&!this.isLog)v(a.min)||v(a.max)?this.minRange=null:(o(this.series,function(a){i=a.xData;for(g=j=a.xIncrement?1:i.length-1;g>0;g--)if(h=i[g]-i[g-1],f===t||h=p?(s=p,k=0):b.dataMax<= -p&&(F=p,j=0)),b.min=q(u,s,b.dataMin),b.max=q(x,F,b.dataMax));if(e)!a&&G(b.min,q(b.dataMin,b.min))<=0&&ga(10,1),b.min=ka(f(b.min),15),b.max=ka(f(b.max),15);if(b.range&&v(b.max))b.userMin=b.min=u=w(b.min,b.minFromRange()),b.userMax=x=b.max,b.range=null;O(b,"foundExtremes");b.beforePadding&&b.beforePadding();b.adjustForMinRange();if(!n&&!b.axisPointRange&&!b.usePercentage&&!i&&v(b.min)&&v(b.max)&&(f=b.max-b.min))!v(u)&&k&&(b.min-=f*k),!v(x)&&j&&(b.max+=f*j);if(C(d.floor))b.min=w(b.min,d.floor);if(C(d.ceiling))b.max= -G(b.max,d.ceiling);if(r&&v(b.dataMin))if(p=p||0,!v(u)&&b.min=p)b.min=p;else if(!v(x)&&b.max>p&&b.dataMax<=p)b.max=p;b.tickInterval=b.min===b.max||b.min===void 0||b.max===void 0?1:i&&!l&&m===b.linkedParent.options.tickPixelInterval?l=b.linkedParent.tickInterval:q(l,this.tickAmount?(b.max-b.min)/w(this.tickAmount-1,1):void 0,n?1:(b.max-b.min)*m/w(b.len,m));h&&!a&&o(b.series,function(a){a.processData(b.min!==b.oldMin||b.max!==b.oldMax)});b.setAxisTranslation(!0);b.beforeSetTickPositions&& -b.beforeSetTickPositions();if(b.postProcessTickInterval)b.tickInterval=b.postProcessTickInterval(b.tickInterval);if(b.pointRange&&!l)b.tickInterval=w(b.pointRange,b.tickInterval);a=q(d.minTickInterval,b.isDatetimeAxis&&b.closestPointRange);if(!l&&b.tickInterval0.5&&b.tickInterval<5&&b.max>1E3&&b.max<9999)),!!this.tickAmount);if(!this.tickAmount&&this.len)b.tickInterval=b.unsquish(); -this.setTickPositions()},setTickPositions:function(){var a=this.options,b,c=a.tickPositions,d=a.tickPositioner,e=a.startOnTick,f=a.endOnTick,g;this.tickmarkOffset=this.categories&&a.tickmarkPlacement==="between"&&this.tickInterval===1?0.5:0;this.minorTickInterval=a.minorTickInterval==="auto"&&this.tickInterval?this.tickInterval/5:a.minorTickInterval;this.tickPositions=b=c&&c.slice();if(!b&&(b=this.isDatetimeAxis?this.getTimeTicks(this.normalizeTimeTickInterval(this.tickInterval,a.units),this.min, -this.max,a.startOfWeek,this.ordinalPositions,this.closestPointRange,!0):this.isLog?this.getLogTickPositions(this.tickInterval,this.min,this.max):this.getLinearTickPositions(this.tickInterval,this.min,this.max),b.length>this.len&&(b=[b[0],b.pop()]),this.tickPositions=b,d&&(d=d.apply(this,[this.min,this.max]))))this.tickPositions=b=d;if(!this.isLinked)this.trimTicks(b,e,f),this.min===this.max&&v(this.min)&&!this.tickAmount&&(g=!0,this.min-=0.5,this.max+=0.5),this.single=g,!c&&!d&&this.adjustTickAmount()}, -trimTicks:function(a,b,c){var d=a[0],e=a[a.length-1],f=this.minPointOffset||0;if(b)this.min=d;else for(;this.min-f>a[0];)a.shift();if(c)this.max=e;else for(;this.max+fc&&(this.tickInterval*=2,this.setTickPositions()); -if(v(d)){for(a=c=b.length;a--;)(d===3&&a%2===1||d<=2&&a>0&&a=e&&(b=e));this.displayBtn=a!==t||b!==t;this.setExtremes(a,b,!1,t,{trigger:"zoom"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,d=this.horiz,e=q(b.width,a.plotWidth-c+(b.offsetRight||0)),f=q(b.height,a.plotHeight),g=q(b.top,a.plotTop),b=q(b.left,a.plotLeft+c),c=/%$/;c.test(f)&&(f=Math.round(parseFloat(f)/100*a.plotHeight));c.test(g)&&(g=Math.round(parseFloat(g)/100*a.plotHeight+ -a.plotTop));this.left=b;this.top=g;this.width=e;this.height=f;this.bottom=a.chartHeight-f-g;this.right=a.chartWidth-e-b;this.len=w(d?e:f,0);this.pos=d?b:g},getExtremes:function(){var a=this.isLog,b=this.lin2log;return{min:a?ka(b(this.min)):this.min,max:a?ka(b(this.max)):this.max,dataMin:this.dataMin,dataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}},getThreshold:function(a){var b=this.isLog,c=this.lin2log,d=b?c(this.min):this.min,b=b?c(this.max):this.max;a===null?a=b<0?b:d:d>a?a=d:b< -a&&(a=b);return this.translate(a,0,1,0,1)},autoLabelAlign:function(a){a=(q(a,0)-this.side*90+720)%360;return a>15&&a<165?"right":a>195&&a<345?"left":"center"},tickSize:function(a){var b=this.options,c=b[a+"Length"],d=q(b[a+"Width"],a==="tick"&&this.isXAxis?1:0);if(d&&c)return b[a+"Position"]==="inside"&&(c=-c),[c,d]},labelMetrics:function(){return this.chart.renderer.fontMetrics(this.options.labels.style.fontSize,this.ticks[0]&&this.ticks[0].label)},unsquish:function(){var a=this.options.labels,b= -this.horiz,c=this.tickInterval,d=c,e=this.len/(((this.categories?1:0)+this.max-this.min)/c),f,g=a.rotation,h=this.labelMetrics(),i,j=Number.MAX_VALUE,k,l=function(a){a/=e||1;a=a>1?Ea(a):1;return a*c};b?(k=!a.staggerLines&&!a.step&&(v(g)?[g]:e=-90&&a<=90)i=l(S(h.h/la(pa*a))),b=i+S(a/360),bm)m=a.labelLength}),m>h&&m>j.h?i.rotation=this.labelRotation:this.labelRotation=0;else if(g&&(l={width:h+"px"},!k)){l.textOverflow="clip";for(n=c.length;!f&&n--;)if(p=c[n],h=d[p].label)if(h.styles.textOverflow==="ellipsis"?h.css({textOverflow:"clip"}):d[p].labelLength>g&&h.css({width:g+"px"}),h.getBBox().height>this.len/c.length-(j.h-j.f))h.specCss={textOverflow:"ellipsis"}}if(i.rotation&&(l={width:(m>a.chartHeight*0.5?a.chartHeight* -0.33:a.chartHeight)+"px"},!k))l.textOverflow="ellipsis";if(this.labelAlign=e.align||this.autoLabelAlign(this.labelRotation))i.align=this.labelAlign;o(c,function(a){var b=(a=d[a])&&a.label;if(b)b.attr(i),l&&b.css(z(l,b.specCss)),delete b.specCss,a.rotation=i.rotation});this.tickRotCorr=b.rotCorr(j.b,this.labelRotation||0,this.side!==0)},hasData:function(){return this.hasVisibleSeries||v(this.min)&&v(this.max)&&!!this.tickPositions},getOffset:function(){var a=this,b=a.chart,c=b.renderer,d=a.options, -e=a.tickPositions,f=a.ticks,g=a.horiz,h=a.side,i=b.inverted?[1,0,3,2][h]:h,j,k,l=0,m,n=0,p=d.title,r=d.labels,s=0,F=a.opposite,u=b.axisOffset,b=b.clipOffset,x=[-1,1,1,-1][h],D,Q=a.axisParent,ha=this.tickSize("tick");j=a.hasData();a.showAxis=k=j||q(d.showEmpty,!0);a.staggerLines=a.horiz&&r.staggerLines;if(!a.axisGroup)a.gridGroup=c.g("grid").attr({zIndex:d.gridZIndex||1}).add(Q),a.axisGroup=c.g("axis").attr({zIndex:d.zIndex||2}).add(Q),a.labelGroup=c.g("axis-labels").attr({zIndex:r.zIndex||7}).addClass("highcharts-"+ -a.coll.toLowerCase()+"-labels").add(Q);if(j||a.isLinked){if(o(e,function(b){f[b]?f[b].addLabel():f[b]=new bb(a,b)}),a.renderUnsquish(),r.reserveSpace!==!1&&(h===0||h===2||{1:"left",3:"right"}[h]===a.labelAlign||a.labelAlign==="center")&&o(e,function(a){s=w(f[a].getLabelSize(),s)}),a.staggerLines)s*=a.staggerLines,a.labelOffset=s*(a.opposite?-1:1)}else for(D in f)f[D].destroy(),delete f[D];if(p&&p.text&&p.enabled!==!1){if(!a.axisTitle)(D=p.textAlign)||(D=(g?{low:"left",middle:"center",high:"right"}: -{low:F?"right":"left",middle:"center",high:F?"left":"right"})[p.align]),a.axisTitle=c.text(p.text,0,0,p.useHTML).attr({zIndex:7,rotation:p.rotation||0,align:D}).addClass("highcharts-"+this.coll.toLowerCase()+"-title").css(p.style).add(a.axisGroup),a.axisTitle.isNew=!0;if(k)l=a.axisTitle.getBBox()[g?"height":"width"],m=p.offset,n=v(m)?0:q(p.margin,g?5:10);a.axisTitle[k?"show":"hide"](!0)}a.offset=x*q(d.offset,u[h]);a.tickRotCorr=a.tickRotCorr||{x:0,y:0};c=h===0?-a.labelMetrics().h:h===2?a.tickRotCorr.y: -0;n=Math.abs(s)+n;s&&(n-=c,n+=x*(g?q(r.y,a.tickRotCorr.y+x*8):r.x));a.axisTitleMargin=q(m,n);u[h]=w(u[h],a.axisTitleMargin+l+x*a.offset,n,j&&e.length&&ha?ha[0]:0);d=d.offset?0:V(d.lineWidth/2)*2;b[i]=w(b[i],d)},getLinePath:function(a){var b=this.chart,c=this.opposite,d=this.offset,e=this.horiz,f=this.left+(c?this.width:0)+d,d=b.chartHeight-this.bottom-(c?this.height:0)+d;c&&(a*=-1);return b.renderer.crispLine(["M",e?this.left:f,e?d:this.top,"L",e?b.chartWidth-this.right:f,e?d:b.chartHeight-this.bottom], -a)},getTitlePosition:function(){var a=this.horiz,b=this.left,c=this.top,d=this.len,e=this.options.title,f=a?b:c,g=this.opposite,h=this.offset,i=e.x||0,j=e.y||0,k=K(e.style.fontSize||12),d={low:f+(a?0:d),middle:f+d/2,high:f+(a?d:0)}[e.align],b=(a?c+this.height:b)+(a?1:-1)*(g?-1:1)*this.axisTitleMargin+(this.side===2?k:0);return{x:a?d+i:b+(g?this.width:0)+h+i,y:a?b+j-(g?this.height:0)+h:d+j}},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.options,e=a.isLog,f=a.lin2log,g=a.isLinked,h=a.tickPositions, -i=a.axisTitle,j=a.ticks,k=a.minorTicks,l=a.alternateBands,m=d.stackLabels,n=d.alternateGridColor,p=a.tickmarkOffset,r=d.lineWidth,s,q=b.hasRendered&&C(a.oldMin),u=a.showAxis,x=gb(c.globalAnimation),D,Q;a.labelEdge.length=0;a.overlap=!1;o([j,k,l],function(a){for(var b in a)a[b].isActive=!1});if(a.hasData()||g){a.minorTickInterval&&!a.categories&&o(a.getMinorTickPositions(),function(b){k[b]||(k[b]=new bb(a,b,"minor"));q&&k[b].isNew&&k[b].render(null,!0);k[b].render(null,!1,1)});if(h.length&&(o(h,function(b, -c){if(!g||b>=a.min&&b<=a.max)j[b]||(j[b]=new bb(a,b)),q&&j[b].isNew&&j[b].render(c,!0,0.1),j[b].render(c)}),p&&(a.min===0||a.single)))j[-1]||(j[-1]=new bb(a,-1,null,!0)),j[-1].render(-1);n&&o(h,function(c,d){Q=h[d+1]!==t?h[d+1]+p:a.max-p;if(d%2===0&&c=M.second?0:k*V(i.getMilliseconds()/k));if(j>=M.second)i[Qb](j>=M.minute?0:k*V(i.getSeconds()/k));if(j>=M.minute)i[Rb](j>=M.hour?0:k*V(i[Bb]()/k));if(j>=M.hour)i[Sb](j>=M.day?0:k*V(i[Cb]()/k)); -if(j>=M.day)i[qb](j>=M.month?1:k*V(i[ab]()/k));j>=M.month&&(i[Eb](j>=M.year?0:k*V(i[hb]()/k)),h=i[ib]());j>=M.year&&(h-=h%k,i[Fb](h));if(j===M.week)i[qb](i[ab]()-i[Db]()+q(d,1));b=1;if(yb||fb)i=i.getTime(),i=new ba(i+eb(i));h=i[ib]();for(var d=i.getTime(),l=i[hb](),m=i[ab](),n=!g||!!fb,p=(M.day+(g?eb(i):i.getTimezoneOffset()*6E4))%M.day;d=0.5)a=y(a),i=this.getLinearTickPositions(a,b,c);else if(a>=0.08)for(var f=V(b),j,k,l,m,n,e=a>0.3?[1,2,4]:a>0.15?[1,2,4,6,8]:[1,2,3,4,5,6,7,8,9];fb&&(!d||m<=c)&&m!==t&&i.push(m),m>c&& -(n=!0),m=l}else if(b=g(b),c=g(c),a=e[d?"minorTickInterval":"tickInterval"],a=q(a==="auto"?null:a,this._minorAutoInterval,(c-b)*(e.tickPixelInterval/(d?5:1))/((d?f/this.tickPositions.length:f)||1)),a=Ab(a,null,zb(a)),i=ta(this.getLinearTickPositions(a,b,c),h),!d)this._minorAutoInterval=a/5;if(!d)this.tickInterval=a;return i};J.prototype.log2lin=function(a){return Y.log(a)/Y.LN10};J.prototype.lin2log=function(a){return Y.pow(10,a)};var Lb=B.Tooltip=function(){this.init.apply(this,arguments)};Lb.prototype= -{init:function(a,b){var c=b.borderWidth,d=b.style,e=K(d.padding);this.chart=a;this.options=b;this.crosshairs=[];this.now={x:0,y:0};this.isHidden=!0;this.label=a.renderer.label("",0,0,b.shape||"callout",null,null,b.useHTML,null,"tooltip").attr({padding:e,fill:b.backgroundColor,"stroke-width":c,r:b.borderRadius,zIndex:8}).css(d).css({padding:0}).add().attr({y:-9999});qa||this.label.shadow(b.shadow);this.shared=b.shared},destroy:function(){if(this.label)this.label=this.label.destroy();clearTimeout(this.hideTimer); -clearTimeout(this.tooltipTimeout)},move:function(a,b,c,d){var e=this,f=e.now,g=e.options.animation!==!1&&!e.isHidden&&(S(a-f.x)>1||S(b-f.y)>1),h=e.followPointer||e.len>1;A(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:h?t:g?(2*f.anchorX+c)/3:c,anchorY:h?t:g?(f.anchorY+d)/2:d});e.label.attr(f);if(g)clearTimeout(this.tooltipTimeout),this.tooltipTimeout=setTimeout(function(){e&&e.move(a,b,c,d)},32)},hide:function(a){var b=this;clearTimeout(this.hideTimer);a=q(a,this.options.hideDelay,500);if(!this.isHidden)this.hideTimer= -Za(function(){b.label[a?"fadeOut":"hide"]();b.isHidden=!0},a)},getAnchor:function(a,b){var c,d=this.chart,e=d.inverted,f=d.plotTop,g=d.plotLeft,h=0,i=0,j,k,a=ua(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===t&&(b=d.pointer.normalize(b)),c=[b.chartX-d.plotLeft,b.chartY-f]);c||(o(a,function(a){j=a.series.yAxis;k=a.series.xAxis;h+=a.plotX+(!e&&k?k.left-g:0);i+=(a.plotLow?(a.plotLow+a.plotHigh)/2:a.plotY)+(!e&&j?j.top-f:0)}),h/=a.length,i/=a.length,c=[e?d.plotWidth-i:h,this.shared&&!e&&a.length> -1&&b?b.chartY-f:e?d.plotHeight-h:i]);return ta(c,y)},getPosition:function(a,b,c){var d=this.chart,e=this.distance,f={},g=c.h||0,h,i=["y",d.chartHeight,b,c.plotY+d.plotTop,d.plotTop,d.plotTop+d.plotHeight],j=["x",d.chartWidth,a,c.plotX+d.plotLeft,d.plotLeft,d.plotLeft+d.plotWidth],k=!this.followPointer&&q(c.ttBelow,!d.inverted===!!c.negative),l=function(a,b,c,d,h,i){var j=cb?d: -d+g);else return!1},m=function(a,b,c,d){var g;db-e?g=!1:f[a]=db-c/2?b-c-2:d-c/2;return g},n=function(a){var b=i;i=j;j=b;h=a},p=function(){l.apply(0,i)!==!1?m.apply(0,j)===!1&&!h&&(n(!0),p()):h?f.x=f.y=0:(n(!0),p())};(d.inverted||this.len>1)&&n();p();return f},defaultFormatter:function(a){var b=this.points||ua(this),c;c=[a.tooltipFooterHeaderFormatter(b[0])];c=c.concat(a.bodyFormatter(b));c.push(a.tooltipFooterHeaderFormatter(b[0],!0));return c.join("")},refresh:function(a,b){var c= -this.chart,d=this.label,e=this.options,f,g,h,i={},j,k=[];j=e.formatter||this.defaultFormatter;var i=c.hoverPoints,l,m=this.shared;clearTimeout(this.hideTimer);this.followPointer=ua(a)[0].series.tooltipOptions.followPointer;h=this.getAnchor(a,b);f=h[0];g=h[1];m&&(!a.series||!a.series.noSharedTooltip)?(c.hoverPoints=a,i&&o(i,function(a){a.setState()}),o(a,function(a){a.setState("hover");k.push(a.getLabelConfig())}),i={x:a[0].category,y:a[0].y},i.points=k,this.len=k.length,a=a[0]):i=a.getLabelConfig(); -j=j.call(i,this);i=a.series;this.distance=q(i.tooltipOptions.distance,16);j===!1?this.hide():(this.isHidden&&(Sa(d),d.attr("opacity",1).show()),d.attr({text:j}),l=e.borderColor||a.color||i.color||"#606060",d.attr({stroke:l}),this.updatePosition({plotX:f,plotY:g,negative:a.negative,ttBelow:a.ttBelow,h:h[2]||0}),this.isHidden=!1);O(c,"tooltipRefresh",{text:j,x:f+c.plotLeft,y:g+c.plotTop,borderColor:l})},updatePosition:function(a){var b=this.chart,c=this.label,c=(this.options.positioner||this.getPosition).call(this, -c.width,c.height,a);this.move(y(c.x),y(c.y||0),a.plotX+b.plotLeft,a.plotY+b.plotTop)},getXDateFormat:function(a,b,c){var d,b=b.dateTimeLabelFormats,e=c&&c.closestPointRange,f,g={millisecond:15,second:12,minute:9,hour:6,day:3},h,i="millisecond";if(e){h=na("%m-%d %H:%M:%S.%L",a.x);for(f in M){if(e===M.week&&+na("%w",a.x)===c.options.startOfWeek&&h.substr(6)==="00:00:00.000"){f="week";break}if(M[f]>e){f=i;break}if(g[f]&&h.substr(g[f])!=="01-01 00:00:00.000".substr(g[f]))break;f!=="week"&&(i=f)}f&&(d= -b[f])}else d=b.day;return d||b.year},tooltipFooterHeaderFormatter:function(a,b){var c=b?"footer":"header",d=a.series,e=d.tooltipOptions,f=e.xDateFormat,g=d.xAxis,h=g&&g.options.type==="datetime"&&C(a.key),c=e[c+"Format"];h&&!f&&(f=this.getXDateFormat(a,e,g));h&&f&&(c=c.replace("{point.key}","{point.key:"+f+"}"));return La(c,{point:a,series:d})},bodyFormatter:function(a){return ta(a,function(a){var c=a.series.tooltipOptions;return(c.pointFormatter||a.point.tooltipFormatter).call(a.point,c.pointFormat)})}}; -var oa;cb=H&&H.documentElement.ontouchstart!==t;var Ya=B.Pointer=function(a,b){this.init(a,b)};Ya.prototype={init:function(a,b){var c=b.chart,d=c.events,e=qa?"":c.zoomType,c=a.inverted,f;this.options=b;this.chart=a;this.zoomX=f=/x/.test(e);this.zoomY=e=/y/.test(e);this.zoomHor=f&&!c||e&&c;this.zoomVert=e&&!c||f&&c;this.hasZoom=f||e;this.runChartClick=d&&!!d.click;this.pinchDown=[];this.lastValidTouch={};if(B.Tooltip&&b.tooltip.enabled)a.tooltip=new Lb(a,b.tooltip),this.followTouchMove=q(b.tooltip.followTouchMove, -!0);this.setDOMEvents()},normalize:function(a,b){var c,d,a=a||L.event;if(!a.target)a.target=a.srcElement;d=a.touches?a.touches.length?a.touches.item(0):a.changedTouches[0]:a;if(!b)this.chartPosition=b=Jb(this.chart.container);d.pageX===t?(c=w(a.x,a.clientX-b.left),d=a.y):(c=d.pageX-b.left,d=d.pageY-b.top);return A(a,{chartX:y(c),chartY:y(d)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};o(this.chart.axes,function(c){b[c.isXAxis?"xAxis":"yAxis"].push({axis:c,value:c.toValue(a[c.horiz?"chartX": -"chartY"])})});return b},runPointActions:function(a){var b=this.chart,c=b.series,d=b.tooltip,e=d?d.shared:!1,f=b.hoverPoint,g=b.hoverSeries,h,i=[Number.MAX_VALUE,Number.MAX_VALUE],j,k,l=[],m=[],n;if(!e&&!g)for(h=0;h=m[c].series.group.zIndex;if(a[b]h+j&&(d=h+j),ei+k&&(e=i+k),this.hasDragged=Math.sqrt(Math.pow(n-d,2)+Math.pow(p-e,2)),this.hasDragged>10){l=b.isInsidePlot(n-h,p-i);if(b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&l&&!r&&!m)this.selectionMarker=m=b.renderer.rect(h,i, -f?1:j,g?1:k,0).attr({fill:c.selectionMarkerFill||"rgba(69,114,167,0.25)",zIndex:7}).add();m&&f&&(d-=n,m.attr({width:S(d),x:(d>0?0:d)+n}));m&&g&&(d=e-p,m.attr({height:S(d),y:(d>0?0:d)+p}));l&&!m&&c.panning&&b.pan(a,c.panning)}},drop:function(a){var b=this,c=this.chart,d=this.hasPinched;if(this.selectionMarker){var e={originalEvent:a,xAxis:[],yAxis:[]},f=this.selectionMarker,g=f.attr?f.attr("x"):f.x,h=f.attr?f.attr("y"):f.y,i=f.attr?f.attr("width"):f.width,j=f.attr?f.attr("height"):f.height,k;if(this.hasDragged|| -d)o(c.axes,function(c){if(c.zoomEnabled&&v(c.min)&&(d||b[{xAxis:"zoomX",yAxis:"zoomY"}[c.coll]])){var f=c.horiz,n=a.type==="touchend"?c.minPixelPadding:0,p=c.toValue((f?g:h)+n),f=c.toValue((f?g+i:h+j)-n);e[c.coll].push({axis:c,min:G(p,f),max:w(p,f)});k=!0}}),k&&O(c,"selection",e,function(a){c.zoom(A(a,d?{animation:!1}:null))});this.selectionMarker=this.selectionMarker.destroy();d&&this.scaleGroups()}if(c)N(c.container,{cursor:c._cursor}),c.cancelClick=this.hasDragged>10,c.mouseIsDown=this.hasDragged= -this.hasPinched=!1,this.pinchDown=[]},onContainerMouseDown:function(a){a=this.normalize(a);a.preventDefault&&a.preventDefault();this.dragStart(a)},onDocumentMouseUp:function(a){$[oa]&&$[oa].pointer.drop(a)},onDocumentMouseMove:function(a){var b=this.chart,c=this.chartPosition,a=this.normalize(a,c);c&&!this.inClass(a.target,"highcharts-tracker")&&!b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)&&this.reset()},onContainerMouseLeave:function(a){var b=$[oa];if(b&&(a.relatedTarget||a.toElement))b.pointer.reset(), -b.pointer.chartPosition=null},onContainerMouseMove:function(a){var b=this.chart;if(!v(oa)||!$[oa]||!$[oa].mouseIsDown)oa=b.index;a=this.normalize(a);a.returnValue=!1;b.mouseIsDown==="mousedown"&&this.drag(a);(this.inClass(a.target,"highcharts-tracker")||b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop))&&!b.openMenu&&this.runPointActions(a)},inClass:function(a,b){for(var c;a;){if(c=X(a,"class")){if(c.indexOf(b)!==-1)return!0;if(c.indexOf("highcharts-container")!==-1)return!1}a=a.parentNode}}, -onTrackerMouseOut:function(a){var b=this.chart.hoverSeries,a=a.relatedTarget||a.toElement;if(b&&a&&!b.options.stickyTracking&&!this.inClass(a,"highcharts-tooltip")&&!this.inClass(a,"highcharts-series-"+b.index))b.onMouseOut()},onContainerClick:function(a){var b=this.chart,c=b.hoverPoint,d=b.plotLeft,e=b.plotTop,a=this.normalize(a);b.cancelClick||(c&&this.inClass(a.target,"highcharts-tracker")?(O(c.series,"click",A(a,{point:c})),b.hoverPoint&&c.firePointEvent("click",a)):(A(a,this.getCoordinates(a)), -b.isInsidePlot(a.chartX-d,a.chartY-e)&&O(b,"click",a)))},setDOMEvents:function(){var a=this,b=a.chart.container;b.onmousedown=function(b){a.onContainerMouseDown(b)};b.onmousemove=function(b){a.onContainerMouseMove(b)};b.onclick=function(b){a.onContainerClick(b)};E(b,"mouseleave",a.onContainerMouseLeave);kb===1&&E(H,"mouseup",a.onDocumentMouseUp);if(cb)b.ontouchstart=function(b){a.onContainerTouchStart(b)},b.ontouchmove=function(b){a.onContainerTouchMove(b)},kb===1&&E(H,"touchend",a.onDocumentTouchEnd)}, -destroy:function(){var a;T(this.chart.container,"mouseleave",this.onContainerMouseLeave);kb||(T(H,"mouseup",this.onDocumentMouseUp),T(H,"touchend",this.onDocumentTouchEnd));clearInterval(this.tooltipTimeout);for(a in this)this[a]=null}};A(B.Pointer.prototype,{pinchTranslate:function(a,b,c,d,e,f){(this.zoomHor||this.pinchHor)&&this.pinchTranslateDirection(!0,a,b,c,d,e,f);(this.zoomVert||this.pinchVert)&&this.pinchTranslateDirection(!1,a,b,c,d,e,f)},pinchTranslateDirection:function(a,b,c,d,e,f,g,h){var i= -this.chart,j=a?"x":"y",k=a?"X":"Y",l="chart"+k,m=a?"width":"height",n=i["plot"+(a?"Left":"Top")],p,r,s=h||1,q=i.inverted,o=i.bounds[a?"h":"v"],x=b.length===1,D=b[0][l],v=c[0][l],w=!x&&b[1][l],y=!x&&c[1][l],t,c=function(){!x&&S(D-w)>20&&(s=h||S(v-y)/S(D-w));r=(n-v)/s+D;p=i["plot"+(a?"Width":"Height")]/s};c();b=r;bo.max&&(b=o.max-p,t=!0);t?(v-=0.8*(v-g[j][0]),x||(y-=0.8*(y-g[j][1])),c()):g[j]=[v,y];q||(f[j]=r-n,f[m]=p);f=q?1/s:s;e[m]=p;e[j]=b;d[q?a?"scaleY":"scaleX":"scale"+ -k]=s;d["translate"+k]=f*n+(v-f*D)},pinch:function(a){var b=this,c=b.chart,d=b.pinchDown,e=a.touches,f=e.length,g=b.lastValidTouch,h=b.hasZoom,i=b.selectionMarker,j={},k=f===1&&(b.inClass(a.target,"highcharts-tracker")&&c.runTrackerClick||b.runChartClick),l={};if(f>1)b.initiated=!0;h&&b.initiated&&!k&&a.preventDefault();ta(e,function(a){return b.normalize(a)});if(a.type==="touchstart")o(e,function(a,b){d[b]={chartX:a.chartX,chartY:a.chartY}}),g.x=[d[0].chartX,d[1]&&d[1].chartX],g.y=[d[0].chartY,d[1]&& -d[1].chartY],o(c.axes,function(a){if(a.zoomEnabled){var b=c.bounds[a.horiz?"h":"v"],d=a.minPixelPadding,e=a.toPixels(q(a.options.min,a.dataMin)),f=a.toPixels(q(a.options.max,a.dataMax)),g=G(e,f),e=w(e,f);b.min=G(a.pos,g-d);b.max=w(a.pos+a.len,e+d)}}),b.res=!0;else if(d.length){if(!i)b.selectionMarker=i=A({destroy:ra,touch:!0},c.plotBox);b.pinchTranslate(d,e,j,i,l,g);b.hasPinched=h;b.scaleGroups(j,l);if(!h&&b.followTouchMove&&f===1)this.runPointActions(b.normalize(a));else if(b.res)b.res=!1,this.reset(!1, -0)}},touch:function(a,b){var c=this.chart,d;oa=c.index;if(a.touches.length===1)if(a=this.normalize(a),c.isInsidePlot(a.chartX-c.plotLeft,a.chartY-c.plotTop)&&!c.openMenu){b&&this.runPointActions(a);if(a.type==="touchmove")c=this.pinchDown,d=c[0]?Math.sqrt(Math.pow(c[0].chartX-a.chartX,2)+Math.pow(c[0].chartY-a.chartY,2))>=4:!1;q(d,!0)&&this.pinch(a)}else b&&this.reset();else a.touches.length===2&&this.pinch(a)},onContainerTouchStart:function(a){this.touch(a,!0)},onContainerTouchMove:function(a){this.touch(a)}, -onDocumentTouchEnd:function(a){$[oa]&&$[oa].pointer.drop(a)}});if(L.PointerEvent||L.MSPointerEvent){var Ga={},Mb=!!L.PointerEvent,ac=function(){var a,b=[];b.item=function(a){return this[a]};for(a in Ga)Ga.hasOwnProperty(a)&&b.push({pageX:Ga[a].pageX,pageY:Ga[a].pageY,target:Ga[a].target});return b},Nb=function(a,b,c,d){if((a.pointerType==="touch"||a.pointerType===a.MSPOINTER_TYPE_TOUCH)&&$[oa])d(a),d=$[oa].pointer,d[b]({type:c,target:a.currentTarget,preventDefault:ra,touches:ac()})};A(Ya.prototype, -{onContainerPointerDown:function(a){Nb(a,"onContainerTouchStart","touchstart",function(a){Ga[a.pointerId]={pageX:a.pageX,pageY:a.pageY,target:a.currentTarget}})},onContainerPointerMove:function(a){Nb(a,"onContainerTouchMove","touchmove",function(a){Ga[a.pointerId]={pageX:a.pageX,pageY:a.pageY};if(!Ga[a.pointerId].target)Ga[a.pointerId].target=a.currentTarget})},onDocumentPointerUp:function(a){Nb(a,"onDocumentTouchEnd","touchend",function(a){delete Ga[a.pointerId]})},batchMSEvents:function(a){a(this.chart.container, -Mb?"pointerdown":"MSPointerDown",this.onContainerPointerDown);a(this.chart.container,Mb?"pointermove":"MSPointerMove",this.onContainerPointerMove);a(H,Mb?"pointerup":"MSPointerUp",this.onDocumentPointerUp)}});U(Ya.prototype,"init",function(a,b,c){a.call(this,b,c);this.hasZoom&&N(b.container,{"-ms-touch-action":"none","touch-action":"none"})});U(Ya.prototype,"setDOMEvents",function(a){a.apply(this);(this.hasZoom||this.followTouchMove)&&this.batchMSEvents(E)});U(Ya.prototype,"destroy",function(a){this.batchMSEvents(T); -a.call(this)})}var ub=B.Legend=function(a,b){this.init(a,b)};ub.prototype={init:function(a,b){var c=this,d=b.itemStyle,e=b.itemMarginTop||0;this.options=b;if(b.enabled)c.itemStyle=d,c.itemHiddenStyle=z(d,b.itemHiddenStyle),c.itemMarginTop=e,c.padding=d=q(b.padding,8),c.initialItemX=d,c.initialItemY=d-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.symbolWidth=q(b.symbolWidth,16),c.pages=[],c.render(),E(c.chart,"endResize",function(){c.positionCheckboxes()})},colorizeItem:function(a,b){var c=this.options, -d=a.legendItem,e=a.legendLine,f=a.legendSymbol,g=this.itemHiddenStyle.color,c=b?c.itemStyle.color:g,h=b?a.legendColor||a.color||"#CCC":g,g=a.options&&a.options.marker,i={fill:h},j;d&&d.css({fill:c,color:c});e&&e.attr({stroke:h});if(f){if(g&&f.isMarker)for(j in i.stroke=h,g=a.convertAttribs(g),g)d=g[j],d!==t&&(i[j]=d);f.attr(i)}},positionItem:function(a){var b=this.options,c=b.symbolPadding,b=!b.rtl,d=a._legendItemPos,e=d[0],d=d[1],f=a.checkbox;(a=a.legendGroup)&&a.element&&a.translate(b?e:this.legendWidth- -e-2*c-4,d);if(f)f.x=e,f.y=d},destroyItem:function(a){var b=a.checkbox;o(["legendItem","legendLine","legendSymbol","legendGroup"],function(b){a[b]&&(a[b]=a[b].destroy())});b&&Ua(a.checkbox)},destroy:function(){var a=this.group,b=this.box;if(b)this.box=b.destroy();if(a)this.group=a.destroy()},positionCheckboxes:function(a){var b=this.group.alignAttr,c,d=this.clipHeight||this.legendHeight,e=this.titleHeight;if(b)c=b.translateY,o(this.allItems,function(f){var g=f.checkbox,h;g&&(h=c+e+g.y+(a||0)+3,N(g, -{left:b.translateX+f.checkboxOffset+g.x-20+"px",top:h+"px",display:h>c-6&&h(m||b.chartWidth-2*j-r-d.x))this.itemX=r,this.itemY+=p+this.lastLineHeight+n,this.lastLineHeight=0;this.maxItemWidth=w(this.maxItemWidth,f);this.lastItemY=p+this.itemY+n;this.lastLineHeight=w(g,this.lastLineHeight);a._legendItemPos=[this.itemX,this.itemY];e?this.itemX+=f:(this.itemY+=p+g+n,this.lastLineHeight=g);this.offsetWidth=m||w((e?this.itemX-r-k:f)+j,this.offsetWidth)}, -getAllItems:function(){var a=[];o(this.chart.series,function(b){var c=b.options;if(q(c.showInLegend,!v(c.linkedTo)?t:!1,!0))a=a.concat(b.legendItems||(c.legendType==="point"?b.data:b))});return a},adjustMargins:function(a,b){var c=this.chart,d=this.options,e=d.align.charAt(0)+d.verticalAlign.charAt(0)+d.layout.charAt(0);this.display&&!d.floating&&o([/(lth|ct|rth)/,/(rtv|rm|rbv)/,/(rbh|cb|lbh)/,/(lbv|lm|ltv)/],function(f,g){f.test(e)&&!v(a[g])&&(c[tb[g]]=w(c[tb[g]],c.legend[(g+1)%2?"legendHeight": -"legendWidth"]+[1,-1,-1,1][g]*d[g%2?"x":"y"]+q(d.margin,12)+b[g]))})},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,e,f,g,h,i=a.box,j=a.options,k=a.padding,l=j.borderWidth,m=j.backgroundColor;a.itemX=a.initialItemX;a.itemY=a.initialItemY;a.offsetWidth=0;a.lastItemY=0;if(!d)a.group=d=c.g("legend").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),a.scrollGroup=c.g().add(a.contentGroup);a.renderTitle();e=a.getAllItems();nb(e,function(a,b){return(a.options&&a.options.legendIndex|| -0)-(b.options&&b.options.legendIndex||0)});j.reversed&&e.reverse();a.allItems=e;a.display=f=!!e.length;a.lastLineHeight=0;o(e,function(b){a.renderItem(b)});g=(j.width||a.offsetWidth)+k;h=a.lastItemY+a.lastLineHeight+a.titleHeight;h=a.handleOverflow(h);h+=k;if(l||m){if(i){if(g>0&&h>0)i[i.isNew?"attr":"animate"](i.crisp({width:g,height:h})),i.isNew=!1}else a.box=i=c.rect(0,0,g,h,j.borderRadius,l||0).attr({stroke:j.borderColor,"stroke-width":l||0,fill:m||"none"}).add(d).shadow(j.shadow),i.isNew=!0;i[f? -"show":"hide"]()}a.legendWidth=g;a.legendHeight=h;o(e,function(b){a.positionItem(b)});f&&d.align(A({width:g,height:h},j),!0,"spacingBox");b.isResizing||this.positionCheckboxes()},handleOverflow:function(a){var b=this,c=this.chart,d=c.renderer,e=this.options,f=e.y,f=c.spacingBox.height+(e.verticalAlign==="top"?-f:f)-this.padding,g=e.maxHeight,h,i=this.clipRect,j=e.navigation,k=q(j.animation,!0),l=j.arrowSize||12,m=this.nav,n=this.pages,p=this.padding,r,s=this.allItems,F=function(a){i.attr({height:a}); -if(b.contentGroup.div)b.contentGroup.div.style.clip="rect("+p+"px,9999px,"+(p+a)+"px,0)"};e.layout==="horizontal"&&(f/=2);g&&(f=G(f,g));n.length=0;if(a>f&&j.enabled!==!1){this.clipHeight=h=w(f-20-this.titleHeight-p,0);this.currentPage=q(this.currentPage,1);this.fullHeight=a;o(s,function(a,b){var c=a._legendItemPos[1],d=y(a.legendItem.getBBox().height),e=n.length;if(!e||c-n[e-1]>h&&(r||c)!==n[e-1])n.push(r||c),e++;b===s.length-1&&c+d-n[e-1]>h&&n.push(c);c!==r&&(r=c)});if(!i)i=b.clipRect=d.clipRect(0, -p,9999,0),b.contentGroup.clip(i);F(h);if(!m)this.nav=m=d.g().attr({zIndex:1}).add(this.group),this.up=d.symbol("triangle",0,0,l,l).on("click",function(){b.scroll(-1,k)}).add(m),this.pager=d.text("",15,10).css(j.style).add(m),this.down=d.symbol("triangle-down",0,0,l,l).on("click",function(){b.scroll(1,k)}).add(m);b.scroll(0);a=f}else if(m)F(c.chartHeight),m.hide(),this.scrollGroup.attr({translateY:1}),this.clipHeight=0;return a},scroll:function(a,b){var c=this.pages,d=c.length,e=this.currentPage+a, -f=this.clipHeight,g=this.options.navigation,h=g.activeColor,g=g.inactiveColor,i=this.pager,j=this.padding;e>d&&(e=d);if(e>0)b!==t&&$a(b,this.chart),this.nav.attr({translateX:j,translateY:f+this.padding+7+this.titleHeight,visibility:"visible"}),this.up.attr({fill:e===1?g:h}).css({cursor:e===1?"default":"pointer"}),i.attr({text:e+"/"+d}),this.down.attr({x:18+this.pager.getBBox().width,fill:e===d?g:h}).css({cursor:e===d?"default":"pointer"}),c=-c[e-1]+this.initialItemY,this.scrollGroup.animate({translateY:c}), -this.currentPage=e,this.positionCheckboxes(c)}};aa=B.LegendSymbolMixin={drawRectangle:function(a,b){var c=a.options.symbolHeight||a.fontMetrics.f;b.legendSymbol=this.chart.renderer.rect(0,a.baseline-c+1,a.symbolWidth,c,a.options.symbolRadius||0).attr({zIndex:3}).add(b.legendGroup)},drawLineMarker:function(a){var b=this.options,c=b.marker,d=a.symbolWidth,e=this.chart.renderer,f=this.legendGroup,a=a.baseline-y(a.fontMetrics.b*0.3),g;if(b.lineWidth){g={"stroke-width":b.lineWidth};if(b.dashStyle)g.dashstyle= -b.dashStyle;this.legendLine=e.path(["M",0,a,"L",d,a]).attr(g).add(f)}if(c&&c.enabled!==!1)b=c.radius,this.legendSymbol=c=e.symbol(this.symbol,d/2-b,a-b,2*b,2*b,c).add(f),c.isMarker=!0}};(/Trident\/7\.0/.test(Na)||Wa)&&U(ub.prototype,"positionItem",function(a,b){var c=this,d=function(){b._legendItemPos&&a.call(c,b)};d();setTimeout(d)});var Ba=B.Chart=function(){this.getArgs.apply(this,arguments)};B.chart=function(a,b,c){return new Ba(a,b,c)};Ba.prototype={callbacks:[],getArgs:function(){var a=[].slice.call(arguments); -if(Ca(a[0])||a[0].nodeName)this.renderTo=a.shift();this.init(a[0],a[1])},init:function(a,b){var c,d=a.series;a.series=null;c=z(R,a);c.series=a.series=d;this.userOptions=a;d=c.chart;this.margin=this.splashArray("margin",d);this.spacing=this.splashArray("spacing",d);var e=d.events;this.bounds={h:{},v:{}};this.callback=b;this.isResizing=0;this.options=c;this.axes=[];this.series=[];this.hasCartesianSeries=d.showAxes;var f=this,g;f.index=$.length;$.push(f);kb++;d.reflow!==!1&&E(f,"load",function(){f.initReflow()}); -if(e)for(g in e)E(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=qa?!1:q(d.animation,!0);f.pointCount=f.colorCounter=f.symbolCounter=0;f.firstRender()},initSeries:function(a){var b=this.options.chart;(b=I[a.type||b.type||b.defaultSeriesType])||ga(17,!0);b=new b;b.init(this,a);return b},isInsidePlot:function(a,b,c){var d=c?b:a,a=c?a:b;return d>=0&&d<=this.plotWidth&&a>=0&&a<=this.plotHeight},redraw:function(a){var b=this.axes,c=this.series,d=this.pointer,e=this.legend,f=this.isDirtyLegend,g,h,i=this.hasCartesianSeries, -j=this.isDirtyBox,k=c.length,l=k,m=this.renderer,n=m.isHidden(),p=[];$a(a,this);n&&this.cloneRenderTo();for(this.layOutTitles();l--;)if(a=c[l],a.options.stacking&&(g=!0,a.isDirty)){h=!0;break}if(h)for(l=k;l--;)if(a=c[l],a.options.stacking)a.isDirty=!0;o(c,function(a){a.isDirty&&a.options.legendType==="point"&&(a.updateTotals&&a.updateTotals(),f=!0);a.isDirtyData&&O(a,"updatedData")});if(f&&e.options.enabled)e.render(),this.isDirtyLegend=!1;g&&this.getStacks();if(i&&!this.isResizing)this.maxTicks= -null,o(b,function(a){a.setScale()});this.getMargins();i&&(o(b,function(a){a.isDirty&&(j=!0)}),o(b,function(a){var b=a.min+","+a.max;if(a.extKey!==b)a.extKey=b,p.push(function(){O(a,"afterSetExtremes",A(a.eventArgs,a.getExtremes()));delete a.eventArgs});(j||g)&&a.redraw()}));j&&this.drawChartBox();o(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});d&&d.reset(!0);m.draw();O(this,"redraw");n&&this.cloneRenderTo(!0);o(p,function(a){a.call()})},get:function(a){var b=this.axes, -c=this.series,d,e;for(d=0;d19?this.containerHeight:400))},cloneRenderTo:function(a){var b= -this.renderToClone,c=this.container;a?b&&(this.renderTo.appendChild(c),Ua(b),delete this.renderToClone):(c&&c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=b=this.renderTo.cloneNode(0),N(b,{position:"absolute",top:"-9999px",display:"block"}),b.style.setProperty&&b.style.setProperty("display","block","important"),H.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options,c=b.chart,d,e;a=this.renderTo;var f="highcharts-"+Ib++;if(!a)this.renderTo= -a=c.renderTo;if(Ca(a))this.renderTo=a=H.getElementById(a);a||ga(13,!0);d=K(X(a,"data-highcharts-chart"));C(d)&&$[d]&&$[d].hasRendered&&$[d].destroy();X(a,"data-highcharts-chart",this.index);a.innerHTML="";!c.skipClone&&!a.offsetWidth&&this.cloneRenderTo();this.getChartSize();d=this.chartWidth;e=this.chartHeight;this.container=a=fa(Va,{className:"highcharts-container"+(c.className?" "+c.className:""),id:f},A({position:"relative",overflow:"hidden",width:d+"px",height:e+"px",textAlign:"left",lineHeight:"normal", -zIndex:0,"-webkit-tap-highlight-color":"rgba(0,0,0,0)"},c.style),this.renderToClone||a);this._cursor=a.style.cursor;this.renderer=new (B[c.renderer]||Xa)(a,d,e,c.style,c.forExport,b.exporting&&b.exporting.allowHTML);qa&&this.renderer.create(this,a,d,e);this.renderer.chartIndex=this.index},getMargins:function(a){var b=this.spacing,c=this.margin,d=this.titleOffset;this.resetMargins();if(d&&!v(c[0]))this.plotTop=w(this.plotTop,d+this.options.title.margin+b[0]);this.legend.adjustMargins(c,b);this.extraBottomMargin&& -(this.marginBottom+=this.extraBottomMargin);this.extraTopMargin&&(this.plotTop+=this.extraTopMargin);a||this.getAxisMargins()},getAxisMargins:function(){var a=this,b=a.axisOffset=[0,0,0,0],c=a.margin;a.hasCartesianSeries&&o(a.axes,function(a){a.visible&&a.getOffset()});o(tb,function(d,e){v(c[e])||(a[d]+=b[e])});a.setChartSize()},reflow:function(a){var b=this,c=b.options.chart,d=b.renderTo,e=c.width||wa(d,"width"),f=c.height||wa(d,"height"),c=a?a.target:L;if(!b.hasUserSize&&!b.isPrinting&&e&&f&&(c=== -L||c===H)){if(e!==b.containerWidth||f!==b.containerHeight)clearTimeout(b.reflowTimeout),b.reflowTimeout=Za(function(){if(b.container)b.setSize(e,f,!1),b.hasUserSize=null},a?100:0);b.containerWidth=e;b.containerHeight=f}},initReflow:function(){var a=this,b=function(b){a.reflow(b)};E(L,"resize",b);E(a,"destroy",function(){T(L,"resize",b)})},setSize:function(a,b,c){var d=this,e,f,g=d.renderer;d.isResizing+=1;$a(c,d);d.oldChartHeight=d.chartHeight;d.oldChartWidth=d.chartWidth;if(v(a))d.chartWidth=e=w(0, -y(a)),d.hasUserSize=!!e;if(v(b))d.chartHeight=f=w(0,y(b));a=g.globalAnimation;(a?db:N)(d.container,{width:e+"px",height:f+"px"},a);d.setChartSize(!0);g.setSize(e,f,c);d.maxTicks=null;o(d.axes,function(a){a.isDirty=!0;a.setScale()});o(d.series,function(a){a.isDirty=!0});d.isDirtyLegend=!0;d.isDirtyBox=!0;d.layOutTitles();d.getMargins();d.redraw(c);d.oldChartHeight=null;O(d,"resize");Za(function(){d&&O(d,"endResize",null,function(){d.isResizing-=1})},gb(a).duration)},setChartSize:function(a){var b= -this.inverted,c=this.renderer,d=this.chartWidth,e=this.chartHeight,f=this.options.chart,g=this.spacing,h=this.clipOffset,i,j,k,l;this.plotLeft=i=y(this.plotLeft);this.plotTop=j=y(this.plotTop);this.plotWidth=k=w(0,y(d-i-this.marginRight));this.plotHeight=l=w(0,y(e-j-this.marginBottom));this.plotSizeX=b?l:k;this.plotSizeY=b?k:l;this.plotBorderWidth=f.plotBorderWidth||0;this.spacingBox=c.spacingBox={x:g[3],y:g[0],width:d-g[3]-g[1],height:e-g[0]-g[2]};this.plotBox=c.plotBox={x:i,y:j,width:k,height:l}; -d=2*V(this.plotBorderWidth/2);b=Ea(w(d,h[3])/2);c=Ea(w(d,h[0])/2);this.clipBox={x:b,y:c,width:V(this.plotSizeX-w(d,h[1])/2-b),height:w(0,V(this.plotSizeY-w(d,h[2])/2-c))};a||o(this.axes,function(a){a.setAxisSize();a.setAxisTranslation()})},resetMargins:function(){var a=this;o(tb,function(b,c){a[b]=q(a.margin[c],a.spacing[c])});a.axisOffset=[0,0,0,0];a.clipOffset=[0,0,0,0]},drawChartBox:function(){var a=this.options.chart,b=this.renderer,c=this.chartWidth,d=this.chartHeight,e=this.chartBackground, -f=this.plotBackground,g=this.plotBorder,h=this.plotBGImage,i=a.borderWidth||0,j=a.backgroundColor,k=a.plotBackgroundColor,l=a.plotBackgroundImage,m=a.plotBorderWidth||0,n,p=this.plotLeft,r=this.plotTop,s=this.plotWidth,q=this.plotHeight,o=this.plotBox,x=this.clipRect,D=this.clipBox;n=i+(a.shadow?8:0);if(i||j)if(e)e.animate(e.crisp({width:c-n,height:d-n}));else{e={fill:j||"none"};if(i)e.stroke=a.borderColor,e["stroke-width"]=i;this.chartBackground=b.rect(n/2,n/2,c-n,d-n,a.borderRadius,i).attr(e).addClass("highcharts-background").add().shadow(a.shadow)}if(k)f? -f.animate(o):this.plotBackground=b.rect(p,r,s,q,0).attr({fill:k}).add().shadow(a.plotShadow);if(l)h?h.animate(o):this.plotBGImage=b.image(l,p,r,s,q).add();x?x.animate({width:D.width,height:D.height}):this.clipRect=b.clipRect(D);if(m)g?(g.strokeWidth=-m,g.animate(g.crisp({x:p,y:r,width:s,height:q}))):this.plotBorder=b.rect(p,r,s,q,0,-m).attr({stroke:a.plotBorderColor,"stroke-width":m,fill:"none",zIndex:1}).add();this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,c,d=a.options.series, -e,f;o(["inverted","angular","polar"],function(g){c=I[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&&e--;)(c=I[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},linkSeries:function(){var a=this,b=a.series;o(b,function(a){a.linkedSeries.length=0});o(b,function(b){var d=b.options.linkedTo;if(Ca(d)&&(d=d===":previous"?a.series[b.index-1]:a.get(d)))d.linkedSeries.push(b),b.linkedParent=d,b.visible=q(b.options.visible,d.options.visible,b.visible)})},renderSeries:function(){o(this.series, -function(a){a.translate();a.render()})},renderLabels:function(){var a=this,b=a.options.labels;b.items&&o(b.items,function(c){var d=A(b.style,c.style),e=K(d.left)+a.plotLeft,f=K(d.top)+a.plotTop+12;delete d.left;delete d.top;a.renderer.text(c.html,e,f).attr({zIndex:2}).css(d).add()})},render:function(){var a=this.axes,b=this.renderer,c=this.options,d,e,f,g;this.setTitle();this.legend=new ub(this,c.legend);this.getStacks&&this.getStacks();this.getMargins(!0);this.setChartSize();d=this.plotWidth;e=this.plotHeight-= -21;o(a,function(a){a.setScale()});this.getAxisMargins();f=d/this.plotWidth>1.1;g=e/this.plotHeight>1.05;if(f||g)this.maxTicks=null,o(a,function(a){(a.horiz&&f||!a.horiz&&g)&&a.setTickInterval(!0)}),this.getMargins();this.drawChartBox();this.hasCartesianSeries&&o(a,function(a){a.visible&&a.render()});if(!this.seriesGroup)this.seriesGroup=b.g("series-group").attr({zIndex:3}).add();this.renderSeries();this.renderLabels();this.showCredits(c.credits);this.hasRendered=!0},showCredits:function(a){if(a.enabled&& -!this.credits)this.credits=this.renderer.text(a.text,0,0).on("click",function(){if(a.href)L.location.href=a.href}).attr({align:a.position.align,zIndex:8}).css(a.style).add().align(a.position)},destroy:function(){var a=this,b=a.axes,c=a.series,d=a.container,e,f=d&&d.parentNode;O(a,"destroy");$[a.index]=t;kb--;a.renderTo.removeAttribute("data-highcharts-chart");T(a);for(e=b.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=c[e].destroy();o("title,subtitle,chartBackground,plotBackground,plotBGImage,plotBorder,seriesGroup,clipRect,credits,pointer,scroller,rangeSelector,legend,resetZoomButton,tooltip,renderer".split(","), -function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});if(d)d.innerHTML="",T(d),f&&Ua(d);for(e in a)delete a[e]},isReadyToRender:function(){var a=this;return!ja&&L==L.top&&H.readyState!=="complete"||qa&&!L.canvg?(qa?Wb.push(function(){a.firstRender()},a.options.global.canvasToolsURL):H.attachEvent("onreadystatechange",function(){H.detachEvent("onreadystatechange",a.firstRender);H.readyState==="complete"&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options;if(a.isReadyToRender()){a.getContainer(); -O(a,"init");a.resetMargins();a.setChartSize();a.propFromSeries();a.getAxes();o(b.series||[],function(b){a.initSeries(b)});a.linkSeries();O(a,"beforeRender");if(B.Pointer)a.pointer=new Ya(a,b);a.render();a.renderer.draw();if(!a.renderer.imgCount&&a.onload)a.onload();a.cloneRenderTo(!0)}},onload:function(){var a=this;o([this.callback].concat(this.callbacks),function(b){b&&a.index!==void 0&&b.apply(a,[a])});O(a,"load");this.onload=null},splashArray:function(a,b){var c=b[a],c=ea(c)?c:[c,c,c,c];return[q(b[a+ -"Top"],c[0]),q(b[a+"Right"],c[1]),q(b[a+"Bottom"],c[2]),q(b[a+"Left"],c[3])]}};var bc=B.CenteredSeriesMixin={getCenter:function(){var a=this.options,b=this.chart,c=2*(a.slicedOffset||0),d=b.plotWidth-2*c,b=b.plotHeight-2*c,e=a.center,e=[q(e[0],"50%"),q(e[1],"50%"),a.size||"100%",a.innerSize||0],f=G(d,b),g,h;for(g=0;g<4;++g)h=e[g],a=g<2||g===2&&/%$/.test(h),e[g]=(/%$/.test(h)?[d,b,f,e[2]][g]*parseFloat(h)/100:parseFloat(h))+(a?c:0);e[3]>e[2]&&(e[3]=e[2]);return e}},Ha=function(){};Ha.prototype={init:function(a, -b,c){this.series=a;this.color=a.color;this.applyOptions(b,c);this.pointAttr={};if(a.options.colorByPoint&&(b=a.options.colors||a.chart.options.colors,this.color=this.color||b[a.colorCounter++],a.colorCounter===b.length))a.colorCounter=0;a.chart.pointCount++;return this},applyOptions:function(a,b){var c=this.series,d=c.options.pointValKey||c.pointValKey,a=Ha.prototype.optionsToObject.call(this,a);A(this,a);this.options=this.options?A(this.options,a):a;if(d)this.y=this[d];this.isNull=this.x===null|| -this.y===null;if(this.x===void 0&&c)this.x=b===void 0?c.autoIncrement():b;return this},optionsToObject:function(a){var b={},c=this.series,d=c.options.keys,e=d||c.pointArrayMap||["y"],f=e.length,g=0,h=0;if(C(a)||a===null)b[e[0]]=a;else if(Ja(a)){if(!d&&a.length>f){c=typeof a[0];if(c==="string")b.name=a[0];else if(c==="number")b.x=a[0];g++}for(;hn){for(c=0;k===null&&ci||this.forceCrop))if(b[d-1]p)b=[],c=[];else if(b[0]p)e=this.cropData(this.xData,this.yData,n,p),b=e.xData,c=e.yData,e=e.start,f=!0;for(i=b.length||1;--i;)d=m?j(b[i])-j(b[i-1]):b[i]-b[i-1],d>0&&(g===t||d=c){f=w(0,i-h);break}for(c=i;cd){g=c+h;break}return{xData:a.slice(f,g),yData:b.slice(f,g),start:f,end:g}},generatePoints:function(){var a=this.options.data,b=this.data,c,d=this.processedXData,e=this.processedYData,f=this.pointClass,g=d.length,h=this.cropStart||0,i,j=this.hasGroupedData,k,l=[],m;if(!b&&!j)b=[],b.length=a.length,b=this.data=b;for(m=0;m0),j=this.getExtremesFromAll||this.options.getExtremesFromAll||this.cropped||(c[l+1]||j)>=g&&(c[l-1]||j)<=h,i&&j)if(i=k.length)for(;i--;)k[i]!==null&&(e[f++]=k[i]);else e[f++]=k;this.dataMin=Ma(e);this.dataMax=Da(e)},translate:function(){this.processedXData||this.processData();this.generatePoints();for(var a=this.options,b=a.stacking,c=this.xAxis,d=c.categories,e=this.yAxis,f=this.points,g=f.length,h=!!this.modifyValue,i=a.pointPlacement, -j=i==="between"||C(i),k=a.threshold,l=a.startFromThreshold?k:0,m,n,p,r,s=Number.MAX_VALUE,a=0;a=0&&n<=e.len&&m>=0&&m<=c.len;o.clientX=j?c.translate(u,0,0,0,1):m;o.negative=o.y<(k||0);o.category=d&&d[o.x]!==t?d[o.x]:o.x;o.isNull||(p!==void 0&&(s=G(s,S(m-p))),p=m)}this.closestPointRangePx=s},getValidPoints:function(a, -b){var c=this.chart;return Fa(a||this.points||[],function(a){return b&&!c.isInsidePlot(a.plotX,a.plotY,c.inverted)?!1:!a.isNull})},setClip:function(a){var b=this.chart,c=this.options,d=b.renderer,e=b.inverted,f=this.clipBox,g=f||b.clipBox,h=this.sharedClipKey||["_sharedClip",a&&a.duration,a&&a.easing,g.height,c.xAxis,c.yAxis].join(","),i=b[h],j=b[h+"m"];if(!i){if(a)g.width=0,b[h+"m"]=j=d.clipRect(-99,e?-b.plotLeft:-b.plotTop,99,e?b.chartWidth:b.chartHeight);b[h]=i=d.clipRect(g)}a&&(i.count+=1);if(c.clip!== -!1)this.group.clip(a||f?i:b.clipRect),this.markerGroup.clip(j),this.sharedClipKey=h;a||(i.count-=1,i.count<=0&&h&&b[h]&&(f||(b[h]=b[h].destroy()),b[h+"m"]&&(b[h+"m"]=b[h+"m"].destroy())))},animate:function(a){var b=this.chart,c=this.options.animation,d;if(c&&!ea(c))c=W[this.type].animation;a?this.setClip(c):(d=this.sharedClipKey,(a=b[d])&&a.animate({width:b.plotSizeX},c),b[d+"m"]&&b[d+"m"].animate({width:b.plotSizeX+99},c),this.animate=null)},afterAnimate:function(){this.setClip();O(this,"afterAnimate")}, -drawPoints:function(){var a,b=this.points,c=this.chart,d,e,f,g,h,i,j,k,l=this.options.marker,m=this.pointAttr[""],n,p,r,s=this.markerGroup,o=q(l.enabled,this.xAxis.isRadial,this.closestPointRangePx>2*l.radius);if(l.enabled!==!1||this._hasPointMarkers)for(f=b.length;f--;)if(g=b[f],d=V(g.plotX),e=g.plotY,k=g.graphic,n=g.marker||{},p=!!g.marker,a=o&&n.enabled===t||n.enabled,r=g.isInside,a&&C(e)&&g.y!==null)if(a=g.pointAttr[g.selected?"select":""]||m,h=a.r,i=q(n.symbol,this.symbol),j=i.indexOf("url")=== -0,k)k[r?"show":"hide"](!0).attr(a).animate(A({x:d-h,y:e-h},k.symbolName?{width:2*h,height:2*h}:{}));else{if(r&&(h>0||j))g.graphic=c.renderer.symbol(i,d-h,e-h,2*h,2*h,p?n:l).attr(a).add(s)}else if(k)g.graphic=k.destroy()},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,h={},a=a||{},b=b||{},c=c||{},d=d||{};for(f in e)g=e[f],h[f]=q(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=a.options,c=W[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color,h=a.options.negativeColor, -i={stroke:g,fill:g},j=a.points||[],k,l=[],m,n=a.pointAttrToOptions;f=a.hasPointSpecificOptions;var p=c.lineColor,r=c.fillColor;k=b.turboThreshold;var s=a.zones,F=a.zoneAxis||"y",u,x;b.marker?(e.radius=e.radius||c.radius+e.radiusPlus,e.lineWidth=e.lineWidth||c.lineWidth+e.lineWidthPlus):(e.color=e.color||va(e.color||g).brighten(e.brightness).get(),e.negativeColor=e.negativeColor||va(e.negativeColor||h).brighten(e.brightness).get());l[""]=a.convertAttribs(c,i);o(["hover","select"],function(b){l[b]= -a.convertAttribs(d[b],l[""])});a.pointAttr=l;g=j.length;if(!k||g=i.value;)i=s[++f];k.color=k.fillColor=i=q(i.color,a.color)}f=b.colorByPoint||k.color;if(k.options)for(x in n)v(c[n[x]])&&(f=!0);if(f){c=c||{};m=[];d=c.states||{};f=d.hover=d.hover||{};if(!b.marker||k.negative&&!f.fillColor&&!e.fillColor)f[a.pointAttrToOptions.fill]=f.color||!k.options.color&&e[k.negative&& -h?"negativeColor":"color"]||va(k.color).brighten(f.brightness||e.brightness).get();u={color:k.color};if(!r)u.fillColor=k.color;if(!p)u.lineColor=k.color;c.hasOwnProperty("color")&&!c.color&&delete c.color;if(i&&!e.fillColor)f.fillColor=i;m[""]=a.convertAttribs(A(u,c),l[""]);m.hover=a.convertAttribs(d.hover,l.hover,m[""]);m.select=a.convertAttribs(d.select,l.select,m[""])}else m=l;k.pointAttr=m}},destroy:function(){var a=this,b=a.chart,c=/AppleWebKit\/533/.test(Na),d,e=a.data||[],f,g,h;O(a,"destroy"); -T(a);o(a.axisTypes||[],function(b){if(h=a[b])za(h.series,a),h.isDirty=h.forceRedraw=!0});a.legendItem&&a.chart.legend.destroyItem(a);for(d=e.length;d--;)(f=e[d])&&f.destroy&&f.destroy();a.points=null;clearTimeout(a.animationTimeout);for(g in a)a[g]instanceof Z&&!a[g].survive&&(d=c&&g==="group"?"hide":"destroy",a[g][d]());if(b.hoverSeries===a)b.hoverSeries=null;za(b.series,a);for(g in a)delete a[g]},getGraphPath:function(a,b,c){var d=this,e=d.options,f=e.step,g,h=[],i,a=a||d.points;(g=a.reversed)&& -a.reverse();(f={right:1,center:2}[f]||f&&3)&&g&&(f=4-f);e.connectNulls&&!b&&!c&&(a=this.getValidPoints(a));o(a,function(g,k){var l=g.plotX,m=g.plotY,n=a[k-1];if((g.leftCliff||n&&n.rightCliff)&&!c)i=!0;g.isNull&&!v(b)&&k>0?i=!e.connectNulls:g.isNull&&!b?i=!0:(k===0||i?n=["M",g.plotX,g.plotY]:d.getPointSpline?n=d.getPointSpline(a,g,k):f?(n=f===1?["L",n.plotX,m]:f===2?["L",(n.plotX+l)/2,n.plotY,"L",(n.plotX+l)/2,m]:["L",l,n.plotY],n.push("L",l,m)):n=["L",l,m],h.push.apply(h,n),i=!1)});return d.graphPath= -h},drawGraph:function(){var a=this,b=this.options,c=[["graph",b.lineColor||this.color,b.dashStyle]],d=b.lineWidth,e=b.linecap!=="square",f=(this.gappedPath||this.getGraphPath).call(this),g=this.fillGraph&&this.color||"none";o(this.zones,function(d,e){c.push(["zoneGraph"+e,d.color||a.color,d.dashStyle||b.dashStyle])});o(c,function(c,i){var j=c[0],k=a[j];if(k)k.animate({d:f});else if((d||g)&&f.length)k={stroke:c[1],"stroke-width":d,fill:g,zIndex:1},c[2]?k.dashstyle=c[2]:e&&(k["stroke-linecap"]=k["stroke-linejoin"]= -"round"),a[j]=a.chart.renderer.path(f).attr(k).add(a.group).shadow(i<2&&b.shadow)})},applyZones:function(){var a=this,b=this.chart,c=b.renderer,d=this.zones,e,f,g=this.clips||[],h,i=this.graph,j=this.area,k=w(b.chartWidth,b.chartHeight),l=this[(this.zoneAxis||"y")+"Axis"],m,n=l.reversed,p=b.inverted,r=l.horiz,s,F,u,x=!1;if(d.length&&(i||j)&&l.min!==t)i&&i.hide(),j&&j.hide(),m=l.getExtremes(),o(d,function(d,o){e=n?r?b.plotWidth:0:r?0:l.toPixels(m.min);e=G(w(q(f,e),0),k);f=G(w(y(l.toPixels(q(d.value, -m.max),!0)),0),k);x&&(e=f=l.toPixels(m.max));s=Math.abs(e-f);F=G(e,f);u=w(e,f);if(l.isXAxis){if(h={x:p?u:F,y:0,width:s,height:k},!r)h.x=b.plotHeight-h.x}else if(h={x:0,y:p?u:F,width:k,height:s},r)h.y=b.plotWidth-h.y;b.inverted&&c.isVML&&(h=l.isXAxis?{x:0,y:n?F:u,height:h.width,width:b.chartWidth}:{x:h.y-b.plotLeft-b.spacingBox.x,y:0,width:h.height,height:b.chartHeight});g[o]?g[o].animate(h):(g[o]=c.clipRect(h),i&&a["zoneGraph"+o].clip(g[o]),j&&a["zoneArea"+o].clip(g[o]));x=d.value>m.max}),this.clips= -g},invertGroups:function(){function a(){var a={width:b.yAxis.len,height:b.xAxis.len};o(["group","markerGroup"],function(c){b[c]&&b[c].attr(a).invert()})}var b=this,c=b.chart;if(b.xAxis)E(c,"resize",a),E(b,"destroy",function(){T(c,"resize",a)}),a(),b.invertGroups=a},plotGroup:function(a,b,c,d,e){var f=this[a],g=!f;g&&(this[a]=f=this.chart.renderer.g(b).attr({zIndex:d||0.1}).add(e),f.addClass("highcharts-series-"+this.index));f.attr({visibility:c})[g?"attr":"animate"](this.getPlotBox());return f},getPlotBox:function(){var a= -this.chart,b=this.xAxis,c=this.yAxis;if(a.inverted)b=c,c=this.xAxis;return{translateX:b?b.left:a.plotLeft,translateY:c?c.top:a.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this,b=a.chart,c,d=a.options,e=!!a.animate&&b.renderer.isSVG&&gb(d.animation).duration,f=a.visible?"inherit":"hidden",g=d.zIndex,h=a.hasRendered,i=b.seriesGroup;c=a.plotGroup("group","series",f,g,i);a.markerGroup=a.plotGroup("markerGroup","markers",f,g,i);e&&a.animate(!0);a.getAttribs();c.inverted=a.isCartesian?b.inverted: -!1;a.drawGraph&&(a.drawGraph(),a.applyZones());o(a.points,function(a){a.redraw&&a.redraw()});a.drawDataLabels&&a.drawDataLabels();a.visible&&a.drawPoints();a.drawTracker&&a.options.enableMouseTracking!==!1&&a.drawTracker();b.inverted&&a.invertGroups();d.clip!==!1&&!a.sharedClipKey&&!h&&c.clip(b.clipRect);e&&a.animate();if(!h)a.animationTimeout=Za(function(){a.afterAnimate()},e);a.isDirty=a.isDirtyData=!1;a.hasRendered=!0},redraw:function(){var a=this.chart,b=this.isDirty||this.isDirtyData,c=this.group, -d=this.xAxis,e=this.yAxis;c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:q(d&&d.left,a.plotLeft),translateY:q(e&&e.top,a.plotTop)}));this.translate();this.render();b&&delete this.kdTree},kdDimensions:1,kdAxisArray:["clientX","plotY"],searchPoint:function(a,b){var c=this.xAxis,d=this.yAxis,e=this.chart.inverted;return this.searchKDTree({clientX:e?c.len-a.chartY+c.pos:a.chartX-c.pos,plotY:e?d.len-a.chartX+d.pos:a.chartY-d.pos},b)},buildKDTree:function(){function a(c, -e,f){var g,h;if(h=c&&c.length)return g=b.kdAxisArray[e%f],c.sort(function(a,b){return a[g]-b[g]}),h=Math.floor(h/2),{point:c[h],left:a(c.slice(0,h),e+1,f),right:a(c.slice(h+1),e+1,f)}}var b=this,c=b.kdDimensions;delete b.kdTree;Za(function(){b.kdTree=a(b.getValidPoints(null,!b.directTouch),c,c)},b.options.kdNow?0:1)},searchKDTree:function(a,b){function c(a,b,j,k){var l=b.point,m=d.kdAxisArray[j%k],n,p,r=l;p=v(a[e])&&v(l[e])?Math.pow(a[e]-l[e],2):null;n=v(a[f])&&v(l[f])?Math.pow(a[f]-l[f],2):null; -n=(p||0)+(n||0);l.dist=v(n)?Math.sqrt(n):Number.MAX_VALUE;l.distX=v(p)?Math.sqrt(p):Number.MAX_VALUE;m=a[m]-l[m];n=m<0?"left":"right";p=m<0?"right":"left";b[n]&&(n=c(a,b[n],j+1,k),r=n[g]0&&this.singleStacks===!1&&(s.points[o][0]=s.points[this.index+","+x+",0"][0]);e==="percent"?(r=r?i:j,k&&m[r]&&m[r][x]?(r=m[r][x],s.total=r.total=w(r.total,s.total)+S(D)||0):s.total=ka(s.total+(S(D)||0))):s.total=ka(s.total+(D||0));s.cum=q(s.cum,g)+(D||0);if(D!==null)s.points[o].push(s.cum),c[u]=s.cum}if(e==="percent")l.usePercentage=!0;this.stackedYData=c;l.oldStacks= -{}}};P.prototype.setPercentStacks=function(){var a=this,b=a.stackKey,c=a.yAxis.stacks,d=a.processedXData,e;o([b,"-"+b],function(b){var f;for(var g=d.length,h,i;g--;)if(h=d[g],e=a.getStackIndicator(e,h,a.index),f=(i=c[b]&&c[b][h])&&i.points[e.key],h=f)i=i.total?100/i.total:0,h[0]=ka(h[0]*i),h[1]=ka(h[1]*i),a.stackedYData[g]=h[1]})};P.prototype.getStackIndicator=function(a,b,c){!v(a)||a.x!==b?a={x:b,index:0}:a.index++;a.key=[c,b,a.index].join(",");return a};A(Ba.prototype,{addSeries:function(a,b,c){var d, -e=this;a&&(b=q(b,!0),O(e,"addSeries",{options:a},function(){d=e.initSeries(a);e.isDirtyLegend=!0;e.linkSeries();b&&e.redraw(c)}));return d},addAxis:function(a,b,c,d){var e=b?"xAxis":"yAxis",f=this.options,a=z(a,{index:this[e].length,isX:b});new J(this,a);f[e]=ua(f[e]||{});f[e].push(a);q(c,!0)&&this.redraw(d)},showLoading:function(a){var b=this,c=b.options,d=b.loadingDiv,e=c.loading,f=function(){d&&N(d,{left:b.plotLeft+"px",top:b.plotTop+"px",width:b.plotWidth+"px",height:b.plotHeight+"px"})};if(!d)b.loadingDiv= -d=fa(Va,{className:"highcharts-loading"},A(e.style,{zIndex:10,display:"none"}),b.container),b.loadingSpan=fa("span",null,e.labelStyle,d),E(b,"redraw",f);b.loadingSpan.innerHTML=a||c.lang.loading;if(!b.loadingShown)N(d,{opacity:0,display:""}),db(d,{opacity:e.style.opacity},{duration:e.showDuration||0}),b.loadingShown=!0;f()},hideLoading:function(){var a=this.options,b=this.loadingDiv;b&&db(b,{opacity:0},{duration:a.loading.hideDuration||100,complete:function(){N(b,{display:"none"})}});this.loadingShown= -!1}});A(Ha.prototype,{update:function(a,b,c,d){function e(){f.applyOptions(a);if(f.y===null&&h)f.graphic=h.destroy();if(ea(a)&&!Ja(a))f.redraw=function(){if(h&&h.element&&a&&a.marker&&a.marker.symbol)f.graphic=h.destroy();if(a&&a.dataLabels&&f.dataLabel)f.dataLabel=f.dataLabel.destroy();f.redraw=null};i=f.index;g.updateParallelArrays(f,i);if(l&&f.name)l[f.x]=f.name;k.data[i]=ea(k.data[i])&&!Ja(k.data[i])?f.options:a;g.isDirty=g.isDirtyData=!0;if(!g.fixedBox&&g.hasCartesianSeries)j.isDirtyBox=!0;if(k.legendType=== -"point")j.isDirtyLegend=!0;b&&j.redraw(c)}var f=this,g=f.series,h=f.graphic,i,j=g.chart,k=g.options,l=g.xAxis&&g.xAxis.names,b=q(b,!0);d===!1?e():f.firePointEvent("update",{options:a},e)},remove:function(a,b){this.series.removePoint(sa(this,this.series.data),a,b)}});A(P.prototype,{addPoint:function(a,b,c,d){var e=this,f=e.options,g=e.data,h=e.graph,i=e.area,j=e.chart,k=e.xAxis&&e.xAxis.names,l=h&&h.shift||0,m=["graph","area"],h=f.data,n,p=e.xData;$a(d,j);if(c){for(d=e.zones.length;d--;)m.push("zoneGraph"+ -d,"zoneArea"+d);o(m,function(a){if(e[a])e[a].shift=l+(f.step?2:1)})}if(i)i.isArea=!0;b=q(b,!0);i={series:e};e.pointClass.prototype.applyOptions.apply(i,[a]);m=i.x;d=p.length;if(e.requireSorting&&mm;)d--;e.updateParallelArrays(i,"splice",d,0,0);e.updateParallelArrays(i,d);if(k&&i.name)k[m]=i.name;h.splice(d,0,a);n&&(e.data.splice(d,0,null),e.processData());f.legendType==="point"&&e.generatePoints();c&&(g[0]&&g[0].remove?g[0].remove(!1):(g.shift(),e.updateParallelArrays(i, -"shift"),h.shift()));e.isDirty=!0;e.isDirtyData=!0;b&&(e.getAttribs(),j.redraw())},removePoint:function(a,b,c){var d=this,e=d.data,f=e[a],g=d.points,h=d.chart,i=function(){g&&g.length===e.length&&g.splice(a,1);e.splice(a,1);d.options.data.splice(a,1);d.updateParallelArrays(f||{series:d},"splice",a,1);f&&f.destroy();d.isDirty=!0;d.isDirtyData=!0;b&&h.redraw()};$a(c,h);b=q(b,!0);f?f.firePointEvent("remove",null,i):i()},remove:function(a,b){var c=this,d=c.chart;O(c,"remove",null,function(){c.destroy(); -d.isDirtyLegend=d.isDirtyBox=!0;d.linkSeries();q(a,!0)&&d.redraw(b)})},update:function(a,b){var c=this,d=this.chart,e=this.userOptions,f=this.type,g=I[f].prototype,h=["group","markerGroup","dataLabelsGroup"],i;if(a.type&&a.type!==f||a.zIndex!==void 0)h.length=0;o(h,function(a){h[a]=c[a];delete c[a]});a=z(e,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data},a);this.remove(!1);for(i in g)this[i]=t;A(this,I[a.type||f].prototype);o(h,function(a){c[a]=h[a]});this.init(d, -a);d.linkSeries();q(b,!0)&&d.redraw(!1)}});A(J.prototype,{update:function(a,b){var c=this.chart,a=c.options[this.coll][this.options.index]=z(this.userOptions,a);this.destroy(!0);this._addedPlotLB=this.chart._labelPanes=t;this.init(c,A(a,{events:t}));c.isDirtyBox=!0;q(b,!0)&&c.redraw()},remove:function(a){for(var b=this.chart,c=this.coll,d=this.series,e=d.length;e--;)d[e]&&d[e].remove(!1);za(b.axes,this);za(b[c],this);b.options[c].splice(this.options.index,1);o(b[c],function(a,b){a.options.index=b}); -this.destroy();b.isDirtyBox=!0;q(a,!0)&&b.redraw()},setTitle:function(a,b){this.update({title:a},b)},setCategories:function(a,b){this.update({categories:a},b)}});var Ia=ma(P);I.line=Ia;W.area=z(da,{softThreshold:!1,threshold:0});var ya=ma(P,{type:"area",singleStacks:!1,getStackPoints:function(){var a=[],b=[],c=this.xAxis,d=this.yAxis,e=d.stacks[this.stackKey],f={},g=this.points,h=this.index,i=d.series,j=i.length,k,l=q(d.options.reversedStacks,!0)?1:-1,m,n;if(this.options.stacking){for(m=0;m=0&&m=0&&ma&&h>e?(h=w(a,e),j=2*e-h):hc&&j>e?(j=w(c,e),h=2*e-j):j0.5;b=Math.round(b)+f;d-=b;g&&d&&(b-=1,d+=1);return{x:a,y:b,width:c,height:d}},translate:function(){var a=this,b=a.chart,c=a.options,d=a.borderWidth=q(c.borderWidth,a.closestPointRange*a.xAxis.transA<2?0:1),e=a.yAxis,f=a.translatedThreshold=e.getThreshold(c.threshold), -g=q(c.minPointLength,5),h=a.getColumnMetrics(),i=h.width,j=a.barW=w(i,1+2*d),k=a.pointXOffset=h.offset;b.inverted&&(f-=0.5);c.pointPadding&&(j=Ea(j));P.prototype.translate.apply(a);o(a.points,function(c){var d=G(q(c.yBottom,f),9E4),h=999+S(d),h=G(w(-h,c.plotY),e.len+h),p=c.plotX+k,r=j,o=G(h,d),F,u=w(h,d)-o;S(u)g?d-g:f-(F?g:0));c.barX=p;c.pointWidth=i;c.tooltipPos=b.inverted?[e.len+e.pos-b.plotLeft-h,a.xAxis.len-p-r/2,u]:[p+r/ -2,h+e.pos-b.plotTop,u];c.shapeType="rect";c.shapeArgs=a.crispCol(p,o,r,u)})},getSymbol:ra,drawLegendSymbol:aa.drawRectangle,drawGraph:ra,drawPoints:function(){var a=this,b=this.chart,c=a.options,d=b.renderer,e=c.animationLimit||250,f,g;o(a.points,function(h){var i=h.graphic,j;if(C(h.plotY)&&h.y!==null)f=h.shapeArgs,j=v(a.borderWidth)?{"stroke-width":a.borderWidth}:{},g=h.pointAttr[h.selected?"select":""]||a.pointAttr[""],i?(Sa(i),i.attr(j).attr(g)[b.pointCount\u25cf {series.name}
',pointFormat:"x: {point.x}
y: {point.y}
"}});ya=ma(P,{type:"scatter", -sorted:!1,requireSorting:!1,noSharedTooltip:!0,trackerGroups:["group","markerGroup","dataLabelsGroup"],takeOrdinalPosition:!1,kdDimensions:2,drawGraph:function(){this.options.lineWidth&&P.prototype.drawGraph.call(this)}});I.scatter=ya;W.pie=z(da,{borderColor:"#FFFFFF",borderWidth:1,center:[null,null],clip:!1,colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return this.y===null?void 0:this.point.name},x:0},ignoreHiddenPoint:!0,legendType:"point",marker:null,size:null,showInLegend:!1, -slicedOffset:10,states:{hover:{brightness:0.1,shadow:!1}},stickyTracking:!1,tooltip:{followPointer:!0}});da={type:"pie",isCartesian:!1,pointClass:ma(Ha,{init:function(){Ha.prototype.init.apply(this,arguments);var a=this,b;a.name=q(a.name,"Slice");b=function(b){a.slice(b.type==="select")};E(a,"select",b);E(a,"unselect",b);return a},setVisible:function(a,b){var c=this,d=c.series,e=d.chart,f=d.options.ignoreHiddenPoint,b=q(b,f);if(a!==c.visible){c.visible=c.options.visible=a=a===t?!c.visible:a;d.options.data[sa(c, -d.data)]=c.options;o(["graphic","dataLabel","connector","shadowGroup"],function(b){if(c[b])c[b][a?"show":"hide"](!0)});c.legendItem&&e.legend.colorizeItem(c,a);!a&&c.state==="hover"&&c.setState("");if(f)d.isDirty=!0;b&&e.redraw()}},slice:function(a,b,c){var d=this.series;$a(c,d.chart);q(b,!0);this.sliced=this.options.sliced=a=v(a)?a:!this.sliced;d.options.data[sa(this,d.data)]=this.options;a=a?this.slicedTranslation:{translateX:0,translateY:0};this.graphic.animate(a);this.shadowGroup&&this.shadowGroup.animate(a)}, -haloPath:function(a){var b=this.shapeArgs,c=this.series.chart;return this.sliced||!this.visible?[]:this.series.chart.renderer.symbols.arc(c.plotLeft+b.x,c.plotTop+b.y,b.r+a,b.r+a,{innerR:this.shapeArgs.r,start:b.start,end:b.end})}}),requireSorting:!1,directTouch:!0,noSharedTooltip:!0,trackerGroups:["group","dataLabelsGroup"],axisTypes:[],pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color"},animate:function(a){var b=this,c=b.points,d=b.startAngleRad;if(!a)o(c,function(a){var c= -a.graphic,g=a.shapeArgs;c&&(c.attr({r:a.startR||b.center[3]/2,start:d,end:d}),c.animate({r:g.r,start:g.start,end:g.end},b.options.animation))}),b.animate=null},updateTotals:function(){var a,b=0,c=this.points,d=c.length,e,f=this.options.ignoreHiddenPoint;for(a=0;a0&&(e.visible||!f)?e.y/b*100:0,e.total=b},generatePoints:function(){P.prototype.generatePoints.call(this);this.updateTotals()},translate:function(a){this.generatePoints(); -var b=0,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f,g,h,i=c.startAngle||0,j=this.startAngleRad=Aa/180*(i-90),i=(this.endAngleRad=Aa/180*(q(c.endAngle,i+360)-90))-j,k=this.points,l=c.dataLabels.distance,c=c.ignoreHiddenPoint,m,n=k.length,p;if(!a)this.center=a=this.getCenter();this.getX=function(b,c){h=Y.asin(G((b-a[1])/(a[2]/2+l),1));return a[0]+(c?-1:1)*ca(h)*(a[2]/2+l)};for(m=0;m1.5*Aa?h-=2*Aa:h<-Aa/2&&(h+=2*Aa);p.slicedTranslation={translateX:y(ca(h)*d),translateY:y(la(h)*d)};f=ca(h)*a[2]/2;g=la(h)*a[2]/2;p.tooltipPos=[a[0]+f*0.7,a[1]+g*0.7];p.half=h<-Aa/2||h>Aa/2?1:0;p.angle=h;e=G(e,l/2);p.labelPos=[a[0]+f+ca(h)*l,a[1]+g+la(h)*l,a[0]+f+ca(h)*e,a[1]+g+la(h)*e,a[0]+f,a[1]+g,l<0?"center":p.half?"right":"left",h]}},drawGraph:null,drawPoints:function(){var a=this,b=a.chart.renderer,c,d,e=a.options.shadow, -f,g,h,i;if(e&&!a.shadowGroup)a.shadowGroup=b.g("shadow").add(a.group);o(a.points,function(j){if(j.y!==null){d=j.graphic;h=j.shapeArgs;f=j.shadowGroup;g=j.pointAttr[j.selected?"select":""];if(!g.stroke)g.stroke=g.fill;if(e&&!f)f=j.shadowGroup=b.g("shadow").add(a.shadowGroup);c=j.sliced?j.slicedTranslation:{translateX:0,translateY:0};f&&f.attr(c);if(d)d.setRadialReference(a.center).attr(g).animate(A(h,c));else{i={"stroke-linejoin":"round"};if(!j.visible)i.visibility="hidden";j.graphic=d=b[j.shapeType](h).setRadialReference(a.center).attr(g).attr(i).attr(c).add(a.group).shadow(e, -f)}}})},searchPoint:ra,sortByAngle:function(a,b){a.sort(function(a,d){return a.angle!==void 0&&(d.angle-a.angle)*b})},drawLegendSymbol:aa.drawRectangle,getCenter:bc.getCenter,getSymbol:ra};da=ma(P,da);I.pie=da;P.prototype.drawDataLabels=function(){var a=this,b=a.options,c=b.cursor,d=b.dataLabels,e=a.points,f,g,h=a.hasRendered||0,i,j,k=q(d.defer,!0),l=a.chart.renderer;if(d.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(d),j=a.plotGroup("dataLabelsGroup","data-labels",k&&!h?"hidden": -"visible",d.zIndex||6),k&&(j.attr({opacity:+h}),h||E(a,"afterAnimate",function(){a.visible&&j.show();j[b.animation?"animate":"attr"]({opacity:1},{duration:200})})),g=d,o(e,function(e){var h,k=e.dataLabel,r,o,F=e.connector,u=!0,x,D={};f=e.dlOptions||e.options&&e.options.dataLabels;h=q(f&&f.enabled,g.enabled)&&e.y!==null;if(k&&!h)e.dataLabel=k.destroy();else if(h){d=z(g,f);x=d.style;h=d.rotation;r=e.getLabelConfig();i=d.format?La(d.format,r):d.formatter.call(r,d);x.color=q(d.color,x.color,a.color,"black"); -if(k)if(v(i))k.attr({text:i}),u=!1;else{if(e.dataLabel=k=k.destroy(),F)e.connector=F.destroy()}else if(v(i)){k={fill:d.backgroundColor,stroke:d.borderColor,"stroke-width":d.borderWidth,r:d.borderRadius||0,rotation:h,padding:d.padding,zIndex:1};if(x.color==="contrast")D.color=d.inside||d.distance<0||b.stacking?l.getContrast(e.color||a.color):"#000000";if(c)D.cursor=c;for(o in k)k[o]===t&&delete k[o];k=e.dataLabel=l[h?"text":"label"](i,0,-9999,d.shape,null,null,d.useHTML).attr(k).css(A(x,D)).add(j).shadow(d.shadow)}k&& -a.alignDataLabel(e,k,d,null,u)}})};P.prototype.alignDataLabel=function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=q(a.plotX,-9999),i=q(a.plotY,-9999),j=b.getBBox(),k=f.renderer.fontMetrics(c.style.fontSize).b,l=c.rotation,m=c.align,n=this.visible&&(a.series.forceDL||f.isInsidePlot(h,y(i),g)||d&&f.isInsidePlot(h,g?d.x+1:d.y+d.height-1,g)),p=q(c.overflow,"justify")==="justify";if(n)d=A({x:g?f.plotWidth-i:h,y:y(g?f.plotHeight-h:i),width:0,height:0},d),A(c,{width:j.width,height:j.height}),l?(p=!1,g=f.renderer.rotCorr(k, -l),g={x:d.x+c.x+d.width/2+g.x,y:d.y+c.y+{top:0,middle:0.5,bottom:1}[c.verticalAlign]*d.height},b[e?"attr":"animate"](g).attr({align:m}),h=(l+720)%360,h=h>180&&h<360,m==="left"?g.y-=h?j.height:0:m==="center"?(g.x-=j.width/2,g.y-=j.height/2):m==="right"&&(g.x-=j.width,g.y-=h?0:j.height)):(b.align(c,null,d),g=b.alignAttr),p?this.justifyDataLabel(b,c,g,j,d,e):q(c.crop,!0)&&(n=f.isInsidePlot(g.x,g.y)&&f.isInsidePlot(g.x+j.width,g.y+j.height)),c.shape&&!l&&b.attr({anchorX:a.plotX,anchorY:a.plotY});if(!n)Sa(b), -b.attr({y:-9999}),b.placed=!1};P.prototype.justifyDataLabel=function(a,b,c,d,e,f){var g=this.chart,h=b.align,i=b.verticalAlign,j,k,l=a.box?0:a.padding||0;j=c.x+l;if(j<0)h==="right"?b.align="left":b.x=-j,k=!0;j=c.x+d.width-l;if(j>g.plotWidth)h==="left"?b.align="right":b.x=g.plotWidth-j,k=!0;j=c.y+l;if(j<0)i==="bottom"?b.verticalAlign="top":b.y=-j,k=!0;j=c.y+d.height-l;if(j>g.plotHeight)i==="top"?b.verticalAlign="bottom":b.y=g.plotHeight-j,k=!0;if(k)a.placed=!f,a.align(b,null,e)};if(I.pie)I.pie.prototype.drawDataLabels= -function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=q(e.connectorPadding,10),g=q(e.connectorWidth,1),h=d.plotWidth,i=d.plotHeight,j,k,l=q(e.softConnector,!0),m=e.distance,n=a.center,p=n[2]/2,r=n[1],s=m>0,F,u,x,D=[[],[]],v,t,A,B,z,C=[0,0,0,0],H=function(a,b){return b.y-a.y};if(a.visible&&(e.enabled||a._hasPointLabels)){P.prototype.drawDataLabels.apply(a);o(b,function(a){if(a.dataLabel&&a.visible)D[a.half].push(a),a.dataLabel._pos=null});for(B=2;B--;){var E=[],L=[],K=D[B],I=K.length, -J;if(I){a.sortByAngle(K,B-0.5);for(z=b=0;!b&&K[z];)b=K[z]&&K[z].dataLabel&&(K[z].dataLabel.getBBox().height||21),z++;if(m>0){u=G(r+p+m,d.plotHeight);for(z=w(0,r-p-m);z<=u;z+=b)E.push(z);u=E.length;if(I>u){c=[].concat(K);c.sort(H);for(z=I;z--;)c[z].rank=z;for(z=I;z--;)K[z].rank>=u&&K.splice(z,1);I=K.length}for(z=0;z0){if(u=L.pop(),J=u.i,t=u.y,c>t&&E[J+1]!==null||ch-f&&(C[1]=w(y(v+u-h+f),C[1])),t- -b/2<0?C[0]=w(y(-t+b/2),C[0]):t+b/2>i&&(C[2]=w(y(t+b/2-i),C[2]))}}}if(Da(C)===0||this.verifyDataLabelOverflow(C))this.placeDataLabels(),s&&g&&o(this.points,function(b){j=b.connector;x=b.labelPos;if((F=b.dataLabel)&&F._pos&&b.visible)A=F._attr.visibility,v=F.connX,t=F.connY,k=l?["M",v+(x[6]==="left"?5:-5),t,"C",v,t,2*x[2]-x[4],2*x[3]-x[5],x[2],x[3],"L",x[4],x[5]]:["M",v+(x[6]==="left"?5:-5),t,"L",x[2],x[3],"L",x[4],x[5]],j?(j.animate({d:k}),j.attr("visibility",A)):b.connector=j=a.chart.renderer.path(k).attr({"stroke-width":g, -stroke:e.connectorColor||b.color||"#606060",visibility:A}).add(a.dataLabelsGroup);else if(j)b.connector=j.destroy()})}},I.pie.prototype.placeDataLabels=function(){o(this.points,function(a){var b=a.dataLabel;if(b&&a.visible)(a=b._pos)?(b.attr(b._attr),b[b.moved?"animate":"attr"](a),b.moved=!0):b&&b.attr({y:-9999})})},I.pie.prototype.alignDataLabel=ra,I.pie.prototype.verifyDataLabelOverflow=function(a){var b=this.center,c=this.options,d=c.center,e=c.minSize||80,f=e,g;d[0]!==null?f=w(b[2]-w(a[1],a[3]), -e):(f=w(b[2]-a[1]-a[3],e),b[0]+=(a[3]-a[1])/2);d[1]!==null?f=w(G(f,b[2]-w(a[0],a[2])),e):(f=w(G(f,b[2]-a[0]-a[2]),e),b[1]+=(a[0]-a[2])/2);fq(this.translatedThreshold, -g.yAxis.len)),j=q(c.inside,!!this.options.stacking);if(h){d=z(h);if(d.y<0)d.height+=d.y,d.y=0;h=d.y+d.height-g.yAxis.len;h>0&&(d.height-=h);f&&(d={x:g.yAxis.len-d.y-d.height,y:g.xAxis.len-d.x-d.width,width:d.height,height:d.width});if(!j)f?(d.x+=i?0:d.width,d.width=0):(d.y+=i?d.height:0,d.height=0)}c.align=q(c.align,!f||j?"center":i?"right":"left");c.verticalAlign=q(c.verticalAlign,f||j?"middle":i?"top":"bottom");P.prototype.alignDataLabel.call(this,a,b,c,d,e)};(function(a){var b=a.Chart,c=a.each, -d=a.pick,e=a.addEvent;b.prototype.callbacks.push(function(a){function b(){var e=[];c(a.series,function(a){var b=a.options.dataLabels,f=a.dataLabelCollections||["dataLabel"];(b.enabled||a._hasPointLabels)&&!b.allowOverlap&&a.visible&&c(f,function(b){c(a.points,function(a){if(a[b])a[b].labelrank=d(a.labelrank,a.shapeArgs&&a.shapeArgs.height),e.push(a[b])})})});a.hideOverlappingLabels(e)}b();e(a,"redraw",b)});b.prototype.hideOverlappingLabels=function(a){var b=a.length,d,e,j,k,l,m,n,p,r;for(e=0;el.x+n.translateX+(j.width-r)||m.x+p.translateX+(k.width-r)l.y+n.translateY+(j.height-r)||m.y+p.translateY+(k.height-r)h;if(b.series.length&&(i||l>G(k.dataMin,k.min))&&(!i||jd;f[j]g*5||p){if(f[j]>q){for(k=a.call(this,b,f[i],f[j],e);k.length&&k[0]<=q;)k.shift();k.length&&(q=k[k.length-1]);o=o.concat(k)}i=j+1}if(p)break}a=k.info;if(h&&a.unitRange<=M.hour){j=o.length-1;for(i=1;id?a-1:a;for(u=void 0;h--;)i=j[h],d=u-i,u&&d2){d=b[1]-b[0];for(h=a-1;h--&&!c;)b[h+1]-b[h]!==d&&(c=!0);if(!this.options.keepOrdinalPadding&&(b[0]-f>d||g-b[b.length-1]>d))c=!0}c?(this.ordinalPositions=b,d=this.val2lin(w(f,b[0]),!0),h=w(this.val2lin(G(g,b[b.length-1]),!0),1),this.ordinalSlope=g=(g-f)/(h-d),this.ordinalOffset=f-d*g):this.ordinalPositions=this.ordinalSlope=this.ordinalOffset=t}this.isOrdinal=e&&c;this.groupIntervalFactor= -null},val2lin:function(a,b){var c=this.ordinalPositions,d;if(c){var e=c.length,f;for(d=e;d--;)if(c[d]===a){f=d;break}for(d=e-1;d--;)if(a>c[d]||d===0){c=(a-c[d])/(c[d+1]-c[d]);f=d+c;break}d=b?f:this.ordinalSlope*(f||0)+this.ordinalOffset}else d=a;return d},lin2val:function(a,b){var c=this.ordinalPositions;if(c){var d=this.ordinalSlope,e=this.ordinalOffset,f=c.length-1,g,h;if(b)a<0?a=c[0]:a>f?a=c[f]:(f=V(a),h=a-f);else for(;f--;)if(g=d*f+e,a>=g){d=d*(f+1)+e;h=(a-g)/(d-g);break}c=h!==t&&c[f]!==t?c[f]+ -(h?h*(c[f+1]-c[f]):0):a}else c=a;return c},getExtendedPositions:function(){var a=this.chart,b=this.series[0].currentDataGrouping,c=this.ordinalIndex,d=b?b.count+b.unitName:"raw",e=this.getExtremes(),f,g;if(!c)c=this.ordinalIndex={};if(!c[d])f={series:[],getExtremes:function(){return{min:e.dataMin,max:e.dataMax}},options:{ordinal:!0},val2lin:J.prototype.val2lin},o(this.series,function(c){g={xAxis:f,xData:c.xData,chart:a,destroyGroupedData:ra};g.options={dataGrouping:b?{enabled:!0,forced:!0,approximation:"open", -units:[[b.unitName,[b.count]]]}:{enabled:!1}};c.processData.apply(g);f.series.push(g)}),this.beforeSetTickPositions.apply(f),c[d]=f.ordinalPositions;return c[d]},getGroupIntervalFactor:function(a,b,c){var d,c=c.processedXData,e=c.length,f=[];d=this.groupIntervalFactor;if(!d){for(d=0;d1)k&&o(k,function(a){a.setState()}),f<0?(k=m,p=c.ordinalPositions?c:m):(k= -c.ordinalPositions?c:m,p=m),m=p.ordinalPositions,h>m[m.length-1]&&m.push(h),this.fixedRange=j-i,f=c.toFixedRange(null,null,l.apply(k,[n.apply(k,[i,!0])+f,!0]),l.apply(p,[n.apply(p,[j,!0])+f,!0])),f.min>=G(g.dataMin,i)&&f.max<=w(h,j)&&c.setExtremes(f.min,f.max,!0,!1,{trigger:"pan"}),this.mouseDownX=d,N(this.container,{cursor:"move"})}else e=!0}else e=!0;e&&a.apply(this,Array.prototype.slice.call(arguments,1))});P.prototype.gappedPath=function(){var a=this.options.gapSize,b=this.points.slice(),c=b.length- -1;if(a&&c>0)for(;c--;)b[c+1].x-b[c].x>this.closestPointRange*a&&b.splice(c+1,0,{isNull:!0});return this.getGraphPath(b)};(function(a){a(B)})(function(a){function b(){return Array.prototype.slice.call(arguments,1)}function c(a){a.apply(this);this.drawBreaks(this.xAxis,["x"]);this.drawBreaks(this.yAxis,d(this.pointArrayMap,["y"]))}var d=a.pick,e=a.wrap,f=a.each,g=a.extend,h=a.fireEvent,i=a.Axis,j=a.Series;g(i.prototype,{isInBreak:function(a,b){var c=a.repeat||Infinity,d=a.from,e=a.to-a.from,c=b>=d? -(b-d)%c:c-(d-b)%c;return a.inclusive?c<=e:c=a)break;else if(d.isInBreak(c,a)){b-=a-c.from;break}return b};this.lin2val=function(a){var b,c;for(c=0;c=a)break;else b.tok;)l-=f;for(;lb.to||i>b.from&&ob.from&&ob.from&&o>b.to&&o=c[0])break;for(;q<=j;q++){for(;c[1]!==t&&a[q]>=c[1]||q===j;)if(k=c.shift(),l=d.apply(0,n),l!==t&&(g.push(k),h.push(l),i.push({start:v,length:n[0].length})),v=q,n[0]=[],n[1]=[],n[2]=[],n[3]=[],q===j)break;if(q===j)break;if(p){k=this.cropStart+q;k=e&&e[k]||this.pointClass.prototype.applyOptions.apply({series:this}, -[f[k]]);var u;for(l=0;l0;)f[b]+= -g/2;f[0]=Math.max(f[0],c)}this.currentDataGrouping=i.info;this.closestPointRange=i.info.totalRange;this.groupMap=e[2];if(v(f[0])&&f[0]this.chart.plotSizeX/d||b&&f.forced)e=!0;return e?d:0};J.prototype.setDataGrouping=function(a,b){var c,b=q(b,!0);a||(a={forced:!1,units:null});if(this instanceof J)for(c=this.series.length;c--;)this.series[c].update({dataGrouping:a}, -!1);else o(this.chart.options.series,function(b){b.dataGrouping=a},!1);b&&this.chart.redraw()};W.ohlc=z(W.column,{lineWidth:1,tooltip:{pointFormat:'\u25cf {series.name}
Open: {point.open}
High: {point.high}
Low: {point.low}
Close: {point.close}
'},states:{hover:{lineWidth:3}},threshold:null});da=ma(I.column,{type:"ohlc",pointArrayMap:["open","high","low","close"],toYData:function(a){return[a.open,a.high,a.low,a.close]},pointValKey:"high", -pointAttrToOptions:{stroke:"color","stroke-width":"lineWidth"},upColorProp:"stroke",getAttribs:function(){I.column.prototype.getAttribs.apply(this,arguments);var a=this.options,b=a.states,a=a.upColor||this.color,c=z(this.pointAttr),d=this.upColorProp;c[""][d]=a;c.hover[d]=b.hover.upColor||a;c.select[d]=b.select.upColor||a;o(this.points,function(a){if(a.open"},threshold:null,y:-30});I.flags=ma(I.column,{type:"flags",sorted:!1,noSharedTooltip:!0,allowDG:!1,takeOrdinalPosition:!1,trackerGroups:["markerGroup"],forceCrop:!0,init:P.prototype.init,pointAttrToOptions:{fill:"fillColor",stroke:"color","stroke-width":"lineWidth",r:"radius"},translate:function(){I.column.prototype.translate.apply(this);var a=this.options,b=this.chart,c=this.points,d=c.length-1,e,f,g=a.onSeries;e=g&&b.get(g); -var a=a.onKey||"y",g=e&&e.options.step,h=e&&e.points,i=h&&h.length,j=this.xAxis,k=j.getExtremes(),l,m,n;if(e&&e.visible&&i){e=e.currentDataGrouping;m=h[i-1].x+(e?e.totalRange:0);c.sort(function(a,b){return a.x-b.x});for(a="plot"+a[0].toUpperCase()+a.substr(1);i--&&c[d];)if(e=c[d],l=h[i],l.x<=e.x&&l[a]!==void 0){if(e.x<=m)e.plotY=l[a],l.x=k.min&&a.x<=k.max?a.plotY= -b.chartHeight-j.bottom-(j.opposite?j.height:0)+j.offset-b.plotTop:a.shapeArgs={};if((f=c[d-1])&&f.plotX===a.plotX){if(f.stackIndex===t)f.stackIndex=0;e=f.stackIndex+1}a.stackIndex=e})},drawPoints:function(){var a,b=this.pointAttr[""],c=this.points,d=this.chart,e=d.renderer,f,g,h=this.options,i=h.y,j,k,l,m,n,p,o=this.yAxis;for(k=c.length;k--;)if(l=c[k],a=l.plotX>this.xAxis.len,f=l.plotX,f>0&&(f-=q(l.lineWidth,h.lineWidth)%2),m=l.stackIndex,j=l.options.shape||h.shape,g=l.plotY,g!==t&&(g=l.plotY+i-(m!== -t&&m*h.stackDistance)),n=m?t:l.plotX,p=m?t:l.plotY,m=l.graphic,g!==t&&f>=0&&!a)a=l.pointAttr[l.selected?"select":""]||b,m?m.attr({x:f,y:g,r:a.r,anchorX:n,anchorY:p}):l.graphic=e.label(l.options.title||h.title||"A",f,g,j,n,p,h.useHTML).css(z(h.style,l.style)).attr(a).attr({align:j==="flag"?"left":"center",width:h.width,height:h.height}).add(this.markerGroup).shadow(h.shadow),l.tooltipPos=d.inverted?[o.len+o.pos-d.plotLeft-g,this.xAxis.len-f]:[f,g];else if(m)l.graphic=m.destroy()},drawTracker:function(){var a= -this.points;mb.drawTrackerPoint.apply(this);o(a,function(b){var c=b.graphic;c&&E(c.element,"mouseover",function(){if(b.stackIndex>0&&!b.raised)b._y=c.y,c.attr({y:b._y-8}),b.raised=!0;o(a,function(a){if(a!==b&&a.raised&&a.graphic)a.graphic.attr({y:a._y}),a.raised=!1})})})},animate:ra,buildKDTree:ra,setClip:ra});vb.flag=function(a,b,c,d,e){return["M",e&&e.anchorX||a,e&&e.anchorY||b,"L",a,b+d,a,b,a+c,b,a+c,b+d,a,b+d,"Z"]};o(["circle","square"],function(a){vb[a+"pin"]=function(b,c,d,e,f){var g=f&&f.anchorX, -f=f&&f.anchorY;a==="circle"&&e>d&&(b-=y((e-d)/2),d=e);b=vb[a](b,c,d,e);g&&f&&b.push("M",g,c>f?c:c+e,"L",g,f);return b}});Xa===B.VMLRenderer&&o(["flag","circlepin","squarepin"],function(a){lb.prototype.symbols[a]=vb[a]});var da=[].concat(Yb),wb=function(a){var b=Fa(arguments,function(a){return C(a)});if(b.length)return Math[a].apply(0,b)};da[4]=["day",[1,2,3,4]];da[5]=["week",[1,2,3]];A(R,{navigator:{handles:{backgroundColor:"#ebe7e8",borderColor:"#b2b1b6"},height:40,margin:25,maskFill:"rgba(128,179,236,0.3)", -maskInside:!0,outlineColor:"#b2b1b6",outlineWidth:1,series:{type:I.areaspline===t?"line":"areaspline",color:"#4572A7",compare:null,fillOpacity:0.05,dataGrouping:{approximation:"average",enabled:!0,groupPixelWidth:2,smoothed:!0,units:da},dataLabels:{enabled:!1,zIndex:2},id:"highcharts-navigator-series",lineColor:null,lineWidth:1,marker:{enabled:!1},pointRange:0,shadow:!1,threshold:null},xAxis:{tickWidth:0,lineWidth:0,gridLineColor:"#EEE",gridLineWidth:1,tickPixelInterval:200,labels:{align:"left",style:{color:"#888"}, -x:3,y:-4},crosshair:!1},yAxis:{gridLineWidth:0,startOnTick:!1,endOnTick:!1,minPadding:0.1,maxPadding:0.1,labels:{enabled:!1},crosshair:!1,title:{text:null},tickWidth:0}},scrollbar:{height:jb?20:14,barBackgroundColor:"#bfc8d1",barBorderRadius:0,barBorderWidth:1,barBorderColor:"#bfc8d1",buttonArrowColor:"#666",buttonBackgroundColor:"#ebe7e8",buttonBorderColor:"#bbb",buttonBorderRadius:0,buttonBorderWidth:1,minWidth:6,rifleColor:"#666",trackBackgroundColor:"#eeeeee",trackBorderColor:"#eeeeee",trackBorderWidth:1, -liveRedraw:ja&&!jb}});Gb.prototype={drawHandle:function(a,b){var c=this.chart.renderer,d=this.elementsToDestroy,e=this.handles,f=this.navigatorOptions.handles,f={fill:f.backgroundColor,stroke:f.borderColor,"stroke-width":1},g;this.rendered||(e[b]=c.g("navigator-handle-"+["left","right"][b]).css({cursor:"ew-resize"}).attr({zIndex:10-b}).add(),g=c.rect(-4.5,0,9,16,0,1).attr(f).add(e[b]),d.push(g),g=c.path(["M",-1.5,4,"L",-1.5,12,"M",0.5,4,"L",0.5,12]).attr(f).add(e[b]),d.push(g));e[b][this.rendered? -"animate":"attr"]({translateX:this.scrollerLeft+this.scrollbarHeight+parseInt(a,10),translateY:this.top+this.height/2-8})},drawScrollbarButton:function(a){var b=this.chart.renderer,c=this.elementsToDestroy,d=this.scrollbarButtons,e=this.scrollbarHeight,f=this.scrollbarOptions,g;this.rendered||(d[a]=b.g().add(this.scrollbarGroup),g=b.rect(-0.5,-0.5,e+1,e+1,f.buttonBorderRadius,f.buttonBorderWidth).attr({stroke:f.buttonBorderColor,"stroke-width":f.buttonBorderWidth,fill:f.buttonBackgroundColor}).add(d[a]), -c.push(g),g=b.path(["M",e/2+(a?-1:1),e/2-3,"L",e/2+(a?-1:1),e/2+3,e/2+(a?2:-2),e/2]).attr({fill:f.buttonArrowColor}).add(d[a]),c.push(g));a&&d[a].attr({translateX:this.scrollerWidth-e})},render:function(a,b,c,d){var e=this.chart,f=e.renderer,g,h,i,j,k=this.scrollbarGroup,l=this.navigatorGroup,m=this.scrollbar,l=this.xAxis,n=this.scrollbarTrack,p=this.scrollbarHeight,o=this.scrollbarEnabled,s=this.navigatorOptions,t=this.scrollbarOptions,u=t.minWidth,x=this.height,D=this.top,z=this.navigatorEnabled, -A=s.outlineWidth,B=A/2,E=0,H=this.outlineHeight,K=t.barBorderRadius,J=t.barBorderWidth,I=D+B,L=this.rendered;if(C(a)&&C(b)&&(!this.hasDragged||v(c))){this.navigatorLeft=g=q(l.left,e.plotLeft+p);this.navigatorWidth=h=q(l.len,e.plotWidth-2*p);this.scrollerLeft=i=g-p;this.scrollerWidth=j=j=h+2*p;c=q(c,l.translate(a));d=q(d,l.translate(b));if(!C(c)||S(c)===Infinity)c=0,d=j;if(!(l.translate(d,!0)-l.translate(c,!0)12?"visible":"hidden"})[f]({d:["M", -u-3,p/4,"L",u-3,2*p/3,"M",u,p/4,"L",u,2*p/3,"M",u+3,p/4,"L",u+3,2*p/3]});this.scrollbarPad=E;this.rendered=!0}}},addEvents:function(){var a=this.chart,b=a.container,c=this.mouseDownHandler,d=this.mouseMoveHandler,e=this.mouseUpHandler,f;f=[[b,"mousedown",c],[b,"mousemove",d],[H,"mouseup",e]];cb&&f.push([b,"touchstart",c],[b,"touchmove",d],[H,"touchend",e]);o(f,function(a){E.apply(null,a)});this._events=f;this.series&&E(this.series.xAxis,"foundExtremes",function(){a.scroller.modifyNavigatorAxisExtremes()}); -E(a,"redraw",function(){var a=this.scroller,b;if(a)(b=a.baseSeries.xAxis)&&a.render(b.min,b.max)})},removeEvents:function(){o(this._events,function(a){T.apply(null,a)});this._events=t;this.navigatorEnabled&&this.baseSeries&&T(this.baseSeries,"updatedData",this.updatedDataHandler)},init:function(){var a=this,b=a.chart,c,d,e=a.scrollbarHeight,f=a.navigatorOptions,g=a.height,h=a.top,i,j=a.baseSeries;a.mouseDownHandler=function(d){var d=b.pointer.normalize(d),e=a.zoomedMin,f=a.zoomedMax,h=a.top,j=a.scrollbarHeight, -k=a.scrollerLeft,l=a.scrollerWidth,o=a.navigatorLeft,q=a.navigatorWidth,v=a.scrollbarPad,t=a.range,w=d.chartX,y=d.chartY,d=b.xAxis[0],z,A=jb?10:7;if(y>h&&yo+e-v&&wk&&wk+ -l-j?e+t*0.2:w=q)f=q-t,z=a.getUnionExtremes().dataMax;if(f!==e)a.fixedWidth=t,e=c.toFixedRange(f,f+t,null,z),d.setExtremes(e.min,e.max,!0,!1,{trigger:"navigator"})}};a.mouseMoveHandler=function(c){var d=a.scrollbarHeight,e=a.navigatorLeft,f=a.navigatorWidth,g=a.scrollerLeft,h=a.scrollerWidth,j=a.range,k,l;if(!c.touches||c.touches[0].pageX!==0)c=b.pointer.normalize(c),k=c.chartX,kg+h-d&&(k=g+h-d),a.grabbedLeft?(l=!0,a.render(0,0,k-e,a.otherHandlePos)):a.grabbedRight? -(l=!0,a.render(0,0,a.otherHandlePos,k-e)):a.grabbedCenter&&(l=!0,kf+i-j&&(k=f+i-j),a.render(0,0,k-i,k-i+j)),l&&a.scrollbarOptions.liveRedraw&&setTimeout(function(){a.mouseUpHandler(c)},0),a.hasDragged=l};a.mouseUpHandler=function(d){var e,f;if(a.hasDragged){if(a.zoomedMin===a.otherHandlePos)e=a.fixedExtreme;else if(a.zoomedMax===a.otherHandlePos)f=a.fixedExtreme;if(a.zoomedMax===a.navigatorWidth)f=a.getUnionExtremes().dataMax;e=c.toFixedRange(a.zoomedMin,a.zoomedMax,e,f);v(e.min)&&b.xAxis[0].setExtremes(e.min, -e.max,!0,!1,{trigger:"navigator",triggerOp:"navigator-drag",DOMEvent:d})}if(d.type!=="mousemove")a.grabbedLeft=a.grabbedRight=a.grabbedCenter=a.fixedWidth=a.fixedExtreme=a.otherHandlePos=a.hasDragged=i=null};var k=b.xAxis.length,l=b.yAxis.length;b.extraBottomMargin=a.outlineHeight+f.margin;a.navigatorEnabled?(a.xAxis=c=new J(b,z({breaks:j&&j.xAxis.options.breaks,ordinal:j&&j.xAxis.options.ordinal},f.xAxis,{id:"navigator-x-axis",isX:!0,type:"datetime",index:k,height:g,offset:0,offsetLeft:e,offsetRight:-e, -keepOrdinalPadding:!0,startOnTick:!1,endOnTick:!1,minPadding:0,maxPadding:0,zoomEnabled:!1})),a.yAxis=d=new J(b,z(f.yAxis,{id:"navigator-y-axis",alignTicks:!1,height:g,offset:0,index:l,zoomEnabled:!1})),j||f.series.data?a.addBaseSeries():b.series.length===0&&U(b,"redraw",function(c,d){if(b.series.length>0&&!a.series)a.setBaseSeries(),b.redraw=c;c.call(b,d)})):a.xAxis=c={translate:function(a,c){var d=b.xAxis[0],f=d.getExtremes(),g=b.plotWidth-2*e,h=wb("min",d.options.min,f.dataMin),d=wb("max",d.options.max, -f.dataMax)-h;return c?a*d/g+h:g*(a-h)/d},toFixedRange:J.prototype.toFixedRange};if(j&&j.xAxis&&this.navigatorOptions.adaptToUpdatedData!==!1)E(j,"updatedData",this.updatedDataHandler),E(j.xAxis,"foundExtremes",function(){j.xAxis&&this.chart.scroller.modifyBaseAxisExtremes()}),j.userOptions.events=A(j.userOptions.event,{updatedData:this.updatedDataHandler});U(b,"getMargins",function(b){var e=this.legend,f=e.options;b.apply(this,[].slice.call(arguments,1));a.top=h=a.navigatorOptions.top||this.chartHeight- -a.height-a.scrollbarHeight-this.spacing[2]-(f.verticalAlign==="bottom"&&f.enabled&&!f.floating?e.legendHeight+q(f.margin,10):0);if(c&&d)c.options.top=d.options.top=h,c.setAxisSize(),d.setAxisSize()});a.addEvents()},getUnionExtremes:function(a){var b=this.chart.xAxis[0],c=this.xAxis,d=c.options,e=b.options,f;if(!a||b.dataMin!==null)f={dataMin:q(d&&d.min,wb("min",e.min,b.dataMin,c.dataMin,c.min)),dataMax:q(d&&d.max,wb("max",e.max,b.dataMax,c.dataMax,c.max))};return f},setBaseSeries:function(a){var b= -this.chart,a=a||b.options.navigator.baseSeries;this.series&&this.series.remove();this.baseSeries=b.series[a]||typeof a==="string"&&b.get(a)||b.series[0];this.xAxis&&this.addBaseSeries()},addBaseSeries:function(){var a=this.baseSeries,b=a?a.options:{},a=b.data,c=this.navigatorOptions.series,d;d=c.data;this.hasNavigatorData=!!d;b=z(b,c,{enableMouseTracking:!1,group:"nav",padXAxis:!1,xAxis:"navigator-x-axis",yAxis:"navigator-y-axis",name:"Navigator",showInLegend:!1,stacking:!1,isInternal:!0,visible:!0}); -b.data=d||a.slice(0);this.series=this.chart.initSeries(b)},modifyNavigatorAxisExtremes:function(){var a=this.xAxis,b;if(a.getExtremes&&(b=this.getUnionExtremes(!0))&&(b.dataMin!==a.min||b.dataMax!==a.max))a.min=b.dataMin,a.max=b.dataMax},modifyBaseAxisExtremes:function(){var a=this.baseSeries.xAxis,b=a.getExtremes(),c=b.dataMin,d=b.dataMax,b=b.max-b.min,e=this.stickToMin,f=this.stickToMax,g,h,i=this.series,j=!!a.setExtremes;e&&(h=c,g=h+b);f&&(g=d,e||(h=w(g-b,i?i.xData[0]:-Number.MAX_VALUE)));if(j&& -(e||f)&&C(h))a.min=a.userMin=h,a.max=a.userMax=g;this.stickToMin=this.stickToMax=null},updatedDataHandler:function(){var a=this.chart.scroller,b=a.baseSeries,c=a.series;a.stickToMin=b.xAxis.min<=b.xData[0];a.stickToMax=a.zoomedMax>=a.navigatorWidth;if(c&&!a.hasNavigatorData&&(c.options.pointStart=b.xData[0],c.setData(b.options.data,!1),c.graph&&b.graph))c.graph.shift=b.graph.shift},destroy:function(){this.removeEvents();o([this.xAxis,this.yAxis,this.leftShade,this.rightShade,this.outline,this.scrollbarTrack, -this.scrollbarRifles,this.scrollbarGroup,this.scrollbar],function(a){a&&a.destroy&&a.destroy()});this.xAxis=this.yAxis=this.leftShade=this.rightShade=this.outline=this.scrollbarTrack=this.scrollbarRifles=this.scrollbarGroup=this.scrollbar=null;o([this.scrollbarButtons,this.handles,this.elementsToDestroy],function(a){Pa(a)})}};B.Scroller=Gb;U(J.prototype,"zoom",function(a,b,c){var d=this.chart,e=d.options,f=e.chart.zoomType,g=e.navigator,e=e.rangeSelector,h;if(this.isXAxis&&(g&&g.enabled||e&&e.enabled))if(f=== -"x")d.resetZoomButton="blocked";else if(f==="y")h=!1;else if(f==="xy")d=this.previousZoom,v(b)?this.previousZoom=[this.min,this.max]:d&&(b=d[0],c=d[1],delete this.previousZoom);return h!==t?h:a.call(this,b,c)});U(Ba.prototype,"init",function(a,b,c){E(this,"beforeRender",function(){var a=this.options;if(a.navigator.enabled||a.scrollbar.enabled)this.scroller=new Gb(this)});a.call(this,b,c)});U(P.prototype,"addPoint",function(a,b,c,d,e){var f=this.options.turboThreshold;f&&this.xData.length>f&&ea(b)&& -!Ja(b)&&this.chart.scroller&&ga(20,!0);a.call(this,b,c,d,e)});A(R,{rangeSelector:{buttonTheme:{width:28,height:18,fill:"#f7f7f7",padding:2,r:0,"stroke-width":0,style:{color:"#444",cursor:"pointer",fontWeight:"normal"},zIndex:7,states:{hover:{fill:"#e7e7e7"},select:{fill:"#e7f0f9",style:{color:"black",fontWeight:"bold"}}}},height:35,inputPosition:{align:"right"},labelStyle:{color:"#666"}}});R.lang=z(R.lang,{rangeSelectorZoom:"Zoom",rangeSelectorFrom:"From",rangeSelectorTo:"To"});Hb.prototype={clickButton:function(a, -b){var c=this,d=c.selected,e=c.chart,f=c.buttons,g=c.buttonOptions[a],h=e.xAxis[0],i=e.scroller&&e.scroller.getUnionExtremes()||h||{},j=i.dataMin,k=i.dataMax,l,m=h&&y(G(h.max,q(k,h.max))),n=g.type,p,i=g._range,r,s,v,u=g.dataGrouping;if(!(j===null||k===null||a===c.selected)){e.fixedRange=i;if(u)this.forcedDataGrouping=!0,J.prototype.setDataGrouping.call(h||{chart:this.chart},u,!1);if(n==="month"||n==="year")if(h){if(n={range:g,max:m,dataMin:j,dataMax:k},l=h.minFromRange.call(n),C(n.newMax))m=n.newMax}else i= -g;else if(i)l=w(m-i,j),m=G(l+i,k);else if(n==="ytd")if(h){if(k===t)j=Number.MAX_VALUE,k=Number.MIN_VALUE,o(e.series,function(a){a=a.xData;j=G(a[0],j);k=w(a[a.length-1],k)}),b=!1;m=new ba(k);l=m.getFullYear();l=r=w(j||0,ba.UTC(l,0,1));m=m.getTime();m=G(k||m,m)}else{E(e,"beforeRender",function(){c.clickButton(a)});return}else n==="all"&&h&&(l=j,m=k);f[d]&&f[d].setState(0);if(f[a])f[a].setState(2),c.lastSelected=a;h?(h.setExtremes(l,m,q(b,1),0,{trigger:"rangeSelectorButton",rangeSelectorButton:g}),c.setSelected(a)): -(p=e.options.xAxis[0],v=p.range,p.range=i,s=p.min,p.min=r,c.setSelected(a),E(e,"load",function(){p.range=v;p.min=s}))}},setSelected:function(a){this.selected=this.options.selected=a},defaultButtons:[{type:"month",count:1,text:"1m"},{type:"month",count:3,text:"3m"},{type:"month",count:6,text:"6m"},{type:"ytd",text:"YTD"},{type:"year",count:1,text:"1y"},{type:"all",text:"All"}],init:function(a){var b=this,c=a.options.rangeSelector,d=c.buttons||[].concat(b.defaultButtons),e=c.selected,f=b.blurInputs= -function(){var a=b.minInput,c=b.maxInput;a&&a.blur&&O(a,"blur");c&&c.blur&&O(c,"blur")};b.chart=a;b.options=c;b.buttons=[];a.extraTopMargin=c.height;b.buttonOptions=d;E(a.container,"mousedown",f);E(a,"resize",f);o(d,b.computeButtonRange);e!==t&&d[e]&&this.clickButton(e,!1);E(a,"load",function(){E(a.xAxis[0],"setExtremes",function(c){this.max-this.min!==a.fixedRange&&c.trigger!=="rangeSelectorButton"&&c.trigger!=="updatedData"&&b.forcedDataGrouping&&this.setDataGrouping(!1,!1)});E(a.xAxis[0],"afterSetExtremes", -function(){b.updateButtonStates(!0)})})},updateButtonStates:function(a){var b=this,c=this.chart,d=c.xAxis[0],e=c.scroller&&c.scroller.getUnionExtremes()||d,f=e.dataMin,g=e.dataMax,h=b.selected,i=b.options.allButtonsEnabled,j=b.buttons;a&&c.fixedRange!==y(d.max-d.min)&&(j[h]&&j[h].setState(0),b.setSelected(null));o(b.buttonOptions,function(a,e){var m=y(d.max-d.min),n=a._range,o=a.type,q=a.count||1,s=n>g-f,t=n=g-f&&j[e].state!==2,v=a.type==="ytd"&&na("%Y",f)=== -na("%Y",g),w=c.renderer.forExport&&e===h,n=n===m,z=!d.hasVisibleSeries;if((o==="month"||o==="year")&&m>={month:28,year:365}[o]*864E5*q&&m<={month:31,year:366}[o]*864E5*q)n=!0;w||n&&e!==h&&e===b.lastSelected?(b.setSelected(e),j[e].setState(2)):!i&&(s||t||u||v||z)?j[e].setState(3):j[e].state===3&&j[e].setState(0)})},computeButtonRange:function(a){var b=a.type,c=a.count||1,d={millisecond:1,second:1E3,minute:6E4,hour:36E5,day:864E5,week:6048E5};if(d[b])a._range=d[b]*c;else if(b==="month"||b==="year")a._range= -{month:30,year:365}[b]*864E5*c},setInputValue:function(a,b){var c=this.chart.options.rangeSelector;if(v(b))this[a+"Input"].HCTime=b;this[a+"Input"].value=na(c.inputEditDateFormat||"%Y-%m-%d",this[a+"Input"].HCTime);this[a+"DateBox"].attr({text:na(c.inputDateFormat||"%b %e, %Y",this[a+"Input"].HCTime)})},showInput:function(a){var b=this.inputGroup,c=this[a+"DateBox"];N(this[a+"Input"],{left:b.translateX+c.x+"px",top:b.translateY+"px",width:c.width-2+"px",height:c.height-2+"px",border:"2px solid silver"})}, -hideInput:function(a){N(this[a+"Input"],{border:0,width:"1px",height:"1px"});this.setInputValue(a)},drawInput:function(a){function b(){var a=j.value,b=(g.inputDateParser||ba.parse)(a),e=d.xAxis[0],f=e.dataMin,h=e.dataMax;if(b!==j.previousValue)j.previousValue=b,C(b)||(b=a.split("-"),b=ba.UTC(K(b[0]),K(b[1])-1,K(b[2]))),C(b)&&(R.global.useUTC||(b+=(new ba).getTimezoneOffset()*6E4),i?b>c.maxInput.HCTime?b=t:bh&&(b=h),b!==t&&d.xAxis[0].setExtremes(i?b:e.min,i?e.max: -b,t,t,{trigger:"rangeSelectorInput"}))}var c=this,d=c.chart,e=d.renderer.style,f=d.renderer,g=d.options.rangeSelector,h=c.div,i=a==="min",j,k,l=this.inputGroup;this[a+"Label"]=k=f.label(R.lang[i?"rangeSelectorFrom":"rangeSelectorTo"],this.inputGroup.offset).attr({padding:2}).css(z(e,g.labelStyle)).add(l);l.offset+=k.width+5;this[a+"DateBox"]=f=f.label("",l.offset).attr({padding:2,width:g.inputBoxWidth||90,height:g.inputBoxHeight||17,stroke:g.inputBoxBorderColor||"silver","stroke-width":1}).css(z({textAlign:"center", -color:"#444"},e,g.inputStyle)).on("click",function(){c.showInput(a);c[a+"Input"].focus()}).add(l);l.offset+=f.width+(i?10:0);this[a+"Input"]=j=fa("input",{name:a,className:"highcharts-range-selector",type:"text"},A({position:"absolute",border:0,width:"1px",height:"1px",padding:0,textAlign:"center",fontSize:e.fontSize,fontFamily:e.fontFamily,left:"-9em",top:d.plotTop+"px"},g.inputStyle),h);j.onfocus=function(){c.showInput(a)};j.onblur=function(){c.hideInput(a)};j.onchange=b;j.onkeypress=function(a){a.keyCode=== -13&&b()}},getPosition:function(){var a=this.chart,b=a.options.rangeSelector,a=q((b.buttonPosition||{}).y,a.plotTop-a.axisOffset[0]-b.height);return{buttonTop:a,inputTop:a-10}},render:function(a,b){var c=this,d=c.chart,e=d.renderer,f=d.container,g=d.options,h=g.exporting&&g.exporting.enabled!==!1&&g.navigation&&g.navigation.buttonOptions,i=g.rangeSelector,j=c.buttons,g=R.lang,k=c.div,k=c.inputGroup,l=i.buttonTheme,m=i.buttonPosition||{},n=i.inputEnabled,p=l&&l.states,r=d.plotLeft,s,t=this.getPosition(), -u=c.group,w=c.rendered;if(!w&&(c.group=u=e.g("range-selector-buttons").add(),c.zoomText=e.text(g.rangeSelectorZoom,q(m.x,r),15).css(i.labelStyle).add(u),s=q(m.x,r)+c.zoomText.getBBox().width+5,o(c.buttonOptions,function(a,b){j[b]=e.button(a.text,s,0,function(){c.clickButton(b);c.isActive=!0},l,p&&p.hover,p&&p.select,p&&p.disabled).css({textAlign:"center"}).add(u);s+=j[b].width+q(i.buttonSpacing,5);c.selected===b&&j[b].setState(2)}),c.updateButtonStates(),n!==!1))c.div=k=fa("div",null,{position:"relative", -height:0,zIndex:1}),f.parentNode.insertBefore(k,f),c.inputGroup=k=e.g("input-group").add(),k.offset=0,c.drawInput("min"),c.drawInput("max");u[w?"animate":"attr"]({translateY:t.buttonTop});n!==!1&&(k.align(A({y:t.inputTop,width:k.offset,x:h&&t.inputTop<(h.y||0)+h.height-d.spacing[0]?-40:0},i.inputPosition),!0,d.spacingBox),v(n)||(d=u.getBBox(),k[k.translateX0.7&&c<1.3&&(d?a=b-e:b=a+e);C(a)||(a=b=void 0);return{min:a, -max:b}};J.prototype.minFromRange=function(){var a=this.range,b={month:"Month",year:"FullYear"}[a.type],c,d=this.max,e,f,g=function(a,c){var d=new ba(a);d["set"+b](d["get"+b]()+c);return d.getTime()-a};C(a)?(c=this.max-a,f=a):c=d+g(d,-a.count);e=q(this.dataMin,Number.MIN_VALUE);C(c)||(c=e);if(c<=e)c=e,f===void 0&&(f=g(c,a.count)),this.newMax=G(c+f,this.dataMax);C(d)||(c=void 0);return c};U(Ba.prototype,"init",function(a,b,c){E(this,"init",function(){if(this.options.rangeSelector.enabled)this.rangeSelector= -new Hb(this)});a.call(this,b,c)});B.RangeSelector=Hb;Ba.prototype.callbacks.push(function(a){function b(){d=a.xAxis[0].getExtremes();C(d.min)&&f.render(d.min,d.max)}function c(a){f.render(a.min,a.max)}var d,e=a.scroller,f=a.rangeSelector;e&&(d=a.xAxis[0].getExtremes(),e.render(d.min,d.max));f&&(E(a.xAxis[0],"afterSetExtremes",c),E(a,"resize",b),b());E(a,"destroy",function(){f&&(T(a,"resize",b),T(a.xAxis[0],"afterSetExtremes",c))})});B.StockChart=B.stockChart=function(a,b,c){var d=Ca(a)||a.nodeName, -e=arguments[d?1:0],f=e.series,g,h=q(e.navigator&&e.navigator.enabled,!0)?{startOnTick:!1,endOnTick:!1}:null,i={marker:{enabled:!1,radius:2}},j={shadow:!1,borderWidth:0};e.xAxis=ta(ua(e.xAxis||{}),function(a){return z({minPadding:0,maxPadding:0,ordinal:!0,title:{text:null},labels:{overflow:"justify"},showLastLabel:!0},a,{type:"datetime",categories:null},h)});e.yAxis=ta(ua(e.yAxis||{}),function(a){g=q(a.opposite,!0);return z({labels:{y:-2},opposite:g,showLastLabel:!1,title:{text:null}},a)});e.series= -null;e=z({chart:{panning:!0,pinchType:"x"},navigator:{enabled:!0},scrollbar:{enabled:!0},rangeSelector:{enabled:!0},title:{text:null,style:{fontSize:"16px"}},tooltip:{shared:!0,crosshairs:!0},legend:{enabled:!1},plotOptions:{line:i,spline:i,area:i,areaspline:i,arearange:i,areasplinerange:i,column:j,columnrange:j,candlestick:j,ohlc:j}},e,{_stock:!0,chart:{inverted:!1}});e.series=f;return d?new Ba(a,e,c):new Ba(e,b)};U(Ya.prototype,"init",function(a,b,c){var d=c.chart.pinchType||"";a.call(this,b,c); -this.pinchX=this.pinchHor=d.indexOf("x")!==-1;this.pinchY=this.pinchVert=d.indexOf("y")!==-1;this.hasZoom=this.hasZoom||this.pinchHor||this.pinchVert});U(J.prototype,"autoLabelAlign",function(a){var b=this.chart,c=this.options,b=b._labelPanes=b._labelPanes||{},d=this.options.labels;if(this.chart.options._stock&&this.coll==="yAxis"&&(c=c.top+","+c.height,!b[c]&&d.enabled)){if(d.x===15)d.x=0;if(d.align===void 0)d.align="right";b[c]=1;return"right"}return a.call(this,[].slice.call(arguments,1))});U(J.prototype, -"getPlotLinePath",function(a,b,c,d,e,f){var g=this,h=this.isLinked&&!this.series?this.linkedParent.series:this.series,i=g.chart,j=i.renderer,k=g.left,l=g.top,m,n,p,r,s=[],t=[],u,x;if(g.coll==="colorAxis")return a.apply(this,[].slice.call(arguments,1));t=g.isXAxis?v(g.options.yAxis)?[i.yAxis[g.options.yAxis]]:ta(h,function(a){return a.yAxis}):v(g.options.xAxis)?[i.xAxis[g.options.xAxis]]:ta(h,function(a){return a.xAxis});o(g.isXAxis?i.yAxis:i.xAxis,function(a){if(v(a.options.id)?a.options.id.indexOf("navigator")=== --1:1){var b=a.isXAxis?"yAxis":"xAxis",b=v(a.options[b])?i[b][a.options[b]]:i[b][0];g===b&&t.push(a)}});u=t.length?[]:[g.isXAxis?i.yAxis[0]:i.xAxis[0]];o(t,function(a){sa(a,u)===-1&&u.push(a)});x=q(f,g.translate(b,null,null,d));C(x)&&(g.horiz?o(u,function(a){var b;n=a.pos;r=n+a.len;m=p=y(x+g.transB);if(mk+g.width)e?m=p=G(w(k,m),k+g.width):b=!0;b||s.push("M",m,n,"L",p,r)}):o(u,function(a){var b;m=a.pos;p=m+a.len;n=r=y(l+g.height-x);if(nl+g.height)e?n=r=G(w(l,n),g.top+g.height):b=!0;b||s.push("M", -m,n,"L",p,r)}));return s.length>0?j.crispPolyLine(s,c||1):null});J.prototype.getPlotBandPath=function(a,b){var c=this.getPlotLinePath(b,null,null,!0),d=this.getPlotLinePath(a,null,null,!0),e=[],f;if(d&&c&&d.toString()!==c.toString())for(f=0;f=e&&(l-=k.translateX+b.width-e);k.attr({x:l,y:j,visibility:"visible"})}});var gc=ia.init,hc=ia.processData,ic=Ha.prototype.tooltipFormatter;ia.init=function(){gc.apply(this,arguments);this.setCompare(this.options.compare)};ia.setCompare=function(a){this.modifyValue=a==="value"||a==="percent"?function(b,c){var d=this.compareValue;if(b!==t&&(b=a==="value"? -b-d:b=100*(b/d)-100,c))c.change=b;return b}:null;if(this.chart.hasRendered)this.isDirty=!0};ia.processData=function(){var a,b=-1,c,d,e,f;hc.apply(this,arguments);if(this.xAxis&&this.processedYData){c=this.processedXData;d=this.processedYData;e=d.length;this.pointArrayMap&&(b=sa(this.pointValKey||"y",this.pointArrayMap));for(a=0;a-1?d[a][b]:d[a],C(f)&&c[a]>=this.xAxis.min&&f!==0){this.compareValue=f;break}}};U(ia,"getExtremes",function(a){var b;a.apply(this,[].slice.call(arguments,1)); -if(this.modifyValue)b=[this.modifyValue(this.dataMin),this.modifyValue(this.dataMax)],this.dataMin=Ma(b),this.dataMax=Da(b)});J.prototype.setCompare=function(a,b){this.isXAxis||(o(this.series,function(b){b.setCompare(a)}),q(b,!0)&&this.chart.redraw())};Ha.prototype.tooltipFormatter=function(a){a=a.replace("{point.change}",(this.change>0?"+":"")+B.numberFormat(this.change,q(this.series.tooltipOptions.changeDecimals,2)));return ic.apply(this,[a])};U(P.prototype,"render",function(a){if(this.chart.options._stock&& -this.xAxis)!this.clipBox&&this.animate?(this.clipBox=z(this.chart.clipBox),this.clipBox.width=this.xAxis.len,this.clipBox.height=this.yAxis.len):this.chart[this.sharedClipKey]&&(Sa(this.chart[this.sharedClipKey]),this.chart[this.sharedClipKey].attr({width:this.xAxis.len,height:this.yAxis.len}));a.call(this)});A(B,{Color:va,Point:Ha,Tick:bb,Renderer:Xa,SVGElement:Z,SVGRenderer:xa,arrayMin:Ma,arrayMax:Da,charts:$,correctFloat:ka,dateFormat:na,error:ga,format:La,pathAnim:void 0,getOptions:function(){return R}, -hasBidiBug:Zb,isTouchDevice:jb,setOptions:function(a){R=z(!0,R,a);Ob();return R},addEvent:E,removeEvent:T,createElement:fa,discardElement:Ua,css:N,each:o,map:ta,merge:z,splat:ua,stableSort:nb,extendClass:ma,pInt:K,svg:ja,canvas:qa,vml:!ja&&!qa,product:"Highstock",version:"4.2.5"});return B}); diff --git a/public/vendor/highcharts-4.2.5/highstock.src.js b/public/vendor/highcharts-4.2.5/highstock.src.js deleted file mode 100644 index c6a42d29b8..0000000000 --- a/public/vendor/highcharts-4.2.5/highstock.src.js +++ /dev/null @@ -1,24597 +0,0 @@ -// ==ClosureCompiler== -// @compilation_level SIMPLE_OPTIMIZATIONS - -/** - * @license Highstock JS v4.2.5 (2016-05-06) - * - * (c) 2009-2016 Torstein Honsi - * - * License: www.highcharts.com/license - */ - -(function (root, factory) { - if (typeof module === 'object' && module.exports) { - module.exports = root.document ? - factory(root) : - factory; - } else { - root.Highcharts = factory(root); - } -}(typeof window !== 'undefined' ? window : this, function (win) { // eslint-disable-line no-undef -// encapsulated variables - var UNDEFINED, - doc = win.document, - math = Math, - mathRound = math.round, - mathFloor = math.floor, - mathCeil = math.ceil, - mathMax = math.max, - mathMin = math.min, - mathAbs = math.abs, - mathCos = math.cos, - mathSin = math.sin, - mathPI = math.PI, - deg2rad = mathPI * 2 / 360, - - - // some variables - userAgent = (win.navigator && win.navigator.userAgent) || '', - isOpera = win.opera, - isMS = /(msie|trident|edge)/i.test(userAgent) && !isOpera, - docMode8 = doc && doc.documentMode === 8, - isWebKit = !isMS && /AppleWebKit/.test(userAgent), - isFirefox = /Firefox/.test(userAgent), - isTouchDevice = /(Mobile|Android|Windows Phone)/.test(userAgent), - SVG_NS = 'http://www.w3.org/2000/svg', - hasSVG = doc && doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect, - hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38 - useCanVG = doc && !hasSVG && !isMS && !!doc.createElement('canvas').getContext, - Renderer, - hasTouch, - symbolSizes = {}, - idCounter = 0, - garbageBin, - defaultOptions, - dateFormat, // function - pathAnim, - timeUnits, - noop = function () {}, - charts = [], - chartCount = 0, - PRODUCT = 'Highstock', - VERSION = '4.2.5', - - // some constants for frequently used strings - DIV = 'div', - ABSOLUTE = 'absolute', - RELATIVE = 'relative', - HIDDEN = 'hidden', - PREFIX = 'highcharts-', - VISIBLE = 'visible', - PX = 'px', - NONE = 'none', - M = 'M', - L = 'L', - numRegex = /^[0-9]+$/, - NORMAL_STATE = '', - HOVER_STATE = 'hover', - SELECT_STATE = 'select', - marginNames = ['plotTop', 'marginRight', 'marginBottom', 'plotLeft'], - - // Object for extending Axis - AxisPlotLineOrBandExtension, - - // constants for attributes - STROKE_WIDTH = 'stroke-width', - - // time methods, changed based on whether or not UTC is used - Date, // Allow using a different Date class - makeTime, - timezoneOffset, - getTimezoneOffset, - getMinutes, - getHours, - getDay, - getDate, - getMonth, - getFullYear, - setMilliseconds, - setSeconds, - setMinutes, - setHours, - setDate, - setMonth, - setFullYear, - - - // lookup over the types and the associated classes - seriesTypes = {}, - Highcharts; - - /** - * Provide error messages for debugging, with links to online explanation - */ - function error(code, stop) { - var msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code; - if (stop) { - throw new Error(msg); - } - // else ... - if (win.console) { - console.log(msg); // eslint-disable-line no-console - } - } - - // The Highcharts namespace - Highcharts = win.Highcharts ? error(16, true) : { win: win }; - - Highcharts.seriesTypes = seriesTypes; - var timers = [], - getStyle, - - // Previous adapter functions - inArray, - each, - grep, - offset, - map, - addEvent, - removeEvent, - fireEvent, - animate, - stop; - - /** - * An animator object. One instance applies to one property (attribute or style prop) - * on one element. - * - * @param {object} elem The element to animate. May be a DOM element or a Highcharts SVGElement wrapper. - * @param {object} options Animation options, including duration, easing, step and complete. - * @param {object} prop The property to animate. - */ - function Fx(elem, options, prop) { - this.options = options; - this.elem = elem; - this.prop = prop; - } - Fx.prototype = { - - /** - * Animating a path definition on SVGElement - * @returns {undefined} - */ - dSetter: function () { - var start = this.paths[0], - end = this.paths[1], - ret = [], - now = this.now, - i = start.length, - startVal; - - if (now === 1) { // land on the final path without adjustment points appended in the ends - ret = this.toD; - - } else if (i === end.length && now < 1) { - while (i--) { - startVal = parseFloat(start[i]); - ret[i] = - isNaN(startVal) ? // a letter instruction like M or L - start[i] : - now * (parseFloat(end[i] - startVal)) + startVal; - - } - } else { // if animation is finished or length not matching, land on right value - ret = end; - } - this.elem.attr('d', ret); - }, - - /** - * Update the element with the current animation step - * @returns {undefined} - */ - update: function () { - var elem = this.elem, - prop = this.prop, // if destroyed, it is null - now = this.now, - step = this.options.step; - - // Animation setter defined from outside - if (this[prop + 'Setter']) { - this[prop + 'Setter'](); - - // Other animations on SVGElement - } else if (elem.attr) { - if (elem.element) { - elem.attr(prop, now); - } - - // HTML styles, raw HTML content like container size - } else { - elem.style[prop] = now + this.unit; - } - - if (step) { - step.call(elem, now, this); - } - - }, - - /** - * Run an animation - */ - run: function (from, to, unit) { - var self = this, - timer = function (gotoEnd) { - return timer.stopped ? false : self.step(gotoEnd); - }, - i; - - this.startTime = +new Date(); - this.start = from; - this.end = to; - this.unit = unit; - this.now = this.start; - this.pos = 0; - - timer.elem = this.elem; - - if (timer() && timers.push(timer) === 1) { - timer.timerId = setInterval(function () { - - for (i = 0; i < timers.length; i++) { - if (!timers[i]()) { - timers.splice(i--, 1); - } - } - - if (!timers.length) { - clearInterval(timer.timerId); - } - }, 13); - } - }, - - /** - * Run a single step in the animation - * @param {Boolean} gotoEnd Whether to go to then endpoint of the animation after abort - * @returns {Boolean} True if animation continues - */ - step: function (gotoEnd) { - var t = +new Date(), - ret, - done, - options = this.options, - elem = this.elem, - complete = options.complete, - duration = options.duration, - curAnim = options.curAnim, - i; - - if (elem.attr && !elem.element) { // #2616, element including flag is destroyed - ret = false; - - } else if (gotoEnd || t >= duration + this.startTime) { - this.now = this.end; - this.pos = 1; - this.update(); - - curAnim[this.prop] = true; - - done = true; - for (i in curAnim) { - if (curAnim[i] !== true) { - done = false; - } - } - - if (done && complete) { - complete.call(elem); - } - ret = false; - - } else { - this.pos = options.easing((t - this.startTime) / duration); - this.now = this.start + ((this.end - this.start) * this.pos); - this.update(); - ret = true; - } - return ret; - }, - - /** - * Prepare start and end values so that the path can be animated one to one - */ - initPath: function (elem, fromD, toD) { - fromD = fromD || ''; - var shift = elem.shift, - bezier = fromD.indexOf('C') > -1, - numParams = bezier ? 7 : 3, - endLength, - slice, - i, - start = fromD.split(' '), - end = [].concat(toD), // copy - isArea = elem.isArea, - positionFactor = isArea ? 2 : 1, - sixify = function (arr) { // in splines make move points have six parameters like bezier curves - i = arr.length; - while (i--) { - if (arr[i] === M || arr[i] === L) { - arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]); - } - } - }; - - if (bezier) { - sixify(start); - sixify(end); - } - - // If shifting points, prepend a dummy point to the end path. For areas, - // prepend both at the beginning and end of the path. - if (shift <= end.length / numParams && start.length === end.length) { - while (shift--) { - end = end.slice(0, numParams).concat(end); - if (isArea) { - end = end.concat(end.slice(end.length - numParams)); - } - } - } - elem.shift = 0; // reset for following animations - - - // Copy and append last point until the length matches the end length - if (start.length) { - endLength = end.length; - while (start.length < endLength) { - - // Pull out the slice that is going to be appended or inserted. In a line graph, - // the positionFactor is 1, and the last point is sliced out. In an area graph, - // the positionFactor is 2, causing the middle two points to be sliced out, since - // an area path starts at left, follows the upper path then turns and follows the - // bottom back. - slice = start.slice().splice( - (start.length / positionFactor) - numParams, - numParams * positionFactor - ); - - // Disable first control point - if (bezier) { - slice[numParams - 6] = slice[numParams - 2]; - slice[numParams - 5] = slice[numParams - 1]; - } - - // Now insert the slice, either in the middle (for areas) or at the end (for lines) - [].splice.apply( - start, - [(start.length / positionFactor), 0].concat(slice) - ); - - } - } - - return [start, end]; - } - }; // End of Fx prototype - - - /** - * Extend an object with the members of another - * @param {Object} a The object to be extended - * @param {Object} b The object to add to the first one - */ - var extend = Highcharts.extend = function (a, b) { - var n; - if (!a) { - a = {}; - } - for (n in b) { - a[n] = b[n]; - } - return a; - }; - - /** - * Deep merge two or more objects and return a third object. If the first argument is - * true, the contents of the second object is copied into the first object. - * Previously this function redirected to jQuery.extend(true), but this had two limitations. - * First, it deep merged arrays, which lead to workarounds in Highcharts. Second, - * it copied properties from extended prototypes. - */ - function merge() { - var i, - args = arguments, - len, - ret = {}, - doCopy = function (copy, original) { - var value, key; - - // An object is replacing a primitive - if (typeof copy !== 'object') { - copy = {}; - } - - for (key in original) { - if (original.hasOwnProperty(key)) { - value = original[key]; - - // Copy the contents of objects, but not arrays or DOM nodes - if (value && typeof value === 'object' && Object.prototype.toString.call(value) !== '[object Array]' && - key !== 'renderTo' && typeof value.nodeType !== 'number') { - copy[key] = doCopy(copy[key] || {}, value); - - // Primitives and arrays are copied over directly - } else { - copy[key] = original[key]; - } - } - } - return copy; - }; - - // If first argument is true, copy into the existing object. Used in setOptions. - if (args[0] === true) { - ret = args[1]; - args = Array.prototype.slice.call(args, 2); - } - - // For each argument, extend the return - len = args.length; - for (i = 0; i < len; i++) { - ret = doCopy(ret, args[i]); - } - - return ret; - } - - /** - * Shortcut for parseInt - * @param {Object} s - * @param {Number} mag Magnitude - */ - function pInt(s, mag) { - return parseInt(s, mag || 10); - } - - /** - * Check for string - * @param {Object} s - */ - function isString(s) { - return typeof s === 'string'; - } - - /** - * Check for object - * @param {Object} obj - */ - function isObject(obj) { - return obj && typeof obj === 'object'; - } - - /** - * Check for array - * @param {Object} obj - */ - function isArray(obj) { - return Object.prototype.toString.call(obj) === '[object Array]'; - } - - /** - * Check for number - * @param {Object} n - */ - var isNumber = Highcharts.isNumber = function isNumber(n) { - return typeof n === 'number' && !isNaN(n); - }; - - /** - * Remove last occurence of an item from an array - * @param {Array} arr - * @param {Mixed} item - */ - function erase(arr, item) { - var i = arr.length; - while (i--) { - if (arr[i] === item) { - arr.splice(i, 1); - break; - } - } - //return arr; - } - - /** - * Returns true if the object is not null or undefined. - * @param {Object} obj - */ - function defined(obj) { - return obj !== UNDEFINED && obj !== null; - } - - /** - * Set or get an attribute or an object of attributes. Can't use jQuery attr because - * it attempts to set expando properties on the SVG element, which is not allowed. - * - * @param {Object} elem The DOM element to receive the attribute(s) - * @param {String|Object} prop The property or an abject of key-value pairs - * @param {String} value The value if a single property is set - */ - function attr(elem, prop, value) { - var key, - ret; - - // if the prop is a string - if (isString(prop)) { - // set the value - if (defined(value)) { - elem.setAttribute(prop, value); - - // get the value - } else if (elem && elem.getAttribute) { // elem not defined when printing pie demo... - ret = elem.getAttribute(prop); - } - - // else if prop is defined, it is a hash of key/value pairs - } else if (defined(prop) && isObject(prop)) { - for (key in prop) { - elem.setAttribute(key, prop[key]); - } - } - return ret; - } - /** - * Check if an element is an array, and if not, make it into an array. - */ - function splat(obj) { - return isArray(obj) ? obj : [obj]; - } - - /** - * Set a timeout if the delay is given, otherwise perform the function synchronously - * @param {Function} fn The function to perform - * @param {Number} delay Delay in milliseconds - * @param {Ojbect} context The context - * @returns {Nubmer} An identifier for the timeout - */ - function syncTimeout(fn, delay, context) { - if (delay) { - return setTimeout(fn, delay, context); - } - fn.call(0, context); - } - - - /** - * Return the first value that is defined. - */ - var pick = Highcharts.pick = function () { - var args = arguments, - i, - arg, - length = args.length; - for (i = 0; i < length; i++) { - arg = args[i]; - if (arg !== UNDEFINED && arg !== null) { - return arg; - } - } - }; - - /** - * Set CSS on a given element - * @param {Object} el - * @param {Object} styles Style object with camel case property names - */ - function css(el, styles) { - if (isMS && !hasSVG) { // #2686 - if (styles && styles.opacity !== UNDEFINED) { - styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')'; - } - } - extend(el.style, styles); - } - - /** - * Utility function to create element with attributes and styles - * @param {Object} tag - * @param {Object} attribs - * @param {Object} styles - * @param {Object} parent - * @param {Object} nopad - */ - function createElement(tag, attribs, styles, parent, nopad) { - var el = doc.createElement(tag); - if (attribs) { - extend(el, attribs); - } - if (nopad) { - css(el, { padding: 0, border: 'none', margin: 0 }); - } - if (styles) { - css(el, styles); - } - if (parent) { - parent.appendChild(el); - } - return el; - } - - /** - * Extend a prototyped class by new members - * @param {Object} parent - * @param {Object} members - */ - function extendClass(Parent, members) { - var object = function () { - }; - object.prototype = new Parent(); - extend(object.prototype, members); - return object; - } - - /** - * Pad a string to a given length by adding 0 to the beginning - * @param {Number} number - * @param {Number} length - */ - function pad(number, length, padder) { - return new Array((length || 2) + 1 - String(number).length).join(padder || 0) + number; - } - - /** - * Return a length based on either the integer value, or a percentage of a base. - */ - function relativeLength(value, base) { - return (/%$/).test(value) ? base * parseFloat(value) / 100 : parseFloat(value); - } - - /** - * Wrap a method with extended functionality, preserving the original function - * @param {Object} obj The context object that the method belongs to - * @param {String} method The name of the method to extend - * @param {Function} func A wrapper function callback. This function is called with the same arguments - * as the original function, except that the original function is unshifted and passed as the first - * argument. - */ - var wrap = Highcharts.wrap = function (obj, method, func) { - var proceed = obj[method]; - obj[method] = function () { - var args = Array.prototype.slice.call(arguments); - args.unshift(proceed); - return func.apply(this, args); - }; - }; - - - function getTZOffset(timestamp) { - return ((getTimezoneOffset && getTimezoneOffset(timestamp)) || timezoneOffset || 0) * 60000; - } - - /** - * Based on http://www.php.net/manual/en/function.strftime.php - * @param {String} format - * @param {Number} timestamp - * @param {Boolean} capitalize - */ - dateFormat = function (format, timestamp, capitalize) { - if (!isNumber(timestamp)) { - return defaultOptions.lang.invalidDate || ''; - } - format = pick(format, '%Y-%m-%d %H:%M:%S'); - - var date = new Date(timestamp - getTZOffset(timestamp)), - key, // used in for constuct below - // get the basic time values - hours = date[getHours](), - day = date[getDay](), - dayOfMonth = date[getDate](), - month = date[getMonth](), - fullYear = date[getFullYear](), - lang = defaultOptions.lang, - langWeekdays = lang.weekdays, - shortWeekdays = lang.shortWeekdays, - - // List all format keys. Custom formats can be added from the outside. - replacements = extend({ - - // Day - 'a': shortWeekdays ? shortWeekdays[day] : langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon' - 'A': langWeekdays[day], // Long weekday, like 'Monday' - 'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31 - 'e': pad(dayOfMonth, 2, ' '), // Day of the month, 1 through 31 - 'w': day, - - // Week (none implemented) - //'W': weekNumber(), - - // Month - 'b': lang.shortMonths[month], // Short month, like 'Jan' - 'B': lang.months[month], // Long month, like 'January' - 'm': pad(month + 1), // Two digit month number, 01 through 12 - - // Year - 'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009 - 'Y': fullYear, // Four digits year, like 2009 - - // Time - 'H': pad(hours), // Two digits hours in 24h format, 00 through 23 - 'k': hours, // Hours in 24h format, 0 through 23 - 'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11 - 'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12 - 'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59 - 'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM - 'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM - 'S': pad(date.getSeconds()), // Two digits seconds, 00 through 59 - 'L': pad(mathRound(timestamp % 1000), 3) // Milliseconds (naming from Ruby) - }, Highcharts.dateFormats); - - - // do the replaces - for (key in replacements) { - while (format.indexOf('%' + key) !== -1) { // regex would do it in one line, but this is faster - format = format.replace('%' + key, typeof replacements[key] === 'function' ? replacements[key](timestamp) : replacements[key]); - } - } - - // Optionally capitalize the string and return - return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format; - }; - - /** - * Format a single variable. Similar to sprintf, without the % prefix. - */ - function formatSingle(format, val) { - var floatRegex = /f$/, - decRegex = /\.([0-9])/, - lang = defaultOptions.lang, - decimals; - - if (floatRegex.test(format)) { // float - decimals = format.match(decRegex); - decimals = decimals ? decimals[1] : -1; - if (val !== null) { - val = Highcharts.numberFormat( - val, - decimals, - lang.decimalPoint, - format.indexOf(',') > -1 ? lang.thousandsSep : '' - ); - } - } else { - val = dateFormat(format, val); - } - return val; - } - - /** - * Format a string according to a subset of the rules of Python's String.format method. - */ - function format(str, ctx) { - var splitter = '{', - isInside = false, - segment, - valueAndFormat, - path, - i, - len, - ret = [], - val, - index; - - while ((index = str.indexOf(splitter)) !== -1) { - - segment = str.slice(0, index); - if (isInside) { // we're on the closing bracket looking back - - valueAndFormat = segment.split(':'); - path = valueAndFormat.shift().split('.'); // get first and leave format - len = path.length; - val = ctx; - - // Assign deeper paths - for (i = 0; i < len; i++) { - val = val[path[i]]; - } - - // Format the replacement - if (valueAndFormat.length) { - val = formatSingle(valueAndFormat.join(':'), val); - } - - // Push the result and advance the cursor - ret.push(val); - - } else { - ret.push(segment); - - } - str = str.slice(index + 1); // the rest - isInside = !isInside; // toggle - splitter = isInside ? '}' : '{'; // now look for next matching bracket - } - ret.push(str); - return ret.join(''); - } - - /** - * Get the magnitude of a number - */ - function getMagnitude(num) { - return math.pow(10, mathFloor(math.log(num) / math.LN10)); - } - - /** - * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5 - * @param {Number} interval - * @param {Array} multiples - * @param {Number} magnitude - * @param {Object} options - */ - function normalizeTickInterval(interval, multiples, magnitude, allowDecimals, preventExceed) { - var normalized, - i, - retInterval = interval; - - // round to a tenfold of 1, 2, 2.5 or 5 - magnitude = pick(magnitude, 1); - normalized = interval / magnitude; - - // multiples for a linear scale - if (!multiples) { - multiples = [1, 2, 2.5, 5, 10]; - - // the allowDecimals option - if (allowDecimals === false) { - if (magnitude === 1) { - multiples = [1, 2, 5, 10]; - } else if (magnitude <= 0.1) { - multiples = [1 / magnitude]; - } - } - } - - // normalize the interval to the nearest multiple - for (i = 0; i < multiples.length; i++) { - retInterval = multiples[i]; - if ((preventExceed && retInterval * magnitude >= interval) || // only allow tick amounts smaller than natural - (!preventExceed && (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2))) { - break; - } - } - - // multiply back to the correct magnitude - retInterval *= magnitude; - - return retInterval; - } - - - /** - * Utility method that sorts an object array and keeping the order of equal items. - * ECMA script standard does not specify the behaviour when items are equal. - */ - function stableSort(arr, sortFunction) { - var length = arr.length, - sortValue, - i; - - // Add index to each item - for (i = 0; i < length; i++) { - arr[i].safeI = i; // stable sort index - } - - arr.sort(function (a, b) { - sortValue = sortFunction(a, b); - return sortValue === 0 ? a.safeI - b.safeI : sortValue; - }); - - // Remove index from items - for (i = 0; i < length; i++) { - delete arr[i].safeI; // stable sort index - } - } - - /** - * Non-recursive method to find the lowest member of an array. Math.min raises a maximum - * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This - * method is slightly slower, but safe. - */ - function arrayMin(data) { - var i = data.length, - min = data[0]; - - while (i--) { - if (data[i] < min) { - min = data[i]; - } - } - return min; - } - - /** - * Non-recursive method to find the lowest member of an array. Math.min raises a maximum - * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This - * method is slightly slower, but safe. - */ - function arrayMax(data) { - var i = data.length, - max = data[0]; - - while (i--) { - if (data[i] > max) { - max = data[i]; - } - } - return max; - } - - /** - * Utility method that destroys any SVGElement or VMLElement that are properties on the given object. - * It loops all properties and invokes destroy if there is a destroy method. The property is - * then delete'ed. - * @param {Object} The object to destroy properties on - * @param {Object} Exception, do not destroy this property, only delete it. - */ - function destroyObjectProperties(obj, except) { - var n; - for (n in obj) { - // If the object is non-null and destroy is defined - if (obj[n] && obj[n] !== except && obj[n].destroy) { - // Invoke the destroy - obj[n].destroy(); - } - - // Delete the property from the object. - delete obj[n]; - } - } - - - /** - * Discard an element by moving it to the bin and delete - * @param {Object} The HTML node to discard - */ - function discardElement(element) { - // create a garbage bin element, not part of the DOM - if (!garbageBin) { - garbageBin = createElement(DIV); - } - - // move the node and empty bin - if (element) { - garbageBin.appendChild(element); - } - garbageBin.innerHTML = ''; - } - - /** - * Fix JS round off float errors - * @param {Number} num - */ - function correctFloat(num, prec) { - return parseFloat( - num.toPrecision(prec || 14) - ); - } - - /** - * Set the global animation to either a given value, or fall back to the - * given chart's animation option - * @param {Object} animation - * @param {Object} chart - */ - function setAnimation(animation, chart) { - chart.renderer.globalAnimation = pick(animation, chart.animation); - } - - /** - * Get the animation in object form, where a disabled animation is always - * returned with duration: 0 - */ - function animObject(animation) { - return isObject(animation) ? merge(animation) : { duration: animation ? 500 : 0 }; - } - - /** - * The time unit lookup - */ - timeUnits = { - millisecond: 1, - second: 1000, - minute: 60000, - hour: 3600000, - day: 24 * 3600000, - week: 7 * 24 * 3600000, - month: 28 * 24 * 3600000, - year: 364 * 24 * 3600000 - }; - - - /** - * Format a number and return a string based on input settings - * @param {Number} number The input number to format - * @param {Number} decimals The amount of decimals - * @param {String} decimalPoint The decimal point, defaults to the one given in the lang options - * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options - */ - Highcharts.numberFormat = function (number, decimals, decimalPoint, thousandsSep) { - - number = +number || 0; - decimals = +decimals; - - var lang = defaultOptions.lang, - origDec = (number.toString().split('.')[1] || '').length, - decimalComponent, - strinteger, - thousands, - absNumber = Math.abs(number), - ret; - - if (decimals === -1) { - decimals = Math.min(origDec, 20); // Preserve decimals. Not huge numbers (#3793). - } else if (!isNumber(decimals)) { - decimals = 2; - } - - // A string containing the positive integer component of the number - strinteger = String(pInt(absNumber.toFixed(decimals))); - - // Leftover after grouping into thousands. Can be 0, 1 or 3. - thousands = strinteger.length > 3 ? strinteger.length % 3 : 0; - - // Language - decimalPoint = pick(decimalPoint, lang.decimalPoint); - thousandsSep = pick(thousandsSep, lang.thousandsSep); - - // Start building the return - ret = number < 0 ? '-' : ''; - - // Add the leftover after grouping into thousands. For example, in the number 42 000 000, - // this line adds 42. - ret += thousands ? strinteger.substr(0, thousands) + thousandsSep : ''; - - // Add the remaining thousands groups, joined by the thousands separator - ret += strinteger.substr(thousands).replace(/(\d{3})(?=\d)/g, '$1' + thousandsSep); - - // Add the decimal point and the decimal component - if (decimals) { - // Get the decimal component, and add power to avoid rounding errors with float numbers (#4573) - decimalComponent = Math.abs(absNumber - strinteger + Math.pow(10, -Math.max(decimals, origDec) - 1)); - ret += decimalPoint + decimalComponent.toFixed(decimals).slice(2); - } - - return ret; - }; - - /** - * Easing definition - * @param {Number} pos Current position, ranging from 0 to 1 - */ - Math.easeInOutSine = function (pos) { - return -0.5 * (Math.cos(Math.PI * pos) - 1); - }; - - /** - * Internal method to return CSS value for given element and property - */ - getStyle = function (el, prop) { - - var style; - - // For width and height, return the actual inner pixel size (#4913) - if (prop === 'width') { - return Math.min(el.offsetWidth, el.scrollWidth) - getStyle(el, 'padding-left') - getStyle(el, 'padding-right'); - } else if (prop === 'height') { - return Math.min(el.offsetHeight, el.scrollHeight) - getStyle(el, 'padding-top') - getStyle(el, 'padding-bottom'); - } - - // Otherwise, get the computed style - style = win.getComputedStyle(el, undefined); - return style && pInt(style.getPropertyValue(prop)); - }; - - /** - * Return the index of an item in an array, or -1 if not found - */ - inArray = function (item, arr) { - return arr.indexOf ? arr.indexOf(item) : [].indexOf.call(arr, item); - }; - - /** - * Filter an array - */ - grep = function (elements, callback) { - return [].filter.call(elements, callback); - }; - - /** - * Map an array - */ - map = function (arr, fn) { - var results = [], - i = 0, - len = arr.length; - - for (; i < len; i++) { - results[i] = fn.call(arr[i], arr[i], i, arr); - } - - return results; - }; - - /** - * Get the element's offset position, corrected by overflow:auto. - */ - offset = function (el) { - var docElem = doc.documentElement, - box = el.getBoundingClientRect(); - - return { - top: box.top + (win.pageYOffset || docElem.scrollTop) - (docElem.clientTop || 0), - left: box.left + (win.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || 0) - }; - }; - - /** - * Stop running animation. - * A possible extension to this would be to stop a single property, when - * we want to continue animating others. Then assign the prop to the timer - * in the Fx.run method, and check for the prop here. This would be an improvement - * in all cases where we stop the animation from .attr. Instead of stopping - * everything, we can just stop the actual attributes we're setting. - */ - stop = function (el) { - - var i = timers.length; - - // Remove timers related to this element (#4519) - while (i--) { - if (timers[i].elem === el) { - timers[i].stopped = true; // #4667 - } - } - }; - - /** - * Utility for iterating over an array. - * @param {Array} arr - * @param {Function} fn - */ - each = function (arr, fn) { // modern browsers - return Array.prototype.forEach.call(arr, fn); - }; - - /** - * Add an event listener - */ - addEvent = function (el, type, fn) { - - var events = el.hcEvents = el.hcEvents || {}; - - function wrappedFn(e) { - e.target = e.srcElement || win; // #2820 - fn.call(el, e); - } - - // Handle DOM events in modern browsers - if (el.addEventListener) { - el.addEventListener(type, fn, false); - - // Handle old IE implementation - } else if (el.attachEvent) { - - if (!el.hcEventsIE) { - el.hcEventsIE = {}; - } - - // Link wrapped fn with original fn, so we can get this in removeEvent - el.hcEventsIE[fn.toString()] = wrappedFn; - - el.attachEvent('on' + type, wrappedFn); - } - - if (!events[type]) { - events[type] = []; - } - - events[type].push(fn); - }; - - /** - * Remove event added with addEvent - */ - removeEvent = function (el, type, fn) { - - var events, - hcEvents = el.hcEvents, - index; - - function removeOneEvent(type, fn) { - if (el.removeEventListener) { - el.removeEventListener(type, fn, false); - } else if (el.attachEvent) { - fn = el.hcEventsIE[fn.toString()]; - el.detachEvent('on' + type, fn); - } - } - - function removeAllEvents() { - var types, - len, - n; - - if (!el.nodeName) { - return; // break on non-DOM events - } - - if (type) { - types = {}; - types[type] = true; - } else { - types = hcEvents; - } - - for (n in types) { - if (hcEvents[n]) { - len = hcEvents[n].length; - while (len--) { - removeOneEvent(n, hcEvents[n][len]); - } - } - } - } - - if (hcEvents) { - if (type) { - events = hcEvents[type] || []; - if (fn) { - index = inArray(fn, events); - if (index > -1) { - events.splice(index, 1); - hcEvents[type] = events; - } - removeOneEvent(type, fn); - - } else { - removeAllEvents(); - hcEvents[type] = []; - } - } else { - removeAllEvents(); - el.hcEvents = {}; - } - } - }; - - /** - * Fire an event on a custom object - */ - fireEvent = function (el, type, eventArguments, defaultFunction) { - var e, - hcEvents = el.hcEvents, - events, - len, - i, - fn; - - eventArguments = eventArguments || {}; - - if (doc.createEvent && (el.dispatchEvent || el.fireEvent)) { - e = doc.createEvent('Events'); - e.initEvent(type, true, true); - e.target = el; - - extend(e, eventArguments); - - if (el.dispatchEvent) { - el.dispatchEvent(e); - } else { - el.fireEvent(type, e); - } - - } else if (hcEvents) { - - events = hcEvents[type] || []; - len = events.length; - - // Attach a simple preventDefault function to skip default handler if called. - // The built-in defaultPrevented property is not overwritable (#5112) - if (!eventArguments.preventDefault) { - eventArguments.preventDefault = function () { - eventArguments.defaultPrevented = true; - }; - } - - eventArguments.target = el; - - // If the type is not set, we're running a custom event (#2297). If it is set, - // we're running a browser event, and setting it will cause en error in - // IE8 (#2465). - if (!eventArguments.type) { - eventArguments.type = type; - } - - for (i = 0; i < len; i++) { - fn = events[i]; - - // If the event handler return false, prevent the default handler from executing - if (fn.call(el, eventArguments) === false) { - eventArguments.preventDefault(); - } - } - } - - // Run the default if not prevented - if (defaultFunction && !eventArguments.defaultPrevented) { - defaultFunction(eventArguments); - } - }; - - /** - * The global animate method, which uses Fx to create individual animators. - */ - animate = function (el, params, opt) { - var start, - unit = '', - end, - fx, - args, - prop; - - if (!isObject(opt)) { // Number or undefined/null - args = arguments; - opt = { - duration: args[2], - easing: args[3], - complete: args[4] - }; - } - if (!isNumber(opt.duration)) { - opt.duration = 400; - } - opt.easing = typeof opt.easing === 'function' ? opt.easing : (Math[opt.easing] || Math.easeInOutSine); - opt.curAnim = merge(params); - - for (prop in params) { - fx = new Fx(el, opt, prop); - end = null; - - if (prop === 'd') { - fx.paths = fx.initPath( - el, - el.d, - params.d - ); - fx.toD = params.d; - start = 0; - end = 1; - } else if (el.attr) { - start = el.attr(prop); - } else { - start = parseFloat(getStyle(el, prop)) || 0; - if (prop !== 'opacity') { - unit = 'px'; - } - } - - if (!end) { - end = params[prop]; - } - if (end.match && end.match('px')) { - end = end.replace(/px/g, ''); // #4351 - } - fx.run(start, end, unit); - } - }; - - /** - * Register Highcharts as a plugin in jQuery - */ - if (win.jQuery) { - win.jQuery.fn.highcharts = function () { - var args = [].slice.call(arguments); - - if (this[0]) { // this[0] is the renderTo div - - // Create the chart - if (args[0]) { - new Highcharts[ // eslint-disable-line no-new - isString(args[0]) ? args.shift() : 'Chart' // Constructor defaults to Chart - ](this[0], args[0], args[1]); - return this; - } - - // When called without parameters or with the return argument, return an existing chart - return charts[attr(this[0], 'data-highcharts-chart')]; - } - }; - } - - - /** - * Compatibility section to add support for legacy IE. This can be removed if old IE - * support is not needed. - */ - if (doc && !doc.defaultView) { - getStyle = function (el, prop) { - var val, - alias = { width: 'clientWidth', height: 'clientHeight' }[prop]; - - if (el.style[prop]) { - return pInt(el.style[prop]); - } - if (prop === 'opacity') { - prop = 'filter'; - } - - // Getting the rendered width and height - if (alias) { - el.style.zoom = 1; - return Math.max(el[alias] - 2 * getStyle(el, 'padding'), 0); - } - - val = el.currentStyle[prop.replace(/\-(\w)/g, function (a, b) { - return b.toUpperCase(); - })]; - if (prop === 'filter') { - val = val.replace( - /alpha\(opacity=([0-9]+)\)/, - function (a, b) { - return b / 100; - } - ); - } - - return val === '' ? 1 : pInt(val); - }; - } - - if (!Array.prototype.forEach) { - each = function (arr, fn) { // legacy - var i = 0, - len = arr.length; - for (; i < len; i++) { - if (fn.call(arr[i], arr[i], i, arr) === false) { - return i; - } - } - }; - } - - if (!Array.prototype.indexOf) { - inArray = function (item, arr) { - var len, - i = 0; - - if (arr) { - len = arr.length; - - for (; i < len; i++) { - if (arr[i] === item) { - return i; - } - } - } - - return -1; - }; - } - - if (!Array.prototype.filter) { - grep = function (elements, fn) { - var ret = [], - i = 0, - length = elements.length; - - for (; i < length; i++) { - if (fn(elements[i], i)) { - ret.push(elements[i]); - } - } - - return ret; - }; - } - - //--- End compatibility section --- - - // Expose utilities - Highcharts.Fx = Fx; - Highcharts.inArray = inArray; - Highcharts.each = each; - Highcharts.grep = grep; - Highcharts.offset = offset; - Highcharts.map = map; - Highcharts.addEvent = addEvent; - Highcharts.removeEvent = removeEvent; - Highcharts.fireEvent = fireEvent; - Highcharts.animate = animate; - Highcharts.animObject = animObject; - Highcharts.stop = stop; - - /* **************************************************************************** - * Handle the options * - *****************************************************************************/ - defaultOptions = { - colors: ['#7cb5ec', '#434348', '#90ed7d', '#f7a35c', - '#8085e9', '#f15c80', '#e4d354', '#2b908f', '#f45b5b', '#91e8e1'], - symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'], - lang: { - loading: 'Loading...', - months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', - 'August', 'September', 'October', 'November', 'December'], - shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], - weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], - // invalidDate: '', - decimalPoint: '.', - numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], // SI prefixes used in axis labels - resetZoom: 'Reset zoom', - resetZoomTitle: 'Reset zoom level 1:1', - thousandsSep: ' ' - }, - global: { - useUTC: true, - //timezoneOffset: 0, - canvasToolsURL: 'http://code.highcharts.com/modules/canvas-tools.js', - VMLRadialGradientURL: 'http://code.highcharts.com/stock/4.2.5/gfx/vml-radial-gradient.png' - }, - chart: { - //animation: true, - //alignTicks: false, - //reflow: true, - //className: null, - //events: { load, selection }, - //margin: [null], - //marginTop: null, - //marginRight: null, - //marginBottom: null, - //marginLeft: null, - borderColor: '#4572A7', - //borderWidth: 0, - borderRadius: 0, - defaultSeriesType: 'line', - ignoreHiddenSeries: true, - //inverted: false, - //shadow: false, - spacing: [10, 10, 15, 10], - //spacingTop: 10, - //spacingRight: 10, - //spacingBottom: 15, - //spacingLeft: 10, - //style: { - // fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font - // fontSize: '12px' - //}, - backgroundColor: '#FFFFFF', - //plotBackgroundColor: null, - plotBorderColor: '#C0C0C0', - //plotBorderWidth: 0, - //plotShadow: false, - //zoomType: '' - resetZoomButton: { - theme: { - zIndex: 20 - }, - position: { - align: 'right', - x: -10, - //verticalAlign: 'top', - y: 10 - } - // relativeTo: 'plot' - } - }, - title: { - text: 'Chart title', - align: 'center', - // floating: false, - margin: 15, - // x: 0, - // verticalAlign: 'top', - // y: null, - style: { - color: '#333333', - fontSize: '18px' - }, - widthAdjust: -44 - - }, - subtitle: { - text: '', - align: 'center', - // floating: false - // x: 0, - // verticalAlign: 'top', - // y: null, - style: { - color: '#555555' - }, - widthAdjust: -44 - }, - - plotOptions: { - line: { // base series options - allowPointSelect: false, - showCheckbox: false, - animation: { - duration: 1000 - }, - //connectNulls: false, - //cursor: 'default', - //clip: true, - //dashStyle: null, - //enableMouseTracking: true, - events: {}, - //legendIndex: 0, - //linecap: 'round', - lineWidth: 2, - //shadow: false, - // stacking: null, - marker: { - //enabled: true, - //symbol: null, - lineWidth: 0, - radius: 4, - lineColor: '#FFFFFF', - //fillColor: null, - states: { // states for a single point - hover: { - enabled: true, - lineWidthPlus: 1, - radiusPlus: 2 - }, - select: { - fillColor: '#FFFFFF', - lineColor: '#000000', - lineWidth: 2 - } - } - }, - point: { - events: {} - }, - dataLabels: { - align: 'center', - // defer: true, - // enabled: false, - formatter: function () { - return this.y === null ? '' : Highcharts.numberFormat(this.y, -1); - }, - style: { - color: 'contrast', - fontSize: '11px', - fontWeight: 'bold', - textShadow: '0 0 6px contrast, 0 0 3px contrast' - }, - verticalAlign: 'bottom', // above singular point - x: 0, - y: 0, - // backgroundColor: undefined, - // borderColor: undefined, - // borderRadius: undefined, - // borderWidth: undefined, - padding: 5 - // shadow: false - }, - cropThreshold: 300, // draw points outside the plot area when the number of points is less than this - pointRange: 0, - //pointStart: 0, - //pointInterval: 1, - //showInLegend: null, // auto: true for standalone series, false for linked series - softThreshold: true, - states: { // states for the entire series - hover: { - //enabled: false, - lineWidthPlus: 1, - marker: { - // lineWidth: base + 1, - // radius: base + 1 - }, - halo: { - size: 10, - opacity: 0.25 - } - }, - select: { - marker: {} - } - }, - stickyTracking: true, - //tooltip: { - //pointFormat: '\u25CF {series.name}: {point.y}' - //valueDecimals: null, - //xDateFormat: '%A, %b %e, %Y', - //valuePrefix: '', - //ySuffix: '' - //} - turboThreshold: 1000 - // zIndex: null - } - }, - labels: { - //items: [], - style: { - //font: defaultFont, - position: ABSOLUTE, - color: '#3E576F' - } - }, - legend: { - enabled: true, - align: 'center', - //floating: false, - layout: 'horizontal', - labelFormatter: function () { - return this.name; - }, - //borderWidth: 0, - borderColor: '#909090', - borderRadius: 0, - navigation: { - // animation: true, - activeColor: '#274b6d', - // arrowSize: 12 - inactiveColor: '#CCC' - // style: {} // text styles - }, - // margin: 20, - // reversed: false, - shadow: false, - // backgroundColor: null, - /*style: { - padding: '5px' - },*/ - itemStyle: { - color: '#333333', - fontSize: '12px', - fontWeight: 'bold' - }, - itemHoverStyle: { - //cursor: 'pointer', removed as of #601 - color: '#000' - }, - itemHiddenStyle: { - color: '#CCC' - }, - itemCheckboxStyle: { - position: ABSOLUTE, - width: '13px', // for IE precision - height: '13px' - }, - // itemWidth: undefined, - // symbolRadius: 0, - // symbolWidth: 16, - symbolPadding: 5, - verticalAlign: 'bottom', - // width: undefined, - x: 0, - y: 0, - title: { - //text: null, - style: { - fontWeight: 'bold' - } - } - }, - - loading: { - // hideDuration: 100, - labelStyle: { - fontWeight: 'bold', - position: RELATIVE, - top: '45%' - }, - // showDuration: 0, - style: { - position: ABSOLUTE, - backgroundColor: 'white', - opacity: 0.5, - textAlign: 'center' - } - }, - - tooltip: { - enabled: true, - animation: hasSVG, - //crosshairs: null, - backgroundColor: 'rgba(249, 249, 249, .85)', - borderWidth: 1, - borderRadius: 3, - dateTimeLabelFormats: { - millisecond: '%A, %b %e, %H:%M:%S.%L', - second: '%A, %b %e, %H:%M:%S', - minute: '%A, %b %e, %H:%M', - hour: '%A, %b %e, %H:%M', - day: '%A, %b %e, %Y', - week: 'Week from %A, %b %e, %Y', - month: '%B %Y', - year: '%Y' - }, - footerFormat: '', - //formatter: defaultFormatter, - headerFormat: '{point.key}
', - pointFormat: '\u25CF {series.name}: {point.y}
', - shadow: true, - //shape: 'callout', - //shared: false, - snap: isTouchDevice ? 25 : 10, - style: { - color: '#333333', - cursor: 'default', - fontSize: '12px', - padding: '8px', - pointerEvents: 'none', // #1686 http://caniuse.com/#feat=pointer-events - whiteSpace: 'nowrap' - } - //xDateFormat: '%A, %b %e, %Y', - //valueDecimals: null, - //valuePrefix: '', - //valueSuffix: '' - }, - - credits: { - enabled: true, - text: 'Highcharts.com', - href: 'http://www.highcharts.com', - position: { - align: 'right', - x: -10, - verticalAlign: 'bottom', - y: -5 - }, - style: { - cursor: 'pointer', - color: '#909090', - fontSize: '9px' - } - } - }; - - - - /** - * Set the time methods globally based on the useUTC option. Time method can be either - * local time or UTC (default). - */ - function setTimeMethods() { - var globalOptions = defaultOptions.global, - useUTC = globalOptions.useUTC, - GET = useUTC ? 'getUTC' : 'get', - SET = useUTC ? 'setUTC' : 'set'; - - - Date = globalOptions.Date || win.Date; - timezoneOffset = useUTC && globalOptions.timezoneOffset; - getTimezoneOffset = useUTC && globalOptions.getTimezoneOffset; - makeTime = function (year, month, date, hours, minutes, seconds) { - var d; - if (useUTC) { - d = Date.UTC.apply(0, arguments); - d += getTZOffset(d); - } else { - d = new Date( - year, - month, - pick(date, 1), - pick(hours, 0), - pick(minutes, 0), - pick(seconds, 0) - ).getTime(); - } - return d; - }; - getMinutes = GET + 'Minutes'; - getHours = GET + 'Hours'; - getDay = GET + 'Day'; - getDate = GET + 'Date'; - getMonth = GET + 'Month'; - getFullYear = GET + 'FullYear'; - setMilliseconds = SET + 'Milliseconds'; - setSeconds = SET + 'Seconds'; - setMinutes = SET + 'Minutes'; - setHours = SET + 'Hours'; - setDate = SET + 'Date'; - setMonth = SET + 'Month'; - setFullYear = SET + 'FullYear'; - - } - - /** - * Merge the default options with custom options and return the new options structure - * @param {Object} options The new custom options - */ - function setOptions(options) { - - // Copy in the default options - defaultOptions = merge(true, defaultOptions, options); - - // Apply UTC - setTimeMethods(); - - return defaultOptions; - } - - /** - * Get the updated default options. Until 3.0.7, merely exposing defaultOptions for outside modules - * wasn't enough because the setOptions method created a new object. - */ - function getOptions() { - return defaultOptions; - } - - - - - - - // Series defaults - var defaultPlotOptions = defaultOptions.plotOptions, - defaultSeriesOptions = defaultPlotOptions.line; - - // set the default time methods - setTimeMethods(); - - - /** - * Handle color operations. The object methods are chainable. - * @param {String} input The input color in either rbga or hex format - */ - function Color(input) { - // Backwards compatibility, allow instanciation without new - if (!(this instanceof Color)) { - return new Color(input); - } - // Initialize - this.init(input); - } - Color.prototype = { - - // Collection of parsers. This can be extended from the outside by pushing parsers - // to Highcharts.Colors.prototype.parsers. - parsers: [{ - // RGBA color - regex: /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/, - parse: function (result) { - return [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)]; - } - }, { - // HEX color - regex: /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/, - parse: function (result) { - return [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1]; - } - }, { - // RGB color - regex: /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/, - parse: function (result) { - return [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1]; - } - }], - - /** - * Parse the input color to rgba array - * @param {String} input - */ - init: function (input) { - var result, - rgba, - i, - parser; - - this.input = input; - - // Gradients - if (input && input.stops) { - this.stops = map(input.stops, function (stop) { - return new Color(stop[1]); - }); - - // Solid colors - } else { - i = this.parsers.length; - while (i-- && !rgba) { - parser = this.parsers[i]; - result = parser.regex.exec(input); - if (result) { - rgba = parser.parse(result); - } - } - } - this.rgba = rgba || []; - }, - - /** - * Return the color a specified format - * @param {String} format - */ - get: function (format) { - var input = this.input, - rgba = this.rgba, - ret; - - if (this.stops) { - ret = merge(input); - ret.stops = [].concat(ret.stops); - each(this.stops, function (stop, i) { - ret.stops[i] = [ret.stops[i][0], stop.get(format)]; - }); - - // it's NaN if gradient colors on a column chart - } else if (rgba && isNumber(rgba[0])) { - if (format === 'rgb' || (!format && rgba[3] === 1)) { - ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')'; - } else if (format === 'a') { - ret = rgba[3]; - } else { - ret = 'rgba(' + rgba.join(',') + ')'; - } - } else { - ret = input; - } - return ret; - }, - - /** - * Brighten the color - * @param {Number} alpha - */ - brighten: function (alpha) { - var i, - rgba = this.rgba; - - if (this.stops) { - each(this.stops, function (stop) { - stop.brighten(alpha); - }); - - } else if (isNumber(alpha) && alpha !== 0) { - for (i = 0; i < 3; i++) { - rgba[i] += pInt(alpha * 255); - - if (rgba[i] < 0) { - rgba[i] = 0; - } - if (rgba[i] > 255) { - rgba[i] = 255; - } - } - } - return this; - }, - - /** - * Set the color's opacity to a given alpha value - * @param {Number} alpha - */ - setOpacity: function (alpha) { - this.rgba[3] = alpha; - return this; - } - }; - - - /** - * A wrapper object for SVG elements - */ - function SVGElement() {} - - SVGElement.prototype = { - - // Default base for animation - opacity: 1, - // For labels, these CSS properties are applied to the node directly - textProps: ['direction', 'fontSize', 'fontWeight', 'fontFamily', 'fontStyle', 'color', - 'lineHeight', 'width', 'textDecoration', 'textOverflow', 'textShadow'], - - /** - * Initialize the SVG renderer - * @param {Object} renderer - * @param {String} nodeName - */ - init: function (renderer, nodeName) { - var wrapper = this; - wrapper.element = nodeName === 'span' ? - createElement(nodeName) : - doc.createElementNS(SVG_NS, nodeName); - wrapper.renderer = renderer; - }, - - /** - * Animate a given attribute - * @param {Object} params - * @param {Number} options Options include duration, easing, step and complete - * @param {Function} complete Function to perform at the end of animation - */ - animate: function (params, options, complete) { - var animOptions = pick(options, this.renderer.globalAnimation, true); - stop(this); // stop regardless of animation actually running, or reverting to .attr (#607) - if (animOptions) { - if (complete) { // allows using a callback with the global animation without overwriting it - animOptions.complete = complete; - } - animate(this, params, animOptions); - } else { - this.attr(params, null, complete); - } - return this; - }, - - /** - * Build an SVG gradient out of a common JavaScript configuration object - */ - colorGradient: function (color, prop, elem) { - var renderer = this.renderer, - colorObject, - gradName, - gradAttr, - radAttr, - gradients, - gradientObject, - stops, - stopColor, - stopOpacity, - radialReference, - n, - id, - key = [], - value; - - // Apply linear or radial gradients - if (color.linearGradient) { - gradName = 'linearGradient'; - } else if (color.radialGradient) { - gradName = 'radialGradient'; - } - - if (gradName) { - gradAttr = color[gradName]; - gradients = renderer.gradients; - stops = color.stops; - radialReference = elem.radialReference; - - // Keep < 2.2 kompatibility - if (isArray(gradAttr)) { - color[gradName] = gradAttr = { - x1: gradAttr[0], - y1: gradAttr[1], - x2: gradAttr[2], - y2: gradAttr[3], - gradientUnits: 'userSpaceOnUse' - }; - } - - // Correct the radial gradient for the radial reference system - if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) { - radAttr = gradAttr; // Save the radial attributes for updating - gradAttr = merge(gradAttr, - renderer.getRadialAttr(radialReference, radAttr), - { gradientUnits: 'userSpaceOnUse' } - ); - } - - // Build the unique key to detect whether we need to create a new element (#1282) - for (n in gradAttr) { - if (n !== 'id') { - key.push(n, gradAttr[n]); - } - } - for (n in stops) { - key.push(stops[n]); - } - key = key.join(','); - - // Check if a gradient object with the same config object is created within this renderer - if (gradients[key]) { - id = gradients[key].attr('id'); - - } else { - - // Set the id and create the element - gradAttr.id = id = PREFIX + idCounter++; - gradients[key] = gradientObject = renderer.createElement(gradName) - .attr(gradAttr) - .add(renderer.defs); - - gradientObject.radAttr = radAttr; - - // The gradient needs to keep a list of stops to be able to destroy them - gradientObject.stops = []; - each(stops, function (stop) { - var stopObject; - if (stop[1].indexOf('rgba') === 0) { - colorObject = Color(stop[1]); - stopColor = colorObject.get('rgb'); - stopOpacity = colorObject.get('a'); - } else { - stopColor = stop[1]; - stopOpacity = 1; - } - stopObject = renderer.createElement('stop').attr({ - offset: stop[0], - 'stop-color': stopColor, - 'stop-opacity': stopOpacity - }).add(gradientObject); - - // Add the stop element to the gradient - gradientObject.stops.push(stopObject); - }); - } - - // Set the reference to the gradient object - value = 'url(' + renderer.url + '#' + id + ')'; - elem.setAttribute(prop, value); - elem.gradient = key; - - // Allow the color to be concatenated into tooltips formatters etc. (#2995) - color.toString = function () { - return value; - }; - } - }, - - /** - * Apply a polyfill to the text-stroke CSS property, by copying the text element - * and apply strokes to the copy. - * - * Contrast checks at http://jsfiddle.net/highcharts/43soe9m1/2/ - */ - applyTextShadow: function (textShadow) { - var elem = this.element, - tspans, - hasContrast = textShadow.indexOf('contrast') !== -1, - styles = {}, - forExport = this.renderer.forExport, - // IE10 and IE11 report textShadow in elem.style even though it doesn't work. Check - // this again with new IE release. In exports, the rendering is passed to PhantomJS. - supports = forExport || (elem.style.textShadow !== UNDEFINED && !isMS); - - // When the text shadow is set to contrast, use dark stroke for light text and vice versa - if (hasContrast) { - styles.textShadow = textShadow = textShadow.replace(/contrast/g, this.renderer.getContrast(elem.style.fill)); - } - - // Safari with retina displays as well as PhantomJS bug (#3974). Firefox does not tolerate this, - // it removes the text shadows. - if (isWebKit || forExport) { - styles.textRendering = 'geometricPrecision'; - } - - /* Selective side-by-side testing in supported browser (http://jsfiddle.net/highcharts/73L1ptrh/) - if (elem.textContent.indexOf('2.') === 0) { - elem.style['text-shadow'] = 'none'; - supports = false; - } - // */ - - // No reason to polyfill, we've got native support - if (supports) { - this.css(styles); // Apply altered textShadow or textRendering workaround - } else { - - this.fakeTS = true; // Fake text shadow - - // In order to get the right y position of the clones, - // copy over the y setter - this.ySetter = this.xSetter; - - tspans = [].slice.call(elem.getElementsByTagName('tspan')); - each(textShadow.split(/\s?,\s?/g), function (textShadow) { - var firstChild = elem.firstChild, - color, - strokeWidth; - - textShadow = textShadow.split(' '); - color = textShadow[textShadow.length - 1]; - - // Approximately tune the settings to the text-shadow behaviour - strokeWidth = textShadow[textShadow.length - 2]; - - if (strokeWidth) { - each(tspans, function (tspan, y) { - var clone; - - // Let the first line start at the correct X position - if (y === 0) { - tspan.setAttribute('x', elem.getAttribute('x')); - y = elem.getAttribute('y'); - tspan.setAttribute('y', y || 0); - if (y === null) { - elem.setAttribute('y', 0); - } - } - - // Create the clone and apply shadow properties - clone = tspan.cloneNode(1); - attr(clone, { - 'class': PREFIX + 'text-shadow', - 'fill': color, - 'stroke': color, - 'stroke-opacity': 1 / mathMax(pInt(strokeWidth), 3), - 'stroke-width': strokeWidth, - 'stroke-linejoin': 'round' - }); - elem.insertBefore(clone, firstChild); - }); - } - }); - } - }, - - /** - * Set or get a given attribute - * @param {Object|String} hash - * @param {Mixed|Undefined} val - */ - attr: function (hash, val, complete) { - var key, - value, - element = this.element, - hasSetSymbolSize, - ret = this, - skipAttr, - setter; - - // single key-value pair - if (typeof hash === 'string' && val !== UNDEFINED) { - key = hash; - hash = {}; - hash[key] = val; - } - - // used as a getter: first argument is a string, second is undefined - if (typeof hash === 'string') { - ret = (this[hash + 'Getter'] || this._defaultGetter).call(this, hash, element); - - // setter - } else { - - for (key in hash) { - value = hash[key]; - skipAttr = false; - - - - if (this.symbolName && /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(key)) { - if (!hasSetSymbolSize) { - this.symbolAttr(hash); - hasSetSymbolSize = true; - } - skipAttr = true; - } - - if (this.rotation && (key === 'x' || key === 'y')) { - this.doTransform = true; - } - - if (!skipAttr) { - setter = this[key + 'Setter'] || this._defaultSetter; - setter.call(this, value, key, element); - - // Let the shadow follow the main element - if (this.shadows && /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(key)) { - this.updateShadows(key, value, setter); - } - } - } - - // Update transform. Do this outside the loop to prevent redundant updating for batch setting - // of attributes. - if (this.doTransform) { - this.updateTransform(); - this.doTransform = false; - } - - } - - // In accordance with animate, run a complete callback - if (complete) { - complete(); - } - - return ret; - }, - - /** - * Update the shadow elements with new attributes - * @param {String} key The attribute name - * @param {String|Number} value The value of the attribute - * @param {Function} setter The setter function, inherited from the parent wrapper - * @returns {undefined} - */ - updateShadows: function (key, value, setter) { - var shadows = this.shadows, - i = shadows.length; - - while (i--) { - setter.call( - shadows[i], - key === 'height' ? - Math.max(value - (shadows[i].cutHeight || 0), 0) : - key === 'd' ? this.d : value, - key, - shadows[i] - ); - } - }, - - /** - * Add a class name to an element - */ - addClass: function (className) { - var element = this.element, - currentClassName = attr(element, 'class') || ''; - - if (currentClassName.indexOf(className) === -1) { - attr(element, 'class', currentClassName + ' ' + className); - } - return this; - }, - /* hasClass and removeClass are not (yet) needed - hasClass: function (className) { - return attr(this.element, 'class').indexOf(className) !== -1; - }, - removeClass: function (className) { - attr(this.element, 'class', attr(this.element, 'class').replace(className, '')); - return this; - }, - */ - - /** - * If one of the symbol size affecting parameters are changed, - * check all the others only once for each call to an element's - * .attr() method - * @param {Object} hash - */ - symbolAttr: function (hash) { - var wrapper = this; - - each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) { - wrapper[key] = pick(hash[key], wrapper[key]); - }); - - wrapper.attr({ - d: wrapper.renderer.symbols[wrapper.symbolName]( - wrapper.x, - wrapper.y, - wrapper.width, - wrapper.height, - wrapper - ) - }); - }, - - /** - * Apply a clipping path to this object - * @param {String} id - */ - clip: function (clipRect) { - return this.attr('clip-path', clipRect ? 'url(' + this.renderer.url + '#' + clipRect.id + ')' : NONE); - }, - - /** - * Calculate the coordinates needed for drawing a rectangle crisply and return the - * calculated attributes - * @param {Number} strokeWidth - * @param {Number} x - * @param {Number} y - * @param {Number} width - * @param {Number} height - */ - crisp: function (rect) { - - var wrapper = this, - key, - attribs = {}, - normalizer, - strokeWidth = wrapper.strokeWidth || 0; - - normalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors - - // normalize for crisp edges - rect.x = mathFloor(rect.x || wrapper.x || 0) + normalizer; - rect.y = mathFloor(rect.y || wrapper.y || 0) + normalizer; - rect.width = mathFloor((rect.width || wrapper.width || 0) - 2 * normalizer); - rect.height = mathFloor((rect.height || wrapper.height || 0) - 2 * normalizer); - rect.strokeWidth = strokeWidth; - - for (key in rect) { - if (wrapper[key] !== rect[key]) { // only set attribute if changed - wrapper[key] = attribs[key] = rect[key]; - } - } - - return attribs; - }, - - /** - * Set styles for the element - * @param {Object} styles - */ - css: function (styles) { - var elemWrapper = this, - oldStyles = elemWrapper.styles, - newStyles = {}, - elem = elemWrapper.element, - textWidth, - n, - serializedCss = '', - hyphenate, - hasNew = !oldStyles; - - // convert legacy - if (styles && styles.color) { - styles.fill = styles.color; - } - - // Filter out existing styles to increase performance (#2640) - if (oldStyles) { - for (n in styles) { - if (styles[n] !== oldStyles[n]) { - newStyles[n] = styles[n]; - hasNew = true; - } - } - } - if (hasNew) { - textWidth = elemWrapper.textWidth = - (styles && styles.width && elem.nodeName.toLowerCase() === 'text' && pInt(styles.width)) || - elemWrapper.textWidth; // #3501 - - // Merge the new styles with the old ones - if (oldStyles) { - styles = extend( - oldStyles, - newStyles - ); - } - - // store object - elemWrapper.styles = styles; - - if (textWidth && (useCanVG || (!hasSVG && elemWrapper.renderer.forExport))) { - delete styles.width; - } - - // serialize and set style attribute - if (isMS && !hasSVG) { - css(elemWrapper.element, styles); - } else { - hyphenate = function (a, b) { - return '-' + b.toLowerCase(); - }; - for (n in styles) { - serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';'; - } - attr(elem, 'style', serializedCss); // #1881 - } - - - // re-build text - if (textWidth && elemWrapper.added) { - elemWrapper.renderer.buildText(elemWrapper); - } - } - - return elemWrapper; - }, - - /** - * Add an event listener - * @param {String} eventType - * @param {Function} handler - */ - on: function (eventType, handler) { - var svgElement = this, - element = svgElement.element; - - // touch - if (hasTouch && eventType === 'click') { - element.ontouchstart = function (e) { - svgElement.touchEventFired = Date.now(); - e.preventDefault(); - handler.call(element, e); - }; - element.onclick = function (e) { - if (userAgent.indexOf('Android') === -1 || Date.now() - (svgElement.touchEventFired || 0) > 1100) { // #2269 - handler.call(element, e); - } - }; - } else { - // simplest possible event model for internal use - element['on' + eventType] = handler; - } - return this; - }, - - /** - * Set the coordinates needed to draw a consistent radial gradient across - * pie slices regardless of positioning inside the chart. The format is - * [centerX, centerY, diameter] in pixels. - */ - setRadialReference: function (coordinates) { - var existingGradient = this.renderer.gradients[this.element.gradient]; - - this.element.radialReference = coordinates; - - // On redrawing objects with an existing gradient, the gradient needs - // to be repositioned (#3801) - if (existingGradient && existingGradient.radAttr) { - existingGradient.animate( - this.renderer.getRadialAttr( - coordinates, - existingGradient.radAttr - ) - ); - } - - return this; - }, - - /** - * Move an object and its children by x and y values - * @param {Number} x - * @param {Number} y - */ - translate: function (x, y) { - return this.attr({ - translateX: x, - translateY: y - }); - }, - - /** - * Invert a group, rotate and flip - */ - invert: function () { - var wrapper = this; - wrapper.inverted = true; - wrapper.updateTransform(); - return wrapper; - }, - - /** - * Private method to update the transform attribute based on internal - * properties - */ - updateTransform: function () { - var wrapper = this, - translateX = wrapper.translateX || 0, - translateY = wrapper.translateY || 0, - scaleX = wrapper.scaleX, - scaleY = wrapper.scaleY, - inverted = wrapper.inverted, - rotation = wrapper.rotation, - element = wrapper.element, - transform; - - // flipping affects translate as adjustment for flipping around the group's axis - if (inverted) { - translateX += wrapper.attr('width'); - translateY += wrapper.attr('height'); - } - - // Apply translate. Nearly all transformed elements have translation, so instead - // of checking for translate = 0, do it always (#1767, #1846). - transform = ['translate(' + translateX + ',' + translateY + ')']; - - // apply rotation - if (inverted) { - transform.push('rotate(90) scale(-1,1)'); - } else if (rotation) { // text rotation - transform.push('rotate(' + rotation + ' ' + (element.getAttribute('x') || 0) + ' ' + (element.getAttribute('y') || 0) + ')'); - - // Delete bBox memo when the rotation changes - //delete wrapper.bBox; - } - - // apply scale - if (defined(scaleX) || defined(scaleY)) { - transform.push('scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')'); - } - - if (transform.length) { - element.setAttribute('transform', transform.join(' ')); - } - }, - /** - * Bring the element to the front - */ - toFront: function () { - var element = this.element; - element.parentNode.appendChild(element); - return this; - }, - - - /** - * Break down alignment options like align, verticalAlign, x and y - * to x and y relative to the chart. - * - * @param {Object} alignOptions - * @param {Boolean} alignByTranslate - * @param {String[Object} box The box to align to, needs a width and height. When the - * box is a string, it refers to an object in the Renderer. For example, when - * box is 'spacingBox', it refers to Renderer.spacingBox which holds width, height - * x and y properties. - * - */ - align: function (alignOptions, alignByTranslate, box) { - var align, - vAlign, - x, - y, - attribs = {}, - alignTo, - renderer = this.renderer, - alignedObjects = renderer.alignedObjects; - - // First call on instanciate - if (alignOptions) { - this.alignOptions = alignOptions; - this.alignByTranslate = alignByTranslate; - if (!box || isString(box)) { // boxes other than renderer handle this internally - this.alignTo = alignTo = box || 'renderer'; - erase(alignedObjects, this); // prevent duplicates, like legendGroup after resize - alignedObjects.push(this); - box = null; // reassign it below - } - - // When called on resize, no arguments are supplied - } else { - alignOptions = this.alignOptions; - alignByTranslate = this.alignByTranslate; - alignTo = this.alignTo; - } - - box = pick(box, renderer[alignTo], renderer); - - // Assign variables - align = alignOptions.align; - vAlign = alignOptions.verticalAlign; - x = (box.x || 0) + (alignOptions.x || 0); // default: left align - y = (box.y || 0) + (alignOptions.y || 0); // default: top align - - // Align - if (align === 'right' || align === 'center') { - x += (box.width - (alignOptions.width || 0)) / - { right: 1, center: 2 }[align]; - } - attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x); - - - // Vertical align - if (vAlign === 'bottom' || vAlign === 'middle') { - y += (box.height - (alignOptions.height || 0)) / - ({ bottom: 1, middle: 2 }[vAlign] || 1); - - } - attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y); - - // Animate only if already placed - this[this.placed ? 'animate' : 'attr'](attribs); - this.placed = true; - this.alignAttr = attribs; - - return this; - }, - - /** - * Get the bounding box (width, height, x and y) for the element - */ - getBBox: function (reload, rot) { - var wrapper = this, - bBox, // = wrapper.bBox, - renderer = wrapper.renderer, - width, - height, - rotation, - rad, - element = wrapper.element, - styles = wrapper.styles, - textStr = wrapper.textStr, - textShadow, - elemStyle = element.style, - toggleTextShadowShim, - cache = renderer.cache, - cacheKeys = renderer.cacheKeys, - cacheKey; - - rotation = pick(rot, wrapper.rotation); - rad = rotation * deg2rad; - - if (textStr !== UNDEFINED) { - - // Properties that affect bounding box - cacheKey = ['', rotation || 0, styles && styles.fontSize, element.style.width].join(','); - - // Since numbers are monospaced, and numerical labels appear a lot in a chart, - // we assume that a label of n characters has the same bounding box as others - // of the same length. - if (textStr === '' || numRegex.test(textStr)) { - cacheKey = 'num:' + textStr.toString().length + cacheKey; - - // Caching all strings reduces rendering time by 4-5%. - } else { - cacheKey = textStr + cacheKey; - } - } - - if (cacheKey && !reload) { - bBox = cache[cacheKey]; - } - - // No cache found - if (!bBox) { - - // SVG elements - if (element.namespaceURI === SVG_NS || renderer.forExport) { - try { // Fails in Firefox if the container has display: none. - - // When the text shadow shim is used, we need to hide the fake shadows - // to get the correct bounding box (#3872) - toggleTextShadowShim = this.fakeTS && function (display) { - each(element.querySelectorAll('.' + PREFIX + 'text-shadow'), function (tspan) { - tspan.style.display = display; - }); - }; - - // Workaround for #3842, Firefox reporting wrong bounding box for shadows - if (isFirefox && elemStyle.textShadow) { - textShadow = elemStyle.textShadow; - elemStyle.textShadow = ''; - } else if (toggleTextShadowShim) { - toggleTextShadowShim(NONE); - } - - bBox = element.getBBox ? - // SVG: use extend because IE9 is not allowed to change width and height in case - // of rotation (below) - extend({}, element.getBBox()) : - // Canvas renderer and legacy IE in export mode - { - width: element.offsetWidth, - height: element.offsetHeight - }; - - // #3842 - if (textShadow) { - elemStyle.textShadow = textShadow; - } else if (toggleTextShadowShim) { - toggleTextShadowShim(''); - } - } catch (e) {} - - // If the bBox is not set, the try-catch block above failed. The other condition - // is for Opera that returns a width of -Infinity on hidden elements. - if (!bBox || bBox.width < 0) { - bBox = { width: 0, height: 0 }; - } - - - // VML Renderer or useHTML within SVG - } else { - - bBox = wrapper.htmlGetBBox(); - - } - - // True SVG elements as well as HTML elements in modern browsers using the .useHTML option - // need to compensated for rotation - if (renderer.isSVG) { - width = bBox.width; - height = bBox.height; - - // Workaround for wrong bounding box in IE9 and IE10 (#1101, #1505, #1669, #2568) - if (isMS && styles && styles.fontSize === '11px' && height.toPrecision(3) === '16.9') { - bBox.height = height = 14; - } - - // Adjust for rotated text - if (rotation) { - bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad)); - bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad)); - } - } - - // Cache it - if (cacheKey) { - - // Rotate (#4681) - while (cacheKeys.length > 250) { - delete cache[cacheKeys.shift()]; - } - - if (!cache[cacheKey]) { - cacheKeys.push(cacheKey); - } - cache[cacheKey] = bBox; - } - } - return bBox; - }, - - /** - * Show the element - */ - show: function (inherit) { - return this.attr({ visibility: inherit ? 'inherit' : VISIBLE }); - }, - - /** - * Hide the element - */ - hide: function () { - return this.attr({ visibility: HIDDEN }); - }, - - fadeOut: function (duration) { - var elemWrapper = this; - elemWrapper.animate({ - opacity: 0 - }, { - duration: duration || 150, - complete: function () { - elemWrapper.attr({ y: -9999 }); // #3088, assuming we're only using this for tooltips - } - }); - }, - - /** - * Add the element - * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined - * to append the element to the renderer.box. - */ - add: function (parent) { - - var renderer = this.renderer, - element = this.element, - inserted; - - if (parent) { - this.parentGroup = parent; - } - - // mark as inverted - this.parentInverted = parent && parent.inverted; - - // build formatted text - if (this.textStr !== undefined) { - renderer.buildText(this); - } - - // Mark as added - this.added = true; - - // If we're adding to renderer root, or other elements in the group - // have a z index, we need to handle it - if (!parent || parent.handleZ || this.zIndex) { - inserted = this.zIndexSetter(); - } - - // If zIndex is not handled, append at the end - if (!inserted) { - (parent ? parent.element : renderer.box).appendChild(element); - } - - // fire an event for internal hooks - if (this.onAdd) { - this.onAdd(); - } - - return this; - }, - - /** - * Removes a child either by removeChild or move to garbageBin. - * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not. - */ - safeRemoveChild: function (element) { - var parentNode = element.parentNode; - if (parentNode) { - parentNode.removeChild(element); - } - }, - - /** - * Destroy the element and element wrapper - */ - destroy: function () { - var wrapper = this, - element = wrapper.element || {}, - shadows = wrapper.shadows, - parentToClean = wrapper.renderer.isSVG && element.nodeName === 'SPAN' && wrapper.parentGroup, - grandParent, - key, - i; - - // remove events - element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = element.point = null; - stop(wrapper); // stop running animations - - if (wrapper.clipPath) { - wrapper.clipPath = wrapper.clipPath.destroy(); - } - - // Destroy stops in case this is a gradient object - if (wrapper.stops) { - for (i = 0; i < wrapper.stops.length; i++) { - wrapper.stops[i] = wrapper.stops[i].destroy(); - } - wrapper.stops = null; - } - - // remove element - wrapper.safeRemoveChild(element); - - // destroy shadows - if (shadows) { - each(shadows, function (shadow) { - wrapper.safeRemoveChild(shadow); - }); - } - - // In case of useHTML, clean up empty containers emulating SVG groups (#1960, #2393, #2697). - while (parentToClean && parentToClean.div && parentToClean.div.childNodes.length === 0) { - grandParent = parentToClean.parentGroup; - wrapper.safeRemoveChild(parentToClean.div); - delete parentToClean.div; - parentToClean = grandParent; - } - - // remove from alignObjects - if (wrapper.alignTo) { - erase(wrapper.renderer.alignedObjects, wrapper); - } - - for (key in wrapper) { - delete wrapper[key]; - } - - return null; - }, - - /** - * Add a shadow to the element. Must be done after the element is added to the DOM - * @param {Boolean|Object} shadowOptions - */ - shadow: function (shadowOptions, group, cutOff) { - var shadows = [], - i, - shadow, - element = this.element, - strokeWidth, - shadowWidth, - shadowElementOpacity, - - // compensate for inverted plot area - transform; - - - if (shadowOptions) { - shadowWidth = pick(shadowOptions.width, 3); - shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth; - transform = this.parentInverted ? - '(-1,-1)' : - '(' + pick(shadowOptions.offsetX, 1) + ', ' + pick(shadowOptions.offsetY, 1) + ')'; - for (i = 1; i <= shadowWidth; i++) { - shadow = element.cloneNode(0); - strokeWidth = (shadowWidth * 2) + 1 - (2 * i); - attr(shadow, { - 'isShadow': 'true', - 'stroke': shadowOptions.color || 'black', - 'stroke-opacity': shadowElementOpacity * i, - 'stroke-width': strokeWidth, - 'transform': 'translate' + transform, - 'fill': NONE - }); - if (cutOff) { - attr(shadow, 'height', mathMax(attr(shadow, 'height') - strokeWidth, 0)); - shadow.cutHeight = strokeWidth; - } - - if (group) { - group.element.appendChild(shadow); - } else { - element.parentNode.insertBefore(shadow, element); - } - - shadows.push(shadow); - } - - this.shadows = shadows; - } - return this; - - }, - - xGetter: function (key) { - if (this.element.nodeName === 'circle') { - key = { x: 'cx', y: 'cy' }[key] || key; - } - return this._defaultGetter(key); - }, - - /** - * Get the current value of an attribute or pseudo attribute, used mainly - * for animation. - */ - _defaultGetter: function (key) { - var ret = pick(this[key], this.element ? this.element.getAttribute(key) : null, 0); - - if (/^[\-0-9\.]+$/.test(ret)) { // is numerical - ret = parseFloat(ret); - } - return ret; - }, - - - dSetter: function (value, key, element) { - if (value && value.join) { // join path - value = value.join(' '); - } - if (/(NaN| {2}|^$)/.test(value)) { - value = 'M 0 0'; - } - element.setAttribute(key, value); - - this[key] = value; - }, - dashstyleSetter: function (value) { - var i, - strokeWidth = this['stroke-width']; - - // If "inherit", like maps in IE, assume 1 (#4981). With HC5 and the new strokeWidth - // function, we should be able to use that instead. - if (strokeWidth === 'inherit') { - strokeWidth = 1; - } - value = value && value.toLowerCase(); - if (value) { - value = value - .replace('shortdashdotdot', '3,1,1,1,1,1,') - .replace('shortdashdot', '3,1,1,1') - .replace('shortdot', '1,1,') - .replace('shortdash', '3,1,') - .replace('longdash', '8,3,') - .replace(/dot/g, '1,3,') - .replace('dash', '4,3,') - .replace(/,$/, '') - .split(','); // ending comma - - i = value.length; - while (i--) { - value[i] = pInt(value[i]) * strokeWidth; - } - value = value.join(',') - .replace(/NaN/g, 'none'); // #3226 - this.element.setAttribute('stroke-dasharray', value); - } - }, - alignSetter: function (value) { - this.element.setAttribute('text-anchor', { left: 'start', center: 'middle', right: 'end' }[value]); - }, - opacitySetter: function (value, key, element) { - this[key] = value; - element.setAttribute(key, value); - }, - titleSetter: function (value) { - var titleNode = this.element.getElementsByTagName('title')[0]; - if (!titleNode) { - titleNode = doc.createElementNS(SVG_NS, 'title'); - this.element.appendChild(titleNode); - } - - // Remove text content if it exists - if (titleNode.firstChild) { - titleNode.removeChild(titleNode.firstChild); - } - - titleNode.appendChild( - doc.createTextNode( - (String(pick(value), '')).replace(/<[^>]*>/g, '') // #3276, #3895 - ) - ); - }, - textSetter: function (value) { - if (value !== this.textStr) { - // Delete bBox memo when the text changes - delete this.bBox; - - this.textStr = value; - if (this.added) { - this.renderer.buildText(this); - } - } - }, - fillSetter: function (value, key, element) { - if (typeof value === 'string') { - element.setAttribute(key, value); - } else if (value) { - this.colorGradient(value, key, element); - } - }, - visibilitySetter: function (value, key, element) { - // IE9-11 doesn't handle visibilty:inherit well, so we remove the attribute instead (#2881, #3909) - if (value === 'inherit') { - element.removeAttribute(key); - } else { - element.setAttribute(key, value); - } - }, - zIndexSetter: function (value, key) { - var renderer = this.renderer, - parentGroup = this.parentGroup, - parentWrapper = parentGroup || renderer, - parentNode = parentWrapper.element || renderer.box, - childNodes, - otherElement, - otherZIndex, - element = this.element, - inserted, - run = this.added, - i; - - if (defined(value)) { - element.zIndex = value; // So we can read it for other elements in the group - value = +value; - if (this[key] === value) { // Only update when needed (#3865) - run = false; - } - this[key] = value; - } - - // Insert according to this and other elements' zIndex. Before .add() is called, - // nothing is done. Then on add, or by later calls to zIndexSetter, the node - // is placed on the right place in the DOM. - if (run) { - value = this.zIndex; - - if (value && parentGroup) { - parentGroup.handleZ = true; - } - - childNodes = parentNode.childNodes; - for (i = 0; i < childNodes.length && !inserted; i++) { - otherElement = childNodes[i]; - otherZIndex = otherElement.zIndex; - if (otherElement !== element && ( - // Insert before the first element with a higher zIndex - pInt(otherZIndex) > value || - // If no zIndex given, insert before the first element with a zIndex - (!defined(value) && defined(otherZIndex)) - - )) { - parentNode.insertBefore(element, otherElement); - inserted = true; - } - } - if (!inserted) { - parentNode.appendChild(element); - } - } - return inserted; - }, - _defaultSetter: function (value, key, element) { - element.setAttribute(key, value); - } - }; - - // Some shared setters and getters - SVGElement.prototype.yGetter = SVGElement.prototype.xGetter; - SVGElement.prototype.translateXSetter = SVGElement.prototype.translateYSetter = - SVGElement.prototype.rotationSetter = SVGElement.prototype.verticalAlignSetter = - SVGElement.prototype.scaleXSetter = SVGElement.prototype.scaleYSetter = function (value, key) { - this[key] = value; - this.doTransform = true; - }; - - // WebKit and Batik have problems with a stroke-width of zero, so in this case we remove the - // stroke attribute altogether. #1270, #1369, #3065, #3072. - SVGElement.prototype['stroke-widthSetter'] = SVGElement.prototype.strokeSetter = function (value, key, element) { - this[key] = value; - // Only apply the stroke attribute if the stroke width is defined and larger than 0 - if (this.stroke && this['stroke-width']) { - this.strokeWidth = this['stroke-width']; - SVGElement.prototype.fillSetter.call(this, this.stroke, 'stroke', element); // use prototype as instance may be overridden - element.setAttribute('stroke-width', this['stroke-width']); - this.hasStroke = true; - } else if (key === 'stroke-width' && value === 0 && this.hasStroke) { - element.removeAttribute('stroke'); - this.hasStroke = false; - } - }; - - - /** - * The default SVG renderer - */ - var SVGRenderer = function () { - this.init.apply(this, arguments); - }; - SVGRenderer.prototype = { - Element: SVGElement, - - /** - * Initialize the SVGRenderer - * @param {Object} container - * @param {Number} width - * @param {Number} height - * @param {Boolean} forExport - */ - init: function (container, width, height, style, forExport, allowHTML) { - var renderer = this, - boxWrapper, - element, - desc; - - boxWrapper = renderer.createElement('svg') - .attr({ - version: '1.1' - }) - .css(this.getStyle(style)); - element = boxWrapper.element; - container.appendChild(element); - - // For browsers other than IE, add the namespace attribute (#1978) - if (container.innerHTML.indexOf('xmlns') === -1) { - attr(element, 'xmlns', SVG_NS); - } - - // object properties - renderer.isSVG = true; - renderer.box = element; - renderer.boxWrapper = boxWrapper; - renderer.alignedObjects = []; - - // Page url used for internal references. #24, #672, #1070 - renderer.url = (isFirefox || isWebKit) && doc.getElementsByTagName('base').length ? - win.location.href - .replace(/#.*?$/, '') // remove the hash - .replace(/([\('\)])/g, '\\$1') // escape parantheses and quotes - .replace(/ /g, '%20') : // replace spaces (needed for Safari only) - ''; - - // Add description - desc = this.createElement('desc').add(); - desc.element.appendChild(doc.createTextNode('Created with ' + PRODUCT + ' ' + VERSION)); - - - renderer.defs = this.createElement('defs').add(); - renderer.allowHTML = allowHTML; - renderer.forExport = forExport; - renderer.gradients = {}; // Object where gradient SvgElements are stored - renderer.cache = {}; // Cache for numerical bounding boxes - renderer.cacheKeys = []; - renderer.imgCount = 0; - - renderer.setSize(width, height, false); - - - - // Issue 110 workaround: - // In Firefox, if a div is positioned by percentage, its pixel position may land - // between pixels. The container itself doesn't display this, but an SVG element - // inside this container will be drawn at subpixel precision. In order to draw - // sharp lines, this must be compensated for. This doesn't seem to work inside - // iframes though (like in jsFiddle). - var subPixelFix, rect; - if (isFirefox && container.getBoundingClientRect) { - renderer.subPixelFix = subPixelFix = function () { - css(container, { left: 0, top: 0 }); - rect = container.getBoundingClientRect(); - css(container, { - left: (mathCeil(rect.left) - rect.left) + PX, - top: (mathCeil(rect.top) - rect.top) + PX - }); - }; - - // run the fix now - subPixelFix(); - - // run it on resize - addEvent(win, 'resize', subPixelFix); - } - }, - - getStyle: function (style) { - this.style = extend({ - fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif', // default font - fontSize: '12px' - }, style); - return this.style; - }, - - /** - * Detect whether the renderer is hidden. This happens when one of the parent elements - * has display: none. #608. - */ - isHidden: function () { - return !this.boxWrapper.getBBox().width; - }, - - /** - * Destroys the renderer and its allocated members. - */ - destroy: function () { - var renderer = this, - rendererDefs = renderer.defs; - renderer.box = null; - renderer.boxWrapper = renderer.boxWrapper.destroy(); - - // Call destroy on all gradient elements - destroyObjectProperties(renderer.gradients || {}); - renderer.gradients = null; - - // Defs are null in VMLRenderer - // Otherwise, destroy them here. - if (rendererDefs) { - renderer.defs = rendererDefs.destroy(); - } - - // Remove sub pixel fix handler - // We need to check that there is a handler, otherwise all functions that are registered for event 'resize' are removed - // See issue #982 - if (renderer.subPixelFix) { - removeEvent(win, 'resize', renderer.subPixelFix); - } - - renderer.alignedObjects = null; - - return null; - }, - - /** - * Create a wrapper for an SVG element - * @param {Object} nodeName - */ - createElement: function (nodeName) { - var wrapper = new this.Element(); - wrapper.init(this, nodeName); - return wrapper; - }, - - /** - * Dummy function for use in canvas renderer - */ - draw: function () {}, - - /** - * Get converted radial gradient attributes - */ - getRadialAttr: function (radialReference, gradAttr) { - return { - cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2], - cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2], - r: gradAttr.r * radialReference[2] - }; - }, - - /** - * Parse a simple HTML string into SVG tspans - * - * @param {Object} textNode The parent text SVG node - */ - buildText: function (wrapper) { - var textNode = wrapper.element, - renderer = this, - forExport = renderer.forExport, - textStr = pick(wrapper.textStr, '').toString(), - hasMarkup = textStr.indexOf('<') !== -1, - lines, - childNodes = textNode.childNodes, - styleRegex, - hrefRegex, - wasTooLong, - parentX = attr(textNode, 'x'), - textStyles = wrapper.styles, - width = wrapper.textWidth, - textLineHeight = textStyles && textStyles.lineHeight, - textShadow = textStyles && textStyles.textShadow, - ellipsis = textStyles && textStyles.textOverflow === 'ellipsis', - i = childNodes.length, - tempParent = width && !wrapper.added && this.box, - getLineHeight = function (tspan) { - return textLineHeight ? - pInt(textLineHeight) : - renderer.fontMetrics( - /(px|em)$/.test(tspan && tspan.style.fontSize) ? - tspan.style.fontSize : - ((textStyles && textStyles.fontSize) || renderer.style.fontSize || 12), - tspan - ).h; - }, - unescapeAngleBrackets = function (inputStr) { - return inputStr.replace(/</g, '<').replace(/>/g, '>'); - }; - - /// remove old text - while (i--) { - textNode.removeChild(childNodes[i]); - } - - // Skip tspans, add text directly to text node. The forceTSpan is a hook - // used in text outline hack. - if (!hasMarkup && !textShadow && !ellipsis && textStr.indexOf(' ') === -1) { - textNode.appendChild(doc.createTextNode(unescapeAngleBrackets(textStr))); - - // Complex strings, add more logic - } else { - - styleRegex = /<.*style="([^"]+)".*>/; - hrefRegex = /<.*href="(http[^"]+)".*>/; - - if (tempParent) { - tempParent.appendChild(textNode); // attach it to the DOM to read offset width - } - - if (hasMarkup) { - lines = textStr - .replace(/<(b|strong)>/g, '') - .replace(/<(i|em)>/g, '') - .replace(/
/g, '') - .split(//g); - - } else { - lines = [textStr]; - } - - - // Trim empty lines (#5261) - lines = grep(lines, function (line) { - return line !== ''; - }); - - - // build the lines - each(lines, function buildTextLines(line, lineNo) { - var spans, - spanNo = 0; - line = line - .replace(/^\s+|\s+$/g, '') // Trim to prevent useless/costly process on the spaces (#5258) - .replace(//g, '|||'); - spans = line.split('|||'); - - each(spans, function buildTextSpans(span) { - if (span !== '' || spans.length === 1) { - var attributes = {}, - tspan = doc.createElementNS(SVG_NS, 'tspan'), - spanStyle; // #390 - if (styleRegex.test(span)) { - spanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2'); - attr(tspan, 'style', spanStyle); - } - if (hrefRegex.test(span) && !forExport) { // Not for export - #1529 - attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"'); - css(tspan, { cursor: 'pointer' }); - } - - span = unescapeAngleBrackets(span.replace(/<(.|\n)*?>/g, '') || ' '); - - // Nested tags aren't supported, and cause crash in Safari (#1596) - if (span !== ' ') { - - // add the text node - tspan.appendChild(doc.createTextNode(span)); - - if (!spanNo) { // first span in a line, align it to the left - if (lineNo && parentX !== null) { - attributes.x = parentX; - } - } else { - attributes.dx = 0; // #16 - } - - // add attributes - attr(tspan, attributes); - - // Append it - textNode.appendChild(tspan); - - // first span on subsequent line, add the line height - if (!spanNo && lineNo) { - - // allow getting the right offset height in exporting in IE - if (!hasSVG && forExport) { - css(tspan, { display: 'block' }); - } - - // Set the line height based on the font size of either - // the text element or the tspan element - attr( - tspan, - 'dy', - getLineHeight(tspan) - ); - } - - /*if (width) { - renderer.breakText(wrapper, width); - }*/ - - // Check width and apply soft breaks or ellipsis - if (width) { - var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273 - hasWhiteSpace = spans.length > 1 || lineNo || (words.length > 1 && textStyles.whiteSpace !== 'nowrap'), - tooLong, - actualWidth, - rest = [], - dy = getLineHeight(tspan), - softLineNo = 1, - rotation = wrapper.rotation, - wordStr = span, // for ellipsis - cursor = wordStr.length, // binary search cursor - bBox; - - while ((hasWhiteSpace || ellipsis) && (words.length || rest.length)) { - wrapper.rotation = 0; // discard rotation when computing box - bBox = wrapper.getBBox(true); - actualWidth = bBox.width; - - // Old IE cannot measure the actualWidth for SVG elements (#2314) - if (!hasSVG && renderer.forExport) { - actualWidth = renderer.measureSpanWidth(tspan.firstChild.data, wrapper.styles); - } - - tooLong = actualWidth > width; - - // For ellipsis, do a binary search for the correct string length - if (wasTooLong === undefined) { - wasTooLong = tooLong; // First time - } - if (ellipsis && wasTooLong) { - cursor /= 2; - - if (wordStr === '' || (!tooLong && cursor < 0.5)) { - words = []; // All ok, break out - } else { - wordStr = span.substring(0, wordStr.length + (tooLong ? -1 : 1) * mathCeil(cursor)); - words = [wordStr + (width > 3 ? '\u2026' : '')]; - tspan.removeChild(tspan.firstChild); - } - - // Looping down, this is the first word sequence that is not too long, - // so we can move on to build the next line. - } else if (!tooLong || words.length === 1) { - words = rest; - rest = []; - - if (words.length) { - softLineNo++; - - tspan = doc.createElementNS(SVG_NS, 'tspan'); - attr(tspan, { - dy: dy, - x: parentX - }); - if (spanStyle) { // #390 - attr(tspan, 'style', spanStyle); - } - textNode.appendChild(tspan); - } - if (actualWidth > width) { // a single word is pressing it out - width = actualWidth; - } - } else { // append to existing line tspan - tspan.removeChild(tspan.firstChild); - rest.unshift(words.pop()); - } - if (words.length) { - tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-'))); - } - } - wrapper.rotation = rotation; - } - - spanNo++; - } - } - }); - }); - - if (wasTooLong) { - wrapper.attr('title', wrapper.textStr); - } - if (tempParent) { - tempParent.removeChild(textNode); // attach it to the DOM to read offset width - } - - // Apply the text shadow - if (textShadow && wrapper.applyTextShadow) { - wrapper.applyTextShadow(textShadow); - } - } - }, - - - - /* - breakText: function (wrapper, width) { - var bBox = wrapper.getBBox(), - node = wrapper.element, - textLength = node.textContent.length, - pos = mathRound(width * textLength / bBox.width), // try this position first, based on average character width - increment = 0, - finalPos; - - if (bBox.width > width) { - while (finalPos === undefined) { - textLength = node.getSubStringLength(0, pos); - - if (textLength <= width) { - if (increment === -1) { - finalPos = pos; - } else { - increment = 1; - } - } else { - if (increment === 1) { - finalPos = pos - 1; - } else { - increment = -1; - } - } - pos += increment; - } - } - console.log('width', width, 'stringWidth', node.getSubStringLength(0, finalPos)) - }, - */ - - /** - * Returns white for dark colors and black for bright colors - */ - getContrast: function (color) { - color = Color(color).rgba; - return color[0] + color[1] + color[2] > 384 ? '#000000' : '#FFFFFF'; - }, - - /** - * Create a button with preset states - * @param {String} text - * @param {Number} x - * @param {Number} y - * @param {Function} callback - * @param {Object} normalState - * @param {Object} hoverState - * @param {Object} pressedState - */ - button: function (text, x, y, callback, normalState, hoverState, pressedState, disabledState, shape) { - var label = this.label(text, x, y, shape, null, null, null, null, 'button'), - curState = 0, - stateOptions, - stateStyle, - normalStyle, - hoverStyle, - pressedStyle, - disabledStyle, - verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 }; - - // Normal state - prepare the attributes - normalState = merge({ - 'stroke-width': 1, - stroke: '#CCCCCC', - fill: { - linearGradient: verticalGradient, - stops: [ - [0, '#FEFEFE'], - [1, '#F6F6F6'] - ] - }, - r: 2, - padding: 5, - style: { - color: 'black' - } - }, normalState); - normalStyle = normalState.style; - delete normalState.style; - - // Hover state - hoverState = merge(normalState, { - stroke: '#68A', - fill: { - linearGradient: verticalGradient, - stops: [ - [0, '#FFF'], - [1, '#ACF'] - ] - } - }, hoverState); - hoverStyle = hoverState.style; - delete hoverState.style; - - // Pressed state - pressedState = merge(normalState, { - stroke: '#68A', - fill: { - linearGradient: verticalGradient, - stops: [ - [0, '#9BD'], - [1, '#CDF'] - ] - } - }, pressedState); - pressedStyle = pressedState.style; - delete pressedState.style; - - // Disabled state - disabledState = merge(normalState, { - style: { - color: '#CCC' - } - }, disabledState); - disabledStyle = disabledState.style; - delete disabledState.style; - - // Add the events. IE9 and IE10 need mouseover and mouseout to funciton (#667). - addEvent(label.element, isMS ? 'mouseover' : 'mouseenter', function () { - if (curState !== 3) { - label.attr(hoverState) - .css(hoverStyle); - } - }); - addEvent(label.element, isMS ? 'mouseout' : 'mouseleave', function () { - if (curState !== 3) { - stateOptions = [normalState, hoverState, pressedState][curState]; - stateStyle = [normalStyle, hoverStyle, pressedStyle][curState]; - label.attr(stateOptions) - .css(stateStyle); - } - }); - - label.setState = function (state) { - label.state = curState = state; - if (!state) { - label.attr(normalState) - .css(normalStyle); - } else if (state === 2) { - label.attr(pressedState) - .css(pressedStyle); - } else if (state === 3) { - label.attr(disabledState) - .css(disabledStyle); - } - }; - - return label - .on('click', function (e) { - if (curState !== 3) { - callback.call(label, e); - } - }) - .attr(normalState) - .css(extend({ cursor: 'default' }, normalStyle)); - }, - - /** - * Make a straight line crisper by not spilling out to neighbour pixels - * @param {Array} points - * @param {Number} width - */ - crispLine: function (points, width) { - // points format: [M, 0, 0, L, 100, 0] - // normalize to a crisp line - if (points[1] === points[4]) { - // Substract due to #1129. Now bottom and left axis gridlines behave the same. - points[1] = points[4] = mathRound(points[1]) - (width % 2 / 2); - } - if (points[2] === points[5]) { - points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2); - } - return points; - }, - - - /** - * Draw a path - * @param {Array} path An SVG path in array form - */ - path: function (path) { - var attr = { - fill: NONE - }; - if (isArray(path)) { - attr.d = path; - } else if (isObject(path)) { // attributes - extend(attr, path); - } - return this.createElement('path').attr(attr); - }, - - /** - * Draw and return an SVG circle - * @param {Number} x The x position - * @param {Number} y The y position - * @param {Number} r The radius - */ - circle: function (x, y, r) { - var attr = isObject(x) ? x : { x: x, y: y, r: r }, - wrapper = this.createElement('circle'); - - // Setting x or y translates to cx and cy - wrapper.xSetter = wrapper.ySetter = function (value, key, element) { - element.setAttribute('c' + key, value); - }; - - return wrapper.attr(attr); - }, - - /** - * Draw and return an arc - * @param {Number} x X position - * @param {Number} y Y position - * @param {Number} r Radius - * @param {Number} innerR Inner radius like used in donut charts - * @param {Number} start Starting angle - * @param {Number} end Ending angle - */ - arc: function (x, y, r, innerR, start, end) { - var arc; - - if (isObject(x)) { - y = x.y; - r = x.r; - innerR = x.innerR; - start = x.start; - end = x.end; - x = x.x; - } - - // Arcs are defined as symbols for the ability to set - // attributes in attr and animate - arc = this.symbol('arc', x || 0, y || 0, r || 0, r || 0, { - innerR: innerR || 0, - start: start || 0, - end: end || 0 - }); - arc.r = r; // #959 - return arc; - }, - - /** - * Draw and return a rectangle - * @param {Number} x Left position - * @param {Number} y Top position - * @param {Number} width - * @param {Number} height - * @param {Number} r Border corner radius - * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing - */ - rect: function (x, y, width, height, r, strokeWidth) { - - r = isObject(x) ? x.r : r; - - var wrapper = this.createElement('rect'), - attribs = isObject(x) ? x : x === UNDEFINED ? {} : { - x: x, - y: y, - width: mathMax(width, 0), - height: mathMax(height, 0) - }; - - if (strokeWidth !== UNDEFINED) { - wrapper.strokeWidth = strokeWidth; - attribs = wrapper.crisp(attribs); - } - - if (r) { - attribs.r = r; - } - - wrapper.rSetter = function (value, key, element) { - attr(element, { - rx: value, - ry: value - }); - }; - - return wrapper.attr(attribs); - }, - - /** - * Resize the box and re-align all aligned elements - * @param {Object} width - * @param {Object} height - * @param {Boolean} animate - * - */ - setSize: function (width, height, animate) { - var renderer = this, - alignedObjects = renderer.alignedObjects, - i = alignedObjects.length; - - renderer.width = width; - renderer.height = height; - - renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({ - width: width, - height: height - }); - - while (i--) { - alignedObjects[i].align(); - } - }, - - /** - * Create a group - * @param {String} name The group will be given a class name of 'highcharts-{name}'. - * This can be used for styling and scripting. - */ - g: function (name) { - var elem = this.createElement('g'); - return defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem; - }, - - /** - * Display an image - * @param {String} src - * @param {Number} x - * @param {Number} y - * @param {Number} width - * @param {Number} height - */ - image: function (src, x, y, width, height) { - var attribs = { - preserveAspectRatio: NONE - }, - elemWrapper; - - // optional properties - if (arguments.length > 1) { - extend(attribs, { - x: x, - y: y, - width: width, - height: height - }); - } - - elemWrapper = this.createElement('image').attr(attribs); - - // set the href in the xlink namespace - if (elemWrapper.element.setAttributeNS) { - elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink', - 'href', src); - } else { - // could be exporting in IE - // using href throws "not supported" in ie7 and under, requries regex shim to fix later - elemWrapper.element.setAttribute('hc-svg-href', src); - } - return elemWrapper; - }, - - /** - * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object. - * - * @param {Object} symbol - * @param {Object} x - * @param {Object} y - * @param {Object} radius - * @param {Object} options - */ - symbol: function (symbol, x, y, width, height, options) { - - var ren = this, - obj, - - // get the symbol definition function - symbolFn = this.symbols[symbol], - - // check if there's a path defined for this symbol - path = symbolFn && symbolFn( - mathRound(x), - mathRound(y), - width, - height, - options - ), - - imageRegex = /^url\((.*?)\)$/, - imageSrc, - imageSize, - centerImage; - - if (path) { - - obj = this.path(path); - // expando properties for use in animate and attr - extend(obj, { - symbolName: symbol, - x: x, - y: y, - width: width, - height: height - }); - if (options) { - extend(obj, options); - } - - - // image symbols - } else if (imageRegex.test(symbol)) { - - // On image load, set the size and position - centerImage = function (img, size) { - if (img.element) { // it may be destroyed in the meantime (#1390) - img.attr({ - width: size[0], - height: size[1] - }); - - if (!img.alignByTranslate) { // #185 - img.translate( - mathRound((width - size[0]) / 2), // #1378 - mathRound((height - size[1]) / 2) - ); - } - } - }; - - imageSrc = symbol.match(imageRegex)[1]; - imageSize = symbolSizes[imageSrc] || (options && options.width && options.height && [options.width, options.height]); - - // Ireate the image synchronously, add attribs async - obj = this.image(imageSrc) - .attr({ - x: x, - y: y - }); - obj.isImg = true; - - if (imageSize) { - centerImage(obj, imageSize); - } else { - // Initialize image to be 0 size so export will still function if there's no cached sizes. - obj.attr({ width: 0, height: 0 }); - - // Create a dummy JavaScript image to get the width and height. Due to a bug in IE < 8, - // the created element must be assigned to a variable in order to load (#292). - createElement('img', { - onload: function () { - - // Special case for SVGs on IE11, the width is not accessible until the image is - // part of the DOM (#2854). - if (this.width === 0) { - css(this, { - position: ABSOLUTE, - top: '-999em' - }); - doc.body.appendChild(this); - } - - // Center the image - centerImage(obj, symbolSizes[imageSrc] = [this.width, this.height]); - - // Clean up after #2854 workaround. - if (this.parentNode) { - this.parentNode.removeChild(this); - } - - // Fire the load event when all external images are loaded - ren.imgCount--; - if (!ren.imgCount && charts[ren.chartIndex].onload) { - charts[ren.chartIndex].onload(); - } - }, - src: imageSrc - }); - this.imgCount++; - } - } - - return obj; - }, - - /** - * An extendable collection of functions for defining symbol paths. - */ - symbols: { - 'circle': function (x, y, w, h) { - var cpw = 0.166 * w; - return [ - M, x + w / 2, y, - 'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h, - 'C', x - cpw, y + h, x - cpw, y, x + w / 2, y, - 'Z' - ]; - }, - - 'square': function (x, y, w, h) { - return [ - M, x, y, - L, x + w, y, - x + w, y + h, - x, y + h, - 'Z' - ]; - }, - - 'triangle': function (x, y, w, h) { - return [ - M, x + w / 2, y, - L, x + w, y + h, - x, y + h, - 'Z' - ]; - }, - - 'triangle-down': function (x, y, w, h) { - return [ - M, x, y, - L, x + w, y, - x + w / 2, y + h, - 'Z' - ]; - }, - 'diamond': function (x, y, w, h) { - return [ - M, x + w / 2, y, - L, x + w, y + h / 2, - x + w / 2, y + h, - x, y + h / 2, - 'Z' - ]; - }, - 'arc': function (x, y, w, h, options) { - var start = options.start, - radius = options.r || w || h, - end = options.end - 0.001, // to prevent cos and sin of start and end from becoming equal on 360 arcs (related: #1561) - innerRadius = options.innerR, - open = options.open, - cosStart = mathCos(start), - sinStart = mathSin(start), - cosEnd = mathCos(end), - sinEnd = mathSin(end), - longArc = options.end - start < mathPI ? 0 : 1; - - return [ - M, - x + radius * cosStart, - y + radius * sinStart, - 'A', // arcTo - radius, // x radius - radius, // y radius - 0, // slanting - longArc, // long or short arc - 1, // clockwise - x + radius * cosEnd, - y + radius * sinEnd, - open ? M : L, - x + innerRadius * cosEnd, - y + innerRadius * sinEnd, - 'A', // arcTo - innerRadius, // x radius - innerRadius, // y radius - 0, // slanting - longArc, // long or short arc - 0, // clockwise - x + innerRadius * cosStart, - y + innerRadius * sinStart, - - open ? '' : 'Z' // close - ]; - }, - - /** - * Callout shape used for default tooltips, also used for rounded rectangles in VML - */ - callout: function (x, y, w, h, options) { - var arrowLength = 6, - halfDistance = 6, - r = mathMin((options && options.r) || 0, w, h), - safeDistance = r + halfDistance, - anchorX = options && options.anchorX, - anchorY = options && options.anchorY, - path; - - path = [ - 'M', x + r, y, - 'L', x + w - r, y, // top side - 'C', x + w, y, x + w, y, x + w, y + r, // top-right corner - 'L', x + w, y + h - r, // right side - 'C', x + w, y + h, x + w, y + h, x + w - r, y + h, // bottom-right corner - 'L', x + r, y + h, // bottom side - 'C', x, y + h, x, y + h, x, y + h - r, // bottom-left corner - 'L', x, y + r, // left side - 'C', x, y, x, y, x + r, y // top-right corner - ]; - - if (anchorX && anchorX > w && anchorY > y + safeDistance && anchorY < y + h - safeDistance) { // replace right side - path.splice(13, 3, - 'L', x + w, anchorY - halfDistance, - x + w + arrowLength, anchorY, - x + w, anchorY + halfDistance, - x + w, y + h - r - ); - } else if (anchorX && anchorX < 0 && anchorY > y + safeDistance && anchorY < y + h - safeDistance) { // replace left side - path.splice(33, 3, - 'L', x, anchorY + halfDistance, - x - arrowLength, anchorY, - x, anchorY - halfDistance, - x, y + r - ); - } else if (anchorY && anchorY > h && anchorX > x + safeDistance && anchorX < x + w - safeDistance) { // replace bottom - path.splice(23, 3, - 'L', anchorX + halfDistance, y + h, - anchorX, y + h + arrowLength, - anchorX - halfDistance, y + h, - x + r, y + h - ); - } else if (anchorY && anchorY < 0 && anchorX > x + safeDistance && anchorX < x + w - safeDistance) { // replace top - path.splice(3, 3, - 'L', anchorX - halfDistance, y, - anchorX, y - arrowLength, - anchorX + halfDistance, y, - w - r, y - ); - } - return path; - } - }, - - /** - * Define a clipping rectangle - * @param {String} id - * @param {Number} x - * @param {Number} y - * @param {Number} width - * @param {Number} height - */ - clipRect: function (x, y, width, height) { - var wrapper, - id = PREFIX + idCounter++, - - clipPath = this.createElement('clipPath').attr({ - id: id - }).add(this.defs); - - wrapper = this.rect(x, y, width, height, 0).add(clipPath); - wrapper.id = id; - wrapper.clipPath = clipPath; - wrapper.count = 0; - - return wrapper; - }, - - - - - - /** - * Add text to the SVG object - * @param {String} str - * @param {Number} x Left position - * @param {Number} y Top position - * @param {Boolean} useHTML Use HTML to render the text - */ - text: function (str, x, y, useHTML) { - - // declare variables - var renderer = this, - fakeSVG = useCanVG || (!hasSVG && renderer.forExport), - wrapper, - attr = {}; - - if (useHTML && (renderer.allowHTML || !renderer.forExport)) { - return renderer.html(str, x, y); - } - - attr.x = Math.round(x || 0); // X is always needed for line-wrap logic - if (y) { - attr.y = Math.round(y); - } - if (str || str === 0) { - attr.text = str; - } - - wrapper = renderer.createElement('text') - .attr(attr); - - // Prevent wrapping from creating false offsetWidths in export in legacy IE (#1079, #1063) - if (fakeSVG) { - wrapper.css({ - position: ABSOLUTE - }); - } - - if (!useHTML) { - wrapper.xSetter = function (value, key, element) { - var tspans = element.getElementsByTagName('tspan'), - tspan, - parentVal = element.getAttribute(key), - i; - for (i = 0; i < tspans.length; i++) { - tspan = tspans[i]; - // If the x values are equal, the tspan represents a linebreak - if (tspan.getAttribute(key) === parentVal) { - tspan.setAttribute(key, value); - } - } - element.setAttribute(key, value); - }; - } - - return wrapper; - }, - - /** - * Utility to return the baseline offset and total line height from the font size - */ - fontMetrics: function (fontSize, elem) { - var lineHeight, - baseline, - style; - - fontSize = fontSize || this.style.fontSize; - if (!fontSize && elem && win.getComputedStyle) { - elem = elem.element || elem; // SVGElement - style = win.getComputedStyle(elem, ''); - fontSize = style && style.fontSize; // #4309, the style doesn't exist inside a hidden iframe in Firefox - } - fontSize = /px/.test(fontSize) ? pInt(fontSize) : /em/.test(fontSize) ? parseFloat(fontSize) * 12 : 12; - - // Empirical values found by comparing font size and bounding box height. - // Applies to the default font family. http://jsfiddle.net/highcharts/7xvn7/ - lineHeight = fontSize < 24 ? fontSize + 3 : mathRound(fontSize * 1.2); - baseline = mathRound(lineHeight * 0.8); - - return { - h: lineHeight, - b: baseline, - f: fontSize - }; - }, - - /** - * Correct X and Y positioning of a label for rotation (#1764) - */ - rotCorr: function (baseline, rotation, alterY) { - var y = baseline; - if (rotation && alterY) { - y = mathMax(y * mathCos(rotation * deg2rad), 4); - } - return { - x: (-baseline / 3) * mathSin(rotation * deg2rad), - y: y - }; - }, - - /** - * Add a label, a text item that can hold a colored or gradient background - * as well as a border and shadow. - * @param {string} str - * @param {Number} x - * @param {Number} y - * @param {String} shape - * @param {Number} anchorX In case the shape has a pointer, like a flag, this is the - * coordinates it should be pinned to - * @param {Number} anchorY - * @param {Boolean} baseline Whether to position the label relative to the text baseline, - * like renderer.text, or to the upper border of the rectangle. - * @param {String} className Class name for the group - */ - label: function (str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) { - - var renderer = this, - wrapper = renderer.g(className), - text = renderer.text('', 0, 0, useHTML) - .attr({ - zIndex: 1 - }), - //.add(wrapper), - box, - bBox, - alignFactor = 0, - padding = 3, - paddingLeft = 0, - width, - height, - wrapperX, - wrapperY, - crispAdjust = 0, - deferredAttr = {}, - baselineOffset, - needsBox, - updateBoxSize, - updateTextPadding, - boxAttr; - - /** - * This function runs after the label is added to the DOM (when the bounding box is - * available), and after the text of the label is updated to detect the new bounding - * box and reflect it in the border box. - */ - updateBoxSize = function () { - var boxX, - boxY, - style = text.element.style; - - bBox = (width === undefined || height === undefined || wrapper.styles.textAlign) && defined(text.textStr) && - text.getBBox(); //#3295 && 3514 box failure when string equals 0 - wrapper.width = (width || bBox.width || 0) + 2 * padding + paddingLeft; - wrapper.height = (height || bBox.height || 0) + 2 * padding; - - // update the label-scoped y offset - baselineOffset = padding + renderer.fontMetrics(style && style.fontSize, text).b; - - - if (needsBox) { - - if (!box) { - // create the border box if it is not already present - boxX = crispAdjust; - boxY = (baseline ? -baselineOffset : 0) + crispAdjust; - - wrapper.box = box = shape ? - renderer.symbol(shape, boxX, boxY, wrapper.width, wrapper.height, deferredAttr) : - renderer.rect(boxX, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]); - - if (!box.isImg) { // #4324, fill "none" causes it to be ignored by mouse events in IE - box.attr('fill', NONE); - } - box.add(wrapper); - } - - // apply the box attributes - if (!box.isImg) { // #1630 - box.attr(extend({ - width: mathRound(wrapper.width), - height: mathRound(wrapper.height) - }, deferredAttr)); - } - deferredAttr = null; - } - }; - - /** - * This function runs after setting text or padding, but only if padding is changed - */ - updateTextPadding = function () { - var styles = wrapper.styles, - textAlign = styles && styles.textAlign, - x = paddingLeft + padding, - y; - - // determin y based on the baseline - y = baseline ? 0 : baselineOffset; - - // compensate for alignment - if (defined(width) && bBox && (textAlign === 'center' || textAlign === 'right')) { - x += { center: 0.5, right: 1 }[textAlign] * (width - bBox.width); - } - - // update if anything changed - if (x !== text.x || y !== text.y) { - text.attr('x', x); - if (y !== UNDEFINED) { - text.attr('y', y); - } - } - - // record current values - text.x = x; - text.y = y; - }; - - /** - * Set a box attribute, or defer it if the box is not yet created - * @param {Object} key - * @param {Object} value - */ - boxAttr = function (key, value) { - if (box) { - box.attr(key, value); - } else { - deferredAttr[key] = value; - } - }; - - /** - * After the text element is added, get the desired size of the border box - * and add it before the text in the DOM. - */ - wrapper.onAdd = function () { - text.add(wrapper); - wrapper.attr({ - text: (str || str === 0) ? str : '', // alignment is available now // #3295: 0 not rendered if given as a value - x: x, - y: y - }); - - if (box && defined(anchorX)) { - wrapper.attr({ - anchorX: anchorX, - anchorY: anchorY - }); - } - }; - - /* - * Add specific attribute setters. - */ - - // only change local variables - wrapper.widthSetter = function (value) { - width = value; - }; - wrapper.heightSetter = function (value) { - height = value; - }; - wrapper.paddingSetter = function (value) { - if (defined(value) && value !== padding) { - padding = wrapper.padding = value; - updateTextPadding(); - } - }; - wrapper.paddingLeftSetter = function (value) { - if (defined(value) && value !== paddingLeft) { - paddingLeft = value; - updateTextPadding(); - } - }; - - - // change local variable and prevent setting attribute on the group - wrapper.alignSetter = function (value) { - value = { left: 0, center: 0.5, right: 1 }[value]; - if (value !== alignFactor) { - alignFactor = value; - if (bBox) { // Bounding box exists, means we're dynamically changing - wrapper.attr({ x: wrapperX }); // #5134 - } - } - }; - - // apply these to the box and the text alike - wrapper.textSetter = function (value) { - if (value !== UNDEFINED) { - text.textSetter(value); - } - updateBoxSize(); - updateTextPadding(); - }; - - // apply these to the box but not to the text - wrapper['stroke-widthSetter'] = function (value, key) { - if (value) { - needsBox = true; - } - crispAdjust = value % 2 / 2; - boxAttr(key, value); - }; - wrapper.strokeSetter = wrapper.fillSetter = wrapper.rSetter = function (value, key) { - if (key === 'fill' && value) { - needsBox = true; - } - boxAttr(key, value); - }; - wrapper.anchorXSetter = function (value, key) { - anchorX = value; - boxAttr(key, mathRound(value) - crispAdjust - wrapperX); - }; - wrapper.anchorYSetter = function (value, key) { - anchorY = value; - boxAttr(key, value - wrapperY); - }; - - // rename attributes - wrapper.xSetter = function (value) { - wrapper.x = value; // for animation getter - if (alignFactor) { - value -= alignFactor * ((width || bBox.width) + 2 * padding); - } - wrapperX = mathRound(value); - wrapper.attr('translateX', wrapperX); - }; - wrapper.ySetter = function (value) { - wrapperY = wrapper.y = mathRound(value); - wrapper.attr('translateY', wrapperY); - }; - - // Redirect certain methods to either the box or the text - var baseCss = wrapper.css; - return extend(wrapper, { - /** - * Pick up some properties and apply them to the text instead of the wrapper - */ - css: function (styles) { - if (styles) { - var textStyles = {}; - styles = merge(styles); // create a copy to avoid altering the original object (#537) - each(wrapper.textProps, function (prop) { - if (styles[prop] !== UNDEFINED) { - textStyles[prop] = styles[prop]; - delete styles[prop]; - } - }); - text.css(textStyles); - } - return baseCss.call(wrapper, styles); - }, - /** - * Return the bounding box of the box, not the group - */ - getBBox: function () { - return { - width: bBox.width + 2 * padding, - height: bBox.height + 2 * padding, - x: bBox.x - padding, - y: bBox.y - padding - }; - }, - /** - * Apply the shadow to the box - */ - shadow: function (b) { - if (box) { - box.shadow(b); - } - return wrapper; - }, - /** - * Destroy and release memory. - */ - destroy: function () { - - // Added by button implementation - removeEvent(wrapper.element, 'mouseenter'); - removeEvent(wrapper.element, 'mouseleave'); - - if (text) { - text = text.destroy(); - } - if (box) { - box = box.destroy(); - } - // Call base implementation to destroy the rest - SVGElement.prototype.destroy.call(wrapper); - - // Release local pointers (#1298) - wrapper = renderer = updateBoxSize = updateTextPadding = boxAttr = null; - } - }); - } - }; // end SVGRenderer - - - // general renderer - Renderer = SVGRenderer; - // extend SvgElement for useHTML option - extend(SVGElement.prototype, { - /** - * Apply CSS to HTML elements. This is used in text within SVG rendering and - * by the VML renderer - */ - htmlCss: function (styles) { - var wrapper = this, - element = wrapper.element, - textWidth = styles && element.tagName === 'SPAN' && styles.width; - - if (textWidth) { - delete styles.width; - wrapper.textWidth = textWidth; - wrapper.updateTransform(); - } - if (styles && styles.textOverflow === 'ellipsis') { - styles.whiteSpace = 'nowrap'; - styles.overflow = 'hidden'; - } - wrapper.styles = extend(wrapper.styles, styles); - css(wrapper.element, styles); - - return wrapper; - }, - - /** - * VML and useHTML method for calculating the bounding box based on offsets - * @param {Boolean} refresh Whether to force a fresh value from the DOM or to - * use the cached value - * - * @return {Object} A hash containing values for x, y, width and height - */ - - htmlGetBBox: function () { - var wrapper = this, - element = wrapper.element; - - // faking getBBox in exported SVG in legacy IE - // faking getBBox in exported SVG in legacy IE (is this a duplicate of the fix for #1079?) - if (element.nodeName === 'text') { - element.style.position = ABSOLUTE; - } - - return { - x: element.offsetLeft, - y: element.offsetTop, - width: element.offsetWidth, - height: element.offsetHeight - }; - }, - - /** - * VML override private method to update elements based on internal - * properties based on SVG transform - */ - htmlUpdateTransform: function () { - // aligning non added elements is expensive - if (!this.added) { - this.alignOnAdd = true; - return; - } - - var wrapper = this, - renderer = wrapper.renderer, - elem = wrapper.element, - translateX = wrapper.translateX || 0, - translateY = wrapper.translateY || 0, - x = wrapper.x || 0, - y = wrapper.y || 0, - align = wrapper.textAlign || 'left', - alignCorrection = { left: 0, center: 0.5, right: 1 }[align], - shadows = wrapper.shadows, - styles = wrapper.styles; - - // apply translate - css(elem, { - marginLeft: translateX, - marginTop: translateY - }); - if (shadows) { // used in labels/tooltip - each(shadows, function (shadow) { - css(shadow, { - marginLeft: translateX + 1, - marginTop: translateY + 1 - }); - }); - } - - // apply inversion - if (wrapper.inverted) { // wrapper is a group - each(elem.childNodes, function (child) { - renderer.invertChild(child, elem); - }); - } - - if (elem.tagName === 'SPAN') { - - var rotation = wrapper.rotation, - baseline, - textWidth = pInt(wrapper.textWidth), - whiteSpace = styles && styles.whiteSpace, - currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth, wrapper.textAlign].join(','); - - if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed - - - baseline = renderer.fontMetrics(elem.style.fontSize).b; - - // Renderer specific handling of span rotation - if (defined(rotation)) { - wrapper.setSpanRotation(rotation, alignCorrection, baseline); - } - - // Update textWidth - if (elem.offsetWidth > textWidth && /[ \-]/.test(elem.textContent || elem.innerText)) { // #983, #1254 - css(elem, { - width: textWidth + PX, - display: 'block', - whiteSpace: whiteSpace || 'normal' // #3331 - }); - wrapper.hasTextWidth = true; - } else if (wrapper.hasTextWidth) { // #4928 - css(elem, { - width: '', - display: '', - whiteSpace: whiteSpace || 'nowrap' - }); - wrapper.hasTextWidth = false; - } - - wrapper.getSpanCorrection(wrapper.hasTextWidth ? textWidth : elem.offsetWidth, baseline, alignCorrection, rotation, align); - } - - // apply position with correction - css(elem, { - left: (x + (wrapper.xCorr || 0)) + PX, - top: (y + (wrapper.yCorr || 0)) + PX - }); - - // force reflow in webkit to apply the left and top on useHTML element (#1249) - if (isWebKit) { - baseline = elem.offsetHeight; // assigned to baseline for lint purpose - } - - // record current text transform - wrapper.cTT = currentTextTransform; - } - }, - - /** - * Set the rotation of an individual HTML span - */ - setSpanRotation: function (rotation, alignCorrection, baseline) { - var rotationStyle = {}, - cssTransformKey = isMS ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : isOpera ? '-o-transform' : ''; - - rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)'; - rotationStyle[cssTransformKey + (isFirefox ? 'Origin' : '-origin')] = rotationStyle.transformOrigin = (alignCorrection * 100) + '% ' + baseline + 'px'; - css(this.element, rotationStyle); - }, - - /** - * Get the correction in X and Y positioning as the element is rotated. - */ - getSpanCorrection: function (width, baseline, alignCorrection) { - this.xCorr = -width * alignCorrection; - this.yCorr = -baseline; - } - }); - - // Extend SvgRenderer for useHTML option. - extend(SVGRenderer.prototype, { - /** - * Create HTML text node. This is used by the VML renderer as well as the SVG - * renderer through the useHTML option. - * - * @param {String} str - * @param {Number} x - * @param {Number} y - */ - html: function (str, x, y) { - var wrapper = this.createElement('span'), - element = wrapper.element, - renderer = wrapper.renderer, - isSVG = renderer.isSVG, - addSetters = function (element, style) { - // These properties are set as attributes on the SVG group, and as - // identical CSS properties on the div. (#3542) - each(['opacity', 'visibility'], function (prop) { - wrap(element, prop + 'Setter', function (proceed, value, key, elem) { - proceed.call(this, value, key, elem); - style[key] = value; - }); - }); - }; - - // Text setter - wrapper.textSetter = function (value) { - if (value !== element.innerHTML) { - delete this.bBox; - } - element.innerHTML = this.textStr = value; - wrapper.htmlUpdateTransform(); - }; - - // Add setters for the element itself (#4938) - if (isSVG) { // #4938, only for HTML within SVG - addSetters(wrapper, wrapper.element.style); - } - - // Various setters which rely on update transform - wrapper.xSetter = wrapper.ySetter = wrapper.alignSetter = wrapper.rotationSetter = function (value, key) { - if (key === 'align') { - key = 'textAlign'; // Do not overwrite the SVGElement.align method. Same as VML. - } - wrapper[key] = value; - wrapper.htmlUpdateTransform(); - }; - - // Set the default attributes - wrapper - .attr({ - text: str, - x: mathRound(x), - y: mathRound(y) - }) - .css({ - position: ABSOLUTE, - fontFamily: this.style.fontFamily, - fontSize: this.style.fontSize - }); - - // Keep the whiteSpace style outside the wrapper.styles collection - element.style.whiteSpace = 'nowrap'; - - // Use the HTML specific .css method - wrapper.css = wrapper.htmlCss; - - // This is specific for HTML within SVG - if (isSVG) { - wrapper.add = function (svgGroupWrapper) { - - var htmlGroup, - container = renderer.box.parentNode, - parentGroup, - parents = []; - - this.parentGroup = svgGroupWrapper; - - // Create a mock group to hold the HTML elements - if (svgGroupWrapper) { - htmlGroup = svgGroupWrapper.div; - if (!htmlGroup) { - - // Read the parent chain into an array and read from top down - parentGroup = svgGroupWrapper; - while (parentGroup) { - - parents.push(parentGroup); - - // Move up to the next parent group - parentGroup = parentGroup.parentGroup; - } - - // Ensure dynamically updating position when any parent is translated - each(parents.reverse(), function (parentGroup) { - var htmlGroupStyle, - cls = attr(parentGroup.element, 'class'); - - if (cls) { - cls = { className: cls }; - } // else null - - // Create a HTML div and append it to the parent div to emulate - // the SVG group structure - htmlGroup = parentGroup.div = parentGroup.div || createElement(DIV, cls, { - position: ABSOLUTE, - left: (parentGroup.translateX || 0) + PX, - top: (parentGroup.translateY || 0) + PX, - opacity: parentGroup.opacity // #5075 - }, htmlGroup || container); // the top group is appended to container - - // Shortcut - htmlGroupStyle = htmlGroup.style; - - // Set listeners to update the HTML div's position whenever the SVG group - // position is changed - extend(parentGroup, { - translateXSetter: function (value, key) { - htmlGroupStyle.left = value + PX; - parentGroup[key] = value; - parentGroup.doTransform = true; - }, - translateYSetter: function (value, key) { - htmlGroupStyle.top = value + PX; - parentGroup[key] = value; - parentGroup.doTransform = true; - } - }); - addSetters(parentGroup, htmlGroupStyle); - }); - - } - } else { - htmlGroup = container; - } - - htmlGroup.appendChild(element); - - // Shared with VML: - wrapper.added = true; - if (wrapper.alignOnAdd) { - wrapper.htmlUpdateTransform(); - } - - return wrapper; - }; - } - return wrapper; - } - }); - - - /* **************************************************************************** - * * - * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE * - * * - * For applications and websites that don't need IE support, like platform * - * targeted mobile apps and web apps, this code can be removed. * - * * - *****************************************************************************/ - - /** - * @constructor - */ - var VMLRenderer, VMLElement; - if (!hasSVG && !useCanVG) { - - /** - * The VML element wrapper. - */ - VMLElement = { - - /** - * Initialize a new VML element wrapper. It builds the markup as a string - * to minimize DOM traffic. - * @param {Object} renderer - * @param {Object} nodeName - */ - init: function (renderer, nodeName) { - var wrapper = this, - markup = ['<', nodeName, ' filled="f" stroked="f"'], - style = ['position: ', ABSOLUTE, ';'], - isDiv = nodeName === DIV; - - // divs and shapes need size - if (nodeName === 'shape' || isDiv) { - style.push('left:0;top:0;width:1px;height:1px;'); - } - style.push('visibility: ', isDiv ? HIDDEN : VISIBLE); - - markup.push(' style="', style.join(''), '"/>'); - - // create element with default attributes and style - if (nodeName) { - markup = isDiv || nodeName === 'span' || nodeName === 'img' ? - markup.join('') : - renderer.prepVML(markup); - wrapper.element = createElement(markup); - } - - wrapper.renderer = renderer; - }, - - /** - * Add the node to the given parent - * @param {Object} parent - */ - add: function (parent) { - var wrapper = this, - renderer = wrapper.renderer, - element = wrapper.element, - box = renderer.box, - inverted = parent && parent.inverted, - - // get the parent node - parentNode = parent ? - parent.element || parent : - box; - - if (parent) { - this.parentGroup = parent; - } - - // if the parent group is inverted, apply inversion on all children - if (inverted) { // only on groups - renderer.invertChild(element, parentNode); - } - - // append it - parentNode.appendChild(element); - - // align text after adding to be able to read offset - wrapper.added = true; - if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) { - wrapper.updateTransform(); - } - - // fire an event for internal hooks - if (wrapper.onAdd) { - wrapper.onAdd(); - } - - return wrapper; - }, - - /** - * VML always uses htmlUpdateTransform - */ - updateTransform: SVGElement.prototype.htmlUpdateTransform, - - /** - * Set the rotation of a span with oldIE's filter - */ - setSpanRotation: function () { - // Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented - // but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+ - // has support for CSS3 transform. The getBBox method also needs to be updated - // to compensate for the rotation, like it currently does for SVG. - // Test case: http://jsfiddle.net/highcharts/Ybt44/ - - var rotation = this.rotation, - costheta = mathCos(rotation * deg2rad), - sintheta = mathSin(rotation * deg2rad); - - css(this.element, { - filter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta, - ', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta, - ', sizingMethod=\'auto expand\')'].join('') : NONE - }); - }, - - /** - * Get the positioning correction for the span after rotating. - */ - getSpanCorrection: function (width, baseline, alignCorrection, rotation, align) { - - var costheta = rotation ? mathCos(rotation * deg2rad) : 1, - sintheta = rotation ? mathSin(rotation * deg2rad) : 0, - height = pick(this.elemHeight, this.element.offsetHeight), - quad, - nonLeft = align && align !== 'left'; - - // correct x and y - this.xCorr = costheta < 0 && -width; - this.yCorr = sintheta < 0 && -height; - - // correct for baseline and corners spilling out after rotation - quad = costheta * sintheta < 0; - this.xCorr += sintheta * baseline * (quad ? 1 - alignCorrection : alignCorrection); - this.yCorr -= costheta * baseline * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1); - // correct for the length/height of the text - if (nonLeft) { - this.xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1); - if (rotation) { - this.yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1); - } - css(this.element, { - textAlign: align - }); - } - }, - - /** - * Converts a subset of an SVG path definition to its VML counterpart. Takes an array - * as the parameter and returns a string. - */ - pathToVML: function (value) { - // convert paths - var i = value.length, - path = []; - - while (i--) { - - // Multiply by 10 to allow subpixel precision. - // Substracting half a pixel seems to make the coordinates - // align with SVG, but this hasn't been tested thoroughly - if (isNumber(value[i])) { - path[i] = mathRound(value[i] * 10) - 5; - } else if (value[i] === 'Z') { // close the path - path[i] = 'x'; - } else { - path[i] = value[i]; - - // When the start X and end X coordinates of an arc are too close, - // they are rounded to the same value above. In this case, substract or - // add 1 from the end X and Y positions. #186, #760, #1371, #1410. - if (value.isArc && (value[i] === 'wa' || value[i] === 'at')) { - // Start and end X - if (path[i + 5] === path[i + 7]) { - path[i + 7] += value[i + 7] > value[i + 5] ? 1 : -1; - } - // Start and end Y - if (path[i + 6] === path[i + 8]) { - path[i + 8] += value[i + 8] > value[i + 6] ? 1 : -1; - } - } - } - } - - - // Loop up again to handle path shortcuts (#2132) - /*while (i++ < path.length) { - if (path[i] === 'H') { // horizontal line to - path[i] = 'L'; - path.splice(i + 2, 0, path[i - 1]); - } else if (path[i] === 'V') { // vertical line to - path[i] = 'L'; - path.splice(i + 1, 0, path[i - 2]); - } - }*/ - return path.join(' ') || 'x'; - }, - - /** - * Set the element's clipping to a predefined rectangle - * - * @param {String} id The id of the clip rectangle - */ - clip: function (clipRect) { - var wrapper = this, - clipMembers, - cssRet; - - if (clipRect) { - clipMembers = clipRect.members; - erase(clipMembers, wrapper); // Ensure unique list of elements (#1258) - clipMembers.push(wrapper); - wrapper.destroyClip = function () { - erase(clipMembers, wrapper); - }; - cssRet = clipRect.getCSS(wrapper); - - } else { - if (wrapper.destroyClip) { - wrapper.destroyClip(); - } - cssRet = { clip: docMode8 ? 'inherit' : 'rect(auto)' }; // #1214 - } - - return wrapper.css(cssRet); - - }, - - /** - * Set styles for the element - * @param {Object} styles - */ - css: SVGElement.prototype.htmlCss, - - /** - * Removes a child either by removeChild or move to garbageBin. - * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not. - */ - safeRemoveChild: function (element) { - // discardElement will detach the node from its parent before attaching it - // to the garbage bin. Therefore it is important that the node is attached and have parent. - if (element.parentNode) { - discardElement(element); - } - }, - - /** - * Extend element.destroy by removing it from the clip members array - */ - destroy: function () { - if (this.destroyClip) { - this.destroyClip(); - } - - return SVGElement.prototype.destroy.apply(this); - }, - - /** - * Add an event listener. VML override for normalizing event parameters. - * @param {String} eventType - * @param {Function} handler - */ - on: function (eventType, handler) { - // simplest possible event model for internal use - this.element['on' + eventType] = function () { - var evt = win.event; - evt.target = evt.srcElement; - handler(evt); - }; - return this; - }, - - /** - * In stacked columns, cut off the shadows so that they don't overlap - */ - cutOffPath: function (path, length) { - - var len; - - path = path.split(/[ ,]/); - len = path.length; - - if (len === 9 || len === 11) { - path[len - 4] = path[len - 2] = pInt(path[len - 2]) - 10 * length; - } - return path.join(' '); - }, - - /** - * Apply a drop shadow by copying elements and giving them different strokes - * @param {Boolean|Object} shadowOptions - */ - shadow: function (shadowOptions, group, cutOff) { - var shadows = [], - i, - element = this.element, - renderer = this.renderer, - shadow, - elemStyle = element.style, - markup, - path = element.path, - strokeWidth, - modifiedPath, - shadowWidth, - shadowElementOpacity; - - // some times empty paths are not strings - if (path && typeof path.value !== 'string') { - path = 'x'; - } - modifiedPath = path; - - if (shadowOptions) { - shadowWidth = pick(shadowOptions.width, 3); - shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth; - for (i = 1; i <= 3; i++) { - - strokeWidth = (shadowWidth * 2) + 1 - (2 * i); - - // Cut off shadows for stacked column items - if (cutOff) { - modifiedPath = this.cutOffPath(path.value, strokeWidth + 0.5); - } - - markup = ['']; - - shadow = createElement(renderer.prepVML(markup), - null, { - left: pInt(elemStyle.left) + pick(shadowOptions.offsetX, 1), - top: pInt(elemStyle.top) + pick(shadowOptions.offsetY, 1) - } - ); - if (cutOff) { - shadow.cutOff = strokeWidth + 1; - } - - // apply the opacity - markup = ['']; - createElement(renderer.prepVML(markup), null, null, shadow); - - - // insert it - if (group) { - group.element.appendChild(shadow); - } else { - element.parentNode.insertBefore(shadow, element); - } - - // record it - shadows.push(shadow); - - } - - this.shadows = shadows; - } - return this; - }, - updateShadows: noop, // Used in SVG only - - setAttr: function (key, value) { - if (docMode8) { // IE8 setAttribute bug - this.element[key] = value; - } else { - this.element.setAttribute(key, value); - } - }, - classSetter: function (value) { - // IE8 Standards mode has problems retrieving the className unless set like this - this.element.className = value; - }, - dashstyleSetter: function (value, key, element) { - var strokeElem = element.getElementsByTagName('stroke')[0] || - createElement(this.renderer.prepVML(['']), null, null, element); - strokeElem[key] = value || 'solid'; - this[key] = value; /* because changing stroke-width will change the dash length - and cause an epileptic effect */ - }, - dSetter: function (value, key, element) { - var i, - shadows = this.shadows; - value = value || []; - this.d = value.join && value.join(' '); // used in getter for animation - - element.path = value = this.pathToVML(value); - - // update shadows - if (shadows) { - i = shadows.length; - while (i--) { - shadows[i].path = shadows[i].cutOff ? this.cutOffPath(value, shadows[i].cutOff) : value; - } - } - this.setAttr(key, value); - }, - fillSetter: function (value, key, element) { - var nodeName = element.nodeName; - if (nodeName === 'SPAN') { // text color - element.style.color = value; - } else if (nodeName !== 'IMG') { // #1336 - element.filled = value !== NONE; - this.setAttr('fillcolor', this.renderer.color(value, element, key, this)); - } - }, - 'fill-opacitySetter': function (value, key, element) { - createElement( - this.renderer.prepVML(['<', key.split('-')[0], ' opacity="', value, '"/>']), - null, - null, - element - ); - }, - opacitySetter: noop, // Don't bother - animation is too slow and filters introduce artifacts - rotationSetter: function (value, key, element) { - var style = element.style; - this[key] = style[key] = value; // style is for #1873 - - // Correction for the 1x1 size of the shape container. Used in gauge needles. - style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX; - style.top = mathRound(mathCos(value * deg2rad)) + PX; - }, - strokeSetter: function (value, key, element) { - this.setAttr('strokecolor', this.renderer.color(value, element, key, this)); - }, - 'stroke-widthSetter': function (value, key, element) { - element.stroked = !!value; // VML "stroked" attribute - this[key] = value; // used in getter, issue #113 - if (isNumber(value)) { - value += PX; - } - this.setAttr('strokeweight', value); - }, - titleSetter: function (value, key) { - this.setAttr(key, value); - }, - visibilitySetter: function (value, key, element) { - - // Handle inherited visibility - if (value === 'inherit') { - value = VISIBLE; - } - - // Let the shadow follow the main element - if (this.shadows) { - each(this.shadows, function (shadow) { - shadow.style[key] = value; - }); - } - - // Instead of toggling the visibility CSS property, move the div out of the viewport. - // This works around #61 and #586 - if (element.nodeName === 'DIV') { - value = value === HIDDEN ? '-999em' : 0; - - // In order to redraw, IE7 needs the div to be visible when tucked away - // outside the viewport. So the visibility is actually opposite of - // the expected value. This applies to the tooltip only. - if (!docMode8) { - element.style[key] = value ? VISIBLE : HIDDEN; - } - key = 'top'; - } - element.style[key] = value; - }, - xSetter: function (value, key, element) { - this[key] = value; // used in getter - - if (key === 'x') { - key = 'left'; - } else if (key === 'y') { - key = 'top'; - }/* else { - value = mathMax(0, value); // don't set width or height below zero (#311) - }*/ - - // clipping rectangle special - if (this.updateClipping) { - this[key] = value; // the key is now 'left' or 'top' for 'x' and 'y' - this.updateClipping(); - } else { - // normal - element.style[key] = value; - } - }, - zIndexSetter: function (value, key, element) { - element.style[key] = value; - } - }; - VMLElement['stroke-opacitySetter'] = VMLElement['fill-opacitySetter']; - - Highcharts.VMLElement = VMLElement = extendClass(SVGElement, VMLElement); - - // Some shared setters - VMLElement.prototype.ySetter = - VMLElement.prototype.widthSetter = - VMLElement.prototype.heightSetter = - VMLElement.prototype.xSetter; - - - /** - * The VML renderer - */ - var VMLRendererExtension = { // inherit SVGRenderer - - Element: VMLElement, - isIE8: userAgent.indexOf('MSIE 8.0') > -1, - - - /** - * Initialize the VMLRenderer - * @param {Object} container - * @param {Number} width - * @param {Number} height - */ - init: function (container, width, height, style) { - var renderer = this, - boxWrapper, - box, - css; - - renderer.alignedObjects = []; - - boxWrapper = renderer.createElement(DIV) - .css(extend(this.getStyle(style), { position: 'relative' })); - box = boxWrapper.element; - container.appendChild(boxWrapper.element); - - - // generate the containing box - renderer.isVML = true; - renderer.box = box; - renderer.boxWrapper = boxWrapper; - renderer.gradients = {}; - renderer.cache = {}; // Cache for numerical bounding boxes - renderer.cacheKeys = []; - renderer.imgCount = 0; - - - renderer.setSize(width, height, false); - - // The only way to make IE6 and IE7 print is to use a global namespace. However, - // with IE8 the only way to make the dynamic shapes visible in screen and print mode - // seems to be to add the xmlns attribute and the behaviour style inline. - if (!doc.namespaces.hcv) { - - doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml'); - - // Setup default CSS (#2153, #2368, #2384) - css = 'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' + - '{ behavior:url(#default#VML); display: inline-block; } '; - try { - doc.createStyleSheet().cssText = css; - } catch (e) { - doc.styleSheets[0].cssText += css; - } - - } - }, - - - /** - * Detect whether the renderer is hidden. This happens when one of the parent elements - * has display: none - */ - isHidden: function () { - return !this.box.offsetWidth; - }, - - /** - * Define a clipping rectangle. In VML it is accomplished by storing the values - * for setting the CSS style to all associated members. - * - * @param {Number} x - * @param {Number} y - * @param {Number} width - * @param {Number} height - */ - clipRect: function (x, y, width, height) { - - // create a dummy element - var clipRect = this.createElement(), - isObj = isObject(x); - - // mimic a rectangle with its style object for automatic updating in attr - return extend(clipRect, { - members: [], - count: 0, - left: (isObj ? x.x : x) + 1, - top: (isObj ? x.y : y) + 1, - width: (isObj ? x.width : width) - 1, - height: (isObj ? x.height : height) - 1, - getCSS: function (wrapper) { - var element = wrapper.element, - nodeName = element.nodeName, - isShape = nodeName === 'shape', - inverted = wrapper.inverted, - rect = this, - top = rect.top - (isShape ? element.offsetTop : 0), - left = rect.left, - right = left + rect.width, - bottom = top + rect.height, - ret = { - clip: 'rect(' + - mathRound(inverted ? left : top) + 'px,' + - mathRound(inverted ? bottom : right) + 'px,' + - mathRound(inverted ? right : bottom) + 'px,' + - mathRound(inverted ? top : left) + 'px)' - }; - - // issue 74 workaround - if (!inverted && docMode8 && nodeName === 'DIV') { - extend(ret, { - width: right + PX, - height: bottom + PX - }); - } - return ret; - }, - - // used in attr and animation to update the clipping of all members - updateClipping: function () { - each(clipRect.members, function (member) { - if (member.element) { // Deleted series, like in stock/members/series-remove demo. Should be removed from members, but this will do. - member.css(clipRect.getCSS(member)); - } - }); - } - }); - - }, - - - /** - * Take a color and return it if it's a string, make it a gradient if it's a - * gradient configuration object, and apply opacity. - * - * @param {Object} color The color or config object - */ - color: function (color, elem, prop, wrapper) { - var renderer = this, - colorObject, - regexRgba = /^rgba/, - markup, - fillType, - ret = NONE; - - // Check for linear or radial gradient - if (color && color.linearGradient) { - fillType = 'gradient'; - } else if (color && color.radialGradient) { - fillType = 'pattern'; - } - - - if (fillType) { - - var stopColor, - stopOpacity, - gradient = color.linearGradient || color.radialGradient, - x1, - y1, - x2, - y2, - opacity1, - opacity2, - color1, - color2, - fillAttr = '', - stops = color.stops, - firstStop, - lastStop, - colors = [], - addFillNode = function () { - // Add the fill subnode. When colors attribute is used, the meanings of opacity and o:opacity2 - // are reversed. - markup = ['']; - createElement(renderer.prepVML(markup), null, null, elem); - }; - - // Extend from 0 to 1 - firstStop = stops[0]; - lastStop = stops[stops.length - 1]; - if (firstStop[0] > 0) { - stops.unshift([ - 0, - firstStop[1] - ]); - } - if (lastStop[0] < 1) { - stops.push([ - 1, - lastStop[1] - ]); - } - - // Compute the stops - each(stops, function (stop, i) { - if (regexRgba.test(stop[1])) { - colorObject = Color(stop[1]); - stopColor = colorObject.get('rgb'); - stopOpacity = colorObject.get('a'); - } else { - stopColor = stop[1]; - stopOpacity = 1; - } - - // Build the color attribute - colors.push((stop[0] * 100) + '% ' + stopColor); - - // Only start and end opacities are allowed, so we use the first and the last - if (!i) { - opacity1 = stopOpacity; - color2 = stopColor; - } else { - opacity2 = stopOpacity; - color1 = stopColor; - } - }); - - // Apply the gradient to fills only. - if (prop === 'fill') { - - // Handle linear gradient angle - if (fillType === 'gradient') { - x1 = gradient.x1 || gradient[0] || 0; - y1 = gradient.y1 || gradient[1] || 0; - x2 = gradient.x2 || gradient[2] || 0; - y2 = gradient.y2 || gradient[3] || 0; - fillAttr = 'angle="' + (90 - math.atan( - (y2 - y1) / // y vector - (x2 - x1) // x vector - ) * 180 / mathPI) + '"'; - - addFillNode(); - - // Radial (circular) gradient - } else { - - var r = gradient.r, - sizex = r * 2, - sizey = r * 2, - cx = gradient.cx, - cy = gradient.cy, - radialReference = elem.radialReference, - bBox, - applyRadialGradient = function () { - if (radialReference) { - bBox = wrapper.getBBox(); - cx += (radialReference[0] - bBox.x) / bBox.width - 0.5; - cy += (radialReference[1] - bBox.y) / bBox.height - 0.5; - sizex *= radialReference[2] / bBox.width; - sizey *= radialReference[2] / bBox.height; - } - fillAttr = 'src="' + defaultOptions.global.VMLRadialGradientURL + '" ' + - 'size="' + sizex + ',' + sizey + '" ' + - 'origin="0.5,0.5" ' + - 'position="' + cx + ',' + cy + '" ' + - 'color2="' + color2 + '" '; - - addFillNode(); - }; - - // Apply radial gradient - if (wrapper.added) { - applyRadialGradient(); - } else { - // We need to know the bounding box to get the size and position right - wrapper.onAdd = applyRadialGradient; - } - - // The fill element's color attribute is broken in IE8 standards mode, so we - // need to set the parent shape's fillcolor attribute instead. - ret = color1; - } - - // Gradients are not supported for VML stroke, return the first color. #722. - } else { - ret = stopColor; - } - - // If the color is an rgba color, split it and add a fill node - // to hold the opacity component - } else if (regexRgba.test(color) && elem.tagName !== 'IMG') { - - colorObject = Color(color); - - wrapper[prop + '-opacitySetter'](colorObject.get('a'), prop, elem); - - ret = colorObject.get('rgb'); - - - } else { - var propNodes = elem.getElementsByTagName(prop); // 'stroke' or 'fill' node - if (propNodes.length) { - propNodes[0].opacity = 1; - propNodes[0].type = 'solid'; - } - ret = color; - } - - return ret; - }, - - /** - * Take a VML string and prepare it for either IE8 or IE6/IE7. - * @param {Array} markup A string array of the VML markup to prepare - */ - prepVML: function (markup) { - var vmlStyle = 'display:inline-block;behavior:url(#default#VML);', - isIE8 = this.isIE8; - - markup = markup.join(''); - - if (isIE8) { // add xmlns and style inline - markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />'); - if (markup.indexOf('style="') === -1) { - markup = markup.replace('/>', ' style="' + vmlStyle + '" />'); - } else { - markup = markup.replace('style="', 'style="' + vmlStyle); - } - - } else { // add namespace - markup = markup.replace('<', ' 1) { - obj.attr({ - x: x, - y: y, - width: width, - height: height - }); - } - return obj; - }, - - /** - * For rectangles, VML uses a shape for rect to overcome bugs and rotation problems - */ - createElement: function (nodeName) { - return nodeName === 'rect' ? this.symbol(nodeName) : SVGRenderer.prototype.createElement.call(this, nodeName); - }, - - /** - * In the VML renderer, each child of an inverted div (group) is inverted - * @param {Object} element - * @param {Object} parentNode - */ - invertChild: function (element, parentNode) { - var ren = this, - parentStyle = parentNode.style, - imgStyle = element.tagName === 'IMG' && element.style; // #1111 - - css(element, { - flip: 'x', - left: pInt(parentStyle.width) - (imgStyle ? pInt(imgStyle.top) : 1), - top: pInt(parentStyle.height) - (imgStyle ? pInt(imgStyle.left) : 1), - rotation: -90 - }); - - // Recursively invert child elements, needed for nested composite shapes like box plots and error bars. #1680, #1806. - each(element.childNodes, function (child) { - ren.invertChild(child, element); - }); - }, - - /** - * Symbol definitions that override the parent SVG renderer's symbols - * - */ - symbols: { - // VML specific arc function - arc: function (x, y, w, h, options) { - var start = options.start, - end = options.end, - radius = options.r || w || h, - innerRadius = options.innerR, - cosStart = mathCos(start), - sinStart = mathSin(start), - cosEnd = mathCos(end), - sinEnd = mathSin(end), - ret; - - if (end - start === 0) { // no angle, don't show it. - return ['x']; - } - - ret = [ - 'wa', // clockwise arc to - x - radius, // left - y - radius, // top - x + radius, // right - y + radius, // bottom - x + radius * cosStart, // start x - y + radius * sinStart, // start y - x + radius * cosEnd, // end x - y + radius * sinEnd // end y - ]; - - if (options.open && !innerRadius) { - ret.push( - 'e', - M, - x, // - innerRadius, - y// - innerRadius - ); - } - - ret.push( - 'at', // anti clockwise arc to - x - innerRadius, // left - y - innerRadius, // top - x + innerRadius, // right - y + innerRadius, // bottom - x + innerRadius * cosEnd, // start x - y + innerRadius * sinEnd, // start y - x + innerRadius * cosStart, // end x - y + innerRadius * sinStart, // end y - 'x', // finish path - 'e' // close - ); - - ret.isArc = true; - return ret; - - }, - // Add circle symbol path. This performs significantly faster than v:oval. - circle: function (x, y, w, h, wrapper) { - - if (wrapper) { - w = h = 2 * wrapper.r; - } - - // Center correction, #1682 - if (wrapper && wrapper.isCircle) { - x -= w / 2; - y -= h / 2; - } - - // Return the path - return [ - 'wa', // clockwisearcto - x, // left - y, // top - x + w, // right - y + h, // bottom - x + w, // start x - y + h / 2, // start y - x + w, // end x - y + h / 2, // end y - //'x', // finish path - 'e' // close - ]; - }, - /** - * Add rectangle symbol path which eases rotation and omits arcsize problems - * compared to the built-in VML roundrect shape. When borders are not rounded, - * use the simpler square path, else use the callout path without the arrow. - */ - rect: function (x, y, w, h, options) { - return SVGRenderer.prototype.symbols[ - !defined(options) || !options.r ? 'square' : 'callout' - ].call(0, x, y, w, h, options); - } - } - }; - Highcharts.VMLRenderer = VMLRenderer = function () { - this.init.apply(this, arguments); - }; - VMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension); - - // general renderer - Renderer = VMLRenderer; - } - - // This method is used with exporting in old IE, when emulating SVG (see #2314) - SVGRenderer.prototype.measureSpanWidth = function (text, styles) { - var measuringSpan = doc.createElement('span'), - offsetWidth, - textNode = doc.createTextNode(text); - - measuringSpan.appendChild(textNode); - css(measuringSpan, styles); - this.box.appendChild(measuringSpan); - offsetWidth = measuringSpan.offsetWidth; - discardElement(measuringSpan); // #2463 - return offsetWidth; - }; - - - /* **************************************************************************** - * * - * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE * - * * - *****************************************************************************/ - /* **************************************************************************** - * * - * START OF ANDROID < 3 SPECIFIC CODE. THIS CAN BE REMOVED IF YOU'RE NOT * - * TARGETING THAT SYSTEM. * - * * - *****************************************************************************/ - var CanVGRenderer, - CanVGController; - - /** - * Downloads a script and executes a callback when done. - * @param {String} scriptLocation - * @param {Function} callback - */ - function getScript(scriptLocation, callback) { - var head = doc.getElementsByTagName('head')[0], - script = doc.createElement('script'); - - script.type = 'text/javascript'; - script.src = scriptLocation; - script.onload = callback; - - head.appendChild(script); - } - - if (useCanVG) { - /** - * The CanVGRenderer is empty from start to keep the source footprint small. - * When requested, the CanVGController downloads the rest of the source packaged - * together with the canvg library. - */ - Highcharts.CanVGRenderer = CanVGRenderer = function () { - // Override the global SVG namespace to fake SVG/HTML that accepts CSS - SVG_NS = 'http://www.w3.org/1999/xhtml'; - }; - - /** - * Start with an empty symbols object. This is needed when exporting is used (exporting.src.js will add a few symbols), but - * the implementation from SvgRenderer will not be merged in until first render. - */ - CanVGRenderer.prototype.symbols = {}; - - /** - * Handles on demand download of canvg rendering support. - */ - CanVGController = (function () { - // List of renderering calls - var deferredRenderCalls = []; - - /** - * When downloaded, we are ready to draw deferred charts. - */ - function drawDeferred() { - var callLength = deferredRenderCalls.length, - callIndex; - - // Draw all pending render calls - for (callIndex = 0; callIndex < callLength; callIndex++) { - deferredRenderCalls[callIndex](); - } - // Clear the list - deferredRenderCalls = []; - } - - return { - push: function (func, scriptLocation) { - // Only get the script once - if (deferredRenderCalls.length === 0) { - getScript(scriptLocation, drawDeferred); - } - // Register render call - deferredRenderCalls.push(func); - } - }; - }()); - - Renderer = CanVGRenderer; - } // end CanVGRenderer - - /* **************************************************************************** - * * - * END OF ANDROID < 3 SPECIFIC CODE * - * * - *****************************************************************************/ - - /** - * The Tick class - */ - function Tick(axis, pos, type, noLabel) { - this.axis = axis; - this.pos = pos; - this.type = type || ''; - this.isNew = true; - - if (!type && !noLabel) { - this.addLabel(); - } - } - - Tick.prototype = { - /** - * Write the tick label - */ - addLabel: function () { - var tick = this, - axis = tick.axis, - options = axis.options, - chart = axis.chart, - categories = axis.categories, - names = axis.names, - pos = tick.pos, - labelOptions = options.labels, - str, - tickPositions = axis.tickPositions, - isFirst = pos === tickPositions[0], - isLast = pos === tickPositions[tickPositions.length - 1], - value = categories ? - pick(categories[pos], names[pos], pos) : - pos, - label = tick.label, - tickPositionInfo = tickPositions.info, - dateTimeLabelFormat; - - // Set the datetime label format. If a higher rank is set for this position, use that. If not, - // use the general format. - if (axis.isDatetimeAxis && tickPositionInfo) { - dateTimeLabelFormat = options.dateTimeLabelFormats[tickPositionInfo.higherRanks[pos] || tickPositionInfo.unitName]; - } - // set properties for access in render method - tick.isFirst = isFirst; - tick.isLast = isLast; - - // get the string - str = axis.labelFormatter.call({ - axis: axis, - chart: chart, - isFirst: isFirst, - isLast: isLast, - dateTimeLabelFormat: dateTimeLabelFormat, - value: axis.isLog ? correctFloat(axis.lin2log(value)) : value - }); - - // prepare CSS - //css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX }; - - // first call - if (!defined(label)) { - - tick.label = label = - defined(str) && labelOptions.enabled ? - chart.renderer.text( - str, - 0, - 0, - labelOptions.useHTML - ) - //.attr(attr) - // without position absolute, IE export sometimes is wrong - .css(merge(labelOptions.style)) - .add(axis.labelGroup) : - null; - tick.labelLength = label && label.getBBox().width; // Un-rotated length - tick.rotation = 0; // Base value to detect change for new calls to getBBox - - // update - } else if (label) { - label.attr({ text: str }); - } - }, - - /** - * Get the offset height or width of the label - */ - getLabelSize: function () { - return this.label ? - this.label.getBBox()[this.axis.horiz ? 'height' : 'width'] : - 0; - }, - - /** - * Handle the label overflow by adjusting the labels to the left and right edge, or - * hide them if they collide into the neighbour label. - */ - handleOverflow: function (xy) { - var axis = this.axis, - pxPos = xy.x, - chartWidth = axis.chart.chartWidth, - spacing = axis.chart.spacing, - leftBound = pick(axis.labelLeft, mathMin(axis.pos, spacing[3])), - rightBound = pick(axis.labelRight, mathMax(axis.pos + axis.len, chartWidth - spacing[1])), - label = this.label, - rotation = this.rotation, - factor = { left: 0, center: 0.5, right: 1 }[axis.labelAlign], - labelWidth = label.getBBox().width, - slotWidth = axis.getSlotWidth(), - modifiedSlotWidth = slotWidth, - xCorrection = factor, - goRight = 1, - leftPos, - rightPos, - textWidth, - css = {}; - - // Check if the label overshoots the chart spacing box. If it does, move it. - // If it now overshoots the slotWidth, add ellipsis. - if (!rotation) { - leftPos = pxPos - factor * labelWidth; - rightPos = pxPos + (1 - factor) * labelWidth; - - if (leftPos < leftBound) { - modifiedSlotWidth = xy.x + modifiedSlotWidth * (1 - factor) - leftBound; - } else if (rightPos > rightBound) { - modifiedSlotWidth = rightBound - xy.x + modifiedSlotWidth * factor; - goRight = -1; - } - - modifiedSlotWidth = mathMin(slotWidth, modifiedSlotWidth); // #4177 - if (modifiedSlotWidth < slotWidth && axis.labelAlign === 'center') { - xy.x += goRight * (slotWidth - modifiedSlotWidth - xCorrection * (slotWidth - mathMin(labelWidth, modifiedSlotWidth))); - } - // If the label width exceeds the available space, set a text width to be - // picked up below. Also, if a width has been set before, we need to set a new - // one because the reported labelWidth will be limited by the box (#3938). - if (labelWidth > modifiedSlotWidth || (axis.autoRotation && label.styles.width)) { - textWidth = modifiedSlotWidth; - } - - // Add ellipsis to prevent rotated labels to be clipped against the edge of the chart - } else if (rotation < 0 && pxPos - factor * labelWidth < leftBound) { - textWidth = mathRound(pxPos / mathCos(rotation * deg2rad) - leftBound); - } else if (rotation > 0 && pxPos + factor * labelWidth > rightBound) { - textWidth = mathRound((chartWidth - pxPos) / mathCos(rotation * deg2rad)); - } - - if (textWidth) { - css.width = textWidth; - if (!axis.options.labels.style.textOverflow) { - css.textOverflow = 'ellipsis'; - } - label.css(css); - } - }, - - /** - * Get the x and y position for ticks and labels - */ - getPosition: function (horiz, pos, tickmarkOffset, old) { - var axis = this.axis, - chart = axis.chart, - cHeight = (old && chart.oldChartHeight) || chart.chartHeight; - - return { - x: horiz ? - axis.translate(pos + tickmarkOffset, null, null, old) + axis.transB : - axis.left + axis.offset + (axis.opposite ? ((old && chart.oldChartWidth) || chart.chartWidth) - axis.right - axis.left : 0), - - y: horiz ? - cHeight - axis.bottom + axis.offset - (axis.opposite ? axis.height : 0) : - cHeight - axis.translate(pos + tickmarkOffset, null, null, old) - axis.transB - }; - - }, - - /** - * Get the x, y position of the tick label - */ - getLabelPosition: function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) { - var axis = this.axis, - transA = axis.transA, - reversed = axis.reversed, - staggerLines = axis.staggerLines, - rotCorr = axis.tickRotCorr || { x: 0, y: 0 }, - yOffset = labelOptions.y, - line; - - if (!defined(yOffset)) { - if (axis.side === 0) { - yOffset = label.rotation ? -8 : -label.getBBox().height; - } else if (axis.side === 2) { - yOffset = rotCorr.y + 8; - } else { - // #3140, #3140 - yOffset = mathCos(label.rotation * deg2rad) * (rotCorr.y - label.getBBox(false, 0).height / 2); - } - } - - x = x + labelOptions.x + rotCorr.x - (tickmarkOffset && horiz ? - tickmarkOffset * transA * (reversed ? -1 : 1) : 0); - y = y + yOffset - (tickmarkOffset && !horiz ? - tickmarkOffset * transA * (reversed ? 1 : -1) : 0); - - // Correct for staggered labels - if (staggerLines) { - line = (index / (step || 1) % staggerLines); - if (axis.opposite) { - line = staggerLines - line - 1; - } - y += line * (axis.labelOffset / staggerLines); - } - - return { - x: x, - y: mathRound(y) - }; - }, - - /** - * Extendible method to return the path of the marker - */ - getMarkPath: function (x, y, tickLength, tickWidth, horiz, renderer) { - return renderer.crispLine([ - M, - x, - y, - L, - x + (horiz ? 0 : -tickLength), - y + (horiz ? tickLength : 0) - ], tickWidth); - }, - - /** - * Put everything in place - * - * @param index {Number} - * @param old {Boolean} Use old coordinates to prepare an animation into new position - */ - render: function (index, old, opacity) { - var tick = this, - axis = tick.axis, - options = axis.options, - chart = axis.chart, - renderer = chart.renderer, - horiz = axis.horiz, - type = tick.type, - label = tick.label, - pos = tick.pos, - labelOptions = options.labels, - gridLine = tick.gridLine, - gridPrefix = type ? type + 'Grid' : 'grid', - tickPrefix = type ? type + 'Tick' : 'tick', - gridLineWidth = options[gridPrefix + 'LineWidth'], - gridLineColor = options[gridPrefix + 'LineColor'], - dashStyle = options[gridPrefix + 'LineDashStyle'], - tickSize = axis.tickSize(tickPrefix), - tickColor = options[tickPrefix + 'Color'], - gridLinePath, - mark = tick.mark, - markPath, - step = /*axis.labelStep || */labelOptions.step, - attribs, - show = true, - tickmarkOffset = axis.tickmarkOffset, - xy = tick.getPosition(horiz, pos, tickmarkOffset, old), - x = xy.x, - y = xy.y, - reverseCrisp = ((horiz && x === axis.pos + axis.len) || (!horiz && y === axis.pos)) ? -1 : 1; // #1480, #1687 - - opacity = pick(opacity, 1); - this.isActive = true; - - // create the grid line - if (gridLineWidth) { - gridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLineWidth * reverseCrisp, old, true); - - if (gridLine === UNDEFINED) { - attribs = { - stroke: gridLineColor, - 'stroke-width': gridLineWidth - }; - if (dashStyle) { - attribs.dashstyle = dashStyle; - } - if (!type) { - attribs.zIndex = 1; - } - if (old) { - attribs.opacity = 0; - } - tick.gridLine = gridLine = - gridLineWidth ? - renderer.path(gridLinePath) - .attr(attribs).add(axis.gridGroup) : - null; - } - - // If the parameter 'old' is set, the current call will be followed - // by another call, therefore do not do any animations this time - if (!old && gridLine && gridLinePath) { - gridLine[tick.isNew ? 'attr' : 'animate']({ - d: gridLinePath, - opacity: opacity - }); - } - } - - // create the tick mark - if (tickSize) { - if (axis.opposite) { - tickSize[0] = -tickSize[0]; - } - markPath = tick.getMarkPath(x, y, tickSize[0], tickSize[1] * reverseCrisp, horiz, renderer); - if (mark) { // updating - mark.animate({ - d: markPath, - opacity: opacity - }); - } else { // first time - tick.mark = renderer.path( - markPath - ).attr({ - stroke: tickColor, - 'stroke-width': tickSize[1], - opacity: opacity - }).add(axis.axisGroup); - } - } - - // the label is created on init - now move it into place - if (label && isNumber(x)) { - label.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step); - - // Apply show first and show last. If the tick is both first and last, it is - // a single centered tick, in which case we show the label anyway (#2100). - if ((tick.isFirst && !tick.isLast && !pick(options.showFirstLabel, 1)) || - (tick.isLast && !tick.isFirst && !pick(options.showLastLabel, 1))) { - show = false; - - // Handle label overflow and show or hide accordingly - } else if (horiz && !axis.isRadial && !labelOptions.step && !labelOptions.rotation && !old && opacity !== 0) { - tick.handleOverflow(xy); - } - - // apply step - if (step && index % step) { - // show those indices dividable by step - show = false; - } - - // Set the new position, and show or hide - if (show && isNumber(xy.y)) { - xy.opacity = opacity; - label[tick.isNew ? 'attr' : 'animate'](xy); - tick.isNew = false; - } else { - label.attr('y', -9999); // #1338 - } - } - }, - - /** - * Destructor for the tick prototype - */ - destroy: function () { - destroyObjectProperties(this, this.axis); - } - }; - - /** - * The object wrapper for plot lines and plot bands - * @param {Object} options - */ - Highcharts.PlotLineOrBand = function (axis, options) { - this.axis = axis; - - if (options) { - this.options = options; - this.id = options.id; - } - }; - - Highcharts.PlotLineOrBand.prototype = { - - /** - * Render the plot line or plot band. If it is already existing, - * move it. - */ - render: function () { - var plotLine = this, - axis = plotLine.axis, - horiz = axis.horiz, - options = plotLine.options, - optionsLabel = options.label, - label = plotLine.label, - width = options.width, - to = options.to, - from = options.from, - isBand = defined(from) && defined(to), - value = options.value, - dashStyle = options.dashStyle, - svgElem = plotLine.svgElem, - path = [], - addEvent, - eventType, - color = options.color, - zIndex = pick(options.zIndex, 0), - events = options.events, - attribs = {}, - renderer = axis.chart.renderer, - log2lin = axis.log2lin; - - // logarithmic conversion - if (axis.isLog) { - from = log2lin(from); - to = log2lin(to); - value = log2lin(value); - } - - // plot line - if (width) { - path = axis.getPlotLinePath(value, width); - attribs = { - stroke: color, - 'stroke-width': width - }; - if (dashStyle) { - attribs.dashstyle = dashStyle; - } - } else if (isBand) { // plot band - - path = axis.getPlotBandPath(from, to, options); - if (color) { - attribs.fill = color; - } - if (options.borderWidth) { - attribs.stroke = options.borderColor; - attribs['stroke-width'] = options.borderWidth; - } - } else { - return; - } - // zIndex - attribs.zIndex = zIndex; - - // common for lines and bands - if (svgElem) { - if (path) { - svgElem.show(); - svgElem.animate({ d: path }); - } else { - svgElem.hide(); - if (label) { - plotLine.label = label = label.destroy(); - } - } - } else if (path && path.length) { - plotLine.svgElem = svgElem = renderer.path(path) - .attr(attribs).add(); - - // events - if (events) { - addEvent = function (eventType) { - svgElem.on(eventType, function (e) { - events[eventType].apply(plotLine, [e]); - }); - }; - for (eventType in events) { - addEvent(eventType); - } - } - } - - // the plot band/line label - if (optionsLabel && defined(optionsLabel.text) && path && path.length && - axis.width > 0 && axis.height > 0 && !path.flat) { - // apply defaults - optionsLabel = merge({ - align: horiz && isBand && 'center', - x: horiz ? !isBand && 4 : 10, - verticalAlign: !horiz && isBand && 'middle', - y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4, - rotation: horiz && !isBand && 90 - }, optionsLabel); - - this.renderLabel(optionsLabel, path, isBand, zIndex); - - } else if (label) { // move out of sight - label.hide(); - } - - // chainable - return plotLine; - }, - - /** - * Render and align label for plot line or band. - */ - renderLabel: function (optionsLabel, path, isBand, zIndex) { - var plotLine = this, - label = plotLine.label, - renderer = plotLine.axis.chart.renderer, - attribs, - xs, - ys, - x, - y; - - // add the SVG element - if (!label) { - attribs = { - align: optionsLabel.textAlign || optionsLabel.align, - rotation: optionsLabel.rotation - }; - - attribs.zIndex = zIndex; - - plotLine.label = label = renderer.text( - optionsLabel.text, - 0, - 0, - optionsLabel.useHTML - ) - .attr(attribs) - .css(optionsLabel.style) - .add(); - } - - // get the bounding box and align the label - // #3000 changed to better handle choice between plotband or plotline - xs = [path[1], path[4], (isBand ? path[6] : path[1])]; - ys = [path[2], path[5], (isBand ? path[7] : path[2])]; - x = arrayMin(xs); - y = arrayMin(ys); - - label.align(optionsLabel, false, { - x: x, - y: y, - width: arrayMax(xs) - x, - height: arrayMax(ys) - y - }); - label.show(); - }, - - /** - * Remove the plot line or band - */ - destroy: function () { - // remove it from the lookup - erase(this.axis.plotLinesAndBands, this); - - delete this.axis; - destroyObjectProperties(this); - } - }; - - /** - * Object with members for extending the Axis prototype - */ - - AxisPlotLineOrBandExtension = { - - /** - * Create the path for a plot band - */ - getPlotBandPath: function (from, to) { - var toPath = this.getPlotLinePath(to, null, null, true), - path = this.getPlotLinePath(from, null, null, true); - - if (path && toPath) { - - // Flat paths don't need labels (#3836) - path.flat = path.toString() === toPath.toString(); - - path.push( - toPath[4], - toPath[5], - toPath[1], - toPath[2] - ); - } else { // outside the axis area - path = null; - } - - return path; - }, - - addPlotBand: function (options) { - return this.addPlotBandOrLine(options, 'plotBands'); - }, - - addPlotLine: function (options) { - return this.addPlotBandOrLine(options, 'plotLines'); - }, - - /** - * Add a plot band or plot line after render time - * - * @param options {Object} The plotBand or plotLine configuration object - */ - addPlotBandOrLine: function (options, coll) { - var obj = new Highcharts.PlotLineOrBand(this, options).render(), - userOptions = this.userOptions; - - if (obj) { // #2189 - // Add it to the user options for exporting and Axis.update - if (coll) { - userOptions[coll] = userOptions[coll] || []; - userOptions[coll].push(options); - } - this.plotLinesAndBands.push(obj); - } - - return obj; - }, - - /** - * Remove a plot band or plot line from the chart by id - * @param {Object} id - */ - removePlotBandOrLine: function (id) { - var plotLinesAndBands = this.plotLinesAndBands, - options = this.options, - userOptions = this.userOptions, - i = plotLinesAndBands.length; - while (i--) { - if (plotLinesAndBands[i].id === id) { - plotLinesAndBands[i].destroy(); - } - } - each([options.plotLines || [], userOptions.plotLines || [], options.plotBands || [], userOptions.plotBands || []], function (arr) { - i = arr.length; - while (i--) { - if (arr[i].id === id) { - erase(arr, arr[i]); - } - } - }); - } - }; - - /** - * Create a new axis object - * @param {Object} chart - * @param {Object} options - */ - var Axis = Highcharts.Axis = function () { - this.init.apply(this, arguments); - }; - - Axis.prototype = { - - /** - * Default options for the X axis - the Y axis has extended defaults - */ - defaultOptions: { - // allowDecimals: null, - // alternateGridColor: null, - // categories: [], - dateTimeLabelFormats: { - millisecond: '%H:%M:%S.%L', - second: '%H:%M:%S', - minute: '%H:%M', - hour: '%H:%M', - day: '%e. %b', - week: '%e. %b', - month: '%b \'%y', - year: '%Y' - }, - endOnTick: false, - gridLineColor: '#D8D8D8', - // gridLineDashStyle: 'solid', - // gridLineWidth: 0, - // reversed: false, - - labels: { - enabled: true, - // rotation: 0, - // align: 'center', - // step: null, - style: { - color: '#606060', - cursor: 'default', - fontSize: '11px' - }, - x: 0 - //y: undefined - /*formatter: function () { - return this.value; - },*/ - }, - lineColor: '#C0D0E0', - lineWidth: 1, - //linkedTo: null, - //max: undefined, - //min: undefined, - minPadding: 0.01, - maxPadding: 0.01, - //minRange: null, - minorGridLineColor: '#E0E0E0', - // minorGridLineDashStyle: null, - minorGridLineWidth: 1, - minorTickColor: '#A0A0A0', - //minorTickInterval: null, - minorTickLength: 2, - minorTickPosition: 'outside', // inside or outside - //minorTickWidth: 0, - //opposite: false, - //offset: 0, - //plotBands: [{ - // events: {}, - // zIndex: 1, - // labels: { align, x, verticalAlign, y, style, rotation, textAlign } - //}], - //plotLines: [{ - // events: {} - // dashStyle: {} - // zIndex: - // labels: { align, x, verticalAlign, y, style, rotation, textAlign } - //}], - //reversed: false, - // showFirstLabel: true, - // showLastLabel: true, - startOfWeek: 1, - startOnTick: false, - tickColor: '#C0D0E0', - //tickInterval: null, - tickLength: 10, - tickmarkPlacement: 'between', // on or between - tickPixelInterval: 100, - tickPosition: 'outside', - //tickWidth: 1, - title: { - //text: null, - align: 'middle', // low, middle or high - //margin: 0 for horizontal, 10 for vertical axes, - //rotation: 0, - //side: 'outside', - style: { - color: '#707070' - } - //x: 0, - //y: 0 - }, - type: 'linear' // linear, logarithmic or datetime - //visible: true - }, - - /** - * This options set extends the defaultOptions for Y axes - */ - defaultYAxisOptions: { - endOnTick: true, - gridLineWidth: 1, - tickPixelInterval: 72, - showLastLabel: true, - labels: { - x: -8 - }, - lineWidth: 0, - maxPadding: 0.05, - minPadding: 0.05, - startOnTick: true, - //tickWidth: 0, - title: { - rotation: 270, - text: 'Values' - }, - stackLabels: { - enabled: false, - //align: dynamic, - //y: dynamic, - //x: dynamic, - //verticalAlign: dynamic, - //textAlign: dynamic, - //rotation: 0, - formatter: function () { - return Highcharts.numberFormat(this.total, -1); - }, - style: merge(defaultPlotOptions.line.dataLabels.style, { color: '#000000' }) - } - }, - - /** - * These options extend the defaultOptions for left axes - */ - defaultLeftAxisOptions: { - labels: { - x: -15 - }, - title: { - rotation: 270 - } - }, - - /** - * These options extend the defaultOptions for right axes - */ - defaultRightAxisOptions: { - labels: { - x: 15 - }, - title: { - rotation: 90 - } - }, - - /** - * These options extend the defaultOptions for bottom axes - */ - defaultBottomAxisOptions: { - labels: { - autoRotation: [-45], - x: 0 - // overflow: undefined, - // staggerLines: null - }, - title: { - rotation: 0 - } - }, - /** - * These options extend the defaultOptions for top axes - */ - defaultTopAxisOptions: { - labels: { - autoRotation: [-45], - x: 0 - // overflow: undefined - // staggerLines: null - }, - title: { - rotation: 0 - } - }, - - /** - * Initialize the axis - */ - init: function (chart, userOptions) { - - - var isXAxis = userOptions.isX, - axis = this; - - axis.chart = chart; - - // Flag, is the axis horizontal - axis.horiz = chart.inverted ? !isXAxis : isXAxis; - - // Flag, isXAxis - axis.isXAxis = isXAxis; - axis.coll = isXAxis ? 'xAxis' : 'yAxis'; - - axis.opposite = userOptions.opposite; // needed in setOptions - axis.side = userOptions.side || (axis.horiz ? - (axis.opposite ? 0 : 2) : // top : bottom - (axis.opposite ? 1 : 3)); // right : left - - axis.setOptions(userOptions); - - - var options = this.options, - type = options.type, - isDatetimeAxis = type === 'datetime'; - - axis.labelFormatter = options.labels.formatter || axis.defaultLabelFormatter; // can be overwritten by dynamic format - - - // Flag, stagger lines or not - axis.userOptions = userOptions; - - //axis.axisTitleMargin = UNDEFINED,// = options.title.margin, - axis.minPixelPadding = 0; - - axis.reversed = options.reversed; - axis.visible = options.visible !== false; - axis.zoomEnabled = options.zoomEnabled !== false; - - // Initial categories - axis.categories = options.categories || type === 'category'; - axis.names = axis.names || []; // Preserve on update (#3830) - - // Elements - //axis.axisGroup = UNDEFINED; - //axis.gridGroup = UNDEFINED; - //axis.axisTitle = UNDEFINED; - //axis.axisLine = UNDEFINED; - - // Shorthand types - axis.isLog = type === 'logarithmic'; - axis.isDatetimeAxis = isDatetimeAxis; - - // Flag, if axis is linked to another axis - axis.isLinked = defined(options.linkedTo); - // Linked axis. - //axis.linkedParent = UNDEFINED; - - // Tick positions - //axis.tickPositions = UNDEFINED; // array containing predefined positions - // Tick intervals - //axis.tickInterval = UNDEFINED; - //axis.minorTickInterval = UNDEFINED; - - - // Major ticks - axis.ticks = {}; - axis.labelEdge = []; - // Minor ticks - axis.minorTicks = {}; - - // List of plotLines/Bands - axis.plotLinesAndBands = []; - - // Alternate bands - axis.alternateBands = {}; - - // Axis metrics - //axis.left = UNDEFINED; - //axis.top = UNDEFINED; - //axis.width = UNDEFINED; - //axis.height = UNDEFINED; - //axis.bottom = UNDEFINED; - //axis.right = UNDEFINED; - //axis.transA = UNDEFINED; - //axis.transB = UNDEFINED; - //axis.oldTransA = UNDEFINED; - axis.len = 0; - //axis.oldMin = UNDEFINED; - //axis.oldMax = UNDEFINED; - //axis.oldUserMin = UNDEFINED; - //axis.oldUserMax = UNDEFINED; - //axis.oldAxisLength = UNDEFINED; - axis.minRange = axis.userMinRange = options.minRange || options.maxZoom; - axis.range = options.range; - axis.offset = options.offset || 0; - - - // Dictionary for stacks - axis.stacks = {}; - axis.oldStacks = {}; - axis.stacksTouched = 0; - - // Min and max in the data - //axis.dataMin = UNDEFINED, - //axis.dataMax = UNDEFINED, - - // The axis range - axis.max = null; - axis.min = null; - - // User set min and max - //axis.userMin = UNDEFINED, - //axis.userMax = UNDEFINED, - - // Crosshair options - axis.crosshair = pick(options.crosshair, splat(chart.options.tooltip.crosshairs)[isXAxis ? 0 : 1], false); - // Run Axis - - var eventType, - events = axis.options.events; - - // Register - if (inArray(axis, chart.axes) === -1) { // don't add it again on Axis.update() - if (isXAxis && !this.isColorAxis) { // #2713 - chart.axes.splice(chart.xAxis.length, 0, axis); - } else { - chart.axes.push(axis); - } - - chart[axis.coll].push(axis); - } - - axis.series = axis.series || []; // populated by Series - - // inverted charts have reversed xAxes as default - if (chart.inverted && isXAxis && axis.reversed === UNDEFINED) { - axis.reversed = true; - } - - axis.removePlotBand = axis.removePlotBandOrLine; - axis.removePlotLine = axis.removePlotBandOrLine; - - - // register event listeners - for (eventType in events) { - addEvent(axis, eventType, events[eventType]); - } - - // extend logarithmic axis - if (axis.isLog) { - axis.val2lin = axis.log2lin; - axis.lin2val = axis.lin2log; - } - }, - - /** - * Merge and set options - */ - setOptions: function (userOptions) { - this.options = merge( - this.defaultOptions, - this.isXAxis ? {} : this.defaultYAxisOptions, - [this.defaultTopAxisOptions, this.defaultRightAxisOptions, - this.defaultBottomAxisOptions, this.defaultLeftAxisOptions][this.side], - merge( - defaultOptions[this.coll], // if set in setOptions (#1053) - userOptions - ) - ); - }, - - /** - * The default label formatter. The context is a special config object for the label. - */ - defaultLabelFormatter: function () { - var axis = this.axis, - value = this.value, - categories = axis.categories, - dateTimeLabelFormat = this.dateTimeLabelFormat, - numericSymbols = defaultOptions.lang.numericSymbols, - i = numericSymbols && numericSymbols.length, - multi, - ret, - formatOption = axis.options.labels.format, - - // make sure the same symbol is added for all labels on a linear axis - numericSymbolDetector = axis.isLog ? value : axis.tickInterval; - - if (formatOption) { - ret = format(formatOption, this); - - } else if (categories) { - ret = value; - - } else if (dateTimeLabelFormat) { // datetime axis - ret = dateFormat(dateTimeLabelFormat, value); - - } else if (i && numericSymbolDetector >= 1000) { - // Decide whether we should add a numeric symbol like k (thousands) or M (millions). - // If we are to enable this in tooltip or other places as well, we can move this - // logic to the numberFormatter and enable it by a parameter. - while (i-- && ret === UNDEFINED) { - multi = Math.pow(1000, i + 1); - if (numericSymbolDetector >= multi && (value * 10) % multi === 0 && numericSymbols[i] !== null) { - ret = Highcharts.numberFormat(value / multi, -1) + numericSymbols[i]; - } - } - } - - if (ret === UNDEFINED) { - if (mathAbs(value) >= 10000) { // add thousands separators - ret = Highcharts.numberFormat(value, -1); - - } else { // small numbers - ret = Highcharts.numberFormat(value, -1, UNDEFINED, ''); // #2466 - } - } - - return ret; - }, - - /** - * Get the minimum and maximum for the series of each axis - */ - getSeriesExtremes: function () { - var axis = this, - chart = axis.chart; - - axis.hasVisibleSeries = false; - - // Reset properties in case we're redrawing (#3353) - axis.dataMin = axis.dataMax = axis.threshold = null; - axis.softThreshold = !axis.isXAxis; - - if (axis.buildStacks) { - axis.buildStacks(); - } - - // loop through this axis' series - each(axis.series, function (series) { - - if (series.visible || !chart.options.chart.ignoreHiddenSeries) { - - var seriesOptions = series.options, - xData, - threshold = seriesOptions.threshold, - seriesDataMin, - seriesDataMax; - - axis.hasVisibleSeries = true; - - // Validate threshold in logarithmic axes - if (axis.isLog && threshold <= 0) { - threshold = null; - } - - // Get dataMin and dataMax for X axes - if (axis.isXAxis) { - xData = series.xData; - if (xData.length) { - // If xData contains values which is not numbers, then filter them out. - // To prevent performance hit, we only do this after we have already - // found seriesDataMin because in most cases all data is valid. #5234. - seriesDataMin = arrayMin(xData); - if (!isNumber(seriesDataMin) && !(seriesDataMin instanceof Date)) { // Date for #5010 - xData = grep(xData, function (x) { - return isNumber(x); - }); - seriesDataMin = arrayMin(xData); // Do it again with valid data - } - - axis.dataMin = mathMin(pick(axis.dataMin, xData[0]), seriesDataMin); - axis.dataMax = mathMax(pick(axis.dataMax, xData[0]), arrayMax(xData)); - - } - - // Get dataMin and dataMax for Y axes, as well as handle stacking and processed data - } else { - - // Get this particular series extremes - series.getExtremes(); - seriesDataMax = series.dataMax; - seriesDataMin = series.dataMin; - - // Get the dataMin and dataMax so far. If percentage is used, the min and max are - // always 0 and 100. If seriesDataMin and seriesDataMax is null, then series - // doesn't have active y data, we continue with nulls - if (defined(seriesDataMin) && defined(seriesDataMax)) { - axis.dataMin = mathMin(pick(axis.dataMin, seriesDataMin), seriesDataMin); - axis.dataMax = mathMax(pick(axis.dataMax, seriesDataMax), seriesDataMax); - } - - // Adjust to threshold - if (defined(threshold)) { - axis.threshold = threshold; - } - // If any series has a hard threshold, it takes precedence - if (!seriesOptions.softThreshold || axis.isLog) { - axis.softThreshold = false; - } - } - } - }); - }, - - /** - * Translate from axis value to pixel position on the chart, or back - * - */ - translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacement) { - var axis = this.linkedParent || this, // #1417 - sign = 1, - cvsOffset = 0, - localA = old ? axis.oldTransA : axis.transA, - localMin = old ? axis.oldMin : axis.min, - returnValue, - minPixelPadding = axis.minPixelPadding, - doPostTranslate = (axis.isOrdinal || axis.isBroken || (axis.isLog && handleLog)) && axis.lin2val; - - if (!localA) { - localA = axis.transA; - } - - // In vertical axes, the canvas coordinates start from 0 at the top like in - // SVG. - if (cvsCoord) { - sign *= -1; // canvas coordinates inverts the value - cvsOffset = axis.len; - } - - // Handle reversed axis - if (axis.reversed) { - sign *= -1; - cvsOffset -= sign * (axis.sector || axis.len); - } - - // From pixels to value - if (backwards) { // reverse translation - - val = val * sign + cvsOffset; - val -= minPixelPadding; - returnValue = val / localA + localMin; // from chart pixel to value - if (doPostTranslate) { // log and ordinal axes - returnValue = axis.lin2val(returnValue); - } - - // From value to pixels - } else { - if (doPostTranslate) { // log and ordinal axes - val = axis.val2lin(val); - } - if (pointPlacement === 'between') { - pointPlacement = 0.5; - } - returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding) + - (isNumber(pointPlacement) ? localA * pointPlacement * axis.pointRange : 0); - } - - return returnValue; - }, - - /** - * Utility method to translate an axis value to pixel position. - * @param {Number} value A value in terms of axis units - * @param {Boolean} paneCoordinates Whether to return the pixel coordinate relative to the chart - * or just the axis/pane itself. - */ - toPixels: function (value, paneCoordinates) { - return this.translate(value, false, !this.horiz, null, true) + (paneCoordinates ? 0 : this.pos); - }, - - /* - * Utility method to translate a pixel position in to an axis value - * @param {Number} pixel The pixel value coordinate - * @param {Boolean} paneCoordiantes Whether the input pixel is relative to the chart or just the - * axis/pane itself. - */ - toValue: function (pixel, paneCoordinates) { - return this.translate(pixel - (paneCoordinates ? 0 : this.pos), true, !this.horiz, null, true); - }, - - /** - * Create the path for a plot line that goes from the given value on - * this axis, across the plot to the opposite side - * @param {Number} value - * @param {Number} lineWidth Used for calculation crisp line - * @param {Number] old Use old coordinates (for resizing and rescaling) - */ - getPlotLinePath: function (value, lineWidth, old, force, translatedValue) { - var axis = this, - chart = axis.chart, - axisLeft = axis.left, - axisTop = axis.top, - x1, - y1, - x2, - y2, - cHeight = (old && chart.oldChartHeight) || chart.chartHeight, - cWidth = (old && chart.oldChartWidth) || chart.chartWidth, - skip, - transB = axis.transB, - /** - * Check if x is between a and b. If not, either move to a/b or skip, - * depending on the force parameter. - */ - between = function (x, a, b) { - if (x < a || x > b) { - if (force) { - x = mathMin(mathMax(a, x), b); - } else { - skip = true; - } - } - return x; - }; - - translatedValue = pick(translatedValue, axis.translate(value, null, null, old)); - x1 = x2 = mathRound(translatedValue + transB); - y1 = y2 = mathRound(cHeight - translatedValue - transB); - if (!isNumber(translatedValue)) { // no min or max - skip = true; - - } else if (axis.horiz) { - y1 = axisTop; - y2 = cHeight - axis.bottom; - x1 = x2 = between(x1, axisLeft, axisLeft + axis.width); - } else { - x1 = axisLeft; - x2 = cWidth - axis.right; - y1 = y2 = between(y1, axisTop, axisTop + axis.height); - } - return skip && !force ? - null : - chart.renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 1); - }, - - /** - * Set the tick positions of a linear axis to round values like whole tens or every five. - */ - getLinearTickPositions: function (tickInterval, min, max) { - var pos, - lastPos, - roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval), - roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval), - tickPositions = []; - - // For single points, add a tick regardless of the relative position (#2662) - if (min === max && isNumber(min)) { - return [min]; - } - - // Populate the intermediate values - pos = roundedMin; - while (pos <= roundedMax) { - - // Place the tick on the rounded value - tickPositions.push(pos); - - // Always add the raw tickInterval, not the corrected one. - pos = correctFloat(pos + tickInterval); - - // If the interval is not big enough in the current min - max range to actually increase - // the loop variable, we need to break out to prevent endless loop. Issue #619 - if (pos === lastPos) { - break; - } - - // Record the last value - lastPos = pos; - } - return tickPositions; - }, - - /** - * Return the minor tick positions. For logarithmic axes, reuse the same logic - * as for major ticks. - */ - getMinorTickPositions: function () { - var axis = this, - options = axis.options, - tickPositions = axis.tickPositions, - minorTickInterval = axis.minorTickInterval, - minorTickPositions = [], - pos, - i, - pointRangePadding = axis.pointRangePadding || 0, - min = axis.min - pointRangePadding, // #1498 - max = axis.max + pointRangePadding, // #1498 - range = max - min, - len; - - // If minor ticks get too dense, they are hard to read, and may cause long running script. So we don't draw them. - if (range && range / minorTickInterval < axis.len / 3) { // #3875 - - if (axis.isLog) { - len = tickPositions.length; - for (i = 1; i < len; i++) { - minorTickPositions = minorTickPositions.concat( - axis.getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true) - ); - } - } else if (axis.isDatetimeAxis && options.minorTickInterval === 'auto') { // #1314 - minorTickPositions = minorTickPositions.concat( - axis.getTimeTicks( - axis.normalizeTimeTickInterval(minorTickInterval), - min, - max, - options.startOfWeek - ) - ); - } else { - for (pos = min + (tickPositions[0] - min) % minorTickInterval; pos <= max; pos += minorTickInterval) { - minorTickPositions.push(pos); - } - } - } - - if (minorTickPositions.length !== 0) { // don't change the extremes, when there is no minor ticks - axis.trimTicks(minorTickPositions, options.startOnTick, options.endOnTick); // #3652 #3743 #1498 - } - return minorTickPositions; - }, - - /** - * Adjust the min and max for the minimum range. Keep in mind that the series data is - * not yet processed, so we don't have information on data cropping and grouping, or - * updated axis.pointRange or series.pointRange. The data can't be processed until - * we have finally established min and max. - */ - adjustForMinRange: function () { - var axis = this, - options = axis.options, - min = axis.min, - max = axis.max, - zoomOffset, - spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange, - closestDataRange, - i, - distance, - xData, - loopLength, - minArgs, - maxArgs, - minRange; - - // Set the automatic minimum range based on the closest point distance - if (axis.isXAxis && axis.minRange === UNDEFINED && !axis.isLog) { - - if (defined(options.min) || defined(options.max)) { - axis.minRange = null; // don't do this again - - } else { - - // Find the closest distance between raw data points, as opposed to - // closestPointRange that applies to processed points (cropped and grouped) - each(axis.series, function (series) { - xData = series.xData; - loopLength = series.xIncrement ? 1 : xData.length - 1; - for (i = loopLength; i > 0; i--) { - distance = xData[i] - xData[i - 1]; - if (closestDataRange === UNDEFINED || distance < closestDataRange) { - closestDataRange = distance; - } - } - }); - axis.minRange = mathMin(closestDataRange * 5, axis.dataMax - axis.dataMin); - } - } - - // if minRange is exceeded, adjust - if (max - min < axis.minRange) { - minRange = axis.minRange; - zoomOffset = (minRange - max + min) / 2; - - // if min and max options have been set, don't go beyond it - minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)]; - if (spaceAvailable) { // if space is available, stay within the data range - minArgs[2] = axis.dataMin; - } - min = arrayMax(minArgs); - - maxArgs = [min + minRange, pick(options.max, min + minRange)]; - if (spaceAvailable) { // if space is availabe, stay within the data range - maxArgs[2] = axis.dataMax; - } - - max = arrayMin(maxArgs); - - // now if the max is adjusted, adjust the min back - if (max - min < minRange) { - minArgs[0] = max - minRange; - minArgs[1] = pick(options.min, max - minRange); - min = arrayMax(minArgs); - } - } - - // Record modified extremes - axis.min = min; - axis.max = max; - }, - - /** - * Find the closestPointRange across all series - */ - getClosest: function () { - var ret; - each(this.series, function (series) { - var seriesClosest = series.closestPointRange; - if (!series.noSharedTooltip && defined(seriesClosest)) { - ret = defined(ret) ? - mathMin(ret, seriesClosest) : - seriesClosest; - } - }); - return ret; - }, - - /** - * Update translation information - */ - setAxisTranslation: function (saveOld) { - var axis = this, - range = axis.max - axis.min, - pointRange = axis.axisPointRange || 0, - closestPointRange, - minPointOffset = 0, - pointRangePadding = 0, - linkedParent = axis.linkedParent, - ordinalCorrection, - hasCategories = !!axis.categories, - transA = axis.transA, - isXAxis = axis.isXAxis; - - // Adjust translation for padding. Y axis with categories need to go through the same (#1784). - if (isXAxis || hasCategories || pointRange) { - if (linkedParent) { - minPointOffset = linkedParent.minPointOffset; - pointRangePadding = linkedParent.pointRangePadding; - - } else { - - // Get the closest points - closestPointRange = axis.getClosest(); - - each(axis.series, function (series) { - var seriesPointRange = hasCategories ? - 1 : - (isXAxis ? - pick(series.options.pointRange, closestPointRange, 0) : - (axis.axisPointRange || 0)), // #2806 - pointPlacement = series.options.pointPlacement; - - pointRange = mathMax(pointRange, seriesPointRange); - - if (!axis.single) { - // minPointOffset is the value padding to the left of the axis in order to make - // room for points with a pointRange, typically columns. When the pointPlacement option - // is 'between' or 'on', this padding does not apply. - minPointOffset = mathMax( - minPointOffset, - isString(pointPlacement) ? 0 : seriesPointRange / 2 - ); - - // Determine the total padding needed to the length of the axis to make room for the - // pointRange. If the series' pointPlacement is 'on', no padding is added. - pointRangePadding = mathMax( - pointRangePadding, - pointPlacement === 'on' ? 0 : seriesPointRange - ); - } - }); - } - - // Record minPointOffset and pointRangePadding - ordinalCorrection = axis.ordinalSlope && closestPointRange ? axis.ordinalSlope / closestPointRange : 1; // #988, #1853 - axis.minPointOffset = minPointOffset = minPointOffset * ordinalCorrection; - axis.pointRangePadding = pointRangePadding = pointRangePadding * ordinalCorrection; - - // pointRange means the width reserved for each point, like in a column chart - axis.pointRange = mathMin(pointRange, range); - - // closestPointRange means the closest distance between points. In columns - // it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange - // is some other value - if (isXAxis) { - axis.closestPointRange = closestPointRange; - } - } - - // Secondary values - if (saveOld) { - axis.oldTransA = transA; - } - axis.translationSlope = axis.transA = transA = axis.len / ((range + pointRangePadding) || 1); - axis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend - axis.minPixelPadding = transA * minPointOffset; - }, - - minFromRange: function () { - return this.max - this.range; - }, - - /** - * Set the tick positions to round values and optionally extend the extremes - * to the nearest tick - */ - setTickInterval: function (secondPass) { - var axis = this, - chart = axis.chart, - options = axis.options, - isLog = axis.isLog, - log2lin = axis.log2lin, - isDatetimeAxis = axis.isDatetimeAxis, - isXAxis = axis.isXAxis, - isLinked = axis.isLinked, - maxPadding = options.maxPadding, - minPadding = options.minPadding, - length, - linkedParentExtremes, - tickIntervalOption = options.tickInterval, - minTickInterval, - tickPixelIntervalOption = options.tickPixelInterval, - categories = axis.categories, - threshold = axis.threshold, - softThreshold = axis.softThreshold, - thresholdMin, - thresholdMax, - hardMin, - hardMax; - - if (!isDatetimeAxis && !categories && !isLinked) { - this.getTickAmount(); - } - - // Min or max set either by zooming/setExtremes or initial options - hardMin = pick(axis.userMin, options.min); - hardMax = pick(axis.userMax, options.max); - - // Linked axis gets the extremes from the parent axis - if (isLinked) { - axis.linkedParent = chart[axis.coll][options.linkedTo]; - linkedParentExtremes = axis.linkedParent.getExtremes(); - axis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin); - axis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax); - if (options.type !== axis.linkedParent.options.type) { - error(11, 1); // Can't link axes of different type - } - - // Initial min and max from the extreme data values - } else { - - // Adjust to hard threshold - if (!softThreshold && defined(threshold)) { - if (axis.dataMin >= threshold) { - thresholdMin = threshold; - minPadding = 0; - } else if (axis.dataMax <= threshold) { - thresholdMax = threshold; - maxPadding = 0; - } - } - - axis.min = pick(hardMin, thresholdMin, axis.dataMin); - axis.max = pick(hardMax, thresholdMax, axis.dataMax); - - } - - if (isLog) { - if (!secondPass && mathMin(axis.min, pick(axis.dataMin, axis.min)) <= 0) { // #978 - error(10, 1); // Can't plot negative values on log axis - } - // The correctFloat cures #934, float errors on full tens. But it - // was too aggressive for #4360 because of conversion back to lin, - // therefore use precision 15. - axis.min = correctFloat(log2lin(axis.min), 15); - axis.max = correctFloat(log2lin(axis.max), 15); - } - - // handle zoomed range - if (axis.range && defined(axis.max)) { - axis.userMin = axis.min = hardMin = mathMax(axis.min, axis.minFromRange()); // #618 - axis.userMax = hardMax = axis.max; - - axis.range = null; // don't use it when running setExtremes - } - - // Hook for Highstock Scroller. Consider combining with beforePadding. - fireEvent(axis, 'foundExtremes'); - - // Hook for adjusting this.min and this.max. Used by bubble series. - if (axis.beforePadding) { - axis.beforePadding(); - } - - // adjust min and max for the minimum range - axis.adjustForMinRange(); - - // Pad the values to get clear of the chart's edges. To avoid tickInterval taking the padding - // into account, we do this after computing tick interval (#1337). - if (!categories && !axis.axisPointRange && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) { - length = axis.max - axis.min; - if (length) { - if (!defined(hardMin) && minPadding) { - axis.min -= length * minPadding; - } - if (!defined(hardMax) && maxPadding) { - axis.max += length * maxPadding; - } - } - } - - // Stay within floor and ceiling - if (isNumber(options.floor)) { - axis.min = mathMax(axis.min, options.floor); - } - if (isNumber(options.ceiling)) { - axis.max = mathMin(axis.max, options.ceiling); - } - - // When the threshold is soft, adjust the extreme value only if - // the data extreme and the padded extreme land on either side of the threshold. For example, - // a series of [0, 1, 2, 3] would make the yAxis add a tick for -1 because of the - // default minPadding and startOnTick options. This is prevented by the softThreshold - // option. - if (softThreshold && defined(axis.dataMin)) { - threshold = threshold || 0; - if (!defined(hardMin) && axis.min < threshold && axis.dataMin >= threshold) { - axis.min = threshold; - } else if (!defined(hardMax) && axis.max > threshold && axis.dataMax <= threshold) { - axis.max = threshold; - } - } - - - // get tickInterval - if (axis.min === axis.max || axis.min === undefined || axis.max === undefined) { - axis.tickInterval = 1; - } else if (isLinked && !tickIntervalOption && - tickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) { - axis.tickInterval = tickIntervalOption = axis.linkedParent.tickInterval; - } else { - axis.tickInterval = pick( - tickIntervalOption, - this.tickAmount ? ((axis.max - axis.min) / mathMax(this.tickAmount - 1, 1)) : undefined, - categories ? // for categoried axis, 1 is default, for linear axis use tickPix - 1 : - // don't let it be more than the data range - (axis.max - axis.min) * tickPixelIntervalOption / mathMax(axis.len, tickPixelIntervalOption) - ); - } - - // Now we're finished detecting min and max, crop and group series data. This - // is in turn needed in order to find tick positions in ordinal axes. - if (isXAxis && !secondPass) { - each(axis.series, function (series) { - series.processData(axis.min !== axis.oldMin || axis.max !== axis.oldMax); - }); - } - - // set the translation factor used in translate function - axis.setAxisTranslation(true); - - // hook for ordinal axes and radial axes - if (axis.beforeSetTickPositions) { - axis.beforeSetTickPositions(); - } - - // hook for extensions, used in Highstock ordinal axes - if (axis.postProcessTickInterval) { - axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval); - } - - // In column-like charts, don't cramp in more ticks than there are points (#1943, #4184) - if (axis.pointRange && !tickIntervalOption) { - axis.tickInterval = mathMax(axis.pointRange, axis.tickInterval); - } - - // Before normalizing the tick interval, handle minimum tick interval. This applies only if tickInterval is not defined. - minTickInterval = pick(options.minTickInterval, axis.isDatetimeAxis && axis.closestPointRange); - if (!tickIntervalOption && axis.tickInterval < minTickInterval) { - axis.tickInterval = minTickInterval; - } - - // for linear axes, get magnitude and normalize the interval - if (!isDatetimeAxis && !isLog && !tickIntervalOption) { - axis.tickInterval = normalizeTickInterval( - axis.tickInterval, - null, - getMagnitude(axis.tickInterval), - // If the tick interval is between 0.5 and 5 and the axis max is in the order of - // thousands, chances are we are dealing with years. Don't allow decimals. #3363. - pick(options.allowDecimals, !(axis.tickInterval > 0.5 && axis.tickInterval < 5 && axis.max > 1000 && axis.max < 9999)), - !!this.tickAmount - ); - } - - // Prevent ticks from getting so close that we can't draw the labels - if (!this.tickAmount && this.len) { // Color axis with disabled legend has no length - axis.tickInterval = axis.unsquish(); - } - - this.setTickPositions(); - }, - - /** - * Now we have computed the normalized tickInterval, get the tick positions - */ - setTickPositions: function () { - - var options = this.options, - tickPositions, - tickPositionsOption = options.tickPositions, - tickPositioner = options.tickPositioner, - startOnTick = options.startOnTick, - endOnTick = options.endOnTick, - single; - - // Set the tickmarkOffset - this.tickmarkOffset = (this.categories && options.tickmarkPlacement === 'between' && - this.tickInterval === 1) ? 0.5 : 0; // #3202 - - - // get minorTickInterval - this.minorTickInterval = options.minorTickInterval === 'auto' && this.tickInterval ? - this.tickInterval / 5 : options.minorTickInterval; - - // Find the tick positions - this.tickPositions = tickPositions = tickPositionsOption && tickPositionsOption.slice(); // Work on a copy (#1565) - if (!tickPositions) { - - if (this.isDatetimeAxis) { - tickPositions = this.getTimeTicks( - this.normalizeTimeTickInterval(this.tickInterval, options.units), - this.min, - this.max, - options.startOfWeek, - this.ordinalPositions, - this.closestPointRange, - true - ); - } else if (this.isLog) { - tickPositions = this.getLogTickPositions(this.tickInterval, this.min, this.max); - } else { - tickPositions = this.getLinearTickPositions(this.tickInterval, this.min, this.max); - } - - // Too dense ticks, keep only the first and last (#4477) - if (tickPositions.length > this.len) { - tickPositions = [tickPositions[0], tickPositions.pop()]; - } - - this.tickPositions = tickPositions; - - // Run the tick positioner callback, that allows modifying auto tick positions. - if (tickPositioner) { - tickPositioner = tickPositioner.apply(this, [this.min, this.max]); - if (tickPositioner) { - this.tickPositions = tickPositions = tickPositioner; - } - } - - } - - if (!this.isLinked) { - - // reset min/max or remove extremes based on start/end on tick - this.trimTicks(tickPositions, startOnTick, endOnTick); - - // When there is only one point, or all points have the same value on this axis, then min - // and max are equal and tickPositions.length is 0 or 1. In this case, add some padding - // in order to center the point, but leave it with one tick. #1337. - if (this.min === this.max && defined(this.min) && !this.tickAmount) { - // Substract half a unit (#2619, #2846, #2515, #3390) - single = true; - this.min -= 0.5; - this.max += 0.5; - } - this.single = single; - - if (!tickPositionsOption && !tickPositioner) { - this.adjustTickAmount(); - } - } - }, - - /** - * Handle startOnTick and endOnTick by either adapting to padding min/max or rounded min/max - */ - trimTicks: function (tickPositions, startOnTick, endOnTick) { - var roundedMin = tickPositions[0], - roundedMax = tickPositions[tickPositions.length - 1], - minPointOffset = this.minPointOffset || 0; - - if (startOnTick) { - this.min = roundedMin; - } else { - while (this.min - minPointOffset > tickPositions[0]) { - tickPositions.shift(); - } - } - - if (endOnTick) { - this.max = roundedMax; - } else { - while (this.max + minPointOffset < tickPositions[tickPositions.length - 1]) { - tickPositions.pop(); - } - } - - // If no tick are left, set one tick in the middle (#3195) - if (tickPositions.length === 0 && defined(roundedMin)) { - tickPositions.push((roundedMax + roundedMin) / 2); - } - }, - - /** - * Check if there are multiple axes in the same pane - * @returns {Boolean} There are other axes - */ - alignToOthers: function () { - var others = {}, // Whether there is another axis to pair with this one - hasOther, - options = this.options; - - if (this.chart.options.chart.alignTicks !== false && options.alignTicks !== false) { - each(this.chart[this.coll], function (axis) { - var otherOptions = axis.options, - horiz = axis.horiz, - key = [ - horiz ? otherOptions.left : otherOptions.top, - otherOptions.width, - otherOptions.height, - otherOptions.pane - ].join(','); - - - if (axis.series.length) { // #4442 - if (others[key]) { - hasOther = true; // #4201 - } else { - others[key] = 1; - } - } - }); - } - return hasOther; - }, - - /** - * Set the max ticks of either the x and y axis collection - */ - getTickAmount: function () { - var options = this.options, - tickAmount = options.tickAmount, - tickPixelInterval = options.tickPixelInterval; - - if (!defined(options.tickInterval) && this.len < tickPixelInterval && !this.isRadial && - !this.isLog && options.startOnTick && options.endOnTick) { - tickAmount = 2; - } - - if (!tickAmount && this.alignToOthers()) { - // Add 1 because 4 tick intervals require 5 ticks (including first and last) - tickAmount = mathCeil(this.len / tickPixelInterval) + 1; - } - - // For tick amounts of 2 and 3, compute five ticks and remove the intermediate ones. This - // prevents the axis from adding ticks that are too far away from the data extremes. - if (tickAmount < 4) { - this.finalTickAmt = tickAmount; - tickAmount = 5; - } - - this.tickAmount = tickAmount; - }, - - /** - * When using multiple axes, adjust the number of ticks to match the highest - * number of ticks in that group - */ - adjustTickAmount: function () { - var tickInterval = this.tickInterval, - tickPositions = this.tickPositions, - tickAmount = this.tickAmount, - finalTickAmt = this.finalTickAmt, - currentTickAmount = tickPositions && tickPositions.length, - i, - len; - - if (currentTickAmount < tickAmount) { - while (tickPositions.length < tickAmount) { - tickPositions.push(correctFloat( - tickPositions[tickPositions.length - 1] + tickInterval - )); - } - this.transA *= (currentTickAmount - 1) / (tickAmount - 1); - this.max = tickPositions[tickPositions.length - 1]; - - // We have too many ticks, run second pass to try to reduce ticks - } else if (currentTickAmount > tickAmount) { - this.tickInterval *= 2; - this.setTickPositions(); - } - - // The finalTickAmt property is set in getTickAmount - if (defined(finalTickAmt)) { - i = len = tickPositions.length; - while (i--) { - if ( - (finalTickAmt === 3 && i % 2 === 1) || // Remove every other tick - (finalTickAmt <= 2 && i > 0 && i < len - 1) // Remove all but first and last - ) { - tickPositions.splice(i, 1); - } - } - this.finalTickAmt = UNDEFINED; - } - }, - - /** - * Set the scale based on data min and max, user set min and max or options - * - */ - setScale: function () { - var axis = this, - isDirtyData, - isDirtyAxisLength; - - axis.oldMin = axis.min; - axis.oldMax = axis.max; - axis.oldAxisLength = axis.len; - - // set the new axisLength - axis.setAxisSize(); - //axisLength = horiz ? axisWidth : axisHeight; - isDirtyAxisLength = axis.len !== axis.oldAxisLength; - - // is there new data? - each(axis.series, function (series) { - if (series.isDirtyData || series.isDirty || - series.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well - isDirtyData = true; - } - }); - - // do we really need to go through all this? - if (isDirtyAxisLength || isDirtyData || axis.isLinked || axis.forceRedraw || - axis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax || axis.alignToOthers()) { - - if (axis.resetStacks) { - axis.resetStacks(); - } - - axis.forceRedraw = false; - - // get data extremes if needed - axis.getSeriesExtremes(); - - // get fixed positions based on tickInterval - axis.setTickInterval(); - - // record old values to decide whether a rescale is necessary later on (#540) - axis.oldUserMin = axis.userMin; - axis.oldUserMax = axis.userMax; - - // Mark as dirty if it is not already set to dirty and extremes have changed. #595. - if (!axis.isDirty) { - axis.isDirty = isDirtyAxisLength || axis.min !== axis.oldMin || axis.max !== axis.oldMax; - } - } else if (axis.cleanStacks) { - axis.cleanStacks(); - } - }, - - /** - * Set the extremes and optionally redraw - * @param {Number} newMin - * @param {Number} newMax - * @param {Boolean} redraw - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - * @param {Object} eventArguments - * - */ - setExtremes: function (newMin, newMax, redraw, animation, eventArguments) { - var axis = this, - chart = axis.chart; - - redraw = pick(redraw, true); // defaults to true - - each(axis.series, function (serie) { - delete serie.kdTree; - }); - - // Extend the arguments with min and max - eventArguments = extend(eventArguments, { - min: newMin, - max: newMax - }); - - // Fire the event - fireEvent(axis, 'setExtremes', eventArguments, function () { // the default event handler - - axis.userMin = newMin; - axis.userMax = newMax; - axis.eventArgs = eventArguments; - - if (redraw) { - chart.redraw(animation); - } - }); - }, - - /** - * Overridable method for zooming chart. Pulled out in a separate method to allow overriding - * in stock charts. - */ - zoom: function (newMin, newMax) { - var dataMin = this.dataMin, - dataMax = this.dataMax, - options = this.options, - min = mathMin(dataMin, pick(options.min, dataMin)), - max = mathMax(dataMax, pick(options.max, dataMax)); - - // Prevent pinch zooming out of range. Check for defined is for #1946. #1734. - if (!this.allowZoomOutside) { - if (defined(dataMin) && newMin <= min) { - newMin = min; - } - if (defined(dataMax) && newMax >= max) { - newMax = max; - } - } - - // In full view, displaying the reset zoom button is not required - this.displayBtn = newMin !== UNDEFINED || newMax !== UNDEFINED; - - // Do it - this.setExtremes( - newMin, - newMax, - false, - UNDEFINED, - { trigger: 'zoom' } - ); - return true; - }, - - /** - * Update the axis metrics - */ - setAxisSize: function () { - var chart = this.chart, - options = this.options, - offsetLeft = options.offsetLeft || 0, - offsetRight = options.offsetRight || 0, - horiz = this.horiz, - width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight), - height = pick(options.height, chart.plotHeight), - top = pick(options.top, chart.plotTop), - left = pick(options.left, chart.plotLeft + offsetLeft), - percentRegex = /%$/; - - // Check for percentage based input values. Rounding fixes problems with - // column overflow and plot line filtering (#4898, #4899) - if (percentRegex.test(height)) { - height = Math.round(parseFloat(height) / 100 * chart.plotHeight); - } - if (percentRegex.test(top)) { - top = Math.round(parseFloat(top) / 100 * chart.plotHeight + chart.plotTop); - } - - // Expose basic values to use in Series object and navigator - this.left = left; - this.top = top; - this.width = width; - this.height = height; - this.bottom = chart.chartHeight - height - top; - this.right = chart.chartWidth - width - left; - - // Direction agnostic properties - this.len = mathMax(horiz ? width : height, 0); // mathMax fixes #905 - this.pos = horiz ? left : top; // distance from SVG origin - }, - - /** - * Get the actual axis extremes - */ - getExtremes: function () { - var axis = this, - isLog = axis.isLog, - lin2log = axis.lin2log; - - return { - min: isLog ? correctFloat(lin2log(axis.min)) : axis.min, - max: isLog ? correctFloat(lin2log(axis.max)) : axis.max, - dataMin: axis.dataMin, - dataMax: axis.dataMax, - userMin: axis.userMin, - userMax: axis.userMax - }; - }, - - /** - * Get the zero plane either based on zero or on the min or max value. - * Used in bar and area plots - */ - getThreshold: function (threshold) { - var axis = this, - isLog = axis.isLog, - lin2log = axis.lin2log, - realMin = isLog ? lin2log(axis.min) : axis.min, - realMax = isLog ? lin2log(axis.max) : axis.max; - - // With a threshold of null, make the columns/areas rise from the top or bottom - // depending on the value, assuming an actual threshold of 0 (#4233). - if (threshold === null) { - threshold = realMax < 0 ? realMax : realMin; - } else if (realMin > threshold) { - threshold = realMin; - } else if (realMax < threshold) { - threshold = realMax; - } - - return axis.translate(threshold, 0, 1, 0, 1); - }, - - /** - * Compute auto alignment for the axis label based on which side the axis is on - * and the given rotation for the label - */ - autoLabelAlign: function (rotation) { - var ret, - angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360; - - if (angle > 15 && angle < 165) { - ret = 'right'; - } else if (angle > 195 && angle < 345) { - ret = 'left'; - } else { - ret = 'center'; - } - return ret; - }, - - /** - * Get the tick length and width for the axis. - * @param {String} prefix 'tick' or 'minorTick' - * @returns {Array} An array of tickLength and tickWidth - */ - tickSize: function (prefix) { - var options = this.options, - tickLength = options[prefix + 'Length'], - tickWidth = pick(options[prefix + 'Width'], prefix === 'tick' && this.isXAxis ? 1 : 0); // X axis defaults to 1 - - if (tickWidth && tickLength) { - // Negate the length - if (options[prefix + 'Position'] === 'inside') { - tickLength = -tickLength; - } - return [tickLength, tickWidth]; - } - - }, - - /** - * Return the size of the labels - */ - labelMetrics: function () { - return this.chart.renderer.fontMetrics( - this.options.labels.style.fontSize, - this.ticks[0] && this.ticks[0].label - ); - }, - - /** - * Prevent the ticks from getting so close we can't draw the labels. On a horizontal - * axis, this is handled by rotating the labels, removing ticks and adding ellipsis. - * On a vertical axis remove ticks and add ellipsis. - */ - unsquish: function () { - var labelOptions = this.options.labels, - horiz = this.horiz, - tickInterval = this.tickInterval, - newTickInterval = tickInterval, - slotSize = this.len / (((this.categories ? 1 : 0) + this.max - this.min) / tickInterval), - rotation, - rotationOption = labelOptions.rotation, - labelMetrics = this.labelMetrics(), - step, - bestScore = Number.MAX_VALUE, - autoRotation, - // Return the multiple of tickInterval that is needed to avoid collision - getStep = function (spaceNeeded) { - var step = spaceNeeded / (slotSize || 1); - step = step > 1 ? mathCeil(step) : 1; - return step * tickInterval; - }; - - if (horiz) { - autoRotation = !labelOptions.staggerLines && !labelOptions.step && ( // #3971 - defined(rotationOption) ? - [rotationOption] : - slotSize < pick(labelOptions.autoRotationLimit, 80) && labelOptions.autoRotation - ); - - if (autoRotation) { - - // Loop over the given autoRotation options, and determine which gives the best score. The - // best score is that with the lowest number of steps and a rotation closest to horizontal. - each(autoRotation, function (rot) { - var score; - - if (rot === rotationOption || (rot && rot >= -90 && rot <= 90)) { // #3891 - - step = getStep(mathAbs(labelMetrics.h / mathSin(deg2rad * rot))); - - score = step + mathAbs(rot / 360); - - if (score < bestScore) { - bestScore = score; - rotation = rot; - newTickInterval = step; - } - } - }); - } - - } else if (!labelOptions.step) { // #4411 - newTickInterval = getStep(labelMetrics.h); - } - - this.autoRotation = autoRotation; - this.labelRotation = pick(rotation, rotationOption); - - return newTickInterval; - }, - - /** - * Get the general slot width for this axis. This may change between the pre-render (from Axis.getOffset) - * and the final tick rendering and placement (#5086). - */ - getSlotWidth: function () { - var chart = this.chart, - horiz = this.horiz, - labelOptions = this.options.labels, - slotCount = Math.max(this.tickPositions.length - (this.categories ? 0 : 1), 1), - marginLeft = chart.margin[3]; - - return (horiz && (labelOptions.step || 0) < 2 && !labelOptions.rotation && // #4415 - ((this.staggerLines || 1) * chart.plotWidth) / slotCount) || - (!horiz && ((marginLeft && (marginLeft - chart.spacing[3])) || chart.chartWidth * 0.33)); // #1580, #1931 - - }, - - /** - * Render the axis labels and determine whether ellipsis or rotation need to be applied - */ - renderUnsquish: function () { - var chart = this.chart, - renderer = chart.renderer, - tickPositions = this.tickPositions, - ticks = this.ticks, - labelOptions = this.options.labels, - horiz = this.horiz, - slotWidth = this.getSlotWidth(), - innerWidth = mathMax(1, mathRound(slotWidth - 2 * (labelOptions.padding || 5))), - attr = {}, - labelMetrics = this.labelMetrics(), - textOverflowOption = labelOptions.style.textOverflow, - css, - labelLength = 0, - label, - i, - pos; - - // Set rotation option unless it is "auto", like in gauges - if (!isString(labelOptions.rotation)) { - attr.rotation = labelOptions.rotation || 0; // #4443 - } - - // Handle auto rotation on horizontal axis - if (this.autoRotation) { - - // Get the longest label length - each(tickPositions, function (tick) { - tick = ticks[tick]; - if (tick && tick.labelLength > labelLength) { - labelLength = tick.labelLength; - } - }); - - // Apply rotation only if the label is too wide for the slot, and - // the label is wider than its height. - if (labelLength > innerWidth && labelLength > labelMetrics.h) { - attr.rotation = this.labelRotation; - } else { - this.labelRotation = 0; - } - - // Handle word-wrap or ellipsis on vertical axis - } else if (slotWidth) { - // For word-wrap or ellipsis - css = { width: innerWidth + PX }; - - if (!textOverflowOption) { - css.textOverflow = 'clip'; - - // On vertical axis, only allow word wrap if there is room for more lines. - i = tickPositions.length; - while (!horiz && i--) { - pos = tickPositions[i]; - label = ticks[pos].label; - if (label) { - // Reset ellipsis in order to get the correct bounding box (#4070) - if (label.styles.textOverflow === 'ellipsis') { - label.css({ textOverflow: 'clip' }); - - // Set the correct width in order to read the bounding box height (#4678, #5034) - } else if (ticks[pos].labelLength > slotWidth) { - label.css({ width: slotWidth + 'px' }); - } - - if (label.getBBox().height > this.len / tickPositions.length - (labelMetrics.h - labelMetrics.f)) { - label.specCss = { textOverflow: 'ellipsis' }; - } - } - } - } - } - - - // Add ellipsis if the label length is significantly longer than ideal - if (attr.rotation) { - css = { - width: (labelLength > chart.chartHeight * 0.5 ? chart.chartHeight * 0.33 : chart.chartHeight) + PX - }; - if (!textOverflowOption) { - css.textOverflow = 'ellipsis'; - } - } - - // Set the explicit or automatic label alignment - this.labelAlign = labelOptions.align || this.autoLabelAlign(this.labelRotation); - if (this.labelAlign) { - attr.align = this.labelAlign; - } - - // Apply general and specific CSS - each(tickPositions, function (pos) { - var tick = ticks[pos], - label = tick && tick.label; - if (label) { - label.attr(attr); // This needs to go before the CSS in old IE (#4502) - if (css) { - label.css(merge(css, label.specCss)); - } - delete label.specCss; - tick.rotation = attr.rotation; - } - }); - - // Note: Why is this not part of getLabelPosition? - this.tickRotCorr = renderer.rotCorr(labelMetrics.b, this.labelRotation || 0, this.side !== 0); - }, - - /** - * Return true if the axis has associated data - */ - hasData: function () { - return this.hasVisibleSeries || (defined(this.min) && defined(this.max) && !!this.tickPositions); - }, - - /** - * Render the tick labels to a preliminary position to get their sizes - */ - getOffset: function () { - var axis = this, - chart = axis.chart, - renderer = chart.renderer, - options = axis.options, - tickPositions = axis.tickPositions, - ticks = axis.ticks, - horiz = axis.horiz, - side = axis.side, - invertedSide = chart.inverted ? [1, 0, 3, 2][side] : side, - hasData, - showAxis, - titleOffset = 0, - titleOffsetOption, - titleMargin = 0, - axisTitleOptions = options.title, - labelOptions = options.labels, - labelOffset = 0, // reset - labelOffsetPadded, - opposite = axis.opposite, - axisOffset = chart.axisOffset, - clipOffset = chart.clipOffset, - clip, - directionFactor = [-1, 1, 1, -1][side], - n, - textAlign, - axisParent = axis.axisParent, // Used in color axis - lineHeightCorrection, - tickSize = this.tickSize('tick'); - - // For reuse in Axis.render - hasData = axis.hasData(); - axis.showAxis = showAxis = hasData || pick(options.showEmpty, true); - - // Set/reset staggerLines - axis.staggerLines = axis.horiz && labelOptions.staggerLines; - - // Create the axisGroup and gridGroup elements on first iteration - if (!axis.axisGroup) { - axis.gridGroup = renderer.g('grid') - .attr({ zIndex: options.gridZIndex || 1 }) - .add(axisParent); - axis.axisGroup = renderer.g('axis') - .attr({ zIndex: options.zIndex || 2 }) - .add(axisParent); - axis.labelGroup = renderer.g('axis-labels') - .attr({ zIndex: labelOptions.zIndex || 7 }) - .addClass(PREFIX + axis.coll.toLowerCase() + '-labels') - .add(axisParent); - } - - if (hasData || axis.isLinked) { - - // Generate ticks - each(tickPositions, function (pos) { - if (!ticks[pos]) { - ticks[pos] = new Tick(axis, pos); - } else { - ticks[pos].addLabel(); // update labels depending on tick interval - } - }); - - axis.renderUnsquish(); - - - // Left side must be align: right and right side must have align: left for labels - if (labelOptions.reserveSpace !== false && (side === 0 || side === 2 || - { 1: 'left', 3: 'right' }[side] === axis.labelAlign || axis.labelAlign === 'center')) { - each(tickPositions, function (pos) { - - // get the highest offset - labelOffset = mathMax( - ticks[pos].getLabelSize(), - labelOffset - ); - }); - } - - if (axis.staggerLines) { - labelOffset *= axis.staggerLines; - axis.labelOffset = labelOffset * (axis.opposite ? -1 : 1); - } - - - } else { // doesn't have data - for (n in ticks) { - ticks[n].destroy(); - delete ticks[n]; - } - } - - if (axisTitleOptions && axisTitleOptions.text && axisTitleOptions.enabled !== false) { - if (!axis.axisTitle) { - textAlign = axisTitleOptions.textAlign; - if (!textAlign) { - textAlign = (horiz ? { - low: 'left', - middle: 'center', - high: 'right' - } : { - low: opposite ? 'right' : 'left', - middle: 'center', - high: opposite ? 'left' : 'right' - })[axisTitleOptions.align]; - } - axis.axisTitle = renderer.text( - axisTitleOptions.text, - 0, - 0, - axisTitleOptions.useHTML - ) - .attr({ - zIndex: 7, - rotation: axisTitleOptions.rotation || 0, - align: textAlign - }) - .addClass(PREFIX + this.coll.toLowerCase() + '-title') - .css(axisTitleOptions.style) - .add(axis.axisGroup); - axis.axisTitle.isNew = true; - } - - if (showAxis) { - titleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width']; - titleOffsetOption = axisTitleOptions.offset; - titleMargin = defined(titleOffsetOption) ? 0 : pick(axisTitleOptions.margin, horiz ? 5 : 10); - } - - // hide or show the title depending on whether showEmpty is set - axis.axisTitle[showAxis ? 'show' : 'hide'](true); - } - - // handle automatic or user set offset - axis.offset = directionFactor * pick(options.offset, axisOffset[side]); - - axis.tickRotCorr = axis.tickRotCorr || { x: 0, y: 0 }; // polar - if (side === 0) { - lineHeightCorrection = -axis.labelMetrics().h; - } else if (side === 2) { - lineHeightCorrection = axis.tickRotCorr.y; - } else { - lineHeightCorrection = 0; - } - - // Find the padded label offset - labelOffsetPadded = Math.abs(labelOffset) + titleMargin; - if (labelOffset) { - labelOffsetPadded -= lineHeightCorrection; - labelOffsetPadded += directionFactor * (horiz ? pick(labelOptions.y, axis.tickRotCorr.y + directionFactor * 8) : labelOptions.x); - } - axis.axisTitleMargin = pick(titleOffsetOption, labelOffsetPadded); - - axisOffset[side] = mathMax( - axisOffset[side], - axis.axisTitleMargin + titleOffset + directionFactor * axis.offset, - labelOffsetPadded, // #3027 - hasData && tickPositions.length && tickSize ? tickSize[0] : 0 // #4866 - ); - - // Decide the clipping needed to keep the graph inside the plot area and axis lines - clip = options.offset ? 0 : mathFloor(options.lineWidth / 2) * 2; // #4308, #4371 - clipOffset[invertedSide] = mathMax(clipOffset[invertedSide], clip); - }, - - /** - * Get the path for the axis line - */ - getLinePath: function (lineWidth) { - var chart = this.chart, - opposite = this.opposite, - offset = this.offset, - horiz = this.horiz, - lineLeft = this.left + (opposite ? this.width : 0) + offset, - lineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset; - - if (opposite) { - lineWidth *= -1; // crispify the other way - #1480, #1687 - } - - return chart.renderer - .crispLine([ - M, - horiz ? - this.left : - lineLeft, - horiz ? - lineTop : - this.top, - L, - horiz ? - chart.chartWidth - this.right : - lineLeft, - horiz ? - lineTop : - chart.chartHeight - this.bottom - ], lineWidth); - }, - - /** - * Position the title - */ - getTitlePosition: function () { - // compute anchor points for each of the title align options - var horiz = this.horiz, - axisLeft = this.left, - axisTop = this.top, - axisLength = this.len, - axisTitleOptions = this.options.title, - margin = horiz ? axisLeft : axisTop, - opposite = this.opposite, - offset = this.offset, - xOption = axisTitleOptions.x || 0, - yOption = axisTitleOptions.y || 0, - fontSize = pInt(axisTitleOptions.style.fontSize || 12), - - // the position in the length direction of the axis - alongAxis = { - low: margin + (horiz ? 0 : axisLength), - middle: margin + axisLength / 2, - high: margin + (horiz ? axisLength : 0) - }[axisTitleOptions.align], - - // the position in the perpendicular direction of the axis - offAxis = (horiz ? axisTop + this.height : axisLeft) + - (horiz ? 1 : -1) * // horizontal axis reverses the margin - (opposite ? -1 : 1) * // so does opposite axes - this.axisTitleMargin + - (this.side === 2 ? fontSize : 0); - - return { - x: horiz ? - alongAxis + xOption : - offAxis + (opposite ? this.width : 0) + offset + xOption, - y: horiz ? - offAxis + yOption - (opposite ? this.height : 0) + offset : - alongAxis + yOption - }; - }, - - /** - * Render the axis - */ - render: function () { - var axis = this, - chart = axis.chart, - renderer = chart.renderer, - options = axis.options, - isLog = axis.isLog, - lin2log = axis.lin2log, - isLinked = axis.isLinked, - tickPositions = axis.tickPositions, - axisTitle = axis.axisTitle, - ticks = axis.ticks, - minorTicks = axis.minorTicks, - alternateBands = axis.alternateBands, - stackLabelOptions = options.stackLabels, - alternateGridColor = options.alternateGridColor, - tickmarkOffset = axis.tickmarkOffset, - lineWidth = options.lineWidth, - linePath, - hasRendered = chart.hasRendered, - slideInTicks = hasRendered && isNumber(axis.oldMin), - showAxis = axis.showAxis, - animation = animObject(renderer.globalAnimation), - from, - to; - - // Reset - axis.labelEdge.length = 0; - //axis.justifyToPlot = overflow === 'justify'; - axis.overlap = false; - - // Mark all elements inActive before we go over and mark the active ones - each([ticks, minorTicks, alternateBands], function (coll) { - var pos; - for (pos in coll) { - coll[pos].isActive = false; - } - }); - - // If the series has data draw the ticks. Else only the line and title - if (axis.hasData() || isLinked) { - - // minor ticks - if (axis.minorTickInterval && !axis.categories) { - each(axis.getMinorTickPositions(), function (pos) { - if (!minorTicks[pos]) { - minorTicks[pos] = new Tick(axis, pos, 'minor'); - } - - // render new ticks in old position - if (slideInTicks && minorTicks[pos].isNew) { - minorTicks[pos].render(null, true); - } - - minorTicks[pos].render(null, false, 1); - }); - } - - // Major ticks. Pull out the first item and render it last so that - // we can get the position of the neighbour label. #808. - if (tickPositions.length) { // #1300 - each(tickPositions, function (pos, i) { - - // linked axes need an extra check to find out if - if (!isLinked || (pos >= axis.min && pos <= axis.max)) { - - if (!ticks[pos]) { - ticks[pos] = new Tick(axis, pos); - } - - // render new ticks in old position - if (slideInTicks && ticks[pos].isNew) { - ticks[pos].render(i, true, 0.1); - } - - ticks[pos].render(i); - } - - }); - // In a categorized axis, the tick marks are displayed between labels. So - // we need to add a tick mark and grid line at the left edge of the X axis. - if (tickmarkOffset && (axis.min === 0 || axis.single)) { - if (!ticks[-1]) { - ticks[-1] = new Tick(axis, -1, null, true); - } - ticks[-1].render(-1); - } - - } - - // alternate grid color - if (alternateGridColor) { - each(tickPositions, function (pos, i) { - to = tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] + tickmarkOffset : axis.max - tickmarkOffset; - if (i % 2 === 0 && pos < axis.max && to <= axis.max + (chart.polar ? -tickmarkOffset : tickmarkOffset)) { // #2248, #4660 - if (!alternateBands[pos]) { - alternateBands[pos] = new Highcharts.PlotLineOrBand(axis); - } - from = pos + tickmarkOffset; // #949 - alternateBands[pos].options = { - from: isLog ? lin2log(from) : from, - to: isLog ? lin2log(to) : to, - color: alternateGridColor - }; - alternateBands[pos].render(); - alternateBands[pos].isActive = true; - } - }); - } - - // custom plot lines and bands - if (!axis._addedPlotLB) { // only first time - each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) { - axis.addPlotBandOrLine(plotLineOptions); - }); - axis._addedPlotLB = true; - } - - } // end if hasData - - // Remove inactive ticks - each([ticks, minorTicks, alternateBands], function (coll) { - var pos, - i, - forDestruction = [], - delay = animation.duration, - destroyInactiveItems = function () { - i = forDestruction.length; - while (i--) { - // When resizing rapidly, the same items may be destroyed in different timeouts, - // or the may be reactivated - if (coll[forDestruction[i]] && !coll[forDestruction[i]].isActive) { - coll[forDestruction[i]].destroy(); - delete coll[forDestruction[i]]; - } - } - - }; - - for (pos in coll) { - - if (!coll[pos].isActive) { - // Render to zero opacity - coll[pos].render(pos, false, 0); - coll[pos].isActive = false; - forDestruction.push(pos); - } - } - - // When the objects are finished fading out, destroy them - syncTimeout( - destroyInactiveItems, - coll === alternateBands || !chart.hasRendered || !delay ? 0 : delay - ); - }); - - // Static items. As the axis group is cleared on subsequent calls - // to render, these items are added outside the group. - // axis line - if (lineWidth) { - linePath = axis.getLinePath(lineWidth); - if (!axis.axisLine) { - axis.axisLine = renderer.path(linePath) - .attr({ - stroke: options.lineColor, - 'stroke-width': lineWidth, - zIndex: 7 - }) - .add(axis.axisGroup); - } else { - axis.axisLine.animate({ d: linePath }); - } - - // show or hide the line depending on options.showEmpty - axis.axisLine[showAxis ? 'show' : 'hide'](true); - } - - if (axisTitle && showAxis) { - - axisTitle[axisTitle.isNew ? 'attr' : 'animate']( - axis.getTitlePosition() - ); - axisTitle.isNew = false; - } - - // Stacked totals: - if (stackLabelOptions && stackLabelOptions.enabled) { - axis.renderStackTotals(); - } - // End stacked totals - - axis.isDirty = false; - }, - - /** - * Redraw the axis to reflect changes in the data or axis extremes - */ - redraw: function () { - - if (this.visible) { - // render the axis - this.render(); - - // move plot lines and bands - each(this.plotLinesAndBands, function (plotLine) { - plotLine.render(); - }); - } - - // mark associated series as dirty and ready for redraw - each(this.series, function (series) { - series.isDirty = true; - }); - - }, - - /** - * Destroys an Axis instance. - */ - destroy: function (keepEvents) { - var axis = this, - stacks = axis.stacks, - stackKey, - plotLinesAndBands = axis.plotLinesAndBands, - i; - - // Remove the events - if (!keepEvents) { - removeEvent(axis); - } - - // Destroy each stack total - for (stackKey in stacks) { - destroyObjectProperties(stacks[stackKey]); - - stacks[stackKey] = null; - } - - // Destroy collections - each([axis.ticks, axis.minorTicks, axis.alternateBands], function (coll) { - destroyObjectProperties(coll); - }); - i = plotLinesAndBands.length; - while (i--) { // #1975 - plotLinesAndBands[i].destroy(); - } - - // Destroy local variables - each(['stackTotalGroup', 'axisLine', 'axisTitle', 'axisGroup', 'cross', 'gridGroup', 'labelGroup'], function (prop) { - if (axis[prop]) { - axis[prop] = axis[prop].destroy(); - } - }); - - // Destroy crosshair - if (this.cross) { - this.cross.destroy(); - } - }, - - /** - * Draw the crosshair - * - * @param {Object} e The event arguments from the modified pointer event - * @param {Object} point The Point object - */ - drawCrosshair: function (e, point) { - - var path, - options = this.crosshair, - pos, - attribs, - categorized, - strokeWidth; - - if ( - // Disabled in options - !this.crosshair || - // Snap - ((defined(point) || !pick(options.snap, true)) === false) - ) { - this.hideCrosshair(); - - } else { - - // Get the path - if (!pick(options.snap, true)) { - pos = (this.horiz ? e.chartX - this.pos : this.len - e.chartY + this.pos); - } else if (defined(point)) { - pos = this.isXAxis ? point.plotX : this.len - point.plotY; // #3834 - } - - if (this.isRadial) { - path = this.getPlotLinePath(this.isXAxis ? point.x : pick(point.stackY, point.y)) || null; // #3189 - } else { - path = this.getPlotLinePath(null, null, null, null, pos) || null; // #3189 - } - - if (path === null) { - this.hideCrosshair(); - return; - } - - categorized = this.categories && !this.isRadial; - strokeWidth = pick(options.width, (categorized ? this.transA : 1)); - - // Draw the cross - if (this.cross) { - this.cross - .attr({ - d: path, - visibility: 'visible', - 'stroke-width': strokeWidth // #4737 - }); - } else { - attribs = { - 'pointer-events': 'none', // #5259 - 'stroke-width': strokeWidth, - stroke: options.color || (categorized ? 'rgba(155,200,255,0.2)' : '#C0C0C0'), - zIndex: pick(options.zIndex, 2) - }; - if (options.dashStyle) { - attribs.dashstyle = options.dashStyle; - } - this.cross = this.chart.renderer.path(path).attr(attribs).add(); - } - - } - - }, - - /** - * Hide the crosshair. - */ - hideCrosshair: function () { - if (this.cross) { - this.cross.hide(); - } - } - }; // end Axis - - extend(Axis.prototype, AxisPlotLineOrBandExtension); - - /** - * Set the tick positions to a time unit that makes sense, for example - * on the first of each month or on every Monday. Return an array - * with the time positions. Used in datetime axes as well as for grouping - * data on a datetime axis. - * - * @param {Object} normalizedInterval The interval in axis values (ms) and the count - * @param {Number} min The minimum in axis values - * @param {Number} max The maximum in axis values - * @param {Number} startOfWeek - */ - Axis.prototype.getTimeTicks = function (normalizedInterval, min, max, startOfWeek) { - var tickPositions = [], - i, - higherRanks = {}, - useUTC = defaultOptions.global.useUTC, - minYear, // used in months and years as a basis for Date.UTC() - minDate = new Date(min - getTZOffset(min)), - interval = normalizedInterval.unitRange, - count = normalizedInterval.count; - - if (defined(min)) { // #1300 - minDate[setMilliseconds](interval >= timeUnits.second ? 0 : // #3935 - count * mathFloor(minDate.getMilliseconds() / count)); // #3652, #3654 - - if (interval >= timeUnits.second) { // second - minDate[setSeconds](interval >= timeUnits.minute ? 0 : // #3935 - count * mathFloor(minDate.getSeconds() / count)); - } - - if (interval >= timeUnits.minute) { // minute - minDate[setMinutes](interval >= timeUnits.hour ? 0 : - count * mathFloor(minDate[getMinutes]() / count)); - } - - if (interval >= timeUnits.hour) { // hour - minDate[setHours](interval >= timeUnits.day ? 0 : - count * mathFloor(minDate[getHours]() / count)); - } - - if (interval >= timeUnits.day) { // day - minDate[setDate](interval >= timeUnits.month ? 1 : - count * mathFloor(minDate[getDate]() / count)); - } - - if (interval >= timeUnits.month) { // month - minDate[setMonth](interval >= timeUnits.year ? 0 : - count * mathFloor(minDate[getMonth]() / count)); - minYear = minDate[getFullYear](); - } - - if (interval >= timeUnits.year) { // year - minYear -= minYear % count; - minDate[setFullYear](minYear); - } - - // week is a special case that runs outside the hierarchy - if (interval === timeUnits.week) { - // get start of current week, independent of count - minDate[setDate](minDate[getDate]() - minDate[getDay]() + - pick(startOfWeek, 1)); - } - - - // get tick positions - i = 1; - if (timezoneOffset || getTimezoneOffset) { - minDate = minDate.getTime(); - minDate = new Date(minDate + getTZOffset(minDate)); - } - minYear = minDate[getFullYear](); - var time = minDate.getTime(), - minMonth = minDate[getMonth](), - minDateDate = minDate[getDate](), - variableDayLength = !useUTC || !!getTimezoneOffset, // #4951 - localTimezoneOffset = (timeUnits.day + - (useUTC ? getTZOffset(minDate) : minDate.getTimezoneOffset() * 60 * 1000) - ) % timeUnits.day; // #950, #3359 - - // iterate and add tick positions at appropriate values - while (time < max) { - tickPositions.push(time); - - // if the interval is years, use Date.UTC to increase years - if (interval === timeUnits.year) { - time = makeTime(minYear + i * count, 0); - - // if the interval is months, use Date.UTC to increase months - } else if (interval === timeUnits.month) { - time = makeTime(minYear, minMonth + i * count); - - // if we're using global time, the interval is not fixed as it jumps - // one hour at the DST crossover - } else if (variableDayLength && (interval === timeUnits.day || interval === timeUnits.week)) { - time = makeTime(minYear, minMonth, minDateDate + - i * count * (interval === timeUnits.day ? 1 : 7)); - - // else, the interval is fixed and we use simple addition - } else { - time += interval * count; - } - - i++; - } - - // push the last time - tickPositions.push(time); - - - // mark new days if the time is dividible by day (#1649, #1760) - each(grep(tickPositions, function (time) { - return interval <= timeUnits.hour && time % timeUnits.day === localTimezoneOffset; - }), function (time) { - higherRanks[time] = 'day'; - }); - } - - - // record information on the chosen unit - for dynamic label formatter - tickPositions.info = extend(normalizedInterval, { - higherRanks: higherRanks, - totalRange: interval * count - }); - - return tickPositions; - }; - - /** - * Get a normalized tick interval for dates. Returns a configuration object with - * unit range (interval), count and name. Used to prepare data for getTimeTicks. - * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs - * of segments in stock charts, the normalizing logic was extracted in order to - * prevent it for running over again for each segment having the same interval. - * #662, #697. - */ - Axis.prototype.normalizeTimeTickInterval = function (tickInterval, unitsOption) { - var units = unitsOption || [[ - 'millisecond', // unit name - [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples - ], [ - 'second', - [1, 2, 5, 10, 15, 30] - ], [ - 'minute', - [1, 2, 5, 10, 15, 30] - ], [ - 'hour', - [1, 2, 3, 4, 6, 8, 12] - ], [ - 'day', - [1, 2] - ], [ - 'week', - [1, 2] - ], [ - 'month', - [1, 2, 3, 4, 6] - ], [ - 'year', - null - ]], - unit = units[units.length - 1], // default unit is years - interval = timeUnits[unit[0]], - multiples = unit[1], - count, - i; - - // loop through the units to find the one that best fits the tickInterval - for (i = 0; i < units.length; i++) { - unit = units[i]; - interval = timeUnits[unit[0]]; - multiples = unit[1]; - - - if (units[i + 1]) { - // lessThan is in the middle between the highest multiple and the next unit. - var lessThan = (interval * multiples[multiples.length - 1] + - timeUnits[units[i + 1][0]]) / 2; - - // break and keep the current unit - if (tickInterval <= lessThan) { - break; - } - } - } - - // prevent 2.5 years intervals, though 25, 250 etc. are allowed - if (interval === timeUnits.year && tickInterval < 5 * interval) { - multiples = [1, 2, 5]; - } - - // get the count - count = normalizeTickInterval( - tickInterval / interval, - multiples, - unit[0] === 'year' ? mathMax(getMagnitude(tickInterval / interval), 1) : 1 // #1913, #2360 - ); - - return { - unitRange: interval, - count: count, - unitName: unit[0] - }; - }; - /** - * Methods defined on the Axis prototype - */ - - /** - * Set the tick positions of a logarithmic axis - */ - Axis.prototype.getLogTickPositions = function (interval, min, max, minor) { - var axis = this, - options = axis.options, - axisLength = axis.len, - lin2log = axis.lin2log, - log2lin = axis.log2lin, - // Since we use this method for both major and minor ticks, - // use a local variable and return the result - positions = []; - - // Reset - if (!minor) { - axis._minorAutoInterval = null; - } - - // First case: All ticks fall on whole logarithms: 1, 10, 100 etc. - if (interval >= 0.5) { - interval = mathRound(interval); - positions = axis.getLinearTickPositions(interval, min, max); - - // Second case: We need intermediary ticks. For example - // 1, 2, 4, 6, 8, 10, 20, 40 etc. - } else if (interval >= 0.08) { - var roundedMin = mathFloor(min), - intermediate, - i, - j, - len, - pos, - lastPos, - break2; - - if (interval > 0.3) { - intermediate = [1, 2, 4]; - } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc - intermediate = [1, 2, 4, 6, 8]; - } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc - intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - } - - for (i = roundedMin; i < max + 1 && !break2; i++) { - len = intermediate.length; - for (j = 0; j < len && !break2; j++) { - pos = log2lin(lin2log(i) * intermediate[j]); - if (pos > min && (!minor || lastPos <= max) && lastPos !== UNDEFINED) { // #1670, lastPos is #3113 - positions.push(lastPos); - } - - if (lastPos > max) { - break2 = true; - } - lastPos = pos; - } - } - - // Third case: We are so deep in between whole logarithmic values that - // we might as well handle the tick positions like a linear axis. For - // example 1.01, 1.02, 1.03, 1.04. - } else { - var realMin = lin2log(min), - realMax = lin2log(max), - tickIntervalOption = options[minor ? 'minorTickInterval' : 'tickInterval'], - filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption, - tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1), - totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength; - - interval = pick( - filteredTickIntervalOption, - axis._minorAutoInterval, - (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1) - ); - - interval = normalizeTickInterval( - interval, - null, - getMagnitude(interval) - ); - - positions = map(axis.getLinearTickPositions( - interval, - realMin, - realMax - ), log2lin); - - if (!minor) { - axis._minorAutoInterval = interval / 5; - } - } - - // Set the axis-level tickInterval variable - if (!minor) { - axis.tickInterval = interval; - } - return positions; - }; - - Axis.prototype.log2lin = function (num) { - return math.log(num) / math.LN10; - }; - - Axis.prototype.lin2log = function (num) { - return math.pow(10, num); - }; - /** - * The tooltip object - * @param {Object} chart The chart instance - * @param {Object} options Tooltip options - */ - var Tooltip = Highcharts.Tooltip = function () { - this.init.apply(this, arguments); - }; - - Tooltip.prototype = { - - init: function (chart, options) { - - var borderWidth = options.borderWidth, - style = options.style, - padding = pInt(style.padding); - - // Save the chart and options - this.chart = chart; - this.options = options; - - // Keep track of the current series - //this.currentSeries = UNDEFINED; - - // List of crosshairs - this.crosshairs = []; - - // Current values of x and y when animating - this.now = { x: 0, y: 0 }; - - // The tooltip is initially hidden - this.isHidden = true; - - - // create the label - this.label = chart.renderer.label('', 0, 0, options.shape || 'callout', null, null, options.useHTML, null, 'tooltip') - .attr({ - padding: padding, - fill: options.backgroundColor, - 'stroke-width': borderWidth, - r: options.borderRadius, - zIndex: 8 - }) - .css(style) - .css({ padding: 0 }) // Remove it from VML, the padding is applied as an attribute instead (#1117) - .add() - .attr({ y: -9999 }); // #2301, #2657 - - // When using canVG the shadow shows up as a gray circle - // even if the tooltip is hidden. - if (!useCanVG) { - this.label.shadow(options.shadow); - } - - // Public property for getting the shared state. - this.shared = options.shared; - }, - - /** - * Destroy the tooltip and its elements. - */ - destroy: function () { - // Destroy and clear local variables - if (this.label) { - this.label = this.label.destroy(); - } - clearTimeout(this.hideTimer); - clearTimeout(this.tooltipTimeout); - }, - - /** - * Provide a soft movement for the tooltip - * - * @param {Number} x - * @param {Number} y - * @private - */ - move: function (x, y, anchorX, anchorY) { - var tooltip = this, - now = tooltip.now, - animate = tooltip.options.animation !== false && !tooltip.isHidden && - // When we get close to the target position, abort animation and land on the right place (#3056) - (mathAbs(x - now.x) > 1 || mathAbs(y - now.y) > 1), - skipAnchor = tooltip.followPointer || tooltip.len > 1; - - // Get intermediate values for animation - extend(now, { - x: animate ? (2 * now.x + x) / 3 : x, - y: animate ? (now.y + y) / 2 : y, - anchorX: skipAnchor ? UNDEFINED : animate ? (2 * now.anchorX + anchorX) / 3 : anchorX, - anchorY: skipAnchor ? UNDEFINED : animate ? (now.anchorY + anchorY) / 2 : anchorY - }); - - // Move to the intermediate value - tooltip.label.attr(now); - - - // Run on next tick of the mouse tracker - if (animate) { - - // Never allow two timeouts - clearTimeout(this.tooltipTimeout); - - // Set the fixed interval ticking for the smooth tooltip - this.tooltipTimeout = setTimeout(function () { - // The interval function may still be running during destroy, so check that the chart is really there before calling. - if (tooltip) { - tooltip.move(x, y, anchorX, anchorY); - } - }, 32); - - } - }, - - /** - * Hide the tooltip - */ - hide: function (delay) { - var tooltip = this; - clearTimeout(this.hideTimer); // disallow duplicate timers (#1728, #1766) - delay = pick(delay, this.options.hideDelay, 500); - if (!this.isHidden) { - this.hideTimer = syncTimeout(function () { - tooltip.label[delay ? 'fadeOut' : 'hide'](); - tooltip.isHidden = true; - }, delay); - } - }, - - /** - * Extendable method to get the anchor position of the tooltip - * from a point or set of points - */ - getAnchor: function (points, mouseEvent) { - var ret, - chart = this.chart, - inverted = chart.inverted, - plotTop = chart.plotTop, - plotLeft = chart.plotLeft, - plotX = 0, - plotY = 0, - yAxis, - xAxis; - - points = splat(points); - - // Pie uses a special tooltipPos - ret = points[0].tooltipPos; - - // When tooltip follows mouse, relate the position to the mouse - if (this.followPointer && mouseEvent) { - if (mouseEvent.chartX === UNDEFINED) { - mouseEvent = chart.pointer.normalize(mouseEvent); - } - ret = [ - mouseEvent.chartX - chart.plotLeft, - mouseEvent.chartY - plotTop - ]; - } - // When shared, use the average position - if (!ret) { - each(points, function (point) { - yAxis = point.series.yAxis; - xAxis = point.series.xAxis; - plotX += point.plotX + (!inverted && xAxis ? xAxis.left - plotLeft : 0); - plotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) + - (!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151 - }); - - plotX /= points.length; - plotY /= points.length; - - ret = [ - inverted ? chart.plotWidth - plotY : plotX, - this.shared && !inverted && points.length > 1 && mouseEvent ? - mouseEvent.chartY - plotTop : // place shared tooltip next to the mouse (#424) - inverted ? chart.plotHeight - plotX : plotY - ]; - } - - return map(ret, mathRound); - }, - - /** - * Place the tooltip in a chart without spilling over - * and not covering the point it self. - */ - getPosition: function (boxWidth, boxHeight, point) { - - var chart = this.chart, - distance = this.distance, - ret = {}, - h = point.h || 0, // #4117 - swapped, - first = ['y', chart.chartHeight, boxHeight, point.plotY + chart.plotTop, chart.plotTop, chart.plotTop + chart.plotHeight], - second = ['x', chart.chartWidth, boxWidth, point.plotX + chart.plotLeft, chart.plotLeft, chart.plotLeft + chart.plotWidth], - // The far side is right or bottom - preferFarSide = !this.followPointer && pick(point.ttBelow, !chart.inverted === !!point.negative), // #4984 - /** - * Handle the preferred dimension. When the preferred dimension is tooltip - * on top or bottom of the point, it will look for space there. - */ - firstDimension = function (dim, outerSize, innerSize, point, min, max) { - var roomLeft = innerSize < point - distance, - roomRight = point + distance + innerSize < outerSize, - alignedLeft = point - distance - innerSize, - alignedRight = point + distance; - - if (preferFarSide && roomRight) { - ret[dim] = alignedRight; - } else if (!preferFarSide && roomLeft) { - ret[dim] = alignedLeft; - } else if (roomLeft) { - ret[dim] = mathMin(max - innerSize, alignedLeft - h < 0 ? alignedLeft : alignedLeft - h); - } else if (roomRight) { - ret[dim] = mathMax(min, alignedRight + h + innerSize > outerSize ? alignedRight : alignedRight + h); - } else { - return false; - } - }, - /** - * Handle the secondary dimension. If the preferred dimension is tooltip - * on top or bottom of the point, the second dimension is to align the tooltip - * above the point, trying to align center but allowing left or right - * align within the chart box. - */ - secondDimension = function (dim, outerSize, innerSize, point) { - var retVal; - - // Too close to the edge, return false and swap dimensions - if (point < distance || point > outerSize - distance) { - retVal = false; - // Align left/top - } else if (point < innerSize / 2) { - ret[dim] = 1; - // Align right/bottom - } else if (point > outerSize - innerSize / 2) { - ret[dim] = outerSize - innerSize - 2; - // Align center - } else { - ret[dim] = point - innerSize / 2; - } - return retVal; - }, - /** - * Swap the dimensions - */ - swap = function (count) { - var temp = first; - first = second; - second = temp; - swapped = count; - }, - run = function () { - if (firstDimension.apply(0, first) !== false) { - if (secondDimension.apply(0, second) === false && !swapped) { - swap(true); - run(); - } - } else if (!swapped) { - swap(true); - run(); - } else { - ret.x = ret.y = 0; - } - }; - - // Under these conditions, prefer the tooltip on the side of the point - if (chart.inverted || this.len > 1) { - swap(); - } - run(); - - return ret; - - }, - - /** - * In case no user defined formatter is given, this will be used. Note that the context - * here is an object holding point, series, x, y etc. - */ - defaultFormatter: function (tooltip) { - var items = this.points || splat(this), - s; - - // build the header - s = [tooltip.tooltipFooterHeaderFormatter(items[0])]; //#3397: abstraction to enable formatting of footer and header - - // build the values - s = s.concat(tooltip.bodyFormatter(items)); - - // footer - s.push(tooltip.tooltipFooterHeaderFormatter(items[0], true)); //#3397: abstraction to enable formatting of footer and header - - return s.join(''); - }, - - /** - * Refresh the tooltip's text and position. - * @param {Object} point - */ - refresh: function (point, mouseEvent) { - var tooltip = this, - chart = tooltip.chart, - label = tooltip.label, - options = tooltip.options, - x, - y, - anchor, - textConfig = {}, - text, - pointConfig = [], - formatter = options.formatter || tooltip.defaultFormatter, - hoverPoints = chart.hoverPoints, - borderColor, - shared = tooltip.shared, - currentSeries; - - clearTimeout(this.hideTimer); - - // get the reference point coordinates (pie charts use tooltipPos) - tooltip.followPointer = splat(point)[0].series.tooltipOptions.followPointer; - anchor = tooltip.getAnchor(point, mouseEvent); - x = anchor[0]; - y = anchor[1]; - - // shared tooltip, array is sent over - if (shared && !(point.series && point.series.noSharedTooltip)) { - - // hide previous hoverPoints and set new - - chart.hoverPoints = point; - if (hoverPoints) { - each(hoverPoints, function (point) { - point.setState(); - }); - } - - each(point, function (item) { - item.setState(HOVER_STATE); - - pointConfig.push(item.getLabelConfig()); - }); - - textConfig = { - x: point[0].category, - y: point[0].y - }; - textConfig.points = pointConfig; - this.len = pointConfig.length; - point = point[0]; - - // single point tooltip - } else { - textConfig = point.getLabelConfig(); - } - text = formatter.call(textConfig, tooltip); - - // register the current series - currentSeries = point.series; - this.distance = pick(currentSeries.tooltipOptions.distance, 16); - - // update the inner HTML - if (text === false) { - this.hide(); - } else { - - // show it - if (tooltip.isHidden) { - stop(label); - label.attr('opacity', 1).show(); - } - - // update text - label.attr({ - text: text - }); - - // set the stroke color of the box - borderColor = options.borderColor || point.color || currentSeries.color || '#606060'; - label.attr({ - stroke: borderColor - }); - tooltip.updatePosition({ - plotX: x, - plotY: y, - negative: point.negative, - ttBelow: point.ttBelow, - h: anchor[2] || 0 - }); - - this.isHidden = false; - } - fireEvent(chart, 'tooltipRefresh', { - text: text, - x: x + chart.plotLeft, - y: y + chart.plotTop, - borderColor: borderColor - }); - }, - - /** - * Find the new position and perform the move - */ - updatePosition: function (point) { - var chart = this.chart, - label = this.label, - pos = (this.options.positioner || this.getPosition).call( - this, - label.width, - label.height, - point - ); - - // do the move - this.move( - mathRound(pos.x), - mathRound(pos.y || 0), // can be undefined (#3977) - point.plotX + chart.plotLeft, - point.plotY + chart.plotTop - ); - }, - - /** - * Get the best X date format based on the closest point range on the axis. - */ - getXDateFormat: function (point, options, xAxis) { - var xDateFormat, - dateTimeLabelFormats = options.dateTimeLabelFormats, - closestPointRange = xAxis && xAxis.closestPointRange, - n, - blank = '01-01 00:00:00.000', - strpos = { - millisecond: 15, - second: 12, - minute: 9, - hour: 6, - day: 3 - }, - date, - lastN = 'millisecond'; // for sub-millisecond data, #4223 - - if (closestPointRange) { - date = dateFormat('%m-%d %H:%M:%S.%L', point.x); - for (n in timeUnits) { - - // If the range is exactly one week and we're looking at a Sunday/Monday, go for the week format - if (closestPointRange === timeUnits.week && +dateFormat('%w', point.x) === xAxis.options.startOfWeek && - date.substr(6) === blank.substr(6)) { - n = 'week'; - break; - } - - // The first format that is too great for the range - if (timeUnits[n] > closestPointRange) { - n = lastN; - break; - } - - // If the point is placed every day at 23:59, we need to show - // the minutes as well. #2637. - if (strpos[n] && date.substr(strpos[n]) !== blank.substr(strpos[n])) { - break; - } - - // Weeks are outside the hierarchy, only apply them on Mondays/Sundays like in the first condition - if (n !== 'week') { - lastN = n; - } - } - - if (n) { - xDateFormat = dateTimeLabelFormats[n]; - } - } else { - xDateFormat = dateTimeLabelFormats.day; - } - - return xDateFormat || dateTimeLabelFormats.year; // #2546, 2581 - }, - - /** - * Format the footer/header of the tooltip - * #3397: abstraction to enable formatting of footer and header - */ - tooltipFooterHeaderFormatter: function (point, isFooter) { - var footOrHead = isFooter ? 'footer' : 'header', - series = point.series, - tooltipOptions = series.tooltipOptions, - xDateFormat = tooltipOptions.xDateFormat, - xAxis = series.xAxis, - isDateTime = xAxis && xAxis.options.type === 'datetime' && isNumber(point.key), - formatString = tooltipOptions[footOrHead + 'Format']; - - // Guess the best date format based on the closest point distance (#568, #3418) - if (isDateTime && !xDateFormat) { - xDateFormat = this.getXDateFormat(point, tooltipOptions, xAxis); - } - - // Insert the footer date format if any - if (isDateTime && xDateFormat) { - formatString = formatString.replace('{point.key}', '{point.key:' + xDateFormat + '}'); - } - - return format(formatString, { - point: point, - series: series - }); - }, - - /** - * Build the body (lines) of the tooltip by iterating over the items and returning one entry for each item, - * abstracting this functionality allows to easily overwrite and extend it. - */ - bodyFormatter: function (items) { - return map(items, function (item) { - var tooltipOptions = item.series.tooltipOptions; - return (tooltipOptions.pointFormatter || item.point.tooltipFormatter).call(item.point, tooltipOptions.pointFormat); - }); - } - - }; - - var hoverChartIndex; - - // Global flag for touch support - hasTouch = doc && doc.documentElement.ontouchstart !== UNDEFINED; - - /** - * The mouse tracker object. All methods starting with "on" are primary DOM event handlers. - * Subsequent methods should be named differently from what they are doing. - * @param {Object} chart The Chart instance - * @param {Object} options The root options object - */ - var Pointer = Highcharts.Pointer = function (chart, options) { - this.init(chart, options); - }; - - Pointer.prototype = { - /** - * Initialize Pointer - */ - init: function (chart, options) { - - var chartOptions = options.chart, - chartEvents = chartOptions.events, - zoomType = useCanVG ? '' : chartOptions.zoomType, - inverted = chart.inverted, - zoomX, - zoomY; - - // Store references - this.options = options; - this.chart = chart; - - // Zoom status - this.zoomX = zoomX = /x/.test(zoomType); - this.zoomY = zoomY = /y/.test(zoomType); - this.zoomHor = (zoomX && !inverted) || (zoomY && inverted); - this.zoomVert = (zoomY && !inverted) || (zoomX && inverted); - this.hasZoom = zoomX || zoomY; - - // Do we need to handle click on a touch device? - this.runChartClick = chartEvents && !!chartEvents.click; - - this.pinchDown = []; - this.lastValidTouch = {}; - - if (Highcharts.Tooltip && options.tooltip.enabled) { - chart.tooltip = new Tooltip(chart, options.tooltip); - this.followTouchMove = pick(options.tooltip.followTouchMove, true); - } - - this.setDOMEvents(); - }, - - /** - * Add crossbrowser support for chartX and chartY - * @param {Object} e The event object in standard browsers - */ - normalize: function (e, chartPosition) { - var chartX, - chartY, - ePos; - - // IE normalizing - e = e || win.event; - if (!e.target) { - e.target = e.srcElement; - } - - // iOS (#2757) - ePos = e.touches ? (e.touches.length ? e.touches.item(0) : e.changedTouches[0]) : e; - - // Get mouse position - if (!chartPosition) { - this.chartPosition = chartPosition = offset(this.chart.container); - } - - // chartX and chartY - if (ePos.pageX === UNDEFINED) { // IE < 9. #886. - chartX = mathMax(e.x, e.clientX - chartPosition.left); // #2005, #2129: the second case is - // for IE10 quirks mode within framesets - chartY = e.y; - } else { - chartX = ePos.pageX - chartPosition.left; - chartY = ePos.pageY - chartPosition.top; - } - - return extend(e, { - chartX: mathRound(chartX), - chartY: mathRound(chartY) - }); - }, - - /** - * Get the click position in terms of axis values. - * - * @param {Object} e A pointer event - */ - getCoordinates: function (e) { - var coordinates = { - xAxis: [], - yAxis: [] - }; - - each(this.chart.axes, function (axis) { - coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({ - axis: axis, - value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY']) - }); - }); - return coordinates; - }, - - /** - * With line type charts with a single tracker, get the point closest to the mouse. - * Run Point.onMouseOver and display tooltip for the point or points. - */ - runPointActions: function (e) { - - var pointer = this, - chart = pointer.chart, - series = chart.series, - tooltip = chart.tooltip, - shared = tooltip ? tooltip.shared : false, - followPointer, - hoverPoint = chart.hoverPoint, - hoverSeries = chart.hoverSeries, - i, - distance = [Number.MAX_VALUE, Number.MAX_VALUE], // #4511 - anchor, - noSharedTooltip, - stickToHoverSeries, - directTouch, - kdpoints = [], - kdpoint = [], - kdpointT; - - // For hovering over the empty parts of the plot area (hoverSeries is undefined). - // If there is one series with point tracking (combo chart), don't go to nearest neighbour. - if (!shared && !hoverSeries) { - for (i = 0; i < series.length; i++) { - if (series[i].directTouch || !series[i].options.stickyTracking) { - series = []; - } - } - } - - // If it has a hoverPoint and that series requires direct touch (like columns, #3899), or we're on - // a noSharedTooltip series among shared tooltip series (#4546), use the hoverPoint . Otherwise, - // search the k-d tree. - stickToHoverSeries = hoverSeries && (shared ? hoverSeries.noSharedTooltip : hoverSeries.directTouch); - if (stickToHoverSeries && hoverPoint) { - kdpoint = [hoverPoint]; - - // Handle shared tooltip or cases where a series is not yet hovered - } else { - // Find nearest points on all series - each(series, function (s) { - // Skip hidden series - noSharedTooltip = s.noSharedTooltip && shared; - directTouch = !shared && s.directTouch; - if (s.visible && !noSharedTooltip && !directTouch && pick(s.options.enableMouseTracking, true)) { // #3821 - kdpointT = s.searchPoint(e, !noSharedTooltip && s.kdDimensions === 1); // #3828 - if (kdpointT) { - kdpoints.push(kdpointT); - } - } - }); - // Find absolute nearest point - each(kdpoints, function (p) { - if (p) { - // Store both closest points, using point.dist and point.distX comparisons (#4645): - each(['dist', 'distX'], function (dist, k) { - if (isNumber(p[dist])) { - var - // It is closer than the reference point - isCloser = p[dist] < distance[k], - // It is equally close, but above the reference point (#4679) - isAbove = p[dist] === distance[k] && p.series.group.zIndex >= kdpoint[k].series.group.zIndex; - - if (isCloser || isAbove) { - distance[k] = p[dist]; - kdpoint[k] = p; - } - } - }); - } - }); - } - - // Remove points with different x-positions, required for shared tooltip and crosshairs (#4645): - if (shared) { - i = kdpoints.length; - while (i--) { - if (kdpoints[i].clientX !== kdpoint[1].clientX || kdpoints[i].series.noSharedTooltip) { - kdpoints.splice(i, 1); - } - } - } - - // Refresh tooltip for kdpoint if new hover point or tooltip was hidden // #3926, #4200 - if (kdpoint[0] && (kdpoint[0] !== this.prevKDPoint || (tooltip && tooltip.isHidden))) { - // Draw tooltip if necessary - if (shared && !kdpoint[0].series.noSharedTooltip) { - if (kdpoints.length && tooltip) { - tooltip.refresh(kdpoints, e); - } - - // Do mouseover on all points (#3919, #3985, #4410) - each(kdpoints, function (point) { - point.onMouseOver(e, point !== ((hoverSeries && hoverSeries.directTouch && hoverPoint) || kdpoint[0])); - }); - this.prevKDPoint = kdpoint[1]; - } else { - if (tooltip) { - tooltip.refresh(kdpoint[0], e); - } - if (!hoverSeries || !hoverSeries.directTouch) { // #4448 - kdpoint[0].onMouseOver(e); - } - this.prevKDPoint = kdpoint[0]; - } - - // Update positions (regardless of kdpoint or hoverPoint) - } else { - followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer; - if (tooltip && followPointer && !tooltip.isHidden) { - anchor = tooltip.getAnchor([{}], e); - tooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] }); - } - } - - // Start the event listener to pick up the tooltip and crosshairs - if (!pointer._onDocumentMouseMove) { - pointer._onDocumentMouseMove = function (e) { - if (charts[hoverChartIndex]) { - charts[hoverChartIndex].pointer.onDocumentMouseMove(e); - } - }; - addEvent(doc, 'mousemove', pointer._onDocumentMouseMove); - } - - // Crosshair. For each hover point, loop over axes and draw cross if that point - // belongs to the axis (#4927). - each(shared ? kdpoints : [pick(hoverPoint, kdpoint[1])], function (point) { // #5269 - each(chart.axes, function (axis) { - // In case of snap = false, point is undefined, and we draw the crosshair anyway (#5066) - if (!point || point.series[axis.coll] === axis) { - axis.drawCrosshair(e, point); - } - }); - }); - }, - - /** - * Reset the tracking by hiding the tooltip, the hover series state and the hover point - * - * @param allowMove {Boolean} Instead of destroying the tooltip altogether, allow moving it if possible - */ - reset: function (allowMove, delay) { - var pointer = this, - chart = pointer.chart, - hoverSeries = chart.hoverSeries, - hoverPoint = chart.hoverPoint, - hoverPoints = chart.hoverPoints, - tooltip = chart.tooltip, - tooltipPoints = tooltip && tooltip.shared ? hoverPoints : hoverPoint; - - // Check if the points have moved outside the plot area (#1003, #4736, #5101) - if (allowMove && tooltipPoints) { - each(splat(tooltipPoints), function (point) { - if (point.series.isCartesian && point.plotX === undefined) { - allowMove = false; - } - }); - } - - // Just move the tooltip, #349 - if (allowMove) { - if (tooltip && tooltipPoints) { - tooltip.refresh(tooltipPoints); - if (hoverPoint) { // #2500 - hoverPoint.setState(hoverPoint.state, true); - each(chart.axes, function (axis) { - if (pick(axis.crosshair && axis.crosshair.snap, true)) { - axis.drawCrosshair(null, hoverPoint); - } else { - axis.hideCrosshair(); - } - }); - - } - } - - // Full reset - } else { - - if (hoverPoint) { - hoverPoint.onMouseOut(); - } - - if (hoverPoints) { - each(hoverPoints, function (point) { - point.setState(); - }); - } - - if (hoverSeries) { - hoverSeries.onMouseOut(); - } - - if (tooltip) { - tooltip.hide(delay); - } - - if (pointer._onDocumentMouseMove) { - removeEvent(doc, 'mousemove', pointer._onDocumentMouseMove); - pointer._onDocumentMouseMove = null; - } - - // Remove crosshairs - each(chart.axes, function (axis) { - axis.hideCrosshair(); - }); - - pointer.hoverX = chart.hoverPoints = chart.hoverPoint = null; - - } - }, - - /** - * Scale series groups to a certain scale and translation - */ - scaleGroups: function (attribs, clip) { - - var chart = this.chart, - seriesAttribs; - - // Scale each series - each(chart.series, function (series) { - seriesAttribs = attribs || series.getPlotBox(); // #1701 - if (series.xAxis && series.xAxis.zoomEnabled) { - series.group.attr(seriesAttribs); - if (series.markerGroup) { - series.markerGroup.attr(seriesAttribs); - series.markerGroup.clip(clip ? chart.clipRect : null); - } - if (series.dataLabelsGroup) { - series.dataLabelsGroup.attr(seriesAttribs); - } - } - }); - - // Clip - chart.clipRect.attr(clip || chart.clipBox); - }, - - /** - * Start a drag operation - */ - dragStart: function (e) { - var chart = this.chart; - - // Record the start position - chart.mouseIsDown = e.type; - chart.cancelClick = false; - chart.mouseDownX = this.mouseDownX = e.chartX; - chart.mouseDownY = this.mouseDownY = e.chartY; - }, - - /** - * Perform a drag operation in response to a mousemove event while the mouse is down - */ - drag: function (e) { - - var chart = this.chart, - chartOptions = chart.options.chart, - chartX = e.chartX, - chartY = e.chartY, - zoomHor = this.zoomHor, - zoomVert = this.zoomVert, - plotLeft = chart.plotLeft, - plotTop = chart.plotTop, - plotWidth = chart.plotWidth, - plotHeight = chart.plotHeight, - clickedInside, - size, - selectionMarker = this.selectionMarker, - mouseDownX = this.mouseDownX, - mouseDownY = this.mouseDownY, - panKey = chartOptions.panKey && e[chartOptions.panKey + 'Key']; - - // If the device supports both touch and mouse (like IE11), and we are touch-dragging - // inside the plot area, don't handle the mouse event. #4339. - if (selectionMarker && selectionMarker.touch) { - return; - } - - // If the mouse is outside the plot area, adjust to cooordinates - // inside to prevent the selection marker from going outside - if (chartX < plotLeft) { - chartX = plotLeft; - } else if (chartX > plotLeft + plotWidth) { - chartX = plotLeft + plotWidth; - } - - if (chartY < plotTop) { - chartY = plotTop; - } else if (chartY > plotTop + plotHeight) { - chartY = plotTop + plotHeight; - } - - // determine if the mouse has moved more than 10px - this.hasDragged = Math.sqrt( - Math.pow(mouseDownX - chartX, 2) + - Math.pow(mouseDownY - chartY, 2) - ); - - if (this.hasDragged > 10) { - clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop); - - // make a selection - if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside && !panKey) { - if (!selectionMarker) { - this.selectionMarker = selectionMarker = chart.renderer.rect( - plotLeft, - plotTop, - zoomHor ? 1 : plotWidth, - zoomVert ? 1 : plotHeight, - 0 - ) - .attr({ - fill: chartOptions.selectionMarkerFill || 'rgba(69,114,167,0.25)', - zIndex: 7 - }) - .add(); - } - } - - // adjust the width of the selection marker - if (selectionMarker && zoomHor) { - size = chartX - mouseDownX; - selectionMarker.attr({ - width: mathAbs(size), - x: (size > 0 ? 0 : size) + mouseDownX - }); - } - // adjust the height of the selection marker - if (selectionMarker && zoomVert) { - size = chartY - mouseDownY; - selectionMarker.attr({ - height: mathAbs(size), - y: (size > 0 ? 0 : size) + mouseDownY - }); - } - - // panning - if (clickedInside && !selectionMarker && chartOptions.panning) { - chart.pan(e, chartOptions.panning); - } - } - }, - - /** - * On mouse up or touch end across the entire document, drop the selection. - */ - drop: function (e) { - var pointer = this, - chart = this.chart, - hasPinched = this.hasPinched; - - if (this.selectionMarker) { - var selectionData = { - originalEvent: e, // #4890 - xAxis: [], - yAxis: [] - }, - selectionBox = this.selectionMarker, - selectionLeft = selectionBox.attr ? selectionBox.attr('x') : selectionBox.x, - selectionTop = selectionBox.attr ? selectionBox.attr('y') : selectionBox.y, - selectionWidth = selectionBox.attr ? selectionBox.attr('width') : selectionBox.width, - selectionHeight = selectionBox.attr ? selectionBox.attr('height') : selectionBox.height, - runZoom; - - // a selection has been made - if (this.hasDragged || hasPinched) { - - // record each axis' min and max - each(chart.axes, function (axis) { - if (axis.zoomEnabled && defined(axis.min) && (hasPinched || pointer[{ xAxis: 'zoomX', yAxis: 'zoomY' }[axis.coll]])) { // #859, #3569 - var horiz = axis.horiz, - minPixelPadding = e.type === 'touchend' ? axis.minPixelPadding : 0, // #1207, #3075 - selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop) + minPixelPadding), - selectionMax = axis.toValue((horiz ? selectionLeft + selectionWidth : selectionTop + selectionHeight) - minPixelPadding); - - selectionData[axis.coll].push({ - axis: axis, - min: mathMin(selectionMin, selectionMax), // for reversed axes - max: mathMax(selectionMin, selectionMax) - }); - runZoom = true; - } - }); - if (runZoom) { - fireEvent(chart, 'selection', selectionData, function (args) { - chart.zoom(extend(args, hasPinched ? { animation: false } : null)); - }); - } - - } - this.selectionMarker = this.selectionMarker.destroy(); - - // Reset scaling preview - if (hasPinched) { - this.scaleGroups(); - } - } - - // Reset all - if (chart) { // it may be destroyed on mouse up - #877 - css(chart.container, { cursor: chart._cursor }); - chart.cancelClick = this.hasDragged > 10; // #370 - chart.mouseIsDown = this.hasDragged = this.hasPinched = false; - this.pinchDown = []; - } - }, - - onContainerMouseDown: function (e) { - - e = this.normalize(e); - - // issue #295, dragging not always working in Firefox - if (e.preventDefault) { - e.preventDefault(); - } - - this.dragStart(e); - }, - - - - onDocumentMouseUp: function (e) { - if (charts[hoverChartIndex]) { - charts[hoverChartIndex].pointer.drop(e); - } - }, - - /** - * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea. - * Issue #149 workaround. The mouseleave event does not always fire. - */ - onDocumentMouseMove: function (e) { - var chart = this.chart, - chartPosition = this.chartPosition; - - e = this.normalize(e, chartPosition); - - // If we're outside, hide the tooltip - if (chartPosition && !this.inClass(e.target, 'highcharts-tracker') && - !chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) { - this.reset(); - } - }, - - /** - * When mouse leaves the container, hide the tooltip. - */ - onContainerMouseLeave: function (e) { - var chart = charts[hoverChartIndex]; - if (chart && (e.relatedTarget || e.toElement)) { // #4886, MS Touch end fires mouseleave but with no related target - chart.pointer.reset(); - chart.pointer.chartPosition = null; // also reset the chart position, used in #149 fix - } - }, - - // The mousemove, touchmove and touchstart event handler - onContainerMouseMove: function (e) { - - var chart = this.chart; - - if (!defined(hoverChartIndex) || !charts[hoverChartIndex] || !charts[hoverChartIndex].mouseIsDown) { - hoverChartIndex = chart.index; - } - - e = this.normalize(e); - e.returnValue = false; // #2251, #3224 - - if (chart.mouseIsDown === 'mousedown') { - this.drag(e); - } - - // Show the tooltip and run mouse over events (#977) - if ((this.inClass(e.target, 'highcharts-tracker') || - chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) && !chart.openMenu) { - this.runPointActions(e); - } - }, - - /** - * Utility to detect whether an element has, or has a parent with, a specific - * class name. Used on detection of tracker objects and on deciding whether - * hovering the tooltip should cause the active series to mouse out. - */ - inClass: function (element, className) { - var elemClassName; - while (element) { - elemClassName = attr(element, 'class'); - if (elemClassName) { - if (elemClassName.indexOf(className) !== -1) { - return true; - } - if (elemClassName.indexOf(PREFIX + 'container') !== -1) { - return false; - } - } - element = element.parentNode; - } - }, - - onTrackerMouseOut: function (e) { - var series = this.chart.hoverSeries, - relatedTarget = e.relatedTarget || e.toElement; - - if (series && relatedTarget && !series.options.stickyTracking && // #4886 - !this.inClass(relatedTarget, PREFIX + 'tooltip') && - !this.inClass(relatedTarget, PREFIX + 'series-' + series.index)) { // #2499, #4465 - series.onMouseOut(); - } - }, - - onContainerClick: function (e) { - var chart = this.chart, - hoverPoint = chart.hoverPoint, - plotLeft = chart.plotLeft, - plotTop = chart.plotTop; - - e = this.normalize(e); - - if (!chart.cancelClick) { - - // On tracker click, fire the series and point events. #783, #1583 - if (hoverPoint && this.inClass(e.target, PREFIX + 'tracker')) { - - // the series click event - fireEvent(hoverPoint.series, 'click', extend(e, { - point: hoverPoint - })); - - // the point click event - if (chart.hoverPoint) { // it may be destroyed (#1844) - hoverPoint.firePointEvent('click', e); - } - - // When clicking outside a tracker, fire a chart event - } else { - extend(e, this.getCoordinates(e)); - - // fire a click event in the chart - if (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) { - fireEvent(chart, 'click', e); - } - } - - - } - }, - - /** - * Set the JS DOM events on the container and document. This method should contain - * a one-to-one assignment between methods and their handlers. Any advanced logic should - * be moved to the handler reflecting the event's name. - */ - setDOMEvents: function () { - - var pointer = this, - container = pointer.chart.container; - - container.onmousedown = function (e) { - pointer.onContainerMouseDown(e); - }; - container.onmousemove = function (e) { - pointer.onContainerMouseMove(e); - }; - container.onclick = function (e) { - pointer.onContainerClick(e); - }; - addEvent(container, 'mouseleave', pointer.onContainerMouseLeave); - if (chartCount === 1) { - addEvent(doc, 'mouseup', pointer.onDocumentMouseUp); - } - if (hasTouch) { - container.ontouchstart = function (e) { - pointer.onContainerTouchStart(e); - }; - container.ontouchmove = function (e) { - pointer.onContainerTouchMove(e); - }; - if (chartCount === 1) { - addEvent(doc, 'touchend', pointer.onDocumentTouchEnd); - } - } - - }, - - /** - * Destroys the Pointer object and disconnects DOM events. - */ - destroy: function () { - var prop; - - removeEvent(this.chart.container, 'mouseleave', this.onContainerMouseLeave); - if (!chartCount) { - removeEvent(doc, 'mouseup', this.onDocumentMouseUp); - removeEvent(doc, 'touchend', this.onDocumentTouchEnd); - } - - // memory and CPU leak - clearInterval(this.tooltipTimeout); - - for (prop in this) { - this[prop] = null; - } - } - }; - - - /* Support for touch devices */ - extend(Highcharts.Pointer.prototype, { - - /** - * Run translation operations - */ - pinchTranslate: function (pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) { - if (this.zoomHor || this.pinchHor) { - this.pinchTranslateDirection(true, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); - } - if (this.zoomVert || this.pinchVert) { - this.pinchTranslateDirection(false, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); - } - }, - - /** - * Run translation operations for each direction (horizontal and vertical) independently - */ - pinchTranslateDirection: function (horiz, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch, forcedScale) { - var chart = this.chart, - xy = horiz ? 'x' : 'y', - XY = horiz ? 'X' : 'Y', - sChartXY = 'chart' + XY, - wh = horiz ? 'width' : 'height', - plotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')], - selectionWH, - selectionXY, - clipXY, - scale = forcedScale || 1, - inverted = chart.inverted, - bounds = chart.bounds[horiz ? 'h' : 'v'], - singleTouch = pinchDown.length === 1, - touch0Start = pinchDown[0][sChartXY], - touch0Now = touches[0][sChartXY], - touch1Start = !singleTouch && pinchDown[1][sChartXY], - touch1Now = !singleTouch && touches[1][sChartXY], - outOfBounds, - transformScale, - scaleKey, - setScale = function () { - if (!singleTouch && mathAbs(touch0Start - touch1Start) > 20) { // Don't zoom if fingers are too close on this axis - scale = forcedScale || mathAbs(touch0Now - touch1Now) / mathAbs(touch0Start - touch1Start); - } - - clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start; - selectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] / scale; - }; - - // Set the scale, first pass - setScale(); - - selectionXY = clipXY; // the clip position (x or y) is altered if out of bounds, the selection position is not - - // Out of bounds - if (selectionXY < bounds.min) { - selectionXY = bounds.min; - outOfBounds = true; - } else if (selectionXY + selectionWH > bounds.max) { - selectionXY = bounds.max - selectionWH; - outOfBounds = true; - } - - // Is the chart dragged off its bounds, determined by dataMin and dataMax? - if (outOfBounds) { - - // Modify the touchNow position in order to create an elastic drag movement. This indicates - // to the user that the chart is responsive but can't be dragged further. - touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]); - if (!singleTouch) { - touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]); - } - - // Set the scale, second pass to adapt to the modified touchNow positions - setScale(); - - } else { - lastValidTouch[xy] = [touch0Now, touch1Now]; - } - - // Set geometry for clipping, selection and transformation - if (!inverted) { - clip[xy] = clipXY - plotLeftTop; - clip[wh] = selectionWH; - } - scaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY; - transformScale = inverted ? 1 / scale : scale; - - selectionMarker[wh] = selectionWH; - selectionMarker[xy] = selectionXY; - transform[scaleKey] = scale; - transform['translate' + XY] = (transformScale * plotLeftTop) + (touch0Now - (transformScale * touch0Start)); - }, - - /** - * Handle touch events with two touches - */ - pinch: function (e) { - - var self = this, - chart = self.chart, - pinchDown = self.pinchDown, - touches = e.touches, - touchesLength = touches.length, - lastValidTouch = self.lastValidTouch, - hasZoom = self.hasZoom, - selectionMarker = self.selectionMarker, - transform = {}, - fireClickEvent = touchesLength === 1 && ((self.inClass(e.target, PREFIX + 'tracker') && - chart.runTrackerClick) || self.runChartClick), - clip = {}; - - // Don't initiate panning until the user has pinched. This prevents us from - // blocking page scrolling as users scroll down a long page (#4210). - if (touchesLength > 1) { - self.initiated = true; - } - - // On touch devices, only proceed to trigger click if a handler is defined - if (hasZoom && self.initiated && !fireClickEvent) { - e.preventDefault(); - } - - // Normalize each touch - map(touches, function (e) { - return self.normalize(e); - }); - - // Register the touch start position - if (e.type === 'touchstart') { - each(touches, function (e, i) { - pinchDown[i] = { chartX: e.chartX, chartY: e.chartY }; - }); - lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] && pinchDown[1].chartX]; - lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] && pinchDown[1].chartY]; - - // Identify the data bounds in pixels - each(chart.axes, function (axis) { - if (axis.zoomEnabled) { - var bounds = chart.bounds[axis.horiz ? 'h' : 'v'], - minPixelPadding = axis.minPixelPadding, - min = axis.toPixels(pick(axis.options.min, axis.dataMin)), - max = axis.toPixels(pick(axis.options.max, axis.dataMax)), - absMin = mathMin(min, max), - absMax = mathMax(min, max); - - // Store the bounds for use in the touchmove handler - bounds.min = mathMin(axis.pos, absMin - minPixelPadding); - bounds.max = mathMax(axis.pos + axis.len, absMax + minPixelPadding); - } - }); - self.res = true; // reset on next move - - // Event type is touchmove, handle panning and pinching - } else if (pinchDown.length) { // can be 0 when releasing, if touchend fires first - - - // Set the marker - if (!selectionMarker) { - self.selectionMarker = selectionMarker = extend({ - destroy: noop, - touch: true - }, chart.plotBox); - } - - self.pinchTranslate(pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); - - self.hasPinched = hasZoom; - - // Scale and translate the groups to provide visual feedback during pinching - self.scaleGroups(transform, clip); - - // Optionally move the tooltip on touchmove - if (!hasZoom && self.followTouchMove && touchesLength === 1) { - this.runPointActions(self.normalize(e)); - } else if (self.res) { - self.res = false; - this.reset(false, 0); - } - } - }, - - /** - * General touch handler shared by touchstart and touchmove. - */ - touch: function (e, start) { - var chart = this.chart, - hasMoved, - pinchDown; - - hoverChartIndex = chart.index; - - if (e.touches.length === 1) { - - e = this.normalize(e); - - if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop) && !chart.openMenu) { - - // Run mouse events and display tooltip etc - if (start) { - this.runPointActions(e); - } - - // Android fires touchmove events after the touchstart even if the - // finger hasn't moved, or moved only a pixel or two. In iOS however, - // the touchmove doesn't fire unless the finger moves more than ~4px. - // So we emulate this behaviour in Android by checking how much it - // moved, and cancelling on small distances. #3450. - if (e.type === 'touchmove') { - pinchDown = this.pinchDown; - hasMoved = pinchDown[0] ? Math.sqrt( // #5266 - Math.pow(pinchDown[0].chartX - e.chartX, 2) + - Math.pow(pinchDown[0].chartY - e.chartY, 2) - ) >= 4 : false; - } - - if (pick(hasMoved, true)) { - this.pinch(e); - } - - } else if (start) { - // Hide the tooltip on touching outside the plot area (#1203) - this.reset(); - } - - } else if (e.touches.length === 2) { - this.pinch(e); - } - }, - - onContainerTouchStart: function (e) { - this.touch(e, true); - }, - - onContainerTouchMove: function (e) { - this.touch(e); - }, - - onDocumentTouchEnd: function (e) { - if (charts[hoverChartIndex]) { - charts[hoverChartIndex].pointer.drop(e); - } - } - - }); - if (win.PointerEvent || win.MSPointerEvent) { - - // The touches object keeps track of the points being touched at all times - var touches = {}, - hasPointerEvent = !!win.PointerEvent, - getWebkitTouches = function () { - var key, - fake = []; - fake.item = function (i) { - return this[i]; - }; - for (key in touches) { - if (touches.hasOwnProperty(key)) { - fake.push({ - pageX: touches[key].pageX, - pageY: touches[key].pageY, - target: touches[key].target - }); - } - } - return fake; - }, - translateMSPointer = function (e, method, wktype, func) { - var p; - if ((e.pointerType === 'touch' || e.pointerType === e.MSPOINTER_TYPE_TOUCH) && charts[hoverChartIndex]) { - func(e); - p = charts[hoverChartIndex].pointer; - p[method]({ - type: wktype, - target: e.currentTarget, - preventDefault: noop, - touches: getWebkitTouches() - }); - } - }; - - /** - * Extend the Pointer prototype with methods for each event handler and more - */ - extend(Pointer.prototype, { - onContainerPointerDown: function (e) { - translateMSPointer(e, 'onContainerTouchStart', 'touchstart', function (e) { - touches[e.pointerId] = { pageX: e.pageX, pageY: e.pageY, target: e.currentTarget }; - }); - }, - onContainerPointerMove: function (e) { - translateMSPointer(e, 'onContainerTouchMove', 'touchmove', function (e) { - touches[e.pointerId] = { pageX: e.pageX, pageY: e.pageY }; - if (!touches[e.pointerId].target) { - touches[e.pointerId].target = e.currentTarget; - } - }); - }, - onDocumentPointerUp: function (e) { - translateMSPointer(e, 'onDocumentTouchEnd', 'touchend', function (e) { - delete touches[e.pointerId]; - }); - }, - - /** - * Add or remove the MS Pointer specific events - */ - batchMSEvents: function (fn) { - fn(this.chart.container, hasPointerEvent ? 'pointerdown' : 'MSPointerDown', this.onContainerPointerDown); - fn(this.chart.container, hasPointerEvent ? 'pointermove' : 'MSPointerMove', this.onContainerPointerMove); - fn(doc, hasPointerEvent ? 'pointerup' : 'MSPointerUp', this.onDocumentPointerUp); - } - }); - - // Disable default IE actions for pinch and such on chart element - wrap(Pointer.prototype, 'init', function (proceed, chart, options) { - proceed.call(this, chart, options); - if (this.hasZoom) { // #4014 - css(chart.container, { - '-ms-touch-action': NONE, - 'touch-action': NONE - }); - } - }); - - // Add IE specific touch events to chart - wrap(Pointer.prototype, 'setDOMEvents', function (proceed) { - proceed.apply(this); - if (this.hasZoom || this.followTouchMove) { - this.batchMSEvents(addEvent); - } - }); - // Destroy MS events also - wrap(Pointer.prototype, 'destroy', function (proceed) { - this.batchMSEvents(removeEvent); - proceed.call(this); - }); - } - /** - * The overview of the chart's series - */ - var Legend = Highcharts.Legend = function (chart, options) { - this.init(chart, options); - }; - - Legend.prototype = { - - /** - * Initialize the legend - */ - init: function (chart, options) { - - var legend = this, - itemStyle = options.itemStyle, - padding, - itemMarginTop = options.itemMarginTop || 0; - - this.options = options; - - if (!options.enabled) { - return; - } - - legend.itemStyle = itemStyle; - legend.itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle); - legend.itemMarginTop = itemMarginTop; - legend.padding = padding = pick(options.padding, 8); - legend.initialItemX = padding; - legend.initialItemY = padding - 5; // 5 is the number of pixels above the text - legend.maxItemWidth = 0; - legend.chart = chart; - legend.itemHeight = 0; - legend.symbolWidth = pick(options.symbolWidth, 16); - legend.pages = []; - - - // Render it - legend.render(); - - // move checkboxes - addEvent(legend.chart, 'endResize', function () { - legend.positionCheckboxes(); - }); - - }, - - /** - * Set the colors for the legend item - * @param {Object} item A Series or Point instance - * @param {Object} visible Dimmed or colored - */ - colorizeItem: function (item, visible) { - var legend = this, - options = legend.options, - legendItem = item.legendItem, - legendLine = item.legendLine, - legendSymbol = item.legendSymbol, - hiddenColor = legend.itemHiddenStyle.color, - textColor = visible ? options.itemStyle.color : hiddenColor, - symbolColor = visible ? (item.legendColor || item.color || '#CCC') : hiddenColor, - markerOptions = item.options && item.options.marker, - symbolAttr = { fill: symbolColor }, - key, - val; - - if (legendItem) { - legendItem.css({ fill: textColor, color: textColor }); // color for #1553, oldIE - } - if (legendLine) { - legendLine.attr({ stroke: symbolColor }); - } - - if (legendSymbol) { - - // Apply marker options - if (markerOptions && legendSymbol.isMarker) { // #585 - symbolAttr.stroke = symbolColor; - markerOptions = item.convertAttribs(markerOptions); - for (key in markerOptions) { - val = markerOptions[key]; - if (val !== UNDEFINED) { - symbolAttr[key] = val; - } - } - } - - legendSymbol.attr(symbolAttr); - } - }, - - /** - * Position the legend item - * @param {Object} item A Series or Point instance - */ - positionItem: function (item) { - var legend = this, - options = legend.options, - symbolPadding = options.symbolPadding, - ltr = !options.rtl, - legendItemPos = item._legendItemPos, - itemX = legendItemPos[0], - itemY = legendItemPos[1], - checkbox = item.checkbox, - legendGroup = item.legendGroup; - - if (legendGroup && legendGroup.element) { - legendGroup.translate( - ltr ? itemX : legend.legendWidth - itemX - 2 * symbolPadding - 4, - itemY - ); - } - - if (checkbox) { - checkbox.x = itemX; - checkbox.y = itemY; - } - }, - - /** - * Destroy a single legend item - * @param {Object} item The series or point - */ - destroyItem: function (item) { - var checkbox = item.checkbox; - - // destroy SVG elements - each(['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'], function (key) { - if (item[key]) { - item[key] = item[key].destroy(); - } - }); - - if (checkbox) { - discardElement(item.checkbox); - } - }, - - /** - * Destroys the legend. - */ - destroy: function () { - var legend = this, - legendGroup = legend.group, - box = legend.box; - - if (box) { - legend.box = box.destroy(); - } - - if (legendGroup) { - legend.group = legendGroup.destroy(); - } - }, - - /** - * Position the checkboxes after the width is determined - */ - positionCheckboxes: function (scrollOffset) { - var alignAttr = this.group.alignAttr, - translateY, - clipHeight = this.clipHeight || this.legendHeight, - titleHeight = this.titleHeight; - - if (alignAttr) { - translateY = alignAttr.translateY; - each(this.allItems, function (item) { - var checkbox = item.checkbox, - top; - - if (checkbox) { - top = translateY + titleHeight + checkbox.y + (scrollOffset || 0) + 3; - css(checkbox, { - left: (alignAttr.translateX + item.checkboxOffset + checkbox.x - 20) + PX, - top: top + PX, - display: top > translateY - 6 && top < translateY + clipHeight - 6 ? '' : NONE - }); - } - }); - } - }, - - /** - * Render the legend title on top of the legend - */ - renderTitle: function () { - var options = this.options, - padding = this.padding, - titleOptions = options.title, - titleHeight = 0, - bBox; - - if (titleOptions.text) { - if (!this.title) { - this.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, null, null, null, null, null, 'legend-title') - .attr({ zIndex: 1 }) - .css(titleOptions.style) - .add(this.group); - } - bBox = this.title.getBBox(); - titleHeight = bBox.height; - this.offsetWidth = bBox.width; // #1717 - this.contentGroup.attr({ translateY: titleHeight }); - } - this.titleHeight = titleHeight; - }, - - /** - * Set the legend item text - */ - setText: function (item) { - var options = this.options; - item.legendItem.attr({ - text: options.labelFormat ? format(options.labelFormat, item) : options.labelFormatter.call(item) - }); - }, - - /** - * Render a single specific legend item - * @param {Object} item A series or point - */ - renderItem: function (item) { - var legend = this, - chart = legend.chart, - renderer = chart.renderer, - options = legend.options, - horizontal = options.layout === 'horizontal', - symbolWidth = legend.symbolWidth, - symbolPadding = options.symbolPadding, - itemStyle = legend.itemStyle, - itemHiddenStyle = legend.itemHiddenStyle, - padding = legend.padding, - itemDistance = horizontal ? pick(options.itemDistance, 20) : 0, - ltr = !options.rtl, - itemHeight, - widthOption = options.width, - itemMarginBottom = options.itemMarginBottom || 0, - itemMarginTop = legend.itemMarginTop, - initialItemX = legend.initialItemX, - bBox, - itemWidth, - li = item.legendItem, - series = item.series && item.series.drawLegendSymbol ? item.series : item, - seriesOptions = series.options, - showCheckbox = legend.createCheckboxForItem && seriesOptions && seriesOptions.showCheckbox, - useHTML = options.useHTML; - - if (!li) { // generate it once, later move it - - // Generate the group box - // A group to hold the symbol and text. Text is to be appended in Legend class. - item.legendGroup = renderer.g('legend-item') - .attr({ zIndex: 1 }) - .add(legend.scrollGroup); - - // Generate the list item text and add it to the group - item.legendItem = li = renderer.text( - '', - ltr ? symbolWidth + symbolPadding : -symbolPadding, - legend.baseline || 0, - useHTML - ) - .css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021) - .attr({ - align: ltr ? 'left' : 'right', - zIndex: 2 - }) - .add(item.legendGroup); - - // Get the baseline for the first item - the font size is equal for all - if (!legend.baseline) { - legend.fontMetrics = renderer.fontMetrics(itemStyle.fontSize, li); - legend.baseline = legend.fontMetrics.f + 3 + itemMarginTop; - li.attr('y', legend.baseline); - } - - // Draw the legend symbol inside the group box - series.drawLegendSymbol(legend, item); - - if (legend.setItemEvents) { - legend.setItemEvents(item, li, useHTML, itemStyle, itemHiddenStyle); - } - - // add the HTML checkbox on top - if (showCheckbox) { - legend.createCheckboxForItem(item); - } - } - - // Colorize the items - legend.colorizeItem(item, item.visible); - - // Always update the text - legend.setText(item); - - // calculate the positions for the next line - bBox = li.getBBox(); - - itemWidth = item.checkboxOffset = - options.itemWidth || - item.legendItemWidth || - symbolWidth + symbolPadding + bBox.width + itemDistance + (showCheckbox ? 20 : 0); - legend.itemHeight = itemHeight = mathRound(item.legendItemHeight || bBox.height); - - // if the item exceeds the width, start a new line - if (horizontal && legend.itemX - initialItemX + itemWidth > - (widthOption || (chart.chartWidth - 2 * padding - initialItemX - options.x))) { - legend.itemX = initialItemX; - legend.itemY += itemMarginTop + legend.lastLineHeight + itemMarginBottom; - legend.lastLineHeight = 0; // reset for next line (#915, #3976) - } - - // If the item exceeds the height, start a new column - /*if (!horizontal && legend.itemY + options.y + itemHeight > chart.chartHeight - spacingTop - spacingBottom) { - legend.itemY = legend.initialItemY; - legend.itemX += legend.maxItemWidth; - legend.maxItemWidth = 0; - }*/ - - // Set the edge positions - legend.maxItemWidth = mathMax(legend.maxItemWidth, itemWidth); - legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom; - legend.lastLineHeight = mathMax(itemHeight, legend.lastLineHeight); // #915 - - // cache the position of the newly generated or reordered items - item._legendItemPos = [legend.itemX, legend.itemY]; - - // advance - if (horizontal) { - legend.itemX += itemWidth; - - } else { - legend.itemY += itemMarginTop + itemHeight + itemMarginBottom; - legend.lastLineHeight = itemHeight; - } - - // the width of the widest item - legend.offsetWidth = widthOption || mathMax( - (horizontal ? legend.itemX - initialItemX - itemDistance : itemWidth) + padding, - legend.offsetWidth - ); - }, - - /** - * Get all items, which is one item per series for normal series and one item per point - * for pie series. - */ - getAllItems: function () { - var allItems = []; - each(this.chart.series, function (series) { - var seriesOptions = series.options; - - // Handle showInLegend. If the series is linked to another series, defaults to false. - if (!pick(seriesOptions.showInLegend, !defined(seriesOptions.linkedTo) ? UNDEFINED : false, true)) { - return; - } - - // use points or series for the legend item depending on legendType - allItems = allItems.concat( - series.legendItems || - (seriesOptions.legendType === 'point' ? - series.data : - series) - ); - }); - return allItems; - }, - - /** - * Adjust the chart margins by reserving space for the legend on only one side - * of the chart. If the position is set to a corner, top or bottom is reserved - * for horizontal legends and left or right for vertical ones. - */ - adjustMargins: function (margin, spacing) { - var chart = this.chart, - options = this.options, - // Use the first letter of each alignment option in order to detect the side - alignment = options.align.charAt(0) + options.verticalAlign.charAt(0) + options.layout.charAt(0); // #4189 - use charAt(x) notation instead of [x] for IE7 - - if (this.display && !options.floating) { - - each([ - /(lth|ct|rth)/, - /(rtv|rm|rbv)/, - /(rbh|cb|lbh)/, - /(lbv|lm|ltv)/ - ], function (alignments, side) { - if (alignments.test(alignment) && !defined(margin[side])) { - // Now we have detected on which side of the chart we should reserve space for the legend - chart[marginNames[side]] = mathMax( - chart[marginNames[side]], - chart.legend[(side + 1) % 2 ? 'legendHeight' : 'legendWidth'] + - [1, -1, -1, 1][side] * options[(side % 2) ? 'x' : 'y'] + - pick(options.margin, 12) + - spacing[side] - ); - } - }); - } - }, - - /** - * Render the legend. This method can be called both before and after - * chart.render. If called after, it will only rearrange items instead - * of creating new ones. - */ - render: function () { - var legend = this, - chart = legend.chart, - renderer = chart.renderer, - legendGroup = legend.group, - allItems, - display, - legendWidth, - legendHeight, - box = legend.box, - options = legend.options, - padding = legend.padding, - legendBorderWidth = options.borderWidth, - legendBackgroundColor = options.backgroundColor; - - legend.itemX = legend.initialItemX; - legend.itemY = legend.initialItemY; - legend.offsetWidth = 0; - legend.lastItemY = 0; - - if (!legendGroup) { - legend.group = legendGroup = renderer.g('legend') - .attr({ zIndex: 7 }) - .add(); - legend.contentGroup = renderer.g() - .attr({ zIndex: 1 }) // above background - .add(legendGroup); - legend.scrollGroup = renderer.g() - .add(legend.contentGroup); - } - - legend.renderTitle(); - - // add each series or point - allItems = legend.getAllItems(); - - // sort by legendIndex - stableSort(allItems, function (a, b) { - return ((a.options && a.options.legendIndex) || 0) - ((b.options && b.options.legendIndex) || 0); - }); - - // reversed legend - if (options.reversed) { - allItems.reverse(); - } - - legend.allItems = allItems; - legend.display = display = !!allItems.length; - - // render the items - legend.lastLineHeight = 0; - each(allItems, function (item) { - legend.renderItem(item); - }); - - // Get the box - legendWidth = (options.width || legend.offsetWidth) + padding; - legendHeight = legend.lastItemY + legend.lastLineHeight + legend.titleHeight; - legendHeight = legend.handleOverflow(legendHeight); - legendHeight += padding; - - // Draw the border and/or background - if (legendBorderWidth || legendBackgroundColor) { - - if (!box) { - legend.box = box = renderer.rect( - 0, - 0, - legendWidth, - legendHeight, - options.borderRadius, - legendBorderWidth || 0 - ).attr({ - stroke: options.borderColor, - 'stroke-width': legendBorderWidth || 0, - fill: legendBackgroundColor || NONE - }) - .add(legendGroup) - .shadow(options.shadow); - box.isNew = true; - - } else if (legendWidth > 0 && legendHeight > 0) { - box[box.isNew ? 'attr' : 'animate']( - box.crisp({ width: legendWidth, height: legendHeight }) - ); - box.isNew = false; - } - - // hide the border if no items - box[display ? 'show' : 'hide'](); - } - - legend.legendWidth = legendWidth; - legend.legendHeight = legendHeight; - - // Now that the legend width and height are established, put the items in the - // final position - each(allItems, function (item) { - legend.positionItem(item); - }); - - // 1.x compatibility: positioning based on style - /*var props = ['left', 'right', 'top', 'bottom'], - prop, - i = 4; - while (i--) { - prop = props[i]; - if (options.style[prop] && options.style[prop] !== 'auto') { - options[i < 2 ? 'align' : 'verticalAlign'] = prop; - options[i < 2 ? 'x' : 'y'] = pInt(options.style[prop]) * (i % 2 ? -1 : 1); - } - }*/ - - if (display) { - legendGroup.align(extend({ - width: legendWidth, - height: legendHeight - }, options), true, 'spacingBox'); - } - - if (!chart.isResizing) { - this.positionCheckboxes(); - } - }, - - /** - * Set up the overflow handling by adding navigation with up and down arrows below the - * legend. - */ - handleOverflow: function (legendHeight) { - var legend = this, - chart = this.chart, - renderer = chart.renderer, - options = this.options, - optionsY = options.y, - alignTop = options.verticalAlign === 'top', - spaceHeight = chart.spacingBox.height + (alignTop ? -optionsY : optionsY) - this.padding, - maxHeight = options.maxHeight, - clipHeight, - clipRect = this.clipRect, - navOptions = options.navigation, - animation = pick(navOptions.animation, true), - arrowSize = navOptions.arrowSize || 12, - nav = this.nav, - pages = this.pages, - padding = this.padding, - lastY, - allItems = this.allItems, - clipToHeight = function (height) { - clipRect.attr({ - height: height - }); - - // useHTML - if (legend.contentGroup.div) { - legend.contentGroup.div.style.clip = 'rect(' + padding + 'px,9999px,' + (padding + height) + 'px,0)'; - } - }; - - - // Adjust the height - if (options.layout === 'horizontal') { - spaceHeight /= 2; - } - if (maxHeight) { - spaceHeight = mathMin(spaceHeight, maxHeight); - } - - // Reset the legend height and adjust the clipping rectangle - pages.length = 0; - if (legendHeight > spaceHeight && navOptions.enabled !== false) { - - this.clipHeight = clipHeight = mathMax(spaceHeight - 20 - this.titleHeight - padding, 0); - this.currentPage = pick(this.currentPage, 1); - this.fullHeight = legendHeight; - - // Fill pages with Y positions so that the top of each a legend item defines - // the scroll top for each page (#2098) - each(allItems, function (item, i) { - var y = item._legendItemPos[1], - h = mathRound(item.legendItem.getBBox().height), - len = pages.length; - - if (!len || (y - pages[len - 1] > clipHeight && (lastY || y) !== pages[len - 1])) { - pages.push(lastY || y); - len++; - } - - if (i === allItems.length - 1 && y + h - pages[len - 1] > clipHeight) { - pages.push(y); - } - if (y !== lastY) { - lastY = y; - } - }); - - // Only apply clipping if needed. Clipping causes blurred legend in PDF export (#1787) - if (!clipRect) { - clipRect = legend.clipRect = renderer.clipRect(0, padding, 9999, 0); - legend.contentGroup.clip(clipRect); - } - - clipToHeight(clipHeight); - - // Add navigation elements - if (!nav) { - this.nav = nav = renderer.g().attr({ zIndex: 1 }).add(this.group); - this.up = renderer.symbol('triangle', 0, 0, arrowSize, arrowSize) - .on('click', function () { - legend.scroll(-1, animation); - }) - .add(nav); - this.pager = renderer.text('', 15, 10) - .css(navOptions.style) - .add(nav); - this.down = renderer.symbol('triangle-down', 0, 0, arrowSize, arrowSize) - .on('click', function () { - legend.scroll(1, animation); - }) - .add(nav); - } - - // Set initial position - legend.scroll(0); - - legendHeight = spaceHeight; - - } else if (nav) { - clipToHeight(chart.chartHeight); - nav.hide(); - this.scrollGroup.attr({ - translateY: 1 - }); - this.clipHeight = 0; // #1379 - } - - return legendHeight; - }, - - /** - * Scroll the legend by a number of pages - * @param {Object} scrollBy - * @param {Object} animation - */ - scroll: function (scrollBy, animation) { - var pages = this.pages, - pageCount = pages.length, - currentPage = this.currentPage + scrollBy, - clipHeight = this.clipHeight, - navOptions = this.options.navigation, - activeColor = navOptions.activeColor, - inactiveColor = navOptions.inactiveColor, - pager = this.pager, - padding = this.padding, - scrollOffset; - - // When resizing while looking at the last page - if (currentPage > pageCount) { - currentPage = pageCount; - } - - if (currentPage > 0) { - - if (animation !== UNDEFINED) { - setAnimation(animation, this.chart); - } - - this.nav.attr({ - translateX: padding, - translateY: clipHeight + this.padding + 7 + this.titleHeight, - visibility: VISIBLE - }); - this.up.attr({ - fill: currentPage === 1 ? inactiveColor : activeColor - }) - .css({ - cursor: currentPage === 1 ? 'default' : 'pointer' - }); - pager.attr({ - text: currentPage + '/' + pageCount - }); - this.down.attr({ - x: 18 + this.pager.getBBox().width, // adjust to text width - fill: currentPage === pageCount ? inactiveColor : activeColor - }) - .css({ - cursor: currentPage === pageCount ? 'default' : 'pointer' - }); - - scrollOffset = -pages[currentPage - 1] + this.initialItemY; - - this.scrollGroup.animate({ - translateY: scrollOffset - }); - - this.currentPage = currentPage; - this.positionCheckboxes(scrollOffset); - } - - } - - }; - - /* - * LegendSymbolMixin - */ - - var LegendSymbolMixin = Highcharts.LegendSymbolMixin = { - - /** - * Get the series' symbol in the legend - * - * @param {Object} legend The legend object - * @param {Object} item The series (this) or point - */ - drawRectangle: function (legend, item) { - var symbolHeight = legend.options.symbolHeight || legend.fontMetrics.f; - - item.legendSymbol = this.chart.renderer.rect( - 0, - legend.baseline - symbolHeight + 1, // #3988 - legend.symbolWidth, - symbolHeight, - legend.options.symbolRadius || 0 - ).attr({ - zIndex: 3 - }).add(item.legendGroup); - - }, - - /** - * Get the series' symbol in the legend. This method should be overridable to create custom - * symbols through Highcharts.seriesTypes[type].prototype.drawLegendSymbols. - * - * @param {Object} legend The legend object - */ - drawLineMarker: function (legend) { - - var options = this.options, - markerOptions = options.marker, - radius, - legendSymbol, - symbolWidth = legend.symbolWidth, - renderer = this.chart.renderer, - legendItemGroup = this.legendGroup, - verticalCenter = legend.baseline - mathRound(legend.fontMetrics.b * 0.3), - attr; - - // Draw the line - if (options.lineWidth) { - attr = { - 'stroke-width': options.lineWidth - }; - if (options.dashStyle) { - attr.dashstyle = options.dashStyle; - } - this.legendLine = renderer.path([ - M, - 0, - verticalCenter, - L, - symbolWidth, - verticalCenter - ]) - .attr(attr) - .add(legendItemGroup); - } - - // Draw the marker - if (markerOptions && markerOptions.enabled !== false) { - radius = markerOptions.radius; - this.legendSymbol = legendSymbol = renderer.symbol( - this.symbol, - (symbolWidth / 2) - radius, - verticalCenter - radius, - 2 * radius, - 2 * radius, - markerOptions - ) - .add(legendItemGroup); - legendSymbol.isMarker = true; - } - } - }; - - // Workaround for #2030, horizontal legend items not displaying in IE11 Preview, - // and for #2580, a similar drawing flaw in Firefox 26. - // Explore if there's a general cause for this. The problem may be related - // to nested group elements, as the legend item texts are within 4 group elements. - if (/Trident\/7\.0/.test(userAgent) || isFirefox) { - wrap(Legend.prototype, 'positionItem', function (proceed, item) { - var legend = this, - runPositionItem = function () { // If chart destroyed in sync, this is undefined (#2030) - if (item._legendItemPos) { - proceed.call(legend, item); - } - }; - - // Do it now, for export and to get checkbox placement - runPositionItem(); - - // Do it after to work around the core issue - setTimeout(runPositionItem); - }); - } - /** - * The Chart class - * @param {String|Object} renderTo The DOM element to render to, or its id - * @param {Object} options - * @param {Function} callback Function to run when the chart has loaded - */ - var Chart = Highcharts.Chart = function () { - this.getArgs.apply(this, arguments); - }; - - Highcharts.chart = function (a, b, c) { - return new Chart(a, b, c); - }; - - Chart.prototype = { - - /** - * Hook for modules - */ - callbacks: [], - - /** - * Handle the arguments passed to the constructor - * @returns {Array} Arguments without renderTo - */ - getArgs: function () { - var args = [].slice.call(arguments); - - // Remove the optional first argument, renderTo, and - // set it on this. - if (isString(args[0]) || args[0].nodeName) { - this.renderTo = args.shift(); - } - this.init(args[0], args[1]); - }, - - /** - * Initialize the chart - */ - init: function (userOptions, callback) { - - // Handle regular options - var options, - seriesOptions = userOptions.series; // skip merging data points to increase performance - - userOptions.series = null; - options = merge(defaultOptions, userOptions); // do the merge - options.series = userOptions.series = seriesOptions; // set back the series data - this.userOptions = userOptions; - - var optionsChart = options.chart; - - // Create margin & spacing array - this.margin = this.splashArray('margin', optionsChart); - this.spacing = this.splashArray('spacing', optionsChart); - - var chartEvents = optionsChart.events; - - //this.runChartClick = chartEvents && !!chartEvents.click; - this.bounds = { h: {}, v: {} }; // Pixel data bounds for touch zoom - - this.callback = callback; - this.isResizing = 0; - this.options = options; - //chartTitleOptions = UNDEFINED; - //chartSubtitleOptions = UNDEFINED; - - this.axes = []; - this.series = []; - this.hasCartesianSeries = optionsChart.showAxes; - //this.axisOffset = UNDEFINED; - //this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes - //this.inverted = UNDEFINED; - //this.loadingShown = UNDEFINED; - //this.container = UNDEFINED; - //this.chartWidth = UNDEFINED; - //this.chartHeight = UNDEFINED; - //this.marginRight = UNDEFINED; - //this.marginBottom = UNDEFINED; - //this.containerWidth = UNDEFINED; - //this.containerHeight = UNDEFINED; - //this.oldChartWidth = UNDEFINED; - //this.oldChartHeight = UNDEFINED; - - //this.renderTo = UNDEFINED; - //this.renderToClone = UNDEFINED; - - //this.spacingBox = UNDEFINED - - //this.legend = UNDEFINED; - - // Elements - //this.chartBackground = UNDEFINED; - //this.plotBackground = UNDEFINED; - //this.plotBGImage = UNDEFINED; - //this.plotBorder = UNDEFINED; - //this.loadingDiv = UNDEFINED; - //this.loadingSpan = UNDEFINED; - - var chart = this, - eventType; - - // Add the chart to the global lookup - chart.index = charts.length; - charts.push(chart); - chartCount++; - - // Set up auto resize - if (optionsChart.reflow !== false) { - addEvent(chart, 'load', function () { - chart.initReflow(); - }); - } - - // Chart event handlers - if (chartEvents) { - for (eventType in chartEvents) { - addEvent(chart, eventType, chartEvents[eventType]); - } - } - - chart.xAxis = []; - chart.yAxis = []; - - // Expose methods and variables - chart.animation = useCanVG ? false : pick(optionsChart.animation, true); - chart.pointCount = chart.colorCounter = chart.symbolCounter = 0; - - chart.firstRender(); - }, - - /** - * Initialize an individual series, called internally before render time - */ - initSeries: function (options) { - var chart = this, - optionsChart = chart.options.chart, - type = options.type || optionsChart.type || optionsChart.defaultSeriesType, - series, - constr = seriesTypes[type]; - - // No such series type - if (!constr) { - error(17, true); - } - - series = new constr(); - series.init(this, options); - return series; - }, - - /** - * Check whether a given point is within the plot area - * - * @param {Number} plotX Pixel x relative to the plot area - * @param {Number} plotY Pixel y relative to the plot area - * @param {Boolean} inverted Whether the chart is inverted - */ - isInsidePlot: function (plotX, plotY, inverted) { - var x = inverted ? plotY : plotX, - y = inverted ? plotX : plotY; - - return x >= 0 && - x <= this.plotWidth && - y >= 0 && - y <= this.plotHeight; - }, - - /** - * Redraw legend, axes or series based on updated data - * - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - */ - redraw: function (animation) { - var chart = this, - axes = chart.axes, - series = chart.series, - pointer = chart.pointer, - legend = chart.legend, - redrawLegend = chart.isDirtyLegend, - hasStackedSeries, - hasDirtyStacks, - hasCartesianSeries = chart.hasCartesianSeries, - isDirtyBox = chart.isDirtyBox, - seriesLength = series.length, - i = seriesLength, - serie, - renderer = chart.renderer, - isHiddenChart = renderer.isHidden(), - afterRedraw = []; - - setAnimation(animation, chart); - - if (isHiddenChart) { - chart.cloneRenderTo(); - } - - // Adjust title layout (reflow multiline text) - chart.layOutTitles(); - - // link stacked series - while (i--) { - serie = series[i]; - - if (serie.options.stacking) { - hasStackedSeries = true; - - if (serie.isDirty) { - hasDirtyStacks = true; - break; - } - } - } - if (hasDirtyStacks) { // mark others as dirty - i = seriesLength; - while (i--) { - serie = series[i]; - if (serie.options.stacking) { - serie.isDirty = true; - } - } - } - - // Handle updated data in the series - each(series, function (serie) { - if (serie.isDirty) { - if (serie.options.legendType === 'point') { - if (serie.updateTotals) { - serie.updateTotals(); - } - redrawLegend = true; - } - } - if (serie.isDirtyData) { - fireEvent(serie, 'updatedData'); - } - }); - - // handle added or removed series - if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed - // draw legend graphics - legend.render(); - - chart.isDirtyLegend = false; - } - - // reset stacks - if (hasStackedSeries) { - chart.getStacks(); - } - - - if (hasCartesianSeries) { - if (!chart.isResizing) { - - // reset maxTicks - chart.maxTicks = null; - - // set axes scales - each(axes, function (axis) { - axis.setScale(); - }); - } - } - - chart.getMargins(); // #3098 - - if (hasCartesianSeries) { - // If one axis is dirty, all axes must be redrawn (#792, #2169) - each(axes, function (axis) { - if (axis.isDirty) { - isDirtyBox = true; - } - }); - - // redraw axes - each(axes, function (axis) { - - // Fire 'afterSetExtremes' only if extremes are set - var key = axis.min + ',' + axis.max; - if (axis.extKey !== key) { // #821, #4452 - axis.extKey = key; - afterRedraw.push(function () { // prevent a recursive call to chart.redraw() (#1119) - fireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751 - delete axis.eventArgs; - }); - } - if (isDirtyBox || hasStackedSeries) { - axis.redraw(); - } - }); - } - - // the plot areas size has changed - if (isDirtyBox) { - chart.drawChartBox(); - } - - - // redraw affected series - each(series, function (serie) { - if (serie.isDirty && serie.visible && - (!serie.isCartesian || serie.xAxis)) { // issue #153 - serie.redraw(); - } - }); - - // move tooltip or reset - if (pointer) { - pointer.reset(true); - } - - // redraw if canvas - renderer.draw(); - - // fire the event - fireEvent(chart, 'redraw'); - - if (isHiddenChart) { - chart.cloneRenderTo(true); - } - - // Fire callbacks that are put on hold until after the redraw - each(afterRedraw, function (callback) { - callback.call(); - }); - }, - - /** - * Get an axis, series or point object by id. - * @param id {String} The id as given in the configuration options - */ - get: function (id) { - var chart = this, - axes = chart.axes, - series = chart.series; - - var i, - j, - points; - - // search axes - for (i = 0; i < axes.length; i++) { - if (axes[i].options.id === id) { - return axes[i]; - } - } - - // search series - for (i = 0; i < series.length; i++) { - if (series[i].options.id === id) { - return series[i]; - } - } - - // search points - for (i = 0; i < series.length; i++) { - points = series[i].points || []; - for (j = 0; j < points.length; j++) { - if (points[j].id === id) { - return points[j]; - } - } - } - return null; - }, - - /** - * Create the Axis instances based on the config options - */ - getAxes: function () { - var chart = this, - options = this.options, - xAxisOptions = options.xAxis = splat(options.xAxis || {}), - yAxisOptions = options.yAxis = splat(options.yAxis || {}), - optionsArray; - - // make sure the options are arrays and add some members - each(xAxisOptions, function (axis, i) { - axis.index = i; - axis.isX = true; - }); - - each(yAxisOptions, function (axis, i) { - axis.index = i; - }); - - // concatenate all axis options into one array - optionsArray = xAxisOptions.concat(yAxisOptions); - - each(optionsArray, function (axisOptions) { - new Axis(chart, axisOptions); // eslint-disable-line no-new - }); - }, - - - /** - * Get the currently selected points from all series - */ - getSelectedPoints: function () { - var points = []; - each(this.series, function (serie) { - points = points.concat(grep(serie.points || [], function (point) { - return point.selected; - })); - }); - return points; - }, - - /** - * Get the currently selected series - */ - getSelectedSeries: function () { - return grep(this.series, function (serie) { - return serie.selected; - }); - }, - - /** - * Show the title and subtitle of the chart - * - * @param titleOptions {Object} New title options - * @param subtitleOptions {Object} New subtitle options - * - */ - setTitle: function (titleOptions, subtitleOptions, redraw) { - var chart = this, - options = chart.options, - chartTitleOptions, - chartSubtitleOptions; - - chartTitleOptions = options.title = merge(options.title, titleOptions); - chartSubtitleOptions = options.subtitle = merge(options.subtitle, subtitleOptions); - - // add title and subtitle - each([ - ['title', titleOptions, chartTitleOptions], - ['subtitle', subtitleOptions, chartSubtitleOptions] - ], function (arr) { - var name = arr[0], - title = chart[name], - titleOptions = arr[1], - chartTitleOptions = arr[2]; - - if (title && titleOptions) { - chart[name] = title = title.destroy(); // remove old - } - - if (chartTitleOptions && chartTitleOptions.text && !title) { - chart[name] = chart.renderer.text( - chartTitleOptions.text, - 0, - 0, - chartTitleOptions.useHTML - ) - .attr({ - align: chartTitleOptions.align, - 'class': PREFIX + name, - zIndex: chartTitleOptions.zIndex || 4 - }) - .css(chartTitleOptions.style) - .add(); - - } - }); - chart.layOutTitles(redraw); - }, - - /** - * Lay out the chart titles and cache the full offset height for use in getMargins - */ - layOutTitles: function (redraw) { - var titleOffset = 0, - title = this.title, - subtitle = this.subtitle, - options = this.options, - titleOptions = options.title, - subtitleOptions = options.subtitle, - requiresDirtyBox, - renderer = this.renderer, - spacingBox = this.spacingBox; - - if (title) { - title - .css({ width: (titleOptions.width || spacingBox.width + titleOptions.widthAdjust) + PX }) - .align(extend({ - y: renderer.fontMetrics(titleOptions.style.fontSize, title).b - 3 - }, titleOptions), false, spacingBox); - - if (!titleOptions.floating && !titleOptions.verticalAlign) { - titleOffset = title.getBBox().height; - } - } - if (subtitle) { - subtitle - .css({ width: (subtitleOptions.width || spacingBox.width + subtitleOptions.widthAdjust) + PX }) - .align(extend({ - y: titleOffset + (titleOptions.margin - 13) + renderer.fontMetrics(subtitleOptions.style.fontSize, title).b - }, subtitleOptions), false, spacingBox); - - if (!subtitleOptions.floating && !subtitleOptions.verticalAlign) { - titleOffset = mathCeil(titleOffset + subtitle.getBBox().height); - } - } - - requiresDirtyBox = this.titleOffset !== titleOffset; - this.titleOffset = titleOffset; // used in getMargins - - if (!this.isDirtyBox && requiresDirtyBox) { - this.isDirtyBox = requiresDirtyBox; - // Redraw if necessary (#2719, #2744) - if (this.hasRendered && pick(redraw, true) && this.isDirtyBox) { - this.redraw(); - } - } - }, - - /** - * Get chart width and height according to options and container size - */ - getChartSize: function () { - var chart = this, - optionsChart = chart.options.chart, - widthOption = optionsChart.width, - heightOption = optionsChart.height, - renderTo = chart.renderToClone || chart.renderTo; - - // Get inner width and height - if (!defined(widthOption)) { - chart.containerWidth = getStyle(renderTo, 'width'); - } - if (!defined(heightOption)) { - chart.containerHeight = getStyle(renderTo, 'height'); - } - - chart.chartWidth = mathMax(0, widthOption || chart.containerWidth || 600); // #1393, 1460 - chart.chartHeight = mathMax(0, pick(heightOption, - // the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7: - chart.containerHeight > 19 ? chart.containerHeight : 400)); - }, - - /** - * Create a clone of the chart's renderTo div and place it outside the viewport to allow - * size computation on chart.render and chart.redraw - */ - cloneRenderTo: function (revert) { - var clone = this.renderToClone, - container = this.container; - - // Destroy the clone and bring the container back to the real renderTo div - if (revert) { - if (clone) { - this.renderTo.appendChild(container); - discardElement(clone); - delete this.renderToClone; - } - - // Set up the clone - } else { - if (container && container.parentNode === this.renderTo) { - this.renderTo.removeChild(container); // do not clone this - } - this.renderToClone = clone = this.renderTo.cloneNode(0); - css(clone, { - position: ABSOLUTE, - top: '-9999px', - display: 'block' // #833 - }); - if (clone.style.setProperty) { // #2631 - clone.style.setProperty('display', 'block', 'important'); - } - doc.body.appendChild(clone); - if (container) { - clone.appendChild(container); - } - } - }, - - /** - * Get the containing element, determine the size and create the inner container - * div to hold the chart - */ - getContainer: function () { - var chart = this, - container, - options = chart.options, - optionsChart = options.chart, - chartWidth, - chartHeight, - renderTo = chart.renderTo, - indexAttrName = 'data-highcharts-chart', - oldChartIndex, - Ren, - containerId = 'highcharts-' + idCounter++; - - if (!renderTo) { - chart.renderTo = renderTo = optionsChart.renderTo; - } - - if (isString(renderTo)) { - chart.renderTo = renderTo = doc.getElementById(renderTo); - } - - // Display an error if the renderTo is wrong - if (!renderTo) { - error(13, true); - } - - // If the container already holds a chart, destroy it. The check for hasRendered is there - // because web pages that are saved to disk from the browser, will preserve the data-highcharts-chart - // attribute and the SVG contents, but not an interactive chart. So in this case, - // charts[oldChartIndex] will point to the wrong chart if any (#2609). - oldChartIndex = pInt(attr(renderTo, indexAttrName)); - if (isNumber(oldChartIndex) && charts[oldChartIndex] && charts[oldChartIndex].hasRendered) { - charts[oldChartIndex].destroy(); - } - - // Make a reference to the chart from the div - attr(renderTo, indexAttrName, chart.index); - - // remove previous chart - renderTo.innerHTML = ''; - - // If the container doesn't have an offsetWidth, it has or is a child of a node - // that has display:none. We need to temporarily move it out to a visible - // state to determine the size, else the legend and tooltips won't render - // properly. The allowClone option is used in sparklines as a micro optimization, - // saving about 1-2 ms each chart. - if (!optionsChart.skipClone && !renderTo.offsetWidth) { - chart.cloneRenderTo(); - } - - // get the width and height - chart.getChartSize(); - chartWidth = chart.chartWidth; - chartHeight = chart.chartHeight; - - // create the inner container - chart.container = container = createElement(DIV, { - className: PREFIX + 'container' + - (optionsChart.className ? ' ' + optionsChart.className : ''), - id: containerId - }, extend({ - position: RELATIVE, - overflow: HIDDEN, // needed for context menu (avoid scrollbars) and - // content overflow in IE - width: chartWidth + PX, - height: chartHeight + PX, - textAlign: 'left', - lineHeight: 'normal', // #427 - zIndex: 0, // #1072 - '-webkit-tap-highlight-color': 'rgba(0,0,0,0)' - }, optionsChart.style), - chart.renderToClone || renderTo - ); - - // cache the cursor (#1650) - chart._cursor = container.style.cursor; - - // Initialize the renderer - Ren = Highcharts[optionsChart.renderer] || Renderer; - chart.renderer = new Ren( - container, - chartWidth, - chartHeight, - optionsChart.style, - optionsChart.forExport, - options.exporting && options.exporting.allowHTML - ); - - if (useCanVG) { - // If we need canvg library, extend and configure the renderer - // to get the tracker for translating mouse events - chart.renderer.create(chart, container, chartWidth, chartHeight); - } - // Add a reference to the charts index - chart.renderer.chartIndex = chart.index; - }, - - /** - * Calculate margins by rendering axis labels in a preliminary position. Title, - * subtitle and legend have already been rendered at this stage, but will be - * moved into their final positions - */ - getMargins: function (skipAxes) { - var chart = this, - spacing = chart.spacing, - margin = chart.margin, - titleOffset = chart.titleOffset; - - chart.resetMargins(); - - // Adjust for title and subtitle - if (titleOffset && !defined(margin[0])) { - chart.plotTop = mathMax(chart.plotTop, titleOffset + chart.options.title.margin + spacing[0]); - } - - // Adjust for legend - chart.legend.adjustMargins(margin, spacing); - - // adjust for scroller - if (chart.extraBottomMargin) { - chart.marginBottom += chart.extraBottomMargin; - } - if (chart.extraTopMargin) { - chart.plotTop += chart.extraTopMargin; - } - if (!skipAxes) { - this.getAxisMargins(); - } - }, - - getAxisMargins: function () { - - var chart = this, - axisOffset = chart.axisOffset = [0, 0, 0, 0], // top, right, bottom, left - margin = chart.margin; - - // pre-render axes to get labels offset width - if (chart.hasCartesianSeries) { - each(chart.axes, function (axis) { - if (axis.visible) { - axis.getOffset(); - } - }); - } - - // Add the axis offsets - each(marginNames, function (m, side) { - if (!defined(margin[side])) { - chart[m] += axisOffset[side]; - } - }); - - chart.setChartSize(); - - }, - - /** - * Resize the chart to its container if size is not explicitly set - */ - reflow: function (e) { - var chart = this, - optionsChart = chart.options.chart, - renderTo = chart.renderTo, - width = optionsChart.width || getStyle(renderTo, 'width'), - height = optionsChart.height || getStyle(renderTo, 'height'), - target = e ? e.target : win; - - // Width and height checks for display:none. Target is doc in IE8 and Opera, - // win in Firefox, Chrome and IE9. - if (!chart.hasUserSize && !chart.isPrinting && width && height && (target === win || target === doc)) { // #1093 - if (width !== chart.containerWidth || height !== chart.containerHeight) { - clearTimeout(chart.reflowTimeout); - // When called from window.resize, e is set, else it's called directly (#2224) - chart.reflowTimeout = syncTimeout(function () { - if (chart.container) { // It may have been destroyed in the meantime (#1257) - chart.setSize(width, height, false); - chart.hasUserSize = null; - } - }, e ? 100 : 0); - } - chart.containerWidth = width; - chart.containerHeight = height; - } - }, - - /** - * Add the event handlers necessary for auto resizing - */ - initReflow: function () { - var chart = this, - reflow = function (e) { - chart.reflow(e); - }; - - - addEvent(win, 'resize', reflow); - addEvent(chart, 'destroy', function () { - removeEvent(win, 'resize', reflow); - }); - }, - - /** - * Resize the chart to a given width and height - * @param {Number} width - * @param {Number} height - * @param {Object|Boolean} animation - */ - setSize: function (width, height, animation) { - var chart = this, - chartWidth, - chartHeight, - renderer = chart.renderer, - globalAnimation; - - // Handle the isResizing counter - chart.isResizing += 1; - - // set the animation for the current process - setAnimation(animation, chart); - - chart.oldChartHeight = chart.chartHeight; - chart.oldChartWidth = chart.chartWidth; - if (defined(width)) { - chart.chartWidth = chartWidth = mathMax(0, mathRound(width)); - chart.hasUserSize = !!chartWidth; - } - if (defined(height)) { - chart.chartHeight = chartHeight = mathMax(0, mathRound(height)); - } - - // Resize the container with the global animation applied if enabled (#2503) - globalAnimation = renderer.globalAnimation; - (globalAnimation ? animate : css)(chart.container, { - width: chartWidth + PX, - height: chartHeight + PX - }, globalAnimation); - - chart.setChartSize(true); - renderer.setSize(chartWidth, chartHeight, animation); - - // handle axes - chart.maxTicks = null; - each(chart.axes, function (axis) { - axis.isDirty = true; - axis.setScale(); - }); - - // make sure non-cartesian series are also handled - each(chart.series, function (serie) { - serie.isDirty = true; - }); - - chart.isDirtyLegend = true; // force legend redraw - chart.isDirtyBox = true; // force redraw of plot and chart border - - chart.layOutTitles(); // #2857 - chart.getMargins(); - - chart.redraw(animation); - - - chart.oldChartHeight = null; - fireEvent(chart, 'resize'); - - // Fire endResize and set isResizing back. If animation is disabled, fire without delay - syncTimeout(function () { - if (chart) { - fireEvent(chart, 'endResize', null, function () { - chart.isResizing -= 1; - }); - } - }, animObject(globalAnimation).duration); - }, - - /** - * Set the public chart properties. This is done before and after the pre-render - * to determine margin sizes - */ - setChartSize: function (skipAxes) { - var chart = this, - inverted = chart.inverted, - renderer = chart.renderer, - chartWidth = chart.chartWidth, - chartHeight = chart.chartHeight, - optionsChart = chart.options.chart, - spacing = chart.spacing, - clipOffset = chart.clipOffset, - clipX, - clipY, - plotLeft, - plotTop, - plotWidth, - plotHeight, - plotBorderWidth; - - chart.plotLeft = plotLeft = mathRound(chart.plotLeft); - chart.plotTop = plotTop = mathRound(chart.plotTop); - chart.plotWidth = plotWidth = mathMax(0, mathRound(chartWidth - plotLeft - chart.marginRight)); - chart.plotHeight = plotHeight = mathMax(0, mathRound(chartHeight - plotTop - chart.marginBottom)); - - chart.plotSizeX = inverted ? plotHeight : plotWidth; - chart.plotSizeY = inverted ? plotWidth : plotHeight; - - chart.plotBorderWidth = optionsChart.plotBorderWidth || 0; - - // Set boxes used for alignment - chart.spacingBox = renderer.spacingBox = { - x: spacing[3], - y: spacing[0], - width: chartWidth - spacing[3] - spacing[1], - height: chartHeight - spacing[0] - spacing[2] - }; - chart.plotBox = renderer.plotBox = { - x: plotLeft, - y: plotTop, - width: plotWidth, - height: plotHeight - }; - - plotBorderWidth = 2 * mathFloor(chart.plotBorderWidth / 2); - clipX = mathCeil(mathMax(plotBorderWidth, clipOffset[3]) / 2); - clipY = mathCeil(mathMax(plotBorderWidth, clipOffset[0]) / 2); - chart.clipBox = { - x: clipX, - y: clipY, - width: mathFloor(chart.plotSizeX - mathMax(plotBorderWidth, clipOffset[1]) / 2 - clipX), - height: mathMax(0, mathFloor(chart.plotSizeY - mathMax(plotBorderWidth, clipOffset[2]) / 2 - clipY)) - }; - - if (!skipAxes) { - each(chart.axes, function (axis) { - axis.setAxisSize(); - axis.setAxisTranslation(); - }); - } - }, - - /** - * Initial margins before auto size margins are applied - */ - resetMargins: function () { - var chart = this; - - each(marginNames, function (m, side) { - chart[m] = pick(chart.margin[side], chart.spacing[side]); - }); - chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left - chart.clipOffset = [0, 0, 0, 0]; - }, - - /** - * Draw the borders and backgrounds for chart and plot area - */ - drawChartBox: function () { - var chart = this, - optionsChart = chart.options.chart, - renderer = chart.renderer, - chartWidth = chart.chartWidth, - chartHeight = chart.chartHeight, - chartBackground = chart.chartBackground, - plotBackground = chart.plotBackground, - plotBorder = chart.plotBorder, - plotBGImage = chart.plotBGImage, - chartBorderWidth = optionsChart.borderWidth || 0, - chartBackgroundColor = optionsChart.backgroundColor, - plotBackgroundColor = optionsChart.plotBackgroundColor, - plotBackgroundImage = optionsChart.plotBackgroundImage, - plotBorderWidth = optionsChart.plotBorderWidth || 0, - mgn, - bgAttr, - plotLeft = chart.plotLeft, - plotTop = chart.plotTop, - plotWidth = chart.plotWidth, - plotHeight = chart.plotHeight, - plotBox = chart.plotBox, - clipRect = chart.clipRect, - clipBox = chart.clipBox; - - // Chart area - mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0); - - if (chartBorderWidth || chartBackgroundColor) { - if (!chartBackground) { - - bgAttr = { - fill: chartBackgroundColor || NONE - }; - if (chartBorderWidth) { // #980 - bgAttr.stroke = optionsChart.borderColor; - bgAttr['stroke-width'] = chartBorderWidth; - } - chart.chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn, - optionsChart.borderRadius, chartBorderWidth) - .attr(bgAttr) - .addClass(PREFIX + 'background') - .add() - .shadow(optionsChart.shadow); - - } else { // resize - chartBackground.animate( - chartBackground.crisp({ width: chartWidth - mgn, height: chartHeight - mgn }) - ); - } - } - - - // Plot background - if (plotBackgroundColor) { - if (!plotBackground) { - chart.plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0) - .attr({ - fill: plotBackgroundColor - }) - .add() - .shadow(optionsChart.plotShadow); - } else { - plotBackground.animate(plotBox); - } - } - if (plotBackgroundImage) { - if (!plotBGImage) { - chart.plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight) - .add(); - } else { - plotBGImage.animate(plotBox); - } - } - - // Plot clip - if (!clipRect) { - chart.clipRect = renderer.clipRect(clipBox); - } else { - clipRect.animate({ - width: clipBox.width, - height: clipBox.height - }); - } - - // Plot area border - if (plotBorderWidth) { - if (!plotBorder) { - chart.plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, -plotBorderWidth) - .attr({ - stroke: optionsChart.plotBorderColor, - 'stroke-width': plotBorderWidth, - fill: NONE, - zIndex: 1 - }) - .add(); - } else { - plotBorder.strokeWidth = -plotBorderWidth; - plotBorder.animate( - plotBorder.crisp({ x: plotLeft, y: plotTop, width: plotWidth, height: plotHeight }) //#3282 plotBorder should be negative - ); - } - } - - // reset - chart.isDirtyBox = false; - }, - - /** - * Detect whether a certain chart property is needed based on inspecting its options - * and series. This mainly applies to the chart.invert property, and in extensions to - * the chart.angular and chart.polar properties. - */ - propFromSeries: function () { - var chart = this, - optionsChart = chart.options.chart, - klass, - seriesOptions = chart.options.series, - i, - value; - - - each(['inverted', 'angular', 'polar'], function (key) { - - // The default series type's class - klass = seriesTypes[optionsChart.type || optionsChart.defaultSeriesType]; - - // Get the value from available chart-wide properties - value = ( - chart[key] || // 1. it is set before - optionsChart[key] || // 2. it is set in the options - (klass && klass.prototype[key]) // 3. it's default series class requires it - ); - - // 4. Check if any the chart's series require it - i = seriesOptions && seriesOptions.length; - while (!value && i--) { - klass = seriesTypes[seriesOptions[i].type]; - if (klass && klass.prototype[key]) { - value = true; - } - } - - // Set the chart property - chart[key] = value; - }); - - }, - - /** - * Link two or more series together. This is done initially from Chart.render, - * and after Chart.addSeries and Series.remove. - */ - linkSeries: function () { - var chart = this, - chartSeries = chart.series; - - // Reset links - each(chartSeries, function (series) { - series.linkedSeries.length = 0; - }); - - // Apply new links - each(chartSeries, function (series) { - var linkedTo = series.options.linkedTo; - if (isString(linkedTo)) { - if (linkedTo === ':previous') { - linkedTo = chart.series[series.index - 1]; - } else { - linkedTo = chart.get(linkedTo); - } - if (linkedTo) { - linkedTo.linkedSeries.push(series); - series.linkedParent = linkedTo; - series.visible = pick(series.options.visible, linkedTo.options.visible, series.visible); // #3879 - } - } - }); - }, - - /** - * Render series for the chart - */ - renderSeries: function () { - each(this.series, function (serie) { - serie.translate(); - serie.render(); - }); - }, - - /** - * Render labels for the chart - */ - renderLabels: function () { - var chart = this, - labels = chart.options.labels; - if (labels.items) { - each(labels.items, function (label) { - var style = extend(labels.style, label.style), - x = pInt(style.left) + chart.plotLeft, - y = pInt(style.top) + chart.plotTop + 12; - - // delete to prevent rewriting in IE - delete style.left; - delete style.top; - - chart.renderer.text( - label.html, - x, - y - ) - .attr({ zIndex: 2 }) - .css(style) - .add(); - - }); - } - }, - - /** - * Render all graphics for the chart - */ - render: function () { - var chart = this, - axes = chart.axes, - renderer = chart.renderer, - options = chart.options, - tempWidth, - tempHeight, - redoHorizontal, - redoVertical; - - // Title - chart.setTitle(); - - - // Legend - chart.legend = new Legend(chart, options.legend); - - // Get stacks - if (chart.getStacks) { - chart.getStacks(); - } - - // Get chart margins - chart.getMargins(true); - chart.setChartSize(); - - // Record preliminary dimensions for later comparison - tempWidth = chart.plotWidth; - tempHeight = chart.plotHeight = chart.plotHeight - 21; // 21 is the most common correction for X axis labels - - // Get margins by pre-rendering axes - each(axes, function (axis) { - axis.setScale(); - }); - chart.getAxisMargins(); - - // If the plot area size has changed significantly, calculate tick positions again - redoHorizontal = tempWidth / chart.plotWidth > 1.1; - redoVertical = tempHeight / chart.plotHeight > 1.05; // Height is more sensitive - - if (redoHorizontal || redoVertical) { - - chart.maxTicks = null; // reset for second pass - each(axes, function (axis) { - if ((axis.horiz && redoHorizontal) || (!axis.horiz && redoVertical)) { - axis.setTickInterval(true); // update to reflect the new margins - } - }); - chart.getMargins(); // second pass to check for new labels - } - - // Draw the borders and backgrounds - chart.drawChartBox(); - - - // Axes - if (chart.hasCartesianSeries) { - each(axes, function (axis) { - if (axis.visible) { - axis.render(); - } - }); - } - - // The series - if (!chart.seriesGroup) { - chart.seriesGroup = renderer.g('series-group') - .attr({ zIndex: 3 }) - .add(); - } - chart.renderSeries(); - - // Labels - chart.renderLabels(); - - // Credits - chart.showCredits(options.credits); - - // Set flag - chart.hasRendered = true; - - }, - - /** - * Show chart credits based on config options - */ - showCredits: function (credits) { - if (credits.enabled && !this.credits) { - this.credits = this.renderer.text( - credits.text, - 0, - 0 - ) - .on('click', function () { - if (credits.href) { - win.location.href = credits.href; - } - }) - .attr({ - align: credits.position.align, - zIndex: 8 - }) - .css(credits.style) - .add() - .align(credits.position); - } - }, - - /** - * Clean up memory usage - */ - destroy: function () { - var chart = this, - axes = chart.axes, - series = chart.series, - container = chart.container, - i, - parentNode = container && container.parentNode; - - // fire the chart.destoy event - fireEvent(chart, 'destroy'); - - // Delete the chart from charts lookup array - charts[chart.index] = UNDEFINED; - chartCount--; - chart.renderTo.removeAttribute('data-highcharts-chart'); - - // remove events - removeEvent(chart); - - // ==== Destroy collections: - // Destroy axes - i = axes.length; - while (i--) { - axes[i] = axes[i].destroy(); - } - - // Destroy each series - i = series.length; - while (i--) { - series[i] = series[i].destroy(); - } - - // ==== Destroy chart properties: - each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage', - 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'pointer', 'scroller', - 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) { - var prop = chart[name]; - - if (prop && prop.destroy) { - chart[name] = prop.destroy(); - } - }); - - // remove container and all SVG - if (container) { // can break in IE when destroyed before finished loading - container.innerHTML = ''; - removeEvent(container); - if (parentNode) { - discardElement(container); - } - - } - - // clean it all up - for (i in chart) { - delete chart[i]; - } - - }, - - - /** - * VML namespaces can't be added until after complete. Listening - * for Perini's doScroll hack is not enough. - */ - isReadyToRender: function () { - var chart = this; - - // Note: win == win.top is required - if ((!hasSVG && (win == win.top && doc.readyState !== 'complete')) || (useCanVG && !win.canvg)) { // eslint-disable-line eqeqeq - if (useCanVG) { - // Delay rendering until canvg library is downloaded and ready - CanVGController.push(function () { - chart.firstRender(); - }, chart.options.global.canvasToolsURL); - } else { - doc.attachEvent('onreadystatechange', function () { - doc.detachEvent('onreadystatechange', chart.firstRender); - if (doc.readyState === 'complete') { - chart.firstRender(); - } - }); - } - return false; - } - return true; - }, - - /** - * Prepare for first rendering after all data are loaded - */ - firstRender: function () { - var chart = this, - options = chart.options; - - // Check whether the chart is ready to render - if (!chart.isReadyToRender()) { - return; - } - - // Create the container - chart.getContainer(); - - // Run an early event after the container and renderer are established - fireEvent(chart, 'init'); - - - chart.resetMargins(); - chart.setChartSize(); - - // Set the common chart properties (mainly invert) from the given series - chart.propFromSeries(); - - // get axes - chart.getAxes(); - - // Initialize the series - each(options.series || [], function (serieOptions) { - chart.initSeries(serieOptions); - }); - - chart.linkSeries(); - - // Run an event after axes and series are initialized, but before render. At this stage, - // the series data is indexed and cached in the xData and yData arrays, so we can access - // those before rendering. Used in Highstock. - fireEvent(chart, 'beforeRender'); - - // depends on inverted and on margins being set - if (Highcharts.Pointer) { - chart.pointer = new Pointer(chart, options); - } - - chart.render(); - - // add canvas - chart.renderer.draw(); - - // Fire the load event if there are no external images - if (!chart.renderer.imgCount && chart.onload) { - chart.onload(); - } - - // If the chart was rendered outside the top container, put it back in (#3679) - chart.cloneRenderTo(true); - - }, - - /** - * On chart load - */ - onload: function () { - var chart = this; - - // Run callbacks - each([this.callback].concat(this.callbacks), function (fn) { - if (fn && chart.index !== undefined) { // Chart destroyed in its own callback (#3600) - fn.apply(chart, [chart]); - } - }); - - fireEvent(chart, 'load'); - - // Don't run again - this.onload = null; - }, - - /** - * Creates arrays for spacing and margin from given options. - */ - splashArray: function (target, options) { - var oVar = options[target], - tArray = isObject(oVar) ? oVar : [oVar, oVar, oVar, oVar]; - - return [pick(options[target + 'Top'], tArray[0]), - pick(options[target + 'Right'], tArray[1]), - pick(options[target + 'Bottom'], tArray[2]), - pick(options[target + 'Left'], tArray[3])]; - } - }; // end Chart - - var CenteredSeriesMixin = Highcharts.CenteredSeriesMixin = { - /** - * Get the center of the pie based on the size and center options relative to the - * plot area. Borrowed by the polar and gauge series types. - */ - getCenter: function () { - - var options = this.options, - chart = this.chart, - slicingRoom = 2 * (options.slicedOffset || 0), - handleSlicingRoom, - plotWidth = chart.plotWidth - 2 * slicingRoom, - plotHeight = chart.plotHeight - 2 * slicingRoom, - centerOption = options.center, - positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0], - smallestSize = mathMin(plotWidth, plotHeight), - i, - value; - - for (i = 0; i < 4; ++i) { - value = positions[i]; - handleSlicingRoom = i < 2 || (i === 2 && /%$/.test(value)); - - // i == 0: centerX, relative to width - // i == 1: centerY, relative to height - // i == 2: size, relative to smallestSize - // i == 3: innerSize, relative to size - positions[i] = relativeLength(value, [plotWidth, plotHeight, smallestSize, positions[2]][i]) + - (handleSlicingRoom ? slicingRoom : 0); - - } - // innerSize cannot be larger than size (#3632) - if (positions[3] > positions[2]) { - positions[3] = positions[2]; - } - return positions; - } - }; - - /** - * The Point object and prototype. Inheritable and used as base for PiePoint - */ - var Point = function () {}; - Point.prototype = { - - /** - * Initialize the point - * @param {Object} series The series object containing this point - * @param {Object} options The data in either number, array or object format - */ - init: function (series, options, x) { - - var point = this, - colors; - point.series = series; - point.color = series.color; // #3445 - point.applyOptions(options, x); - point.pointAttr = {}; - - if (series.options.colorByPoint) { - colors = series.options.colors || series.chart.options.colors; - point.color = point.color || colors[series.colorCounter++]; - // loop back to zero - if (series.colorCounter === colors.length) { - series.colorCounter = 0; - } - } - - series.chart.pointCount++; - return point; - }, - /** - * Apply the options containing the x and y data and possible some extra properties. - * This is called on point init or from point.update. - * - * @param {Object} options - */ - applyOptions: function (options, x) { - var point = this, - series = point.series, - pointValKey = series.options.pointValKey || series.pointValKey; - - options = Point.prototype.optionsToObject.call(this, options); - - // copy options directly to point - extend(point, options); - point.options = point.options ? extend(point.options, options) : options; - - // For higher dimension series types. For instance, for ranges, point.y is mapped to point.low. - if (pointValKey) { - point.y = point[pointValKey]; - } - point.isNull = point.x === null || point.y === null; - - // If no x is set by now, get auto incremented value. All points must have an - // x value, however the y value can be null to create a gap in the series - if (point.x === undefined && series) { - point.x = x === undefined ? series.autoIncrement() : x; - } - - return point; - }, - - /** - * Transform number or array configs into objects - */ - optionsToObject: function (options) { - var ret = {}, - series = this.series, - keys = series.options.keys, - pointArrayMap = keys || series.pointArrayMap || ['y'], - valueCount = pointArrayMap.length, - firstItemType, - i = 0, - j = 0; - - if (isNumber(options) || options === null) { - ret[pointArrayMap[0]] = options; - - } else if (isArray(options)) { - // with leading x value - if (!keys && options.length > valueCount) { - firstItemType = typeof options[0]; - if (firstItemType === 'string') { - ret.name = options[0]; - } else if (firstItemType === 'number') { - ret.x = options[0]; - } - i++; - } - while (j < valueCount) { - if (!keys || options[i] !== undefined) { // Skip undefined positions for keys - ret[pointArrayMap[j]] = options[i]; - } - i++; - j++; - } - } else if (typeof options === 'object') { - ret = options; - - // This is the fastest way to detect if there are individual point dataLabels that need - // to be considered in drawDataLabels. These can only occur in object configs. - if (options.dataLabels) { - series._hasPointLabels = true; - } - - // Same approach as above for markers - if (options.marker) { - series._hasPointMarkers = true; - } - } - return ret; - }, - - /** - * Destroy a point to clear memory. Its reference still stays in series.data. - */ - destroy: function () { - var point = this, - series = point.series, - chart = series.chart, - hoverPoints = chart.hoverPoints, - prop; - - chart.pointCount--; - - if (hoverPoints) { - point.setState(); - erase(hoverPoints, point); - if (!hoverPoints.length) { - chart.hoverPoints = null; - } - - } - if (point === chart.hoverPoint) { - point.onMouseOut(); - } - - // remove all events - if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive - removeEvent(point); - point.destroyElements(); - } - - if (point.legendItem) { // pies have legend items - chart.legend.destroyItem(point); - } - - for (prop in point) { - point[prop] = null; - } - - - }, - - /** - * Destroy SVG elements associated with the point - */ - destroyElements: function () { - var point = this, - props = ['graphic', 'dataLabel', 'dataLabelUpper', 'connector', 'shadowGroup'], - prop, - i = 6; - while (i--) { - prop = props[i]; - if (point[prop]) { - point[prop] = point[prop].destroy(); - } - } - }, - - /** - * Return the configuration hash needed for the data label and tooltip formatters - */ - getLabelConfig: function () { - return { - x: this.category, - y: this.y, - color: this.color, - key: this.name || this.category, - series: this.series, - point: this, - percentage: this.percentage, - total: this.total || this.stackTotal - }; - }, - - /** - * Extendable method for formatting each point's tooltip line - * - * @return {String} A string to be concatenated in to the common tooltip text - */ - tooltipFormatter: function (pointFormat) { - - // Insert options for valueDecimals, valuePrefix, and valueSuffix - var series = this.series, - seriesTooltipOptions = series.tooltipOptions, - valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''), - valuePrefix = seriesTooltipOptions.valuePrefix || '', - valueSuffix = seriesTooltipOptions.valueSuffix || ''; - - // Loop over the point array map and replace unformatted values with sprintf formatting markup - each(series.pointArrayMap || ['y'], function (key) { - key = '{point.' + key; // without the closing bracket - if (valuePrefix || valueSuffix) { - pointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix); - } - pointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}'); - }); - - return format(pointFormat, { - point: this, - series: this.series - }); - }, - - /** - * Fire an event on the Point object. - * @param {String} eventType - * @param {Object} eventArgs Additional event arguments - * @param {Function} defaultFunction Default event handler - */ - firePointEvent: function (eventType, eventArgs, defaultFunction) { - var point = this, - series = this.series, - seriesOptions = series.options; - - // load event handlers on demand to save time on mouseover/out - if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) { - this.importEvents(); - } - - // add default handler if in selection mode - if (eventType === 'click' && seriesOptions.allowPointSelect) { - defaultFunction = function (event) { - // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera - if (point.select) { // Could be destroyed by prior event handlers (#2911) - point.select(null, event.ctrlKey || event.metaKey || event.shiftKey); - } - }; - } - - fireEvent(this, eventType, eventArgs, defaultFunction); - }, - visible: true - };/** - * @classDescription The base function which all other series types inherit from. The data in the series is stored - * in various arrays. - * - * - First, series.options.data contains all the original config options for - * each point whether added by options or methods like series.addPoint. - * - Next, series.data contains those values converted to points, but in case the series data length - * exceeds the cropThreshold, or if the data is grouped, series.data doesn't contain all the points. It - * only contains the points that have been created on demand. - * - Then there's series.points that contains all currently visible point objects. In case of cropping, - * the cropped-away points are not part of this array. The series.points array starts at series.cropStart - * compared to series.data and series.options.data. If however the series data is grouped, these can't - * be correlated one to one. - * - series.xData and series.processedXData contain clean x values, equivalent to series.data and series.points. - * - series.yData and series.processedYData contain clean x values, equivalent to series.data and series.points. - * - * @param {Object} chart - * @param {Object} options - */ - var Series = Highcharts.Series = function () {}; - - Series.prototype = { - - isCartesian: true, - type: 'line', - pointClass: Point, - sorted: true, // requires the data to be sorted - requireSorting: true, - pointAttrToOptions: { // mapping between SVG attributes and the corresponding options - stroke: 'lineColor', - 'stroke-width': 'lineWidth', - fill: 'fillColor', - r: 'radius' - }, - directTouch: false, - axisTypes: ['xAxis', 'yAxis'], - colorCounter: 0, - parallelArrays: ['x', 'y'], // each point's x and y values are stored in this.xData and this.yData - init: function (chart, options) { - var series = this, - eventType, - events, - chartSeries = chart.series, - sortByIndex = function (a, b) { - return pick(a.options.index, a._i) - pick(b.options.index, b._i); - }; - - series.chart = chart; - series.options = options = series.setOptions(options); // merge with plotOptions - series.linkedSeries = []; - - // bind the axes - series.bindAxes(); - - // set some variables - extend(series, { - name: options.name, - state: NORMAL_STATE, - pointAttr: {}, - visible: options.visible !== false, // true by default - selected: options.selected === true // false by default - }); - - // special - if (useCanVG) { - options.animation = false; - } - - // register event listeners - events = options.events; - for (eventType in events) { - addEvent(series, eventType, events[eventType]); - } - if ( - (events && events.click) || - (options.point && options.point.events && options.point.events.click) || - options.allowPointSelect - ) { - chart.runTrackerClick = true; - } - - series.getColor(); - series.getSymbol(); - - // Set the data - each(series.parallelArrays, function (key) { - series[key + 'Data'] = []; - }); - series.setData(options.data, false); - - // Mark cartesian - if (series.isCartesian) { - chart.hasCartesianSeries = true; - } - - // Register it in the chart - chartSeries.push(series); - series._i = chartSeries.length - 1; - - // Sort series according to index option (#248, #1123, #2456) - stableSort(chartSeries, sortByIndex); - if (this.yAxis) { - stableSort(this.yAxis.series, sortByIndex); - } - - each(chartSeries, function (series, i) { - series.index = i; - series.name = series.name || 'Series ' + (i + 1); - }); - - }, - - /** - * Set the xAxis and yAxis properties of cartesian series, and register the series - * in the axis.series array - */ - bindAxes: function () { - var series = this, - seriesOptions = series.options, - chart = series.chart, - axisOptions; - - each(series.axisTypes || [], function (AXIS) { // repeat for xAxis and yAxis - - each(chart[AXIS], function (axis) { // loop through the chart's axis objects - axisOptions = axis.options; - - // apply if the series xAxis or yAxis option mathches the number of the - // axis, or if undefined, use the first axis - if ((seriesOptions[AXIS] === axisOptions.index) || - (seriesOptions[AXIS] !== UNDEFINED && seriesOptions[AXIS] === axisOptions.id) || - (seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) { - - // register this series in the axis.series lookup - axis.series.push(series); - - // set this series.xAxis or series.yAxis reference - series[AXIS] = axis; - - // mark dirty for redraw - axis.isDirty = true; - } - }); - - // The series needs an X and an Y axis - if (!series[AXIS] && series.optionalAxis !== AXIS) { - error(18, true); - } - - }); - }, - - /** - * For simple series types like line and column, the data values are held in arrays like - * xData and yData for quick lookup to find extremes and more. For multidimensional series - * like bubble and map, this can be extended with arrays like zData and valueData by - * adding to the series.parallelArrays array. - */ - updateParallelArrays: function (point, i) { - var series = point.series, - args = arguments, - fn = isNumber(i) ? - // Insert the value in the given position - function (key) { - var val = key === 'y' && series.toYData ? series.toYData(point) : point[key]; - series[key + 'Data'][i] = val; - } : - // Apply the method specified in i with the following arguments as arguments - function (key) { - Array.prototype[i].apply(series[key + 'Data'], Array.prototype.slice.call(args, 2)); - }; - - each(series.parallelArrays, fn); - }, - - /** - * Return an auto incremented x value based on the pointStart and pointInterval options. - * This is only used if an x value is not given for the point that calls autoIncrement. - */ - autoIncrement: function () { - - var options = this.options, - xIncrement = this.xIncrement, - date, - pointInterval, - pointIntervalUnit = options.pointIntervalUnit; - - xIncrement = pick(xIncrement, options.pointStart, 0); - - this.pointInterval = pointInterval = pick(this.pointInterval, options.pointInterval, 1); - - // Added code for pointInterval strings - if (pointIntervalUnit) { - date = new Date(xIncrement); - - if (pointIntervalUnit === 'day') { - date = +date[setDate](date[getDate]() + pointInterval); - } else if (pointIntervalUnit === 'month') { - date = +date[setMonth](date[getMonth]() + pointInterval); - } else if (pointIntervalUnit === 'year') { - date = +date[setFullYear](date[getFullYear]() + pointInterval); - } - pointInterval = date - xIncrement; - } - - this.xIncrement = xIncrement + pointInterval; - return xIncrement; - }, - - /** - * Set the series options by merging from the options tree - * @param {Object} itemOptions - */ - setOptions: function (itemOptions) { - var chart = this.chart, - chartOptions = chart.options, - plotOptions = chartOptions.plotOptions, - userOptions = chart.userOptions || {}, - userPlotOptions = userOptions.plotOptions || {}, - typeOptions = plotOptions[this.type], - options, - zones; - - this.userOptions = itemOptions; - - // General series options take precedence over type options because otherwise, default - // type options like column.animation would be overwritten by the general option. - // But issues have been raised here (#3881), and the solution may be to distinguish - // between default option and userOptions like in the tooltip below. - options = merge( - typeOptions, - plotOptions.series, - itemOptions - ); - - // The tooltip options are merged between global and series specific options - this.tooltipOptions = merge( - defaultOptions.tooltip, - defaultOptions.plotOptions[this.type].tooltip, - userOptions.tooltip, - userPlotOptions.series && userPlotOptions.series.tooltip, - userPlotOptions[this.type] && userPlotOptions[this.type].tooltip, - itemOptions.tooltip - ); - - // Delete marker object if not allowed (#1125) - if (typeOptions.marker === null) { - delete options.marker; - } - - // Handle color zones - this.zoneAxis = options.zoneAxis; - zones = this.zones = (options.zones || []).slice(); - if ((options.negativeColor || options.negativeFillColor) && !options.zones) { - zones.push({ - value: options[this.zoneAxis + 'Threshold'] || options.threshold || 0, - color: options.negativeColor, - fillColor: options.negativeFillColor - }); - } - if (zones.length) { // Push one extra zone for the rest - if (defined(zones[zones.length - 1].value)) { - zones.push({ - color: this.color, - fillColor: this.fillColor - }); - } - } - return options; - }, - - getCyclic: function (prop, value, defaults) { - var i, - userOptions = this.userOptions, - indexName = '_' + prop + 'Index', - counterName = prop + 'Counter'; - - if (!value) { - if (defined(userOptions[indexName])) { // after Series.update() - i = userOptions[indexName]; - } else { - userOptions[indexName] = i = this.chart[counterName] % defaults.length; - this.chart[counterName] += 1; - } - value = defaults[i]; - } - this[prop] = value; - }, - - /** - * Get the series' color - */ - getColor: function () { - if (this.options.colorByPoint) { - this.options.color = null; // #4359, selected slice got series.color even when colorByPoint was set. - } else { - this.getCyclic('color', this.options.color || defaultPlotOptions[this.type].color, this.chart.options.colors); - } - }, - /** - * Get the series' symbol - */ - getSymbol: function () { - var seriesMarkerOption = this.options.marker; - - this.getCyclic('symbol', seriesMarkerOption.symbol, this.chart.options.symbols); - - // don't substract radius in image symbols (#604) - if (/^url/.test(this.symbol)) { - seriesMarkerOption.radius = 0; - } - }, - - drawLegendSymbol: LegendSymbolMixin.drawLineMarker, - - /** - * Replace the series data with a new set of data - * @param {Object} data - * @param {Object} redraw - */ - setData: function (data, redraw, animation, updatePoints) { - var series = this, - oldData = series.points, - oldDataLength = (oldData && oldData.length) || 0, - dataLength, - options = series.options, - chart = series.chart, - firstPoint = null, - xAxis = series.xAxis, - hasCategories = xAxis && !!xAxis.categories, - i, - turboThreshold = options.turboThreshold, - pt, - xData = this.xData, - yData = this.yData, - pointArrayMap = series.pointArrayMap, - valueCount = pointArrayMap && pointArrayMap.length; - - data = data || []; - dataLength = data.length; - redraw = pick(redraw, true); - - // If the point count is the same as is was, just run Point.update which is - // cheaper, allows animation, and keeps references to points. - if (updatePoints !== false && dataLength && oldDataLength === dataLength && !series.cropped && !series.hasGroupedData && series.visible) { - each(data, function (point, i) { - // .update doesn't exist on a linked, hidden series (#3709) - if (oldData[i].update && point !== options.data[i]) { - oldData[i].update(point, false, null, false); - } - }); - - } else { - - // Reset properties - series.xIncrement = null; - - series.colorCounter = 0; // for series with colorByPoint (#1547) - - // Update parallel arrays - each(this.parallelArrays, function (key) { - series[key + 'Data'].length = 0; - }); - - // In turbo mode, only one- or twodimensional arrays of numbers are allowed. The - // first value is tested, and we assume that all the rest are defined the same - // way. Although the 'for' loops are similar, they are repeated inside each - // if-else conditional for max performance. - if (turboThreshold && dataLength > turboThreshold) { - - // find the first non-null point - i = 0; - while (firstPoint === null && i < dataLength) { - firstPoint = data[i]; - i++; - } - - - if (isNumber(firstPoint)) { // assume all points are numbers - var x = pick(options.pointStart, 0), - pointInterval = pick(options.pointInterval, 1); - - for (i = 0; i < dataLength; i++) { - xData[i] = x; - yData[i] = data[i]; - x += pointInterval; - } - series.xIncrement = x; - } else if (isArray(firstPoint)) { // assume all points are arrays - if (valueCount) { // [x, low, high] or [x, o, h, l, c] - for (i = 0; i < dataLength; i++) { - pt = data[i]; - xData[i] = pt[0]; - yData[i] = pt.slice(1, valueCount + 1); - } - } else { // [x, y] - for (i = 0; i < dataLength; i++) { - pt = data[i]; - xData[i] = pt[0]; - yData[i] = pt[1]; - } - } - } else { - error(12); // Highcharts expects configs to be numbers or arrays in turbo mode - } - } else { - for (i = 0; i < dataLength; i++) { - if (data[i] !== UNDEFINED) { // stray commas in oldIE - pt = { series: series }; - series.pointClass.prototype.applyOptions.apply(pt, [data[i]]); - series.updateParallelArrays(pt, i); - if (hasCategories && defined(pt.name)) { // #4401 - xAxis.names[pt.x] = pt.name; // #2046 - } - } - } - } - - // Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON - if (isString(yData[0])) { - error(14, true); - } - - series.data = []; - series.options.data = series.userOptions.data = data; - - // destroy old points - i = oldDataLength; - while (i--) { - if (oldData[i] && oldData[i].destroy) { - oldData[i].destroy(); - } - } - - // reset minRange (#878) - if (xAxis) { - xAxis.minRange = xAxis.userMinRange; - } - - // redraw - series.isDirty = series.isDirtyData = chart.isDirtyBox = true; - animation = false; - } - - // Typically for pie series, points need to be processed and generated - // prior to rendering the legend - if (options.legendType === 'point') { - this.processData(); - this.generatePoints(); - } - - if (redraw) { - chart.redraw(animation); - } - }, - - /** - * Process the data by cropping away unused data points if the series is longer - * than the crop threshold. This saves computing time for lage series. - */ - 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, - getExtremesFromAll = series.getExtremesFromAll || options.getExtremesFromAll, // #4599 - isCartesian = series.isCartesian, - xExtremes, - val2lin = xAxis && xAxis.val2lin, - isLog = xAxis && xAxis.isLog, - min, - max; - - // 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; - } - - if (xAxis) { - xExtremes = xAxis.getExtremes(); // corrected for log axis (#3053) - min = xExtremes.min; - max = xExtremes.max; - } - - // optionally filter out points outside the plot area - if (isCartesian && series.sorted && !getExtremesFromAll && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) { - - // it's outside current extremes - if (processedXData[dataLength - 1] < min || processedXData[0] > max) { - processedXData = []; - processedYData = []; - - // only crop if it's actually spilling out - } else if (processedXData[0] < min || processedXData[dataLength - 1] > max) { - croppedData = this.cropData(series.xData, series.yData, min, max); - processedXData = croppedData.xData; - processedYData = croppedData.yData; - cropStart = croppedData.start; - cropped = true; - } - } - - - // Find the closest distance between processed points - i = processedXData.length || 1; - while (--i) { - distance = isLog ? - val2lin(processedXData[i]) - val2lin(processedXData[i - 1]) : - 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; - - series.closestPointRange = closestPointRange; - - }, - - /** - * Iterate over xData and crop values between min and max. Returns object containing crop start/end - * cropped xData with corresponding part of yData, dataMin and dataMax within the cropped range - */ - cropData: function (xData, yData, min, max) { - var dataLength = xData.length, - cropStart = 0, - cropEnd = dataLength, - cropShoulder = pick(this.cropShoulder, 1), // line-type series need one point outside - i, - j; - - // iterate up to find slice start - for (i = 0; i < dataLength; i++) { - if (xData[i] >= min) { - cropStart = mathMax(0, i - cropShoulder); - break; - } - } - - // proceed to find slice end - for (j = i; j < dataLength; j++) { - if (xData[j] > max) { - cropEnd = j + cropShoulder; - break; - } - } - - return { - xData: xData.slice(cropStart, cropEnd), - yData: yData.slice(cropStart, cropEnd), - start: cropStart, - end: cropEnd - }; - }, - - - /** - * Generate the data point after the data has been processed by cropping away - * unused points and optionally grouped in Highcharts Stock. - */ - generatePoints: function () { - var series = this, - options = series.options, - dataOptions = options.data, - data = series.data, - dataLength, - processedXData = series.processedXData, - processedYData = series.processedYData, - pointClass = series.pointClass, - processedDataLength = processedXData.length, - cropStart = series.cropStart || 0, - cursor, - hasGroupedData = series.hasGroupedData, - point, - points = [], - i; - - if (!data && !hasGroupedData) { - var arr = []; - arr.length = dataOptions.length; - data = series.data = arr; - } - - for (i = 0; i < processedDataLength; i++) { - cursor = cropStart + i; - if (!hasGroupedData) { - if (data[cursor]) { - point = data[cursor]; - } else if (dataOptions[cursor] !== UNDEFINED) { // #970 - data[cursor] = point = (new pointClass()).init(series, dataOptions[cursor], processedXData[i]); - } - points[i] = point; - } else { - // splat the y data in case of ohlc data array - points[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i]))); - points[i].dataGroup = series.groupMap[i]; - } - points[i].index = cursor; // For faster access in Point.update - } - - // Hide cropped-away points - this only runs when the number of points is above cropThreshold, or when - // swithching view from non-grouped data to grouped data (#637) - if (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) { - for (i = 0; i < dataLength; i++) { - if (i === cropStart && !hasGroupedData) { // when has grouped data, clear all points - i += processedDataLength; - } - if (data[i]) { - data[i].destroyElements(); - data[i].plotX = UNDEFINED; // #1003 - } - } - } - - series.data = data; - series.points = points; - }, - - /** - * Calculate Y extremes for visible data - */ - getExtremes: function (yData) { - var xAxis = this.xAxis, - yAxis = this.yAxis, - xData = this.processedXData, - yDataLength, - activeYData = [], - activeCounter = 0, - xExtremes = xAxis.getExtremes(), // #2117, need to compensate for log X axis - xMin = xExtremes.min, - xMax = xExtremes.max, - validValue, - withinRange, - x, - y, - i, - j; - - yData = yData || this.stackedYData || this.processedYData || []; - yDataLength = yData.length; - - for (i = 0; i < yDataLength; i++) { - - x = xData[i]; - y = yData[i]; - - // For points within the visible range, including the first point outside the - // visible range, consider y extremes - validValue = y !== null && y !== UNDEFINED && (!yAxis.isLog || (y.length || y > 0)); - withinRange = this.getExtremesFromAll || this.options.getExtremesFromAll || this.cropped || - ((xData[i + 1] || x) >= xMin && (xData[i - 1] || x) <= xMax); - - if (validValue && withinRange) { - - j = y.length; - if (j) { // array, like ohlc or range data - while (j--) { - if (y[j] !== null) { - activeYData[activeCounter++] = y[j]; - } - } - } else { - activeYData[activeCounter++] = y; - } - } - } - this.dataMin = arrayMin(activeYData); - this.dataMax = arrayMax(activeYData); - }, - - /** - * Translate data points from raw data values to chart specific positioning data - * needed later in drawPoints, drawGraph and drawTracker. - */ - translate: function () { - if (!this.processedXData) { // hidden series - this.processData(); - } - this.generatePoints(); - var series = this, - options = series.options, - stacking = options.stacking, - xAxis = series.xAxis, - categories = xAxis.categories, - yAxis = series.yAxis, - points = series.points, - dataLength = points.length, - hasModifyValue = !!series.modifyValue, - i, - pointPlacement = options.pointPlacement, - dynamicallyPlaced = pointPlacement === 'between' || isNumber(pointPlacement), - threshold = options.threshold, - stackThreshold = options.startFromThreshold ? threshold : 0, - plotX, - plotY, - lastPlotX, - stackIndicator, - closestPointRangePx = Number.MAX_VALUE; - - // Translate each point - for (i = 0; i < dataLength; i++) { - var point = points[i], - xValue = point.x, - yValue = point.y, - yBottom = point.low, - stack = stacking && yAxis.stacks[(series.negStacks && yValue < (stackThreshold ? 0 : threshold) ? '-' : '') + series.stackKey], - pointStack, - stackValues; - - // Discard disallowed y values for log axes (#3434) - if (yAxis.isLog && yValue !== null && yValue <= 0) { - point.y = yValue = null; - error(10); - } - - // Get the plotX translation - point.plotX = plotX = correctFloat( // #5236 - mathMin(mathMax(-1e5, xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement, this.type === 'flags')), 1e5) // #3923 - ); - - // Calculate the bottom y value for stacked series - if (stacking && series.visible && !point.isNull && stack && stack[xValue]) { - stackIndicator = series.getStackIndicator(stackIndicator, xValue, series.index); - pointStack = stack[xValue]; - stackValues = pointStack.points[stackIndicator.key]; - yBottom = stackValues[0]; - yValue = stackValues[1]; - - if (yBottom === stackThreshold) { - yBottom = pick(threshold, yAxis.min); - } - if (yAxis.isLog && yBottom <= 0) { // #1200, #1232 - yBottom = null; - } - - point.total = point.stackTotal = pointStack.total; - point.percentage = pointStack.total && (point.y / pointStack.total * 100); - point.stackY = yValue; - - // Place the stack label - pointStack.setOffset(series.pointXOffset || 0, series.barW || 0); - - } - - // Set translated yBottom or remove it - point.yBottom = defined(yBottom) ? - yAxis.translate(yBottom, 0, 1, 0, 1) : - null; - - // general hook, used for Highstock compare mode - if (hasModifyValue) { - yValue = series.modifyValue(yValue, point); - } - - // Set the the plotY value, reset it for redraws - point.plotY = plotY = (typeof yValue === 'number' && yValue !== Infinity) ? - mathMin(mathMax(-1e5, yAxis.translate(yValue, 0, 1, 0, 1)), 1e5) : // #3201 - UNDEFINED; - point.isInside = plotY !== UNDEFINED && plotY >= 0 && plotY <= yAxis.len && // #3519 - plotX >= 0 && plotX <= xAxis.len; - - - // Set client related positions for mouse tracking - point.clientX = dynamicallyPlaced ? xAxis.translate(xValue, 0, 0, 0, 1) : plotX; // #1514 - - point.negative = point.y < (threshold || 0); - - // some API data - point.category = categories && categories[point.x] !== UNDEFINED ? - categories[point.x] : point.x; - - // Determine auto enabling of markers (#3635, #5099) - if (!point.isNull) { - if (lastPlotX !== undefined) { - closestPointRangePx = mathMin(closestPointRangePx, mathAbs(plotX - lastPlotX)); - } - lastPlotX = plotX; - } - - } - series.closestPointRangePx = closestPointRangePx; - }, - - /** - * Return the series points with null points filtered out - */ - getValidPoints: function (points, insideOnly) { - var chart = this.chart; - return grep(points || this.points || [], function isValidPoint(point) { // #3916, #5029 - if (insideOnly && !chart.isInsidePlot(point.plotX, point.plotY, chart.inverted)) { // #5085 - return false; - } - return !point.isNull; - }); - }, - - /** - * Set the clipping for the series. For animated series it is called twice, first to initiate - * animating the clip then the second time without the animation to set the final clip. - */ - setClip: function (animation) { - var chart = this.chart, - options = this.options, - renderer = chart.renderer, - inverted = chart.inverted, - seriesClipBox = this.clipBox, - clipBox = seriesClipBox || chart.clipBox, - sharedClipKey = this.sharedClipKey || ['_sharedClip', animation && animation.duration, animation && animation.easing, clipBox.height, options.xAxis, options.yAxis].join(','), // #4526 - clipRect = chart[sharedClipKey], - markerClipRect = chart[sharedClipKey + 'm']; - - // If a clipping rectangle with the same properties is currently present in the chart, use that. - if (!clipRect) { - - // When animation is set, prepare the initial positions - if (animation) { - clipBox.width = 0; - - chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect( - -99, // include the width of the first marker - inverted ? -chart.plotLeft : -chart.plotTop, - 99, - inverted ? chart.chartWidth : chart.chartHeight - ); - } - chart[sharedClipKey] = clipRect = renderer.clipRect(clipBox); - - } - if (animation) { - clipRect.count += 1; - } - - if (options.clip !== false) { - this.group.clip(animation || seriesClipBox ? clipRect : chart.clipRect); - this.markerGroup.clip(markerClipRect); - this.sharedClipKey = sharedClipKey; - } - - // Remove the shared clipping rectangle when all series are shown - if (!animation) { - clipRect.count -= 1; - if (clipRect.count <= 0 && sharedClipKey && chart[sharedClipKey]) { - if (!seriesClipBox) { - chart[sharedClipKey] = chart[sharedClipKey].destroy(); - } - if (chart[sharedClipKey + 'm']) { - chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy(); - } - } - } - }, - - /** - * Animate in the series - */ - animate: function (init) { - var series = this, - chart = series.chart, - clipRect, - animation = series.options.animation, - sharedClipKey; - - // Animation option is set to true - if (animation && !isObject(animation)) { - animation = defaultPlotOptions[series.type].animation; - } - - // Initialize the animation. Set up the clipping rectangle. - if (init) { - - series.setClip(animation); - - // Run the animation - } else { - sharedClipKey = this.sharedClipKey; - clipRect = chart[sharedClipKey]; - if (clipRect) { - clipRect.animate({ - width: chart.plotSizeX - }, animation); - } - if (chart[sharedClipKey + 'm']) { - chart[sharedClipKey + 'm'].animate({ - width: chart.plotSizeX + 99 - }, animation); - } - - // Delete this function to allow it only once - series.animate = null; - - } - }, - - /** - * This runs after animation to land on the final plot clipping - */ - afterAnimate: function () { - this.setClip(); - fireEvent(this, 'afterAnimate'); - }, - - /** - * Draw the markers - */ - drawPoints: function () { - var series = this, - pointAttr, - points = series.points, - chart = series.chart, - plotX, - plotY, - i, - point, - radius, - symbol, - isImage, - graphic, - options = series.options, - seriesMarkerOptions = options.marker, - seriesPointAttr = series.pointAttr[''], - pointMarkerOptions, - hasPointMarker, - enabled, - isInside, - markerGroup = series.markerGroup, - xAxis = series.xAxis, - globallyEnabled = pick( - seriesMarkerOptions.enabled, - xAxis.isRadial, - series.closestPointRangePx > 2 * seriesMarkerOptions.radius - ); - - if (seriesMarkerOptions.enabled !== false || series._hasPointMarkers) { - - i = points.length; - while (i--) { - point = points[i]; - plotX = mathFloor(point.plotX); // #1843 - plotY = point.plotY; - graphic = point.graphic; - pointMarkerOptions = point.marker || {}; - hasPointMarker = !!point.marker; - enabled = (globallyEnabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled; - isInside = point.isInside; - - // only draw the point if y is defined - if (enabled && isNumber(plotY) && point.y !== null) { - - // shortcuts - pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE] || seriesPointAttr; - radius = pointAttr.r; - symbol = pick(pointMarkerOptions.symbol, series.symbol); - isImage = symbol.indexOf('url') === 0; - - if (graphic) { // update - graphic[isInside ? 'show' : 'hide'](true) // Since the marker group isn't clipped, each individual marker must be toggled - .attr(pointAttr) // #4759 - .animate(extend({ - x: plotX - radius, - y: plotY - radius - }, graphic.symbolName ? { // don't apply to image symbols #507 - width: 2 * radius, - height: 2 * radius - } : {})); - } else if (isInside && (radius > 0 || isImage)) { - point.graphic = graphic = chart.renderer.symbol( - symbol, - plotX - radius, - plotY - radius, - 2 * radius, - 2 * radius, - hasPointMarker ? pointMarkerOptions : seriesMarkerOptions - ) - .attr(pointAttr) - .add(markerGroup); - } - - } else if (graphic) { - point.graphic = graphic.destroy(); // #1269 - } - } - } - - }, - - /** - * Convert state properties from API naming conventions to SVG attributes - * - * @param {Object} options API options object - * @param {Object} base1 SVG attribute object to inherit from - * @param {Object} base2 Second level SVG attribute object to inherit from - */ - convertAttribs: function (options, base1, base2, base3) { - var conversion = this.pointAttrToOptions, - attr, - option, - obj = {}; - - options = options || {}; - base1 = base1 || {}; - base2 = base2 || {}; - base3 = base3 || {}; - - for (attr in conversion) { - option = conversion[attr]; - obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]); - } - return obj; - }, - - /** - * Get the state attributes. Each series type has its own set of attributes - * that are allowed to change on a point's state change. Series wide attributes are stored for - * all series, and additionally point specific attributes are stored for all - * points with individual marker options. If such options are not defined for the point, - * a reference to the series wide attributes is stored in point.pointAttr. - */ - getAttribs: function () { - var series = this, - seriesOptions = series.options, - normalOptions = defaultPlotOptions[series.type].marker ? seriesOptions.marker : seriesOptions, - stateOptions = normalOptions.states, - stateOptionsHover = stateOptions[HOVER_STATE], - pointStateOptionsHover, - seriesColor = series.color, - seriesNegativeColor = series.options.negativeColor, - normalDefaults = { - stroke: seriesColor, - fill: seriesColor - }, - points = series.points || [], // #927 - i, - j, - threshold, - point, - seriesPointAttr = [], - pointAttr, - pointAttrToOptions = series.pointAttrToOptions, - hasPointSpecificOptions = series.hasPointSpecificOptions, - defaultLineColor = normalOptions.lineColor, - defaultFillColor = normalOptions.fillColor, - turboThreshold = seriesOptions.turboThreshold, - zones = series.zones, - zoneAxis = series.zoneAxis || 'y', - zoneColor, - attr, - key; - - // series type specific modifications - if (seriesOptions.marker) { // line, spline, area, areaspline, scatter - - // if no hover radius is given, default to normal radius + 2 - stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + stateOptionsHover.radiusPlus; - stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + stateOptionsHover.lineWidthPlus; - - } else { // column, bar, pie - - // if no hover color is given, brighten the normal color - stateOptionsHover.color = stateOptionsHover.color || - Color(stateOptionsHover.color || seriesColor) - .brighten(stateOptionsHover.brightness).get(); - - // if no hover negativeColor is given, brighten the normal negativeColor - stateOptionsHover.negativeColor = stateOptionsHover.negativeColor || - Color(stateOptionsHover.negativeColor || seriesNegativeColor) - .brighten(stateOptionsHover.brightness).get(); - } - - // general point attributes for the series normal state - seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults); - - // HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius - each([HOVER_STATE, SELECT_STATE], function (state) { - seriesPointAttr[state] = - series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]); - }); - - // set it - series.pointAttr = seriesPointAttr; - - - // Generate the point-specific attribute collections if specific point - // options are given. If not, create a referance to the series wide point - // attributes - i = points.length; - if (!turboThreshold || i < turboThreshold || hasPointSpecificOptions) { - while (i--) { - point = points[i]; - normalOptions = (point.options && point.options.marker) || point.options; - if (normalOptions && normalOptions.enabled === false) { - normalOptions.radius = 0; - } - - zoneColor = null; - if (zones.length) { - j = 0; - threshold = zones[j]; - while (point[zoneAxis] >= threshold.value) { - threshold = zones[++j]; - } - - point.color = point.fillColor = zoneColor = pick(threshold.color, series.color); // #3636, #4267, #4430 - inherit color from series, when color is undefined - - } - - hasPointSpecificOptions = seriesOptions.colorByPoint || point.color; // #868 - - // check if the point has specific visual options - if (point.options) { - for (key in pointAttrToOptions) { - if (defined(normalOptions[pointAttrToOptions[key]])) { - hasPointSpecificOptions = true; - } - } - } - - // a specific marker config object is defined for the individual point: - // create it's own attribute collection - if (hasPointSpecificOptions) { - normalOptions = normalOptions || {}; - pointAttr = []; - stateOptions = normalOptions.states || {}; // reassign for individual point - pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {}; - - // Handle colors for column and pies - if (!seriesOptions.marker || (point.negative && !pointStateOptionsHover.fillColor && !stateOptionsHover.fillColor)) { // column, bar, point or negative threshold for series with markers (#3636) - // If no hover color is given, brighten the normal color. #1619, #2579 - pointStateOptionsHover[series.pointAttrToOptions.fill] = pointStateOptionsHover.color || (!point.options.color && stateOptionsHover[(point.negative && seriesNegativeColor ? 'negativeColor' : 'color')]) || - Color(point.color) - .brighten(pointStateOptionsHover.brightness || stateOptionsHover.brightness) - .get(); - } - - // normal point state inherits series wide normal state - attr = { color: point.color }; // #868 - if (!defaultFillColor) { // Individual point color or negative color markers (#2219) - attr.fillColor = point.color; - } - if (!defaultLineColor) { - attr.lineColor = point.color; // Bubbles take point color, line markers use white - } - // Color is explicitly set to null or undefined (#1288, #4068) - if (normalOptions.hasOwnProperty('color') && !normalOptions.color) { - delete normalOptions.color; - } - - // When zone is set, but series.states.hover.color is not set, apply zone color on hover, #4670: - if (zoneColor && !stateOptionsHover.fillColor) { - pointStateOptionsHover.fillColor = zoneColor; - } - - pointAttr[NORMAL_STATE] = series.convertAttribs(extend(attr, normalOptions), seriesPointAttr[NORMAL_STATE]); - - // inherit from point normal and series hover - pointAttr[HOVER_STATE] = series.convertAttribs( - stateOptions[HOVER_STATE], - seriesPointAttr[HOVER_STATE], - pointAttr[NORMAL_STATE] - ); - - // inherit from point normal and series hover - pointAttr[SELECT_STATE] = series.convertAttribs( - stateOptions[SELECT_STATE], - seriesPointAttr[SELECT_STATE], - pointAttr[NORMAL_STATE] - ); - - - // no marker config object is created: copy a reference to the series-wide - // attribute collection - } else { - pointAttr = seriesPointAttr; - } - - point.pointAttr = pointAttr; - } - } - }, - - /** - * Clear DOM objects and free up memory - */ - destroy: function () { - var series = this, - chart = series.chart, - issue134 = /AppleWebKit\/533/.test(userAgent), - destroy, - i, - data = series.data || [], - point, - prop, - axis; - - // add event hook - fireEvent(series, 'destroy'); - - // remove all events - removeEvent(series); - - // erase from axes - each(series.axisTypes || [], function (AXIS) { - axis = series[AXIS]; - if (axis) { - erase(axis.series, series); - axis.isDirty = axis.forceRedraw = true; - } - }); - - // remove legend items - if (series.legendItem) { - series.chart.legend.destroyItem(series); - } - - // destroy all points with their elements - i = data.length; - while (i--) { - point = data[i]; - if (point && point.destroy) { - point.destroy(); - } - } - series.points = null; - - // Clear the animation timeout if we are destroying the series during initial animation - clearTimeout(series.animationTimeout); - - // Destroy all SVGElements associated to the series - for (prop in series) { - if (series[prop] instanceof SVGElement && !series[prop].survive) { // Survive provides a hook for not destroying - - // issue 134 workaround - destroy = issue134 && prop === 'group' ? - 'hide' : - 'destroy'; - - series[prop][destroy](); - } - } - - // remove from hoverSeries - if (chart.hoverSeries === series) { - chart.hoverSeries = null; - } - erase(chart.series, series); - - // clear all members - for (prop in series) { - delete series[prop]; - } - }, - - /** - * Get the graph path - */ - getGraphPath: function (points, nullsAsZeroes, connectCliffs) { - var series = this, - options = series.options, - step = options.step, - reversed, - graphPath = [], - gap; - - points = points || series.points; - - // Bottom of a stack is reversed - reversed = points.reversed; - if (reversed) { - points.reverse(); - } - // Reverse the steps (#5004) - step = { right: 1, center: 2 }[step] || (step && 3); - if (step && reversed) { - step = 4 - step; - } - - // Remove invalid points, especially in spline (#5015) - if (options.connectNulls && !nullsAsZeroes && !connectCliffs) { - points = this.getValidPoints(points); - } - - // Build the line - each(points, function (point, i) { - - var plotX = point.plotX, - plotY = point.plotY, - lastPoint = points[i - 1], - pathToPoint; // the path to this point from the previous - - if ((point.leftCliff || (lastPoint && lastPoint.rightCliff)) && !connectCliffs) { - gap = true; // ... and continue - } - - // Line series, nullsAsZeroes is not handled - if (point.isNull && !defined(nullsAsZeroes) && i > 0) { - gap = !options.connectNulls; - - // Area series, nullsAsZeroes is set - } else if (point.isNull && !nullsAsZeroes) { - gap = true; - - } else { - - if (i === 0 || gap) { - pathToPoint = [M, point.plotX, point.plotY]; - - } else if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object - - pathToPoint = series.getPointSpline(points, point, i); - - } else if (step) { - - if (step === 1) { // right - pathToPoint = [ - L, - lastPoint.plotX, - plotY - ]; - - } else if (step === 2) { // center - pathToPoint = [ - L, - (lastPoint.plotX + plotX) / 2, - lastPoint.plotY, - L, - (lastPoint.plotX + plotX) / 2, - plotY - ]; - - } else { - pathToPoint = [ - L, - plotX, - lastPoint.plotY - ]; - } - pathToPoint.push(L, plotX, plotY); - - } else { - // normal line to next point - pathToPoint = [ - L, - plotX, - plotY - ]; - } - - - graphPath.push.apply(graphPath, pathToPoint); - gap = false; - } - }); - - series.graphPath = graphPath; - - return graphPath; - - }, - - /** - * Draw the actual graph - */ - drawGraph: function () { - var series = this, - options = this.options, - props = [['graph', options.lineColor || this.color, options.dashStyle]], - lineWidth = options.lineWidth, - roundCap = options.linecap !== 'square', - graphPath = (this.gappedPath || this.getGraphPath).call(this), - fillColor = (this.fillGraph && this.color) || NONE, // polygon series use filled graph - zones = this.zones; - - each(zones, function (threshold, i) { - props.push(['zoneGraph' + i, threshold.color || series.color, threshold.dashStyle || options.dashStyle]); - }); - - // Draw the graph - each(props, function (prop, i) { - var graphKey = prop[0], - graph = series[graphKey], - attribs; - - if (graph) { - graph.animate({ d: graphPath }); - - } else if ((lineWidth || fillColor) && graphPath.length) { // #1487 - attribs = { - stroke: prop[1], - 'stroke-width': lineWidth, - fill: fillColor, - zIndex: 1 // #1069 - }; - if (prop[2]) { - attribs.dashstyle = prop[2]; - } else if (roundCap) { - attribs['stroke-linecap'] = attribs['stroke-linejoin'] = 'round'; - } - - series[graphKey] = series.chart.renderer.path(graphPath) - .attr(attribs) - .add(series.group) - .shadow((i < 2) && options.shadow); // add shadow to normal series (0) or to first zone (1) #3932 - } - }); - }, - - /** - * Clip the graphs into the positive and negative coloured graphs - */ - applyZones: function () { - var series = this, - chart = this.chart, - renderer = chart.renderer, - zones = this.zones, - translatedFrom, - translatedTo, - clips = this.clips || [], - clipAttr, - graph = this.graph, - area = this.area, - chartSizeMax = mathMax(chart.chartWidth, chart.chartHeight), - axis = this[(this.zoneAxis || 'y') + 'Axis'], - extremes, - reversed = axis.reversed, - inverted = chart.inverted, - horiz = axis.horiz, - pxRange, - pxPosMin, - pxPosMax, - ignoreZones = false; - - if (zones.length && (graph || area) && axis.min !== UNDEFINED) { - // The use of the Color Threshold assumes there are no gaps - // so it is safe to hide the original graph and area - if (graph) { - graph.hide(); - } - if (area) { - area.hide(); - } - - // Create the clips - extremes = axis.getExtremes(); - each(zones, function (threshold, i) { - - translatedFrom = reversed ? - (horiz ? chart.plotWidth : 0) : - (horiz ? 0 : axis.toPixels(extremes.min)); - translatedFrom = mathMin(mathMax(pick(translatedTo, translatedFrom), 0), chartSizeMax); - translatedTo = mathMin(mathMax(mathRound(axis.toPixels(pick(threshold.value, extremes.max), true)), 0), chartSizeMax); - - if (ignoreZones) { - translatedFrom = translatedTo = axis.toPixels(extremes.max); - } - - pxRange = Math.abs(translatedFrom - translatedTo); - pxPosMin = mathMin(translatedFrom, translatedTo); - pxPosMax = mathMax(translatedFrom, translatedTo); - if (axis.isXAxis) { - clipAttr = { - x: inverted ? pxPosMax : pxPosMin, - y: 0, - width: pxRange, - height: chartSizeMax - }; - if (!horiz) { - clipAttr.x = chart.plotHeight - clipAttr.x; - } - } else { - clipAttr = { - x: 0, - y: inverted ? pxPosMax : pxPosMin, - width: chartSizeMax, - height: pxRange - }; - if (horiz) { - clipAttr.y = chart.plotWidth - clipAttr.y; - } - } - - /// VML SUPPPORT - if (chart.inverted && renderer.isVML) { - if (axis.isXAxis) { - clipAttr = { - x: 0, - y: reversed ? pxPosMin : pxPosMax, - height: clipAttr.width, - width: chart.chartWidth - }; - } else { - clipAttr = { - x: clipAttr.y - chart.plotLeft - chart.spacingBox.x, - y: 0, - width: clipAttr.height, - height: chart.chartHeight - }; - } - } - /// END OF VML SUPPORT - - if (clips[i]) { - clips[i].animate(clipAttr); - } else { - clips[i] = renderer.clipRect(clipAttr); - - if (graph) { - series['zoneGraph' + i].clip(clips[i]); - } - - if (area) { - series['zoneArea' + i].clip(clips[i]); - } - } - // if this zone extends out of the axis, ignore the others - ignoreZones = threshold.value > extremes.max; - }); - this.clips = clips; - } - }, - - /** - * Initialize and perform group inversion on series.group and series.markerGroup - */ - invertGroups: function () { - var series = this, - chart = series.chart; - - // Pie, go away (#1736) - if (!series.xAxis) { - return; - } - - // A fixed size is needed for inversion to work - function setInvert() { - var size = { - width: series.yAxis.len, - height: series.xAxis.len - }; - - each(['group', 'markerGroup'], function (groupName) { - if (series[groupName]) { - series[groupName].attr(size).invert(); - } - }); - } - - addEvent(chart, 'resize', setInvert); // do it on resize - addEvent(series, 'destroy', function () { - removeEvent(chart, 'resize', setInvert); - }); - - // Do it now - setInvert(); // do it now - - // On subsequent render and redraw, just do setInvert without setting up events again - series.invertGroups = setInvert; - }, - - /** - * General abstraction for creating plot groups like series.group, series.dataLabelsGroup and - * series.markerGroup. On subsequent calls, the group will only be adjusted to the updated plot size. - */ - plotGroup: function (prop, name, visibility, zIndex, parent) { - var group = this[prop], - isNew = !group; - - // Generate it on first call - if (isNew) { - this[prop] = group = this.chart.renderer.g(name) - .attr({ - zIndex: zIndex || 0.1 // IE8 and pointer logic use this - }) - .add(parent); - - group.addClass('highcharts-series-' + this.index); - } - - // Place it on first and subsequent (redraw) calls - group.attr({ visibility: visibility })[isNew ? 'attr' : 'animate'](this.getPlotBox()); - return group; - }, - - /** - * Get the translation and scale for the plot area of this series - */ - getPlotBox: function () { - var chart = this.chart, - xAxis = this.xAxis, - yAxis = this.yAxis; - - // Swap axes for inverted (#2339) - if (chart.inverted) { - xAxis = yAxis; - yAxis = this.xAxis; - } - return { - translateX: xAxis ? xAxis.left : chart.plotLeft, - translateY: yAxis ? yAxis.top : chart.plotTop, - scaleX: 1, // #1623 - scaleY: 1 - }; - }, - - /** - * Render the graph and markers - */ - render: function () { - var series = this, - chart = series.chart, - group, - options = series.options, - // Animation doesn't work in IE8 quirks when the group div is hidden, - // and looks bad in other oldIE - animDuration = !!series.animate && chart.renderer.isSVG && animObject(options.animation).duration, - visibility = series.visible ? 'inherit' : 'hidden', // #2597 - zIndex = options.zIndex, - hasRendered = series.hasRendered, - chartSeriesGroup = chart.seriesGroup; - - // the group - group = series.plotGroup( - 'group', - 'series', - visibility, - zIndex, - chartSeriesGroup - ); - - series.markerGroup = series.plotGroup( - 'markerGroup', - 'markers', - visibility, - zIndex, - chartSeriesGroup - ); - - // initiate the animation - if (animDuration) { - series.animate(true); - } - - // cache attributes for shapes - series.getAttribs(); - - // SVGRenderer needs to know this before drawing elements (#1089, #1795) - group.inverted = series.isCartesian ? chart.inverted : false; - - // draw the graph if any - if (series.drawGraph) { - series.drawGraph(); - series.applyZones(); - } - - each(series.points, function (point) { - if (point.redraw) { - point.redraw(); - } - }); - - // draw the data labels (inn pies they go before the points) - if (series.drawDataLabels) { - series.drawDataLabels(); - } - - // draw the points - if (series.visible) { - series.drawPoints(); - } - - - // draw the mouse tracking area - if (series.drawTracker && series.options.enableMouseTracking !== false) { - series.drawTracker(); - } - - // Handle inverted series and tracker groups - if (chart.inverted) { - series.invertGroups(); - } - - // Initial clipping, must be defined after inverting groups for VML. Applies to columns etc. (#3839). - if (options.clip !== false && !series.sharedClipKey && !hasRendered) { - group.clip(chart.clipRect); - } - - // Run the animation - if (animDuration) { - series.animate(); - } - - // Call the afterAnimate function on animation complete (but don't overwrite the animation.complete option - // which should be available to the user). - if (!hasRendered) { - series.animationTimeout = syncTimeout(function () { - series.afterAnimate(); - }, animDuration); - } - - series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see - // (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see - series.hasRendered = true; - }, - - /** - * Redraw the series after an update in the axes. - */ - redraw: function () { - var series = this, - chart = series.chart, - wasDirty = series.isDirty || series.isDirtyData, // cache it here as it is set to false in render, but used after - group = series.group, - xAxis = series.xAxis, - yAxis = series.yAxis; - - // reposition on resize - if (group) { - if (chart.inverted) { - group.attr({ - width: chart.plotWidth, - height: chart.plotHeight - }); - } - - group.animate({ - translateX: pick(xAxis && xAxis.left, chart.plotLeft), - translateY: pick(yAxis && yAxis.top, chart.plotTop) - }); - } - - series.translate(); - series.render(); - if (wasDirty) { // #3868, #3945 - delete this.kdTree; - } - }, - - /** - * KD Tree && PointSearching Implementation - */ - - kdDimensions: 1, - kdAxisArray: ['clientX', 'plotY'], - - searchPoint: function (e, compareX) { - var series = this, - xAxis = series.xAxis, - yAxis = series.yAxis, - inverted = series.chart.inverted; - - return this.searchKDTree({ - clientX: inverted ? xAxis.len - e.chartY + xAxis.pos : e.chartX - xAxis.pos, - plotY: inverted ? yAxis.len - e.chartX + yAxis.pos : e.chartY - yAxis.pos - }, compareX); - }, - - buildKDTree: function () { - var series = this, - dimensions = series.kdDimensions; - - // Internal function - function _kdtree(points, depth, dimensions) { - var axis, - median, - length = points && points.length; - - if (length) { - - // alternate between the axis - axis = series.kdAxisArray[depth % dimensions]; - - // sort point array - points.sort(function (a, b) { - return a[axis] - b[axis]; - }); - - median = Math.floor(length / 2); - - // build and return nod - return { - point: points[median], - left: _kdtree(points.slice(0, median), depth + 1, dimensions), - right: _kdtree(points.slice(median + 1), depth + 1, dimensions) - }; - - } - } - - // Start the recursive build process with a clone of the points array and null points filtered out (#3873) - function startRecursive() { - series.kdTree = _kdtree( - series.getValidPoints( - null, - !series.directTouch // For line-type series restrict to plot area, but column-type series not (#3916, #4511) - ), - dimensions, - dimensions - ); - } - delete series.kdTree; - - // For testing tooltips, don't build async - syncTimeout(startRecursive, series.options.kdNow ? 0 : 1); - }, - - searchKDTree: function (point, compareX) { - var series = this, - kdX = this.kdAxisArray[0], - kdY = this.kdAxisArray[1], - kdComparer = compareX ? 'distX' : 'dist'; - - // Set the one and two dimensional distance on the point object - function setDistance(p1, p2) { - var x = (defined(p1[kdX]) && defined(p2[kdX])) ? Math.pow(p1[kdX] - p2[kdX], 2) : null, - y = (defined(p1[kdY]) && defined(p2[kdY])) ? Math.pow(p1[kdY] - p2[kdY], 2) : null, - r = (x || 0) + (y || 0); - - p2.dist = defined(r) ? Math.sqrt(r) : Number.MAX_VALUE; - p2.distX = defined(x) ? Math.sqrt(x) : Number.MAX_VALUE; - } - function _search(search, tree, depth, dimensions) { - var point = tree.point, - axis = series.kdAxisArray[depth % dimensions], - tdist, - sideA, - sideB, - ret = point, - nPoint1, - nPoint2; - - setDistance(search, point); - - // Pick side based on distance to splitting point - tdist = search[axis] - point[axis]; - sideA = tdist < 0 ? 'left' : 'right'; - sideB = tdist < 0 ? 'right' : 'left'; - - // End of tree - if (tree[sideA]) { - nPoint1 = _search(search, tree[sideA], depth + 1, dimensions); - - ret = (nPoint1[kdComparer] < ret[kdComparer] ? nPoint1 : point); - } - if (tree[sideB]) { - // compare distance to current best to splitting point to decide wether to check side B or not - if (Math.sqrt(tdist * tdist) < ret[kdComparer]) { - nPoint2 = _search(search, tree[sideB], depth + 1, dimensions); - ret = (nPoint2[kdComparer] < ret[kdComparer] ? nPoint2 : ret); - } - } - - return ret; - } - - if (!this.kdTree) { - this.buildKDTree(); - } - - if (this.kdTree) { - return _search(point, - this.kdTree, this.kdDimensions, this.kdDimensions); - } - } - - }; // end Series prototype - - /** - * The class for stack items - */ - function StackItem(axis, options, isNegative, x, stackOption) { - - var inverted = axis.chart.inverted; - - this.axis = axis; - - // Tells if the stack is negative - this.isNegative = isNegative; - - // Save the options to be able to style the label - this.options = options; - - // Save the x value to be able to position the label later - this.x = x; - - // Initialize total value - this.total = null; - - // This will keep each points' extremes stored by series.index and point index - this.points = {}; - - // Save the stack option on the series configuration object, and whether to treat it as percent - this.stack = stackOption; - this.leftCliff = 0; - this.rightCliff = 0; - - // The align options and text align varies on whether the stack is negative and - // if the chart is inverted or not. - // First test the user supplied value, then use the dynamic. - this.alignOptions = { - align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'), - verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')), - y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)), - x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0) - }; - - this.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center'); - } - - StackItem.prototype = { - destroy: function () { - destroyObjectProperties(this, this.axis); - }, - - /** - * Renders the stack total label and adds it to the stack label group. - */ - render: function (group) { - var options = this.options, - formatOption = options.format, - str = formatOption ? - format(formatOption, this) : - options.formatter.call(this); // format the text in the label - - // Change the text to reflect the new total and set visibility to hidden in case the serie is hidden - if (this.label) { - this.label.attr({ text: str, visibility: 'hidden' }); - // Create new label - } else { - this.label = - this.axis.chart.renderer.text(str, null, null, options.useHTML) // dummy positions, actual position updated with setOffset method in columnseries - .css(options.style) // apply style - .attr({ - align: this.textAlign, // fix the text-anchor - rotation: options.rotation, // rotation - visibility: HIDDEN // hidden until setOffset is called - }) - .add(group); // add to the labels-group - } - }, - - /** - * Sets the offset that the stack has from the x value and repositions the label. - */ - setOffset: function (xOffset, xWidth) { - var stackItem = this, - axis = stackItem.axis, - chart = axis.chart, - inverted = chart.inverted, - reversed = axis.reversed, - neg = (this.isNegative && !reversed) || (!this.isNegative && reversed), // #4056 - y = axis.translate(axis.usePercentage ? 100 : this.total, 0, 0, 0, 1), // stack value translated mapped to chart coordinates - yZero = axis.translate(0), // stack origin - h = mathAbs(y - yZero), // stack height - x = chart.xAxis[0].translate(this.x) + xOffset, // stack x position - plotHeight = chart.plotHeight, - stackBox = { // this is the box for the complete stack - x: inverted ? (neg ? y : y - h) : x, - y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y), - width: inverted ? h : xWidth, - height: inverted ? xWidth : h - }, - label = this.label, - alignAttr; - - if (label) { - label.align(this.alignOptions, null, stackBox); // align the label to the box - - // Set visibility (#678) - alignAttr = label.alignAttr; - label[this.options.crop === false || chart.isInsidePlot(alignAttr.x, alignAttr.y) ? 'show' : 'hide'](true); - } - } - }; - - /** - * Generate stacks for each series and calculate stacks total values - */ - Chart.prototype.getStacks = function () { - var chart = this; - - // reset stacks for each yAxis - each(chart.yAxis, function (axis) { - if (axis.stacks && axis.hasVisibleSeries) { - axis.oldStacks = axis.stacks; - } - }); - - each(chart.series, function (series) { - if (series.options.stacking && (series.visible === true || chart.options.chart.ignoreHiddenSeries === false)) { - series.stackKey = series.type + pick(series.options.stack, ''); - } - }); - }; - - - // Stacking methods defined on the Axis prototype - - /** - * Build the stacks from top down - */ - Axis.prototype.buildStacks = function () { - var axisSeries = this.series, - series, - reversedStacks = pick(this.options.reversedStacks, true), - len = axisSeries.length, - i; - if (!this.isXAxis) { - this.usePercentage = false; - i = len; - while (i--) { - axisSeries[reversedStacks ? i : len - i - 1].setStackedPoints(); - } - - i = len; - while (i--) { - series = axisSeries[reversedStacks ? i : len - i - 1]; - if (series.setStackCliffs) { - series.setStackCliffs(); - } - } - // Loop up again to compute percent stack - if (this.usePercentage) { - for (i = 0; i < len; i++) { - axisSeries[i].setPercentStacks(); - } - } - } - }; - - Axis.prototype.renderStackTotals = function () { - var axis = this, - chart = axis.chart, - renderer = chart.renderer, - stacks = axis.stacks, - stackKey, - oneStack, - stackCategory, - stackTotalGroup = axis.stackTotalGroup; - - // Create a separate group for the stack total labels - if (!stackTotalGroup) { - axis.stackTotalGroup = stackTotalGroup = - renderer.g('stack-labels') - .attr({ - visibility: VISIBLE, - zIndex: 6 - }) - .add(); - } - - // plotLeft/Top will change when y axis gets wider so we need to translate the - // stackTotalGroup at every render call. See bug #506 and #516 - stackTotalGroup.translate(chart.plotLeft, chart.plotTop); - - // Render each stack total - for (stackKey in stacks) { - oneStack = stacks[stackKey]; - for (stackCategory in oneStack) { - oneStack[stackCategory].render(stackTotalGroup); - } - } - }; - - /** - * Set all the stacks to initial states and destroy unused ones. - */ - Axis.prototype.resetStacks = function () { - var stacks = this.stacks, - type, - i; - if (!this.isXAxis) { - for (type in stacks) { - for (i in stacks[type]) { - - // Clean up memory after point deletion (#1044, #4320) - if (stacks[type][i].touched < this.stacksTouched) { - stacks[type][i].destroy(); - delete stacks[type][i]; - - // Reset stacks - } else { - stacks[type][i].total = null; - stacks[type][i].cum = 0; - } - } - } - } - }; - - Axis.prototype.cleanStacks = function () { - var stacks, type, i; - - if (!this.isXAxis) { - if (this.oldStacks) { - stacks = this.stacks = this.oldStacks; - } - - // reset stacks - for (type in stacks) { - for (i in stacks[type]) { - stacks[type][i].cum = stacks[type][i].total; - } - } - } - }; - - - // Stacking methods defnied for Series prototype - - /** - * Adds series' points value to corresponding stack - */ - Series.prototype.setStackedPoints = function () { - if (!this.options.stacking || (this.visible !== true && this.chart.options.chart.ignoreHiddenSeries !== false)) { - return; - } - - var series = this, - xData = series.processedXData, - yData = series.processedYData, - stackedYData = [], - yDataLength = yData.length, - seriesOptions = series.options, - threshold = seriesOptions.threshold, - stackThreshold = seriesOptions.startFromThreshold ? threshold : 0, - stackOption = seriesOptions.stack, - stacking = seriesOptions.stacking, - stackKey = series.stackKey, - negKey = '-' + stackKey, - negStacks = series.negStacks, - yAxis = series.yAxis, - stacks = yAxis.stacks, - oldStacks = yAxis.oldStacks, - stackIndicator, - isNegative, - stack, - other, - key, - pointKey, - i, - x, - y; - - - yAxis.stacksTouched += 1; - - // loop over the non-null y values and read them into a local array - for (i = 0; i < yDataLength; i++) { - x = xData[i]; - y = yData[i]; - stackIndicator = series.getStackIndicator(stackIndicator, x, series.index); - pointKey = stackIndicator.key; - // Read stacked values into a stack based on the x value, - // the sign of y and the stack key. Stacking is also handled for null values (#739) - isNegative = negStacks && y < (stackThreshold ? 0 : threshold); - key = isNegative ? negKey : stackKey; - - // Create empty object for this stack if it doesn't exist yet - if (!stacks[key]) { - stacks[key] = {}; - } - - // Initialize StackItem for this x - if (!stacks[key][x]) { - if (oldStacks[key] && oldStacks[key][x]) { - stacks[key][x] = oldStacks[key][x]; - stacks[key][x].total = null; - } else { - stacks[key][x] = new StackItem(yAxis, yAxis.options.stackLabels, isNegative, x, stackOption); - } - } - - // If the StackItem doesn't exist, create it first - stack = stacks[key][x]; - if (y !== null) { - stack.points[pointKey] = stack.points[series.index] = [pick(stack.cum, stackThreshold)]; - stack.touched = yAxis.stacksTouched; - - - // In area charts, if there are multiple points on the same X value, let the - // area fill the full span of those points - if (stackIndicator.index > 0 && series.singleStacks === false) { - stack.points[pointKey][0] = stack.points[series.index + ',' + x + ',0'][0]; - } - } - - // Add value to the stack total - if (stacking === 'percent') { - - // Percent stacked column, totals are the same for the positive and negative stacks - other = isNegative ? stackKey : negKey; - if (negStacks && stacks[other] && stacks[other][x]) { - other = stacks[other][x]; - stack.total = other.total = mathMax(other.total, stack.total) + mathAbs(y) || 0; - - // Percent stacked areas - } else { - stack.total = correctFloat(stack.total + (mathAbs(y) || 0)); - } - } else { - stack.total = correctFloat(stack.total + (y || 0)); - } - - stack.cum = pick(stack.cum, stackThreshold) + (y || 0); - - if (y !== null) { - stack.points[pointKey].push(stack.cum); - stackedYData[i] = stack.cum; - } - - } - - if (stacking === 'percent') { - yAxis.usePercentage = true; - } - - this.stackedYData = stackedYData; // To be used in getExtremes - - // Reset old stacks - yAxis.oldStacks = {}; - }; - - /** - * Iterate over all stacks and compute the absolute values to percent - */ - Series.prototype.setPercentStacks = function () { - var series = this, - stackKey = series.stackKey, - stacks = series.yAxis.stacks, - processedXData = series.processedXData, - stackIndicator; - - each([stackKey, '-' + stackKey], function (key) { - var i = processedXData.length, - x, - stack, - pointExtremes, - totalFactor; - - while (i--) { - x = processedXData[i]; - stackIndicator = series.getStackIndicator(stackIndicator, x, series.index); - stack = stacks[key] && stacks[key][x]; - pointExtremes = stack && stack.points[stackIndicator.key]; - if (pointExtremes) { - totalFactor = stack.total ? 100 / stack.total : 0; - pointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor); // Y bottom value - pointExtremes[1] = correctFloat(pointExtremes[1] * totalFactor); // Y value - series.stackedYData[i] = pointExtremes[1]; - } - } - }); - }; - - /** - * Get stack indicator, according to it's x-value, to determine points with the same x-value - */ - Series.prototype.getStackIndicator = function (stackIndicator, x, index) { - if (!defined(stackIndicator) || stackIndicator.x !== x) { - stackIndicator = { - x: x, - index: 0 - }; - } else { - stackIndicator.index++; - } - - stackIndicator.key = [index, x, stackIndicator.index].join(','); - - return stackIndicator; - }; - - // Extend the Chart prototype for dynamic methods - extend(Chart.prototype, { - - /** - * Add a series dynamically after time - * - * @param {Object} options The config options - * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true. - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - * - * @return {Object} series The newly created series object - */ - addSeries: function (options, redraw, animation) { - var series, - chart = this; - - if (options) { - redraw = pick(redraw, true); // defaults to true - - fireEvent(chart, 'addSeries', { options: options }, function () { - series = chart.initSeries(options); - - chart.isDirtyLegend = true; // the series array is out of sync with the display - chart.linkSeries(); - if (redraw) { - chart.redraw(animation); - } - }); - } - - return series; - }, - - /** - * Add an axis to the chart - * @param {Object} options The axis option - * @param {Boolean} isX Whether it is an X axis or a value axis - */ - addAxis: function (options, isX, redraw, animation) { - var key = isX ? 'xAxis' : 'yAxis', - chartOptions = this.options, - userOptions = merge(options, { - index: this[key].length, - isX: isX - }); - - new Axis(this, userOptions); // eslint-disable-line no-new - - // Push the new axis options to the chart options - chartOptions[key] = splat(chartOptions[key] || {}); - chartOptions[key].push(userOptions); - - if (pick(redraw, true)) { - this.redraw(animation); - } - }, - - /** - * Dim the chart and show a loading text or symbol - * @param {String} str An optional text to show in the loading label instead of the default one - */ - showLoading: function (str) { - var chart = this, - options = chart.options, - loadingDiv = chart.loadingDiv, - loadingOptions = options.loading, - setLoadingSize = function () { - if (loadingDiv) { - css(loadingDiv, { - left: chart.plotLeft + PX, - top: chart.plotTop + PX, - width: chart.plotWidth + PX, - height: chart.plotHeight + PX - }); - } - }; - - // create the layer at the first call - if (!loadingDiv) { - chart.loadingDiv = loadingDiv = createElement(DIV, { - className: PREFIX + 'loading' - }, extend(loadingOptions.style, { - zIndex: 10, - display: NONE - }), chart.container); - - chart.loadingSpan = createElement( - 'span', - null, - loadingOptions.labelStyle, - loadingDiv - ); - addEvent(chart, 'redraw', setLoadingSize); // #1080 - } - - // update text - chart.loadingSpan.innerHTML = str || options.lang.loading; - - // show it - if (!chart.loadingShown) { - css(loadingDiv, { - opacity: 0, - display: '' - }); - animate(loadingDiv, { - opacity: loadingOptions.style.opacity - }, { - duration: loadingOptions.showDuration || 0 - }); - chart.loadingShown = true; - } - setLoadingSize(); - }, - - /** - * Hide the loading layer - */ - hideLoading: function () { - var options = this.options, - loadingDiv = this.loadingDiv; - - if (loadingDiv) { - animate(loadingDiv, { - opacity: 0 - }, { - duration: options.loading.hideDuration || 100, - complete: function () { - css(loadingDiv, { display: NONE }); - } - }); - } - this.loadingShown = false; - } - }); - - // extend the Point prototype for dynamic methods - extend(Point.prototype, { - /** - * Update the point with new options (typically x/y data) and optionally redraw the series. - * - * @param {Object} options Point options as defined in the series.data array - * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - * - */ - update: function (options, redraw, animation, runEvent) { - var point = this, - series = point.series, - graphic = point.graphic, - i, - chart = series.chart, - seriesOptions = series.options, - names = series.xAxis && series.xAxis.names; - - redraw = pick(redraw, true); - - function update() { - - point.applyOptions(options); - - // Update visuals - if (point.y === null && graphic) { // #4146 - point.graphic = graphic.destroy(); - } - if (isObject(options) && !isArray(options)) { - // Defer the actual redraw until getAttribs has been called (#3260) - point.redraw = function () { - if (graphic && graphic.element) { - if (options && options.marker && options.marker.symbol) { - point.graphic = graphic.destroy(); - } - } - if (options && options.dataLabels && point.dataLabel) { // #2468 - point.dataLabel = point.dataLabel.destroy(); - } - point.redraw = null; - }; - } - - // record changes in the parallel arrays - i = point.index; - series.updateParallelArrays(point, i); - if (names && point.name) { - names[point.x] = point.name; - } - - // Record the options to options.data. If there is an object from before, - // use point options, otherwise use raw options. (#4701) - seriesOptions.data[i] = (isObject(seriesOptions.data[i]) && !isArray(seriesOptions.data[i])) ? point.options : options; - - // redraw - series.isDirty = series.isDirtyData = true; - if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320 - chart.isDirtyBox = true; - } - - if (seriesOptions.legendType === 'point') { // #1831, #1885 - chart.isDirtyLegend = true; - } - if (redraw) { - chart.redraw(animation); - } - } - - // Fire the event with a default handler of doing the update - if (runEvent === false) { // When called from setData - update(); - } else { - point.firePointEvent('update', { options: options }, update); - } - }, - - /** - * Remove a point and optionally redraw the series and if necessary the axes - * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - */ - remove: function (redraw, animation) { - this.series.removePoint(inArray(this, this.series.data), redraw, animation); - } - }); - - // Extend the series prototype for dynamic methods - extend(Series.prototype, { - /** - * Add a point dynamically after chart load time - * @param {Object} options Point options as given in series.data - * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call - * @param {Boolean} shift If shift is true, a point is shifted off the start - * of the series as one is appended to the end. - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - */ - addPoint: function (options, redraw, shift, animation) { - var series = this, - seriesOptions = series.options, - data = series.data, - graph = series.graph, - area = series.area, - chart = series.chart, - names = series.xAxis && series.xAxis.names, - currentShift = (graph && graph.shift) || 0, - shiftShapes = ['graph', 'area'], - dataOptions = seriesOptions.data, - point, - isInTheMiddle, - xData = series.xData, - i, - x; - - setAnimation(animation, chart); - - // Make graph animate sideways - if (shift) { - i = series.zones.length; - while (i--) { - shiftShapes.push('zoneGraph' + i, 'zoneArea' + i); - } - each(shiftShapes, function (shape) { - if (series[shape]) { - series[shape].shift = currentShift + (seriesOptions.step ? 2 : 1); - } - }); - } - if (area) { - area.isArea = true; // needed in animation, both with and without shift - } - - // Optional redraw, defaults to true - redraw = pick(redraw, true); - - // Get options and push the point to xData, yData and series.options. In series.generatePoints - // the Point instance will be created on demand and pushed to the series.data array. - point = { series: series }; - series.pointClass.prototype.applyOptions.apply(point, [options]); - x = point.x; - - // Get the insertion point - i = xData.length; - if (series.requireSorting && x < xData[i - 1]) { - isInTheMiddle = true; - while (i && xData[i - 1] > x) { - i--; - } - } - - series.updateParallelArrays(point, 'splice', i, 0, 0); // insert undefined item - series.updateParallelArrays(point, i); // update it - - if (names && point.name) { - names[x] = point.name; - } - dataOptions.splice(i, 0, options); - - if (isInTheMiddle) { - series.data.splice(i, 0, null); - series.processData(); - } - - // Generate points to be added to the legend (#1329) - if (seriesOptions.legendType === 'point') { - series.generatePoints(); - } - - // Shift the first point off the parallel arrays - if (shift) { - if (data[0] && data[0].remove) { - data[0].remove(false); - } else { - data.shift(); - series.updateParallelArrays(point, 'shift'); - - dataOptions.shift(); - } - } - - // redraw - series.isDirty = true; - series.isDirtyData = true; - if (redraw) { - series.getAttribs(); // #1937 - chart.redraw(); - } - }, - - /** - * Remove a point (rendered or not), by index - */ - removePoint: function (i, redraw, animation) { - - var series = this, - data = series.data, - point = data[i], - points = series.points, - chart = series.chart, - remove = function () { - - if (points && points.length === data.length) { // #4935 - points.splice(i, 1); - } - data.splice(i, 1); - series.options.data.splice(i, 1); - series.updateParallelArrays(point || { series: series }, 'splice', i, 1); - - if (point) { - point.destroy(); - } - - // redraw - series.isDirty = true; - series.isDirtyData = true; - if (redraw) { - chart.redraw(); - } - }; - - setAnimation(animation, chart); - redraw = pick(redraw, true); - - // Fire the event with a default handler of removing the point - if (point) { - point.firePointEvent('remove', null, remove); - } else { - remove(); - } - }, - - /** - * Remove a series and optionally redraw the chart - * - * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call - * @param {Boolean|Object} animation Whether to apply animation, and optionally animation - * configuration - */ - remove: function (redraw, animation) { - var series = this, - chart = series.chart; - - // Fire the event with a default handler of removing the point - fireEvent(series, 'remove', null, function () { - - // Destroy elements - series.destroy(); - - // Redraw - chart.isDirtyLegend = chart.isDirtyBox = true; - chart.linkSeries(); - - if (pick(redraw, true)) { - chart.redraw(animation); - } - }); - }, - - /** - * Update the series with a new set of options - */ - update: function (newOptions, redraw) { - var series = this, - chart = this.chart, - // must use user options when changing type because this.options is merged - // in with type specific plotOptions - oldOptions = this.userOptions, - oldType = this.type, - proto = seriesTypes[oldType].prototype, - preserve = ['group', 'markerGroup', 'dataLabelsGroup'], - n; - - // If we're changing type or zIndex, create new groups (#3380, #3404) - if ((newOptions.type && newOptions.type !== oldType) || newOptions.zIndex !== undefined) { - preserve.length = 0; - } - - // Make sure groups are not destroyed (#3094) - each(preserve, function (prop) { - preserve[prop] = series[prop]; - delete series[prop]; - }); - - // Do the merge, with some forced options - newOptions = merge(oldOptions, { - animation: false, - index: this.index, - pointStart: this.xData[0] // when updating after addPoint - }, { data: this.options.data }, newOptions); - - // Destroy the series and delete all properties. Reinsert all methods - // and properties from the new type prototype (#2270, #3719) - this.remove(false); - for (n in proto) { - this[n] = UNDEFINED; - } - extend(this, seriesTypes[newOptions.type || oldType].prototype); - - // Re-register groups (#3094) - each(preserve, function (prop) { - series[prop] = preserve[prop]; - }); - - this.init(chart, newOptions); - chart.linkSeries(); // Links are lost in this.remove (#3028) - if (pick(redraw, true)) { - chart.redraw(false); - } - } - }); - - // Extend the Axis.prototype for dynamic methods - extend(Axis.prototype, { - - /** - * Update the axis with a new options structure - */ - update: function (newOptions, redraw) { - var chart = this.chart; - - newOptions = chart.options[this.coll][this.options.index] = merge(this.userOptions, newOptions); - - this.destroy(true); - this._addedPlotLB = this.chart._labelPanes = UNDEFINED; // #1611, #2887, #4314 - - this.init(chart, extend(newOptions, { events: UNDEFINED })); - - chart.isDirtyBox = true; - if (pick(redraw, true)) { - chart.redraw(); - } - }, - - /** - * Remove the axis from the chart - */ - remove: function (redraw) { - var chart = this.chart, - key = this.coll, // xAxis or yAxis - axisSeries = this.series, - i = axisSeries.length; - - // Remove associated series (#2687) - while (i--) { - if (axisSeries[i]) { - axisSeries[i].remove(false); - } - } - - // Remove the axis - erase(chart.axes, this); - erase(chart[key], this); - chart.options[key].splice(this.options.index, 1); - each(chart[key], function (axis, i) { // Re-index, #1706 - axis.options.index = i; - }); - this.destroy(); - chart.isDirtyBox = true; - - if (pick(redraw, true)) { - chart.redraw(); - } - }, - - /** - * Update the axis title by options - */ - setTitle: function (newTitleOptions, redraw) { - this.update({ title: newTitleOptions }, redraw); - }, - - /** - * Set new axis categories and optionally redraw - * @param {Array} categories - * @param {Boolean} redraw - */ - setCategories: function (categories, redraw) { - this.update({ categories: categories }, redraw); - } - - }); - - - /** - * LineSeries object - */ - var LineSeries = extendClass(Series); - seriesTypes.line = LineSeries; - - /** - * Set the default options for area - */ - defaultPlotOptions.area = merge(defaultSeriesOptions, { - softThreshold: false, - threshold: 0 - // trackByArea: false, - // lineColor: null, // overrides color, but lets fillColor be unaltered - // fillOpacity: 0.75, - // fillColor: null - }); - - /** - * AreaSeries object - */ - var AreaSeries = extendClass(Series, { - type: 'area', - singleStacks: false, - /** - * Return an array of stacked points, where null and missing points are replaced by - * dummy points in order for gaps to be drawn correctly in stacks. - */ - getStackPoints: function () { - var series = this, - segment = [], - keys = [], - xAxis = this.xAxis, - yAxis = this.yAxis, - stack = yAxis.stacks[this.stackKey], - pointMap = {}, - points = this.points, - seriesIndex = series.index, - yAxisSeries = yAxis.series, - seriesLength = yAxisSeries.length, - visibleSeries, - upOrDown = pick(yAxis.options.reversedStacks, true) ? 1 : -1, - i, - x; - - if (this.options.stacking) { - // Create a map where we can quickly look up the points by their X value. - for (i = 0; i < points.length; i++) { - pointMap[points[i].x] = points[i]; - } - - // Sort the keys (#1651) - for (x in stack) { - if (stack[x].total !== null) { // nulled after switching between grouping and not (#1651, #2336) - keys.push(x); - } - } - keys.sort(function (a, b) { - return a - b; - }); - - visibleSeries = map(yAxisSeries, function () { - return this.visible; - }); - - each(keys, function (x, idx) { - var y = 0, - stackPoint, - stackedValues; - - if (pointMap[x] && !pointMap[x].isNull) { - segment.push(pointMap[x]); - - // Find left and right cliff. -1 goes left, 1 goes right. - each([-1, 1], function (direction) { - var nullName = direction === 1 ? 'rightNull' : 'leftNull', - cliffName = direction === 1 ? 'rightCliff' : 'leftCliff', - cliff = 0, - otherStack = stack[keys[idx + direction]]; - - // If there is a stack next to this one, to the left or to the right... - if (otherStack) { - i = seriesIndex; - while (i >= 0 && i < seriesLength) { // Can go either up or down, depending on reversedStacks - stackPoint = otherStack.points[i]; - if (!stackPoint) { - // If the next point in this series is missing, mark the point - // with point.leftNull or point.rightNull = true. - if (i === seriesIndex) { - pointMap[x][nullName] = true; - - // If there are missing points in the next stack in any of the - // series below this one, we need to substract the missing values - // and add a hiatus to the left or right. - } else if (visibleSeries[i]) { - stackedValues = stack[x].points[i]; - if (stackedValues) { - cliff -= stackedValues[1] - stackedValues[0]; - } - } - } - // When reversedStacks is true, loop up, else loop down - i += upOrDown; - } - } - pointMap[x][cliffName] = cliff; - }); - - - // There is no point for this X value in this series, so we - // insert a dummy point in order for the areas to be drawn - // correctly. - } else { - - // Loop down the stack to find the series below this one that has - // a value (#1991) - i = seriesIndex; - while (i >= 0 && i < seriesLength) { - stackPoint = stack[x].points[i]; - if (stackPoint) { - y = stackPoint[1]; - break; - } - // When reversedStacks is true, loop up, else loop down - i += upOrDown; - } - - y = yAxis.toPixels(y, true); - segment.push({ - isNull: true, - plotX: xAxis.toPixels(x, true), - plotY: y, - yBottom: y - }); - } - }); - - } - - return segment; - }, - - getGraphPath: function (points) { - var getGraphPath = Series.prototype.getGraphPath, - graphPath, - options = this.options, - stacking = options.stacking, - yAxis = this.yAxis, - topPath, - //topPoints = [], - bottomPath, - bottomPoints = [], - graphPoints = [], - seriesIndex = this.index, - i, - areaPath, - plotX, - stacks = yAxis.stacks[this.stackKey], - threshold = options.threshold, - translatedThreshold = yAxis.getThreshold(options.threshold), - isNull, - yBottom, - connectNulls = options.connectNulls || stacking === 'percent', - /** - * To display null points in underlying stacked series, this series graph must be - * broken, and the area also fall down to fill the gap left by the null point. #2069 - */ - addDummyPoints = function (i, otherI, side) { - var point = points[i], - stackedValues = stacking && stacks[point.x].points[seriesIndex], - nullVal = point[side + 'Null'] || 0, - cliffVal = point[side + 'Cliff'] || 0, - top, - bottom, - isNull = true; - - if (cliffVal || nullVal) { - - top = (nullVal ? stackedValues[0] : stackedValues[1]) + cliffVal; - bottom = stackedValues[0] + cliffVal; - isNull = !!nullVal; - - } else if (!stacking && points[otherI] && points[otherI].isNull) { - top = bottom = threshold; - } - - // Add to the top and bottom line of the area - if (top !== undefined) { - graphPoints.push({ - plotX: plotX, - plotY: top === null ? translatedThreshold : yAxis.getThreshold(top), - isNull: isNull - }); - bottomPoints.push({ - plotX: plotX, - plotY: bottom === null ? translatedThreshold : yAxis.getThreshold(bottom) - }); - } - }; - - // Find what points to use - points = points || this.points; - - - // Fill in missing points - if (stacking) { - points = this.getStackPoints(); - } - - for (i = 0; i < points.length; i++) { - isNull = points[i].isNull; - plotX = pick(points[i].rectPlotX, points[i].plotX); - yBottom = pick(points[i].yBottom, translatedThreshold); - - if (!isNull || connectNulls) { - - if (!connectNulls) { - addDummyPoints(i, i - 1, 'left'); - } - - if (!(isNull && !stacking && connectNulls)) { // Skip null point when stacking is false and connectNulls true - graphPoints.push(points[i]); - bottomPoints.push({ - x: i, - plotX: plotX, - plotY: yBottom - }); - } - - if (!connectNulls) { - addDummyPoints(i, i + 1, 'right'); - } - } - } - - topPath = getGraphPath.call(this, graphPoints, true, true); - - bottomPoints.reversed = true; - bottomPath = getGraphPath.call(this, bottomPoints, true, true); - if (bottomPath.length) { - bottomPath[0] = L; - } - - areaPath = topPath.concat(bottomPath); - graphPath = getGraphPath.call(this, graphPoints, false, connectNulls); // TODO: don't set leftCliff and rightCliff when connectNulls? - - this.areaPath = areaPath; - return graphPath; - }, - - /** - * Draw the graph and the underlying area. This method calls the Series base - * function and adds the area. The areaPath is calculated in the getSegmentPath - * method called from Series.prototype.drawGraph. - */ - drawGraph: function () { - - // Define or reset areaPath - this.areaPath = []; - - // Call the base method - Series.prototype.drawGraph.apply(this); - - // Define local variables - var series = this, - areaPath = this.areaPath, - options = this.options, - zones = this.zones, - props = [['area', this.color, options.fillColor]]; // area name, main color, fill color - - each(zones, function (threshold, i) { - props.push(['zoneArea' + i, threshold.color || series.color, threshold.fillColor || options.fillColor]); - }); - each(props, function (prop) { - var areaKey = prop[0], - area = series[areaKey], - attr; - - // Create or update the area - if (area) { // update - area.animate({ d: areaPath }); - - } else { // create - attr = { - fill: prop[2] || prop[1], - zIndex: 0 // #1069 - }; - if (!prop[2]) { - attr['fill-opacity'] = pick(options.fillOpacity, 0.75); - } - series[areaKey] = series.chart.renderer.path(areaPath) - .attr(attr) - .add(series.group); - } - }); - }, - - drawLegendSymbol: LegendSymbolMixin.drawRectangle - }); - - seriesTypes.area = AreaSeries; - /** - * Set the default options for spline - */ - defaultPlotOptions.spline = merge(defaultSeriesOptions); - - /** - * SplineSeries object - */ - var SplineSeries = extendClass(Series, { - type: 'spline', - - /** - * Get the spline segment from a given point's previous neighbour to the given point - */ - getPointSpline: function (points, point, i) { - var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc - denom = smoothing + 1, - plotX = point.plotX, - plotY = point.plotY, - lastPoint = points[i - 1], - nextPoint = points[i + 1], - leftContX, - leftContY, - rightContX, - rightContY, - ret; - - // Find control points - if (lastPoint && !lastPoint.isNull && nextPoint && !nextPoint.isNull) { - var lastX = lastPoint.plotX, - lastY = lastPoint.plotY, - nextX = nextPoint.plotX, - nextY = nextPoint.plotY, - correction = 0; - - leftContX = (smoothing * plotX + lastX) / denom; - leftContY = (smoothing * plotY + lastY) / denom; - rightContX = (smoothing * plotX + nextX) / denom; - rightContY = (smoothing * plotY + nextY) / denom; - - // Have the two control points make a straight line through main point - if (rightContX !== leftContX) { // #5016, division by zero - correction = ((rightContY - leftContY) * (rightContX - plotX)) / - (rightContX - leftContX) + plotY - rightContY; - } - - leftContY += correction; - rightContY += correction; - - // to prevent false extremes, check that control points are between - // neighbouring points' y values - if (leftContY > lastY && leftContY > plotY) { - leftContY = mathMax(lastY, plotY); - rightContY = 2 * plotY - leftContY; // mirror of left control point - } else if (leftContY < lastY && leftContY < plotY) { - leftContY = mathMin(lastY, plotY); - rightContY = 2 * plotY - leftContY; - } - if (rightContY > nextY && rightContY > plotY) { - rightContY = mathMax(nextY, plotY); - leftContY = 2 * plotY - rightContY; - } else if (rightContY < nextY && rightContY < plotY) { - rightContY = mathMin(nextY, plotY); - leftContY = 2 * plotY - rightContY; - } - - // record for drawing in next point - point.rightContX = rightContX; - point.rightContY = rightContY; - - - } - - // Visualize control points for debugging - /* - if (leftContX) { - this.chart.renderer.circle(leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 2) - .attr({ - stroke: 'red', - 'stroke-width': 1, - fill: 'none' - }) - .add(); - this.chart.renderer.path(['M', leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, - 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop]) - .attr({ - stroke: 'red', - 'stroke-width': 1 - }) - .add(); - this.chart.renderer.circle(rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 2) - .attr({ - stroke: 'green', - 'stroke-width': 1, - fill: 'none' - }) - .add(); - this.chart.renderer.path(['M', rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, - 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop]) - .attr({ - stroke: 'green', - 'stroke-width': 1 - }) - .add(); - } - // */ - ret = [ - 'C', - pick(lastPoint.rightContX, lastPoint.plotX), - pick(lastPoint.rightContY, lastPoint.plotY), - pick(leftContX, plotX), - pick(leftContY, plotY), - plotX, - plotY - ]; - lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later - return ret; - } - }); - seriesTypes.spline = SplineSeries; - - /** - * Set the default options for areaspline - */ - defaultPlotOptions.areaspline = merge(defaultPlotOptions.area); - - /** - * AreaSplineSeries object - */ - var areaProto = AreaSeries.prototype, - AreaSplineSeries = extendClass(SplineSeries, { - type: 'areaspline', - getStackPoints: areaProto.getStackPoints, - getGraphPath: areaProto.getGraphPath, - setStackCliffs: areaProto.setStackCliffs, - drawGraph: areaProto.drawGraph, - drawLegendSymbol: LegendSymbolMixin.drawRectangle - }); - - seriesTypes.areaspline = AreaSplineSeries; - - /** - * Set the default options for column - */ - defaultPlotOptions.column = merge(defaultSeriesOptions, { - borderColor: '#FFFFFF', - //borderWidth: 1, - borderRadius: 0, - //colorByPoint: undefined, - groupPadding: 0.2, - //grouping: true, - marker: null, // point options are specified in the base options - pointPadding: 0.1, - //pointWidth: null, - minPointLength: 0, - cropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes - pointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories - states: { - hover: { - brightness: 0.1, - shadow: false, - halo: false - }, - select: { - color: '#C0C0C0', - borderColor: '#000000', - shadow: false - } - }, - dataLabels: { - align: null, // auto - verticalAlign: null, // auto - y: null - }, - softThreshold: false, - startFromThreshold: true, // false doesn't work well: http://jsfiddle.net/highcharts/hz8fopan/14/ - stickyTracking: false, - tooltip: { - distance: 6 - }, - threshold: 0 - }); - - /** - * ColumnSeries object - */ - var ColumnSeries = extendClass(Series, { - type: 'column', - pointAttrToOptions: { // mapping between SVG attributes and the corresponding options - stroke: 'borderColor', - fill: 'color', - r: 'borderRadius' - }, - cropShoulder: 0, - directTouch: true, // When tooltip is not shared, this series (and derivatives) requires direct touch/hover. KD-tree does not apply. - trackerGroups: ['group', 'dataLabelsGroup'], - negStacks: true, // use separate negative stacks, unlike area stacks where a negative - // point is substracted from previous (#1910) - - /** - * Initialize the series - */ - init: function () { - Series.prototype.init.apply(this, arguments); - - var series = this, - chart = series.chart; - - // if the series is added dynamically, force redraw of other - // series affected by a new column - if (chart.hasRendered) { - each(chart.series, function (otherSeries) { - if (otherSeries.type === series.type) { - otherSeries.isDirty = true; - } - }); - } - }, - - /** - * Return the width and x offset of the columns adjusted for grouping, groupPadding, pointPadding, - * pointWidth etc. - */ - getColumnMetrics: function () { - - var series = this, - options = series.options, - xAxis = series.xAxis, - yAxis = series.yAxis, - reversedXAxis = xAxis.reversed, - stackKey, - stackGroups = {}, - columnCount = 0; - - // Get the total number of column type series. - // This is called on every series. Consider moving this logic to a - // chart.orderStacks() function and call it on init, addSeries and removeSeries - if (options.grouping === false) { - columnCount = 1; - } else { - each(series.chart.series, function (otherSeries) { - var otherOptions = otherSeries.options, - otherYAxis = otherSeries.yAxis, - columnIndex; - if (otherSeries.type === series.type && otherSeries.visible && - yAxis.len === otherYAxis.len && yAxis.pos === otherYAxis.pos) { // #642, #2086 - if (otherOptions.stacking) { - stackKey = otherSeries.stackKey; - if (stackGroups[stackKey] === UNDEFINED) { - stackGroups[stackKey] = columnCount++; - } - columnIndex = stackGroups[stackKey]; - } else if (otherOptions.grouping !== false) { // #1162 - columnIndex = columnCount++; - } - otherSeries.columnIndex = columnIndex; - } - }); - } - - var categoryWidth = mathMin( - mathAbs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || xAxis.tickInterval || 1), // #2610 - xAxis.len // #1535 - ), - groupPadding = categoryWidth * options.groupPadding, - groupWidth = categoryWidth - 2 * groupPadding, - pointOffsetWidth = groupWidth / columnCount, - pointWidth = mathMin( - options.maxPointWidth || xAxis.len, - pick(options.pointWidth, pointOffsetWidth * (1 - 2 * options.pointPadding)) - ), - pointPadding = (pointOffsetWidth - pointWidth) / 2, - colIndex = (series.columnIndex || 0) + (reversedXAxis ? 1 : 0), // #1251, #3737 - pointXOffset = pointPadding + (groupPadding + colIndex * - pointOffsetWidth - (categoryWidth / 2)) * - (reversedXAxis ? -1 : 1); - - // Save it for reading in linked series (Error bars particularly) - series.columnMetrics = { - width: pointWidth, - offset: pointXOffset - }; - return series.columnMetrics; - - }, - - /** - * Make the columns crisp. The edges are rounded to the nearest full pixel. - */ - crispCol: function (x, y, w, h) { - var chart = this.chart, - borderWidth = this.borderWidth, - xCrisp = -(borderWidth % 2 ? 0.5 : 0), - yCrisp = borderWidth % 2 ? 0.5 : 1, - right, - bottom, - fromTop; - - if (chart.inverted && chart.renderer.isVML) { - yCrisp += 1; - } - - // Horizontal. We need to first compute the exact right edge, then round it - // and compute the width from there. - right = Math.round(x + w) + xCrisp; - x = Math.round(x) + xCrisp; - w = right - x; - - // Vertical - bottom = Math.round(y + h) + yCrisp; - fromTop = mathAbs(y) <= 0.5 && bottom > 0.5; // #4504, #4656 - y = Math.round(y) + yCrisp; - h = bottom - y; - - // Top edges are exceptions - if (fromTop && h) { // #5146 - y -= 1; - h += 1; - } - - return { - x: x, - y: y, - width: w, - height: h - }; - }, - - /** - * Translate each point to the plot area coordinate system and find shape positions - */ - translate: function () { - var series = this, - chart = series.chart, - options = series.options, - borderWidth = series.borderWidth = pick( - options.borderWidth, - series.closestPointRange * series.xAxis.transA < 2 ? 0 : 1 // #3635 - ), - yAxis = series.yAxis, - threshold = options.threshold, - translatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold), - minPointLength = pick(options.minPointLength, 5), - metrics = series.getColumnMetrics(), - pointWidth = metrics.width, - seriesBarW = series.barW = mathMax(pointWidth, 1 + 2 * borderWidth), // postprocessed for border width - pointXOffset = series.pointXOffset = metrics.offset; - - if (chart.inverted) { - translatedThreshold -= 0.5; // #3355 - } - - // When the pointPadding is 0, we want the columns to be packed tightly, so we allow individual - // columns to have individual sizes. When pointPadding is greater, we strive for equal-width - // columns (#2694). - if (options.pointPadding) { - seriesBarW = mathCeil(seriesBarW); - } - - Series.prototype.translate.apply(series); - - // Record the new values - each(series.points, function (point) { - var yBottom = mathMin(pick(point.yBottom, translatedThreshold), 9e4), // #3575 - safeDistance = 999 + mathAbs(yBottom), - plotY = mathMin(mathMax(-safeDistance, point.plotY), yAxis.len + safeDistance), // Don't draw too far outside plot area (#1303, #2241, #4264) - barX = point.plotX + pointXOffset, - barW = seriesBarW, - barY = mathMin(plotY, yBottom), - up, - barH = mathMax(plotY, yBottom) - barY; - - // Handle options.minPointLength - if (mathAbs(barH) < minPointLength) { - if (minPointLength) { - barH = minPointLength; - up = (!yAxis.reversed && !point.negative) || (yAxis.reversed && point.negative); - barY = mathAbs(barY - translatedThreshold) > minPointLength ? // stacked - yBottom - minPointLength : // keep position - translatedThreshold - (up ? minPointLength : 0); // #1485, #4051 - } - } - - // Cache for access in polar - point.barX = barX; - point.pointWidth = pointWidth; - - // Fix the tooltip on center of grouped columns (#1216, #424, #3648) - point.tooltipPos = chart.inverted ? - [yAxis.len + yAxis.pos - chart.plotLeft - plotY, series.xAxis.len - barX - barW / 2, barH] : - [barX + barW / 2, plotY + yAxis.pos - chart.plotTop, barH]; - - // Register shape type and arguments to be used in drawPoints - point.shapeType = 'rect'; - point.shapeArgs = series.crispCol(barX, barY, barW, barH); - }); - - }, - - getSymbol: noop, - - /** - * Use a solid rectangle like the area series types - */ - drawLegendSymbol: LegendSymbolMixin.drawRectangle, - - - /** - * Columns have no graph - */ - drawGraph: noop, - - /** - * Draw the columns. For bars, the series.group is rotated, so the same coordinates - * apply for columns and bars. This method is inherited by scatter series. - * - */ - drawPoints: function () { - var series = this, - chart = this.chart, - options = series.options, - renderer = chart.renderer, - animationLimit = options.animationLimit || 250, - shapeArgs, - pointAttr; - - // draw the columns - each(series.points, function (point) { - var plotY = point.plotY, - graphic = point.graphic, - borderAttr; - - if (isNumber(plotY) && point.y !== null) { - shapeArgs = point.shapeArgs; - - borderAttr = defined(series.borderWidth) ? { - 'stroke-width': series.borderWidth - } : {}; - - pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE] || series.pointAttr[NORMAL_STATE]; - - if (graphic) { // update - stop(graphic); - graphic.attr(borderAttr).attr(pointAttr)[chart.pointCount < animationLimit ? 'animate' : 'attr'](merge(shapeArgs)); // #4267 - - } else { - point.graphic = graphic = renderer[point.shapeType](shapeArgs) - .attr(borderAttr) - .attr(pointAttr) - .add(point.group || series.group) - .shadow(options.shadow, null, options.stacking && !options.borderRadius); - } - - } else if (graphic) { - point.graphic = graphic.destroy(); // #1269 - } - }); - }, - - /** - * Animate the column heights one by one from zero - * @param {Boolean} init Whether to initialize the animation or run it - */ - animate: function (init) { - var series = this, - yAxis = this.yAxis, - options = series.options, - inverted = this.chart.inverted, - attr = {}, - translatedThreshold; - - if (hasSVG) { // VML is too slow anyway - if (init) { - attr.scaleY = 0.001; - translatedThreshold = mathMin(yAxis.pos + yAxis.len, mathMax(yAxis.pos, yAxis.toPixels(options.threshold))); - if (inverted) { - attr.translateX = translatedThreshold - yAxis.len; - } else { - attr.translateY = translatedThreshold; - } - series.group.attr(attr); - - } else { // run the animation - - attr[inverted ? 'translateX' : 'translateY'] = yAxis.pos; - series.group.animate(attr, extend(animObject(series.options.animation), { - // Do the scale synchronously to ensure smooth updating (#5030) - step: function (val, fx) { - series.group.attr({ - scaleY: mathMax(0.001, fx.pos) // #5250 - }); - } - })); - - // delete this function to allow it only once - series.animate = null; - } - } - }, - - /** - * Remove this series from the chart - */ - remove: function () { - var series = this, - chart = series.chart; - - // column and bar series affects other series of the same type - // as they are either stacked or grouped - if (chart.hasRendered) { - each(chart.series, function (otherSeries) { - if (otherSeries.type === series.type) { - otherSeries.isDirty = true; - } - }); - } - - Series.prototype.remove.apply(series, arguments); - } - }); - seriesTypes.column = ColumnSeries; - /** - * Set the default options for bar - */ - defaultPlotOptions.bar = merge(defaultPlotOptions.column); - /** - * The Bar series class - */ - var BarSeries = extendClass(ColumnSeries, { - type: 'bar', - inverted: true - }); - seriesTypes.bar = BarSeries; - - /** - * Set the default options for scatter - */ - defaultPlotOptions.scatter = merge(defaultSeriesOptions, { - lineWidth: 0, - marker: { - enabled: true // Overrides auto-enabling in line series (#3647) - }, - tooltip: { - headerFormat: '\u25CF {series.name}
', - pointFormat: 'x: {point.x}
y: {point.y}
' - } - }); - - /** - * The scatter series class - */ - var ScatterSeries = extendClass(Series, { - type: 'scatter', - sorted: false, - requireSorting: false, - noSharedTooltip: true, - trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'], - takeOrdinalPosition: false, // #2342 - kdDimensions: 2, - drawGraph: function () { - if (this.options.lineWidth) { - Series.prototype.drawGraph.call(this); - } - } - }); - - seriesTypes.scatter = ScatterSeries; - - /** - * Set the default options for pie - */ - defaultPlotOptions.pie = merge(defaultSeriesOptions, { - borderColor: '#FFFFFF', - borderWidth: 1, - center: [null, null], - clip: false, - colorByPoint: true, // always true for pies - dataLabels: { - // align: null, - // connectorWidth: 1, - // connectorColor: point.color, - // connectorPadding: 5, - distance: 30, - enabled: true, - formatter: function () { // #2945 - return this.y === null ? undefined : this.point.name; - }, - // softConnector: true, - x: 0 - // y: 0 - }, - ignoreHiddenPoint: true, - //innerSize: 0, - legendType: 'point', - marker: null, // point options are specified in the base options - size: null, - showInLegend: false, - slicedOffset: 10, - states: { - hover: { - brightness: 0.1, - shadow: false - } - }, - stickyTracking: false, - tooltip: { - followPointer: true - } - }); - - /** - * Extended point object for pies - */ - var PiePoint = extendClass(Point, { - /** - * Initiate the pie slice - */ - init: function () { - - Point.prototype.init.apply(this, arguments); - - var point = this, - toggleSlice; - - point.name = pick(point.name, 'Slice'); - - // add event listener for select - toggleSlice = function (e) { - point.slice(e.type === 'select'); - }; - addEvent(point, 'select', toggleSlice); - addEvent(point, 'unselect', toggleSlice); - - return point; - }, - - /** - * Toggle the visibility of the pie slice - * @param {Boolean} vis Whether to show the slice or not. If undefined, the - * visibility is toggled - */ - setVisible: function (vis, redraw) { - var point = this, - series = point.series, - chart = series.chart, - ignoreHiddenPoint = series.options.ignoreHiddenPoint; - - redraw = pick(redraw, ignoreHiddenPoint); - - if (vis !== point.visible) { - - // If called without an argument, toggle visibility - point.visible = point.options.visible = vis = vis === UNDEFINED ? !point.visible : vis; - series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data - - // Show and hide associated elements. This is performed regardless of redraw or not, - // because chart.redraw only handles full series. - each(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function (key) { - if (point[key]) { - point[key][vis ? 'show' : 'hide'](true); - } - }); - - if (point.legendItem) { - chart.legend.colorizeItem(point, vis); - } - - // #4170, hide halo after hiding point - if (!vis && point.state === 'hover') { - point.setState(''); - } - - // Handle ignore hidden slices - if (ignoreHiddenPoint) { - series.isDirty = true; - } - - if (redraw) { - chart.redraw(); - } - } - }, - - /** - * Set or toggle whether the slice is cut out from the pie - * @param {Boolean} sliced When undefined, the slice state is toggled - * @param {Boolean} redraw Whether to redraw the chart. True by default. - */ - slice: function (sliced, redraw, animation) { - var point = this, - series = point.series, - chart = series.chart, - translation; - - setAnimation(animation, chart); - - // redraw is true by default - redraw = pick(redraw, true); - - // if called without an argument, toggle - point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced; - series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data - - translation = sliced ? point.slicedTranslation : { - translateX: 0, - translateY: 0 - }; - - point.graphic.animate(translation); - - if (point.shadowGroup) { - point.shadowGroup.animate(translation); - } - - }, - - haloPath: function (size) { - var shapeArgs = this.shapeArgs, - chart = this.series.chart; - - return this.sliced || !this.visible ? [] : this.series.chart.renderer.symbols.arc(chart.plotLeft + shapeArgs.x, chart.plotTop + shapeArgs.y, shapeArgs.r + size, shapeArgs.r + size, { - innerR: this.shapeArgs.r, - start: shapeArgs.start, - end: shapeArgs.end - }); - } - }); - - /** - * The Pie series class - */ - var PieSeries = { - type: 'pie', - isCartesian: false, - pointClass: PiePoint, - requireSorting: false, - directTouch: true, - noSharedTooltip: true, - trackerGroups: ['group', 'dataLabelsGroup'], - axisTypes: [], - pointAttrToOptions: { // mapping between SVG attributes and the corresponding options - stroke: 'borderColor', - 'stroke-width': 'borderWidth', - fill: 'color' - }, - - /** - * Animate the pies in - */ - animate: function (init) { - var series = this, - points = series.points, - startAngleRad = series.startAngleRad; - - if (!init) { - each(points, function (point) { - var graphic = point.graphic, - args = point.shapeArgs; - - if (graphic) { - // start values - graphic.attr({ - r: point.startR || (series.center[3] / 2), // animate from inner radius (#779) - start: startAngleRad, - end: startAngleRad - }); - - // animate - graphic.animate({ - r: args.r, - start: args.start, - end: args.end - }, series.options.animation); - } - }); - - // delete this function to allow it only once - series.animate = null; - } - }, - - /** - * Recompute total chart sum and update percentages of points. - */ - updateTotals: function () { - var i, - total = 0, - points = this.points, - len = points.length, - point, - ignoreHiddenPoint = this.options.ignoreHiddenPoint; - - // Get the total sum - for (i = 0; i < len; i++) { - point = points[i]; - total += (ignoreHiddenPoint && !point.visible) ? 0 : point.y; - } - this.total = total; - - // Set each point's properties - for (i = 0; i < len; i++) { - point = points[i]; - point.percentage = (total > 0 && (point.visible || !ignoreHiddenPoint)) ? point.y / total * 100 : 0; - point.total = total; - } - }, - - /** - * Extend the generatePoints method by adding total and percentage properties to each point - */ - generatePoints: function () { - Series.prototype.generatePoints.call(this); - this.updateTotals(); - }, - - /** - * Do translation for pie slices - */ - translate: function (positions) { - this.generatePoints(); - - var series = this, - cumulative = 0, - precision = 1000, // issue #172 - options = series.options, - slicedOffset = options.slicedOffset, - connectorOffset = slicedOffset + options.borderWidth, - start, - end, - angle, - startAngle = options.startAngle || 0, - startAngleRad = series.startAngleRad = mathPI / 180 * (startAngle - 90), - endAngleRad = series.endAngleRad = mathPI / 180 * ((pick(options.endAngle, startAngle + 360)) - 90), - circ = endAngleRad - startAngleRad, //2 * mathPI, - points = series.points, - radiusX, // the x component of the radius vector for a given point - radiusY, - labelDistance = options.dataLabels.distance, - ignoreHiddenPoint = options.ignoreHiddenPoint, - i, - len = points.length, - point; - - // Get positions - either an integer or a percentage string must be given. - // If positions are passed as a parameter, we're in a recursive loop for adjusting - // space for data labels. - if (!positions) { - series.center = positions = series.getCenter(); - } - - // utility for getting the x value from a given y, used for anticollision logic in data labels - series.getX = function (y, left) { - - angle = math.asin(mathMin((y - positions[1]) / (positions[2] / 2 + labelDistance), 1)); - - return positions[0] + - (left ? -1 : 1) * - (mathCos(angle) * (positions[2] / 2 + labelDistance)); - }; - - // Calculate the geometry for each point - for (i = 0; i < len; i++) { - - point = points[i]; - - // set start and end angle - start = startAngleRad + (cumulative * circ); - if (!ignoreHiddenPoint || point.visible) { - cumulative += point.percentage / 100; - } - end = startAngleRad + (cumulative * circ); - - // set the shape - point.shapeType = 'arc'; - point.shapeArgs = { - x: positions[0], - y: positions[1], - r: positions[2] / 2, - innerR: positions[3] / 2, - start: mathRound(start * precision) / precision, - end: mathRound(end * precision) / precision - }; - - // The angle must stay within -90 and 270 (#2645) - angle = (end + start) / 2; - if (angle > 1.5 * mathPI) { - angle -= 2 * mathPI; - } else if (angle < -mathPI / 2) { - angle += 2 * mathPI; - } - - // Center for the sliced out slice - point.slicedTranslation = { - translateX: mathRound(mathCos(angle) * slicedOffset), - translateY: mathRound(mathSin(angle) * slicedOffset) - }; - - // set the anchor point for tooltips - radiusX = mathCos(angle) * positions[2] / 2; - radiusY = mathSin(angle) * positions[2] / 2; - point.tooltipPos = [ - positions[0] + radiusX * 0.7, - positions[1] + radiusY * 0.7 - ]; - - point.half = angle < -mathPI / 2 || angle > mathPI / 2 ? 1 : 0; - point.angle = angle; - - // set the anchor point for data labels - connectorOffset = mathMin(connectorOffset, labelDistance / 2); // #1678 - point.labelPos = [ - positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector - positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a - positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie - positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a - positions[0] + radiusX, // landing point for connector - positions[1] + radiusY, // a/a - labelDistance < 0 ? // alignment - 'center' : - point.half ? 'right' : 'left', // alignment - angle // center angle - ]; - - } - }, - - drawGraph: null, - - /** - * Draw the data points - */ - drawPoints: function () { - var series = this, - chart = series.chart, - renderer = chart.renderer, - groupTranslation, - //center, - graphic, - //group, - shadow = series.options.shadow, - shadowGroup, - pointAttr, - shapeArgs, - attr; - - if (shadow && !series.shadowGroup) { - series.shadowGroup = renderer.g('shadow') - .add(series.group); - } - - // draw the slices - each(series.points, function (point) { - if (point.y !== null) { - graphic = point.graphic; - shapeArgs = point.shapeArgs; - shadowGroup = point.shadowGroup; - pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE]; - if (!pointAttr.stroke) { - pointAttr.stroke = pointAttr.fill; - } - - // put the shadow behind all points - if (shadow && !shadowGroup) { - shadowGroup = point.shadowGroup = renderer.g('shadow') - .add(series.shadowGroup); - } - - // if the point is sliced, use special translation, else use plot area traslation - groupTranslation = point.sliced ? point.slicedTranslation : { - translateX: 0, - translateY: 0 - }; - - //group.translate(groupTranslation[0], groupTranslation[1]); - if (shadowGroup) { - shadowGroup.attr(groupTranslation); - } - - // draw the slice - if (graphic) { - graphic - .setRadialReference(series.center) - .attr(pointAttr) - .animate(extend(shapeArgs, groupTranslation)); - } else { - attr = { 'stroke-linejoin': 'round' }; - if (!point.visible) { - attr.visibility = 'hidden'; - } - - point.graphic = graphic = renderer[point.shapeType](shapeArgs) - .setRadialReference(series.center) - .attr(pointAttr) - .attr(attr) - .attr(groupTranslation) - .add(series.group) - .shadow(shadow, shadowGroup); - } - } - }); - - }, - - - searchPoint: noop, - - /** - * Utility for sorting data labels - */ - sortByAngle: function (points, sign) { - points.sort(function (a, b) { - return a.angle !== undefined && (b.angle - a.angle) * sign; - }); - }, - - /** - * Use a simple symbol from LegendSymbolMixin - */ - drawLegendSymbol: LegendSymbolMixin.drawRectangle, - - /** - * Use the getCenter method from drawLegendSymbol - */ - getCenter: CenteredSeriesMixin.getCenter, - - /** - * Pies don't have point marker symbols - */ - getSymbol: noop - - }; - PieSeries = extendClass(Series, PieSeries); - seriesTypes.pie = PieSeries; - - /** - * Draw the data labels - */ - Series.prototype.drawDataLabels = function () { - - var series = this, - seriesOptions = series.options, - cursor = seriesOptions.cursor, - options = seriesOptions.dataLabels, - points = series.points, - pointOptions, - generalOptions, - hasRendered = series.hasRendered || 0, - str, - dataLabelsGroup, - defer = pick(options.defer, true), - renderer = series.chart.renderer; - - if (options.enabled || series._hasPointLabels) { - - // Process default alignment of data labels for columns - if (series.dlProcessOptions) { - series.dlProcessOptions(options); - } - - // Create a separate group for the data labels to avoid rotation - dataLabelsGroup = series.plotGroup( - 'dataLabelsGroup', - 'data-labels', - defer && !hasRendered ? 'hidden' : 'visible', // #5133 - options.zIndex || 6 - ); - - if (defer) { - dataLabelsGroup.attr({ opacity: +hasRendered }); // #3300 - if (!hasRendered) { - addEvent(series, 'afterAnimate', function () { - if (series.visible) { // #3023, #3024 - dataLabelsGroup.show(); - } - dataLabelsGroup[seriesOptions.animation ? 'animate' : 'attr']({ opacity: 1 }, { duration: 200 }); - }); - } - } - - // Make the labels for each point - generalOptions = options; - each(points, function (point) { - - var enabled, - dataLabel = point.dataLabel, - labelConfig, - attr, - name, - rotation, - connector = point.connector, - isNew = true, - style, - moreStyle = {}; - - // Determine if each data label is enabled - pointOptions = point.dlOptions || (point.options && point.options.dataLabels); // dlOptions is used in treemaps - enabled = pick(pointOptions && pointOptions.enabled, generalOptions.enabled) && point.y !== null; // #2282, #4641 - - - // If the point is outside the plot area, destroy it. #678, #820 - if (dataLabel && !enabled) { - point.dataLabel = dataLabel.destroy(); - - // Individual labels are disabled if the are explicitly disabled - // in the point options, or if they fall outside the plot area. - } else if (enabled) { - - // Create individual options structure that can be extended without - // affecting others - options = merge(generalOptions, pointOptions); - style = options.style; - - rotation = options.rotation; - - // Get the string - labelConfig = point.getLabelConfig(); - str = options.format ? - format(options.format, labelConfig) : - options.formatter.call(labelConfig, options); - - // Determine the color - style.color = pick(options.color, style.color, series.color, 'black'); - - - // update existing label - if (dataLabel) { - - if (defined(str)) { - dataLabel - .attr({ - text: str - }); - isNew = false; - - } else { // #1437 - the label is shown conditionally - point.dataLabel = dataLabel = dataLabel.destroy(); - if (connector) { - point.connector = connector.destroy(); - } - } - - // create new label - } else if (defined(str)) { - attr = { - //align: align, - fill: options.backgroundColor, - stroke: options.borderColor, - 'stroke-width': options.borderWidth, - r: options.borderRadius || 0, - rotation: rotation, - padding: options.padding, - zIndex: 1 - }; - - // Get automated contrast color - if (style.color === 'contrast') { - moreStyle.color = options.inside || options.distance < 0 || !!seriesOptions.stacking ? - renderer.getContrast(point.color || series.color) : - '#000000'; - } - if (cursor) { - moreStyle.cursor = cursor; - } - - - // Remove unused attributes (#947) - for (name in attr) { - if (attr[name] === UNDEFINED) { - delete attr[name]; - } - } - - dataLabel = point.dataLabel = renderer[rotation ? 'text' : 'label']( // labels don't support rotation - str, - 0, - -9999, - options.shape, - null, - null, - options.useHTML - ) - .attr(attr) - .css(extend(style, moreStyle)) - .add(dataLabelsGroup) - .shadow(options.shadow); - - } - - if (dataLabel) { - // Now the data label is created and placed at 0,0, so we need to align it - series.alignDataLabel(point, dataLabel, options, null, isNew); - } - } - }); - } - }; - - /** - * Align each individual data label - */ - Series.prototype.alignDataLabel = function (point, dataLabel, options, alignTo, isNew) { - var chart = this.chart, - inverted = chart.inverted, - plotX = pick(point.plotX, -9999), - plotY = pick(point.plotY, -9999), - bBox = dataLabel.getBBox(), - baseline = chart.renderer.fontMetrics(options.style.fontSize).b, - rotation = options.rotation, - normRotation, - negRotation, - align = options.align, - rotCorr, // rotation correction - // Math.round for rounding errors (#2683), alignTo to allow column labels (#2700) - visible = this.visible && (point.series.forceDL || chart.isInsidePlot(plotX, mathRound(plotY), inverted) || - (alignTo && chart.isInsidePlot(plotX, inverted ? alignTo.x + 1 : alignTo.y + alignTo.height - 1, inverted))), - alignAttr, // the final position; - justify = pick(options.overflow, 'justify') === 'justify'; - - if (visible) { - - // The alignment box is a singular point - alignTo = extend({ - x: inverted ? chart.plotWidth - plotY : plotX, - y: mathRound(inverted ? chart.plotHeight - plotX : plotY), - width: 0, - height: 0 - }, alignTo); - - // Add the text size for alignment calculation - extend(options, { - width: bBox.width, - height: bBox.height - }); - - // Allow a hook for changing alignment in the last moment, then do the alignment - if (rotation) { - justify = false; // Not supported for rotated text - rotCorr = chart.renderer.rotCorr(baseline, rotation); // #3723 - alignAttr = { - x: alignTo.x + options.x + alignTo.width / 2 + rotCorr.x, - y: alignTo.y + options.y + { top: 0, middle: 0.5, bottom: 1 }[options.verticalAlign] * alignTo.height - }; - dataLabel[isNew ? 'attr' : 'animate'](alignAttr) - .attr({ // #3003 - align: align - }); - - // Compensate for the rotated label sticking out on the sides - normRotation = (rotation + 720) % 360; - negRotation = normRotation > 180 && normRotation < 360; - - if (align === 'left') { - alignAttr.y -= negRotation ? bBox.height : 0; - } else if (align === 'center') { - alignAttr.x -= bBox.width / 2; - alignAttr.y -= bBox.height / 2; - } else if (align === 'right') { - alignAttr.x -= bBox.width; - alignAttr.y -= negRotation ? 0 : bBox.height; - } - - - } else { - dataLabel.align(options, null, alignTo); - alignAttr = dataLabel.alignAttr; - } - - // Handle justify or crop - if (justify) { - this.justifyDataLabel(dataLabel, options, alignAttr, bBox, alignTo, isNew); - - // Now check that the data label is within the plot area - } else if (pick(options.crop, true)) { - visible = chart.isInsidePlot(alignAttr.x, alignAttr.y) && chart.isInsidePlot(alignAttr.x + bBox.width, alignAttr.y + bBox.height); - } - - // When we're using a shape, make it possible with a connector or an arrow pointing to thie point - if (options.shape && !rotation) { - dataLabel.attr({ - anchorX: point.plotX, - anchorY: point.plotY - }); - } - } - - // Show or hide based on the final aligned position - if (!visible) { - stop(dataLabel); - dataLabel.attr({ y: -9999 }); - dataLabel.placed = false; // don't animate back in - } - - }; - - /** - * If data labels fall partly outside the plot area, align them back in, in a way that - * doesn't hide the point. - */ - Series.prototype.justifyDataLabel = function (dataLabel, options, alignAttr, bBox, alignTo, isNew) { - var chart = this.chart, - align = options.align, - verticalAlign = options.verticalAlign, - off, - justified, - padding = dataLabel.box ? 0 : (dataLabel.padding || 0); - - // Off left - off = alignAttr.x + padding; - if (off < 0) { - if (align === 'right') { - options.align = 'left'; - } else { - options.x = -off; - } - justified = true; - } - - // Off right - off = alignAttr.x + bBox.width - padding; - if (off > chart.plotWidth) { - if (align === 'left') { - options.align = 'right'; - } else { - options.x = chart.plotWidth - off; - } - justified = true; - } - - // Off top - off = alignAttr.y + padding; - if (off < 0) { - if (verticalAlign === 'bottom') { - options.verticalAlign = 'top'; - } else { - options.y = -off; - } - justified = true; - } - - // Off bottom - off = alignAttr.y + bBox.height - padding; - if (off > chart.plotHeight) { - if (verticalAlign === 'top') { - options.verticalAlign = 'bottom'; - } else { - options.y = chart.plotHeight - off; - } - justified = true; - } - - if (justified) { - dataLabel.placed = !isNew; - dataLabel.align(options, null, alignTo); - } - }; - - /** - * Override the base drawDataLabels method by pie specific functionality - */ - if (seriesTypes.pie) { - seriesTypes.pie.prototype.drawDataLabels = function () { - var series = this, - data = series.data, - point, - chart = series.chart, - options = series.options.dataLabels, - connectorPadding = pick(options.connectorPadding, 10), - connectorWidth = pick(options.connectorWidth, 1), - plotWidth = chart.plotWidth, - plotHeight = chart.plotHeight, - connector, - connectorPath, - softConnector = pick(options.softConnector, true), - distanceOption = options.distance, - seriesCenter = series.center, - radius = seriesCenter[2] / 2, - centerY = seriesCenter[1], - outside = distanceOption > 0, - dataLabel, - dataLabelWidth, - labelPos, - labelHeight, - halves = [// divide the points into right and left halves for anti collision - [], // right - [] // left - ], - x, - y, - visibility, - rankArr, - i, - j, - overflow = [0, 0, 0, 0], // top, right, bottom, left - sort = function (a, b) { - return b.y - a.y; - }; - - // get out if not enabled - if (!series.visible || (!options.enabled && !series._hasPointLabels)) { - return; - } - - // run parent method - Series.prototype.drawDataLabels.apply(series); - - each(data, function (point) { - if (point.dataLabel && point.visible) { // #407, #2510 - - // Arrange points for detection collision - halves[point.half].push(point); - - // Reset positions (#4905) - point.dataLabel._pos = null; - } - }); - - /* Loop over the points in each half, starting from the top and bottom - * of the pie to detect overlapping labels. - */ - i = 2; - while (i--) { - - var slots = [], - slotsLength, - usedSlots = [], - points = halves[i], - pos, - bottom, - length = points.length, - slotIndex; - - if (!length) { - continue; - } - - // Sort by angle - series.sortByAngle(points, i - 0.5); - - // Assume equal label heights on either hemisphere (#2630) - j = labelHeight = 0; - while (!labelHeight && points[j]) { // #1569 - labelHeight = points[j] && points[j].dataLabel && (points[j].dataLabel.getBBox().height || 21); // 21 is for #968 - j++; - } - - // Only do anti-collision when we are outside the pie and have connectors (#856) - if (distanceOption > 0) { - - // Build the slots - bottom = mathMin(centerY + radius + distanceOption, chart.plotHeight); - for (pos = mathMax(0, centerY - radius - distanceOption); pos <= bottom; pos += labelHeight) { - slots.push(pos); - } - slotsLength = slots.length; - - - /* Visualize the slots - if (!series.slotElements) { - series.slotElements = []; - } - if (i === 1) { - series.slotElements.forEach(function (elem) { - elem.destroy(); - }); - series.slotElements.length = 0; - } - - slots.forEach(function (pos, no) { - var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0), - slotY = pos + chart.plotTop; - - if (isNumber(slotX)) { - series.slotElements.push(chart.renderer.rect(slotX, slotY - 7, 100, labelHeight, 1) - .attr({ - 'stroke-width': 1, - stroke: 'silver', - fill: 'rgba(0,0,255,0.1)' - }) - .add()); - series.slotElements.push(chart.renderer.text('Slot '+ no, slotX, slotY + 4) - .attr({ - fill: 'silver' - }).add()); - } - }); - // */ - - // if there are more values than available slots, remove lowest values - if (length > slotsLength) { - // create an array for sorting and ranking the points within each quarter - rankArr = [].concat(points); - rankArr.sort(sort); - j = length; - while (j--) { - rankArr[j].rank = j; - } - j = length; - while (j--) { - if (points[j].rank >= slotsLength) { - points.splice(j, 1); - } - } - length = points.length; - } - - // The label goes to the nearest open slot, but not closer to the edge than - // the label's index. - for (j = 0; j < length; j++) { - - point = points[j]; - labelPos = point.labelPos; - - var closest = 9999, - distance, - slotI; - - // find the closest slot index - for (slotI = 0; slotI < slotsLength; slotI++) { - distance = mathAbs(slots[slotI] - labelPos[1]); - if (distance < closest) { - closest = distance; - slotIndex = slotI; - } - } - - // if that slot index is closer to the edges of the slots, move it - // to the closest appropriate slot - if (slotIndex < j && slots[j] !== null) { // cluster at the top - slotIndex = j; - } else if (slotsLength < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom - slotIndex = slotsLength - length + j; - while (slots[slotIndex] === null) { // make sure it is not taken - slotIndex++; - } - } else { - // Slot is taken, find next free slot below. In the next run, the next slice will find the - // slot above these, because it is the closest one - while (slots[slotIndex] === null) { // make sure it is not taken - slotIndex++; - } - } - - usedSlots.push({ i: slotIndex, y: slots[slotIndex] }); - slots[slotIndex] = null; // mark as taken - } - // sort them in order to fill in from the top - usedSlots.sort(sort); - } - - // now the used slots are sorted, fill them up sequentially - for (j = 0; j < length; j++) { - - var slot, naturalY; - - point = points[j]; - labelPos = point.labelPos; - dataLabel = point.dataLabel; - visibility = point.visible === false ? HIDDEN : 'inherit'; - naturalY = labelPos[1]; - - if (distanceOption > 0) { - slot = usedSlots.pop(); - slotIndex = slot.i; - - // if the slot next to currrent slot is free, the y value is allowed - // to fall back to the natural position - y = slot.y; - if ((naturalY > y && slots[slotIndex + 1] !== null) || - (naturalY < y && slots[slotIndex - 1] !== null)) { - y = mathMin(mathMax(0, naturalY), chart.plotHeight); - } - - } else { - y = naturalY; - } - - // get the x - use the natural x position for first and last slot, to prevent the top - // and botton slice connectors from touching each other on either side - x = options.justify ? - seriesCenter[0] + (i ? -1 : 1) * (radius + distanceOption) : - series.getX(y === centerY - radius - distanceOption || y === centerY + radius + distanceOption ? naturalY : y, i); - - - // Record the placement and visibility - dataLabel._attr = { - visibility: visibility, - align: labelPos[6] - }; - dataLabel._pos = { - x: x + options.x + - ({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0), - y: y + options.y - 10 // 10 is for the baseline (label vs text) - }; - dataLabel.connX = x; - dataLabel.connY = y; - - - // Detect overflowing data labels - if (this.options.size === null) { - dataLabelWidth = dataLabel.width; - // Overflow left - if (x - dataLabelWidth < connectorPadding) { - overflow[3] = mathMax(mathRound(dataLabelWidth - x + connectorPadding), overflow[3]); - - // Overflow right - } else if (x + dataLabelWidth > plotWidth - connectorPadding) { - overflow[1] = mathMax(mathRound(x + dataLabelWidth - plotWidth + connectorPadding), overflow[1]); - } - - // Overflow top - if (y - labelHeight / 2 < 0) { - overflow[0] = mathMax(mathRound(-y + labelHeight / 2), overflow[0]); - - // Overflow left - } else if (y + labelHeight / 2 > plotHeight) { - overflow[2] = mathMax(mathRound(y + labelHeight / 2 - plotHeight), overflow[2]); - } - } - } // for each point - } // for each half - - // Do not apply the final placement and draw the connectors until we have verified - // that labels are not spilling over. - if (arrayMax(overflow) === 0 || this.verifyDataLabelOverflow(overflow)) { - - // Place the labels in the final position - this.placeDataLabels(); - - // Draw the connectors - if (outside && connectorWidth) { - each(this.points, function (point) { - connector = point.connector; - labelPos = point.labelPos; - dataLabel = point.dataLabel; - - if (dataLabel && dataLabel._pos && point.visible) { - visibility = dataLabel._attr.visibility; - x = dataLabel.connX; - y = dataLabel.connY; - connectorPath = softConnector ? [ - M, - x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label - 'C', - x, y, // first break, next to the label - 2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5], - labelPos[2], labelPos[3], // second break - L, - labelPos[4], labelPos[5] // base - ] : [ - M, - x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label - L, - labelPos[2], labelPos[3], // second break - L, - labelPos[4], labelPos[5] // base - ]; - - if (connector) { - connector.animate({ d: connectorPath }); - connector.attr('visibility', visibility); - - } else { - point.connector = connector = series.chart.renderer.path(connectorPath).attr({ - 'stroke-width': connectorWidth, - stroke: options.connectorColor || point.color || '#606060', - visibility: visibility - //zIndex: 0 // #2722 (reversed) - }) - .add(series.dataLabelsGroup); - } - } else if (connector) { - point.connector = connector.destroy(); - } - }); - } - } - }; - /** - * Perform the final placement of the data labels after we have verified that they - * fall within the plot area. - */ - seriesTypes.pie.prototype.placeDataLabels = function () { - each(this.points, function (point) { - var dataLabel = point.dataLabel, - _pos; - - if (dataLabel && point.visible) { - _pos = dataLabel._pos; - if (_pos) { - dataLabel.attr(dataLabel._attr); - dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos); - dataLabel.moved = true; - } else if (dataLabel) { - dataLabel.attr({ y: -9999 }); - } - } - }); - }; - - seriesTypes.pie.prototype.alignDataLabel = noop; - - /** - * Verify whether the data labels are allowed to draw, or we should run more translation and data - * label positioning to keep them inside the plot area. Returns true when data labels are ready - * to draw. - */ - seriesTypes.pie.prototype.verifyDataLabelOverflow = function (overflow) { - - var center = this.center, - options = this.options, - centerOption = options.center, - minSize = options.minSize || 80, - newSize = minSize, - ret; - - // Handle horizontal size and center - if (centerOption[0] !== null) { // Fixed center - newSize = mathMax(center[2] - mathMax(overflow[1], overflow[3]), minSize); - - } else { // Auto center - newSize = mathMax( - center[2] - overflow[1] - overflow[3], // horizontal overflow - minSize - ); - center[0] += (overflow[3] - overflow[1]) / 2; // horizontal center - } - - // Handle vertical size and center - if (centerOption[1] !== null) { // Fixed center - newSize = mathMax(mathMin(newSize, center[2] - mathMax(overflow[0], overflow[2])), minSize); - - } else { // Auto center - newSize = mathMax( - mathMin( - newSize, - center[2] - overflow[0] - overflow[2] // vertical overflow - ), - minSize - ); - center[1] += (overflow[0] - overflow[2]) / 2; // vertical center - } - - // If the size must be decreased, we need to run translate and drawDataLabels again - if (newSize < center[2]) { - center[2] = newSize; - center[3] = Math.min(relativeLength(options.innerSize || 0, newSize), newSize); // #3632 - this.translate(center); - - if (this.drawDataLabels) { - this.drawDataLabels(); - } - // Else, return true to indicate that the pie and its labels is within the plot area - } else { - ret = true; - } - return ret; - }; - } - - if (seriesTypes.column) { - - /** - * Override the basic data label alignment by adjusting for the position of the column - */ - seriesTypes.column.prototype.alignDataLabel = function (point, dataLabel, options, alignTo, isNew) { - var inverted = this.chart.inverted, - series = point.series, - dlBox = point.dlBox || point.shapeArgs, // data label box for alignment - below = pick(point.below, point.plotY > pick(this.translatedThreshold, series.yAxis.len)), // point.below is used in range series - inside = pick(options.inside, !!this.options.stacking), // draw it inside the box? - overshoot; - - // Align to the column itself, or the top of it - if (dlBox) { // Area range uses this method but not alignTo - alignTo = merge(dlBox); - - if (alignTo.y < 0) { - alignTo.height += alignTo.y; - alignTo.y = 0; - } - overshoot = alignTo.y + alignTo.height - series.yAxis.len; - if (overshoot > 0) { - alignTo.height -= overshoot; - } - - if (inverted) { - alignTo = { - x: series.yAxis.len - alignTo.y - alignTo.height, - y: series.xAxis.len - alignTo.x - alignTo.width, - width: alignTo.height, - height: alignTo.width - }; - } - - // Compute the alignment box - if (!inside) { - if (inverted) { - alignTo.x += below ? 0 : alignTo.width; - alignTo.width = 0; - } else { - alignTo.y += below ? alignTo.height : 0; - alignTo.height = 0; - } - } - } - - - // When alignment is undefined (typically columns and bars), display the individual - // point below or above the point depending on the threshold - options.align = pick( - options.align, - !inverted || inside ? 'center' : below ? 'right' : 'left' - ); - options.verticalAlign = pick( - options.verticalAlign, - inverted || inside ? 'middle' : below ? 'top' : 'bottom' - ); - - // Call the parent method - Series.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew); - }; - } - - - - /** - * Highcharts module to hide overlapping data labels. This module is included in Highcharts. - */ - (function (H) { - var Chart = H.Chart, - each = H.each, - pick = H.pick, - addEvent = H.addEvent; - - // Collect potensial overlapping data labels. Stack labels probably don't need to be - // considered because they are usually accompanied by data labels that lie inside the columns. - Chart.prototype.callbacks.push(function (chart) { - function collectAndHide() { - var labels = []; - - each(chart.series, function (series) { - var dlOptions = series.options.dataLabels, - collections = series.dataLabelCollections || ['dataLabel']; // Range series have two collections - if ((dlOptions.enabled || series._hasPointLabels) && !dlOptions.allowOverlap && series.visible) { // #3866 - each(collections, function (coll) { - each(series.points, function (point) { - if (point[coll]) { - point[coll].labelrank = pick(point.labelrank, point.shapeArgs && point.shapeArgs.height); // #4118 - labels.push(point[coll]); - } - }); - }); - } - }); - chart.hideOverlappingLabels(labels); - } - - // Do it now ... - collectAndHide(); - - // ... and after each chart redraw - addEvent(chart, 'redraw', collectAndHide); - - }); - - /** - * Hide overlapping labels. Labels are moved and faded in and out on zoom to provide a smooth - * visual imression. - */ - Chart.prototype.hideOverlappingLabels = function (labels) { - - var len = labels.length, - label, - i, - j, - label1, - label2, - isIntersecting, - pos1, - pos2, - parent1, - parent2, - padding, - intersectRect = function (x1, y1, w1, h1, x2, y2, w2, h2) { - return !( - x2 > x1 + w1 || - x2 + w2 < x1 || - y2 > y1 + h1 || - y2 + h2 < y1 - ); - }; - - // Mark with initial opacity - for (i = 0; i < len; i++) { - label = labels[i]; - if (label) { - label.oldOpacity = label.opacity; - label.newOpacity = 1; - } - } - - // Prevent a situation in a gradually rising slope, that each label - // will hide the previous one because the previous one always has - // lower rank. - labels.sort(function (a, b) { - return (b.labelrank || 0) - (a.labelrank || 0); - }); - - // Detect overlapping labels - for (i = 0; i < len; i++) { - label1 = labels[i]; - - for (j = i + 1; j < len; ++j) { - label2 = labels[j]; - if (label1 && label2 && label1.placed && label2.placed && label1.newOpacity !== 0 && label2.newOpacity !== 0) { - pos1 = label1.alignAttr; - pos2 = label2.alignAttr; - parent1 = label1.parentGroup; // Different panes have different positions - parent2 = label2.parentGroup; - padding = 2 * (label1.box ? 0 : label1.padding); // Substract the padding if no background or border (#4333) - isIntersecting = intersectRect( - pos1.x + parent1.translateX, - pos1.y + parent1.translateY, - label1.width - padding, - label1.height - padding, - pos2.x + parent2.translateX, - pos2.y + parent2.translateY, - label2.width - padding, - label2.height - padding - ); - - if (isIntersecting) { - (label1.labelrank < label2.labelrank ? label1 : label2).newOpacity = 0; - } - } - } - } - - // Hide or show - each(labels, function (label) { - var complete, - newOpacity; - - if (label) { - newOpacity = label.newOpacity; - - if (label.oldOpacity !== newOpacity && label.placed) { - - // Make sure the label is completely hidden to avoid catching clicks (#4362) - if (newOpacity) { - label.show(true); - } else { - complete = function () { - label.hide(); - }; - } - - // Animate or set the opacity - label.alignAttr.opacity = newOpacity; - label[label.isOld ? 'animate' : 'attr'](label.alignAttr, null, complete); - - } - label.isOld = true; - } - }); - }; - }(Highcharts)); - /** - * TrackerMixin for points and graphs - */ - - var TrackerMixin = Highcharts.TrackerMixin = { - - drawTrackerPoint: function () { - var series = this, - chart = series.chart, - pointer = chart.pointer, - cursor = series.options.cursor, - css = cursor && { cursor: cursor }, - onMouseOver = function (e) { - var target = e.target, - point; - - while (target && !point) { - point = target.point; - target = target.parentNode; - } - - if (point !== UNDEFINED && point !== chart.hoverPoint) { // undefined on graph in scatterchart - point.onMouseOver(e); - } - }; - - // Add reference to the point - each(series.points, function (point) { - if (point.graphic) { - point.graphic.element.point = point; - } - if (point.dataLabel) { - point.dataLabel.element.point = point; - } - }); - - // Add the event listeners, we need to do this only once - if (!series._hasTracking) { - each(series.trackerGroups, function (key) { - if (series[key]) { // we don't always have dataLabelsGroup - series[key] - .addClass(PREFIX + 'tracker') - .on('mouseover', onMouseOver) - .on('mouseout', function (e) { - pointer.onTrackerMouseOut(e); - }) - .css(css); - if (hasTouch) { - series[key].on('touchstart', onMouseOver); - } - } - }); - series._hasTracking = true; - } - }, - - /** - * Draw the tracker object that sits above all data labels and markers to - * track mouse events on the graph or points. For the line type charts - * the tracker uses the same graphPath, but with a greater stroke width - * for better control. - */ - drawTrackerGraph: function () { - var series = this, - options = series.options, - trackByArea = options.trackByArea, - trackerPath = [].concat(trackByArea ? series.areaPath : 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 }, - i, - onMouseOver = function () { - if (chart.hoverSeries !== series) { - series.onMouseOver(); - } - }, - /* - * Empirical lowest possible opacities for TRACKER_FILL for an element to stay invisible but clickable - * IE6: 0.002 - * IE7: 0.002 - * IE8: 0.002 - * IE9: 0.00000000001 (unlimited) - * IE10: 0.0001 (exporting only) - * FF: 0.00000000001 (unlimited) - * Chrome: 0.000001 - * Safari: 0.000001 - * Opera: 0.00000000001 (unlimited) - */ - TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.0001 : 0.002) + ')'; - - // 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]; - 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); - } - }); - } - } - }; - /* End TrackerMixin */ - - - /** - * Add tracking event listener to the series group, so the point graphics - * themselves act as trackers - */ - - if (seriesTypes.column) { - ColumnSeries.prototype.drawTracker = TrackerMixin.drawTrackerPoint; - } - - if (seriesTypes.pie) { - seriesTypes.pie.prototype.drawTracker = TrackerMixin.drawTrackerPoint; - } - - if (seriesTypes.scatter) { - ScatterSeries.prototype.drawTracker = TrackerMixin.drawTrackerPoint; - } - - /* - * Extend Legend for item events - */ - extend(Legend.prototype, { - - setItemEvents: function (item, legendItem, useHTML, itemStyle, itemHiddenStyle) { - var legend = this; - // Set the events on the item group, or in case of useHTML, the item itself (#1249) - (useHTML ? legendItem : item.legendGroup).on('mouseover', function () { - item.setState(HOVER_STATE); - legendItem.css(legend.options.itemHoverStyle); - }) - .on('mouseout', function () { - legendItem.css(item.visible ? itemStyle : itemHiddenStyle); - item.setState(); - }) - .on('click', function (event) { - var strLegendItemClick = 'legendItemClick', - fnLegendItemClick = function () { - if (item.setVisible) { - item.setVisible(); - } - }; - - // Pass over the click/touch event. #4. - event = { - browserEvent: event - }; - - // click the name or symbol - if (item.firePointEvent) { // point - item.firePointEvent(strLegendItemClick, event, fnLegendItemClick); - } else { - fireEvent(item, strLegendItemClick, event, fnLegendItemClick); - } - }); - }, - - createCheckboxForItem: function (item) { - var legend = this; - - item.checkbox = createElement('input', { - type: 'checkbox', - checked: item.selected, - defaultChecked: item.selected // required by IE7 - }, legend.options.itemCheckboxStyle, legend.chart.container); - - addEvent(item.checkbox, 'click', function (event) { - var target = event.target; - fireEvent( - item.series || item, - 'checkboxClick', - { // #3712 - checked: target.checked, - item: item - }, - function () { - item.select(); - } - ); - }); - } - }); - - /* - * Add pointer cursor to legend itemstyle in defaultOptions - */ - defaultOptions.legend.itemStyle.cursor = 'pointer'; - - - /* - * Extend the Chart object with interaction - */ - - extend(Chart.prototype, { - /** - * Display the zoom button - */ - showResetZoom: function () { - var chart = this, - lang = defaultOptions.lang, - btnOptions = chart.options.chart.resetZoomButton, - theme = btnOptions.theme, - states = theme.states, - alignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox'; - - function zoomOut() { - chart.zoomOut(); - } - - this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, zoomOut, theme, states && states.hover) - .attr({ - align: btnOptions.position.align, - title: lang.resetZoomTitle - }) - .add() - .align(btnOptions.position, false, alignTo); - - }, - - /** - * Zoom out to 1:1 - */ - zoomOut: function () { - var chart = this; - fireEvent(chart, 'selection', { resetSelection: true }, function () { - chart.zoom(); - }); - }, - - /** - * Zoom into a given portion of the chart given by axis coordinates - * @param {Object} event - */ - zoom: function (event) { - var chart = this, - hasZoomed, - pointer = chart.pointer, - displayButton = false, - resetZoomButton; - - // If zoom is called with no arguments, reset the axes - if (!event || event.resetSelection) { - each(chart.axes, function (axis) { - hasZoomed = axis.zoom(); - }); - } else { // else, zoom in on all axes - each(event.xAxis.concat(event.yAxis), function (axisData) { - var axis = axisData.axis, - isXAxis = axis.isXAxis; - - // don't zoom more than minRange - if (pointer[isXAxis ? 'zoomX' : 'zoomY'] || pointer[isXAxis ? 'pinchX' : 'pinchY']) { - hasZoomed = axis.zoom(axisData.min, axisData.max); - if (axis.displayBtn) { - displayButton = true; - } - } - }); - } - - // Show or hide the Reset zoom button - resetZoomButton = chart.resetZoomButton; - if (displayButton && !resetZoomButton) { - chart.showResetZoom(); - } else if (!displayButton && isObject(resetZoomButton)) { - chart.resetZoomButton = resetZoomButton.destroy(); - } - - - // Redraw - if (hasZoomed) { - chart.redraw( - pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation - ); - } - }, - - /** - * Pan the chart by dragging the mouse across the pane. This function is called - * on mouse move, and the distance to pan is computed from chartX compared to - * the first chartX position in the dragging operation. - */ - pan: function (e, panning) { - - var chart = this, - hoverPoints = chart.hoverPoints, - doRedraw; - - // remove active points for shared tooltip - if (hoverPoints) { - each(hoverPoints, function (point) { - point.setState(); - }); - } - - each(panning === 'xy' ? [1, 0] : [1], function (isX) { // xy is used in maps - var axis = chart[isX ? 'xAxis' : 'yAxis'][0], - horiz = axis.horiz, - mousePos = e[horiz ? 'chartX' : 'chartY'], - mouseDown = horiz ? 'mouseDownX' : 'mouseDownY', - startPos = chart[mouseDown], - halfPointRange = (axis.pointRange || 0) / 2, - extremes = axis.getExtremes(), - newMin = axis.toValue(startPos - mousePos, true) + halfPointRange, - newMax = axis.toValue(startPos + axis.len - mousePos, true) - halfPointRange, - goingLeft = startPos > mousePos; // #3613 - - if (axis.series.length && - (goingLeft || newMin > mathMin(extremes.dataMin, extremes.min)) && - (!goingLeft || newMax < mathMax(extremes.dataMax, extremes.max))) { - axis.setExtremes(newMin, newMax, false, false, { trigger: 'pan' }); - doRedraw = true; - } - - chart[mouseDown] = mousePos; // set new reference for next run - }); - - if (doRedraw) { - chart.redraw(false); - } - css(chart.container, { cursor: 'move' }); - } - }); - - /* - * Extend the Point object with interaction - */ - extend(Point.prototype, { - /** - * Toggle the selection status of a point - * @param {Boolean} selected Whether to select or unselect the point. - * @param {Boolean} accumulate Whether to add to the previous selection. By default, - * this happens if the control key (Cmd on Mac) was pressed during clicking. - */ - select: function (selected, accumulate) { - var point = this, - series = point.series, - chart = series.chart; - - selected = pick(selected, !point.selected); - - // fire the event with the default handler - point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () { - point.selected = point.options.selected = selected; - series.options.data[inArray(point, series.data)] = point.options; - - point.setState(selected && SELECT_STATE); - - // unselect all other points unless Ctrl or Cmd + click - if (!accumulate) { - each(chart.getSelectedPoints(), function (loopPoint) { - if (loopPoint.selected && loopPoint !== point) { - loopPoint.selected = loopPoint.options.selected = false; - series.options.data[inArray(loopPoint, series.data)] = loopPoint.options; - loopPoint.setState(NORMAL_STATE); - loopPoint.firePointEvent('unselect'); - } - }); - } - }); - }, - - /** - * Runs on mouse over the point - * - * @param {Object} e The event arguments - * @param {Boolean} byProximity Falsy for kd points that are closest to the mouse, or to - * actually hovered points. True for other points in shared tooltip. - */ - onMouseOver: function (e, byProximity) { - var point = this, - series = point.series, - chart = series.chart, - tooltip = chart.tooltip, - hoverPoint = chart.hoverPoint; - - if (chart.hoverSeries !== series) { - series.onMouseOver(); - } - - // set normal state to previous series - if (hoverPoint && hoverPoint !== point) { - hoverPoint.onMouseOut(); - } - - if (point.series) { // It may have been destroyed, #4130 - - // trigger the event - point.firePointEvent('mouseOver'); - - // update the tooltip - if (tooltip && (!tooltip.shared || series.noSharedTooltip)) { - tooltip.refresh(point, e); - } - - // hover this - point.setState(HOVER_STATE); - if (!byProximity) { - chart.hoverPoint = point; - } - } - }, - - /** - * Runs on mouse out from the point - */ - onMouseOut: function () { - var chart = this.series.chart, - hoverPoints = chart.hoverPoints; - - this.firePointEvent('mouseOut'); - - if (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887, #2240 - this.setState(); - chart.hoverPoint = null; - } - }, - - /** - * Import events from the series' and point's options. Only do it on - * demand, to save processing time on hovering. - */ - importEvents: function () { - if (!this.hasImportedEvents) { - var point = this, - options = merge(point.series.options.point, point.options), - events = options.events, - eventType; - - point.events = events; - - for (eventType in events) { - addEvent(point, eventType, events[eventType]); - } - this.hasImportedEvents = true; - - } - }, - - /** - * Set the point's state - * @param {String} state - */ - setState: function (state, move) { - var point = this, - plotX = mathFloor(point.plotX), // #4586 - plotY = point.plotY, - series = point.series, - stateOptions = series.options.states, - markerOptions = defaultPlotOptions[series.type].marker && series.options.marker, - normalDisabled = markerOptions && !markerOptions.enabled, - markerStateOptions = markerOptions && markerOptions.states[state], - stateDisabled = markerStateOptions && markerStateOptions.enabled === false, - stateMarkerGraphic = series.stateMarkerGraphic, - pointMarker = point.marker || {}, - chart = series.chart, - radius, - halo = series.halo, - haloOptions, - newSymbol, - pointAttr; - - state = state || NORMAL_STATE; // empty string - pointAttr = point.pointAttr[state] || series.pointAttr[state]; - - if ( - // already has this state - (state === point.state && !move) || - // selected points don't respond to hover - (point.selected && state !== SELECT_STATE) || - // series' state options is disabled - (stateOptions[state] && stateOptions[state].enabled === false) || - // general point marker's state options is disabled - (state && (stateDisabled || (normalDisabled && markerStateOptions.enabled === false))) || - // individual point marker's state options is disabled - (state && pointMarker.states && pointMarker.states[state] && pointMarker.states[state].enabled === false) // #1610 - - ) { - return; - } - - // apply hover styles to the existing point - if (point.graphic) { - radius = markerOptions && point.graphic.symbolName && pointAttr.r; - point.graphic.attr(merge( - pointAttr, - radius ? { // new symbol attributes (#507, #612) - x: plotX - radius, - y: plotY - radius, - width: 2 * radius, - height: 2 * radius - } : {} - )); - - // Zooming in from a range with no markers to a range with markers - if (stateMarkerGraphic) { - stateMarkerGraphic.hide(); - } - } else { - // if a graphic is not applied to each point in the normal state, create a shared - // graphic for the hover state - if (state && markerStateOptions) { - radius = markerStateOptions.radius; - newSymbol = pointMarker.symbol || series.symbol; - - // If the point has another symbol than the previous one, throw away the - // state marker graphic and force a new one (#1459) - if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) { - stateMarkerGraphic = stateMarkerGraphic.destroy(); - } - - // Add a new state marker graphic - if (!stateMarkerGraphic) { - if (newSymbol) { - series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol( - newSymbol, - plotX - radius, - plotY - radius, - 2 * radius, - 2 * radius - ) - .attr(pointAttr) - .add(series.markerGroup); - stateMarkerGraphic.currentSymbol = newSymbol; - } - - // Move the existing graphic - } else { - stateMarkerGraphic[move ? 'animate' : 'attr']({ // #1054 - x: plotX - radius, - y: plotY - radius - }); - } - } - - if (stateMarkerGraphic) { - stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY, chart.inverted) ? 'show' : 'hide'](); // #2450 - stateMarkerGraphic.element.point = point; // #4310 - } - } - - // Show me your halo - haloOptions = stateOptions[state] && stateOptions[state].halo; - if (haloOptions && haloOptions.size) { - if (!halo) { - series.halo = halo = chart.renderer.path() - .add(chart.seriesGroup); - } - halo.attr(extend({ - 'fill': point.color || series.color, - 'fill-opacity': haloOptions.opacity, - 'zIndex': -1 // #4929, IE8 added halo above everything - }, - haloOptions.attributes))[move ? 'animate' : 'attr']({ - d: point.haloPath(haloOptions.size) - }); - } else if (halo) { - halo.attr({ d: [] }); - } - - point.state = state; - }, - - /** - * Get the circular path definition for the halo - * @param {Number} size The radius of the circular halo - * @returns {Array} The path definition - */ - haloPath: function (size) { - var series = this.series, - chart = series.chart, - plotBox = series.getPlotBox(), - inverted = chart.inverted, - plotX = Math.floor(this.plotX); - - return chart.renderer.symbols.circle( - plotBox.translateX + (inverted ? series.yAxis.len - this.plotY : plotX) - size, - plotBox.translateY + (inverted ? series.xAxis.len - plotX : this.plotY) - size, - size * 2, - size * 2 - ); - } - }); - - /* - * Extend the Series object with interaction - */ - - extend(Series.prototype, { - /** - * Series mouse over handler - */ - onMouseOver: function () { - var series = this, - chart = series.chart, - hoverSeries = chart.hoverSeries; - - // set normal state to previous series - if (hoverSeries && hoverSeries !== series) { - hoverSeries.onMouseOut(); - } - - // trigger the event, but to save processing time, - // only if defined - if (series.options.events.mouseOver) { - fireEvent(series, 'mouseOver'); - } - - // hover this - series.setState(HOVER_STATE); - chart.hoverSeries = series; - }, - - /** - * Series mouse out handler - */ - onMouseOut: function () { - // trigger the event only if listeners exist - var series = this, - options = series.options, - chart = series.chart, - tooltip = chart.tooltip, - hoverPoint = chart.hoverPoint; - - chart.hoverSeries = null; // #182, set to null before the mouseOut event fires - - // trigger mouse out on the point, which must be in this series - if (hoverPoint) { - hoverPoint.onMouseOut(); - } - - // fire the mouse out event - if (series && options.events.mouseOut) { - fireEvent(series, 'mouseOut'); - } - - - // hide the tooltip - if (tooltip && !options.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) { - tooltip.hide(); - } - - // set normal state - series.setState(); - }, - - /** - * Set the state of the graph - */ - setState: function (state) { - var series = this, - options = series.options, - graph = series.graph, - stateOptions = options.states, - lineWidth = options.lineWidth, - attribs, - i = 0; - - 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 + (stateOptions[state].lineWidthPlus || 0); // #4035 - } - - 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 - graph.attr(attribs); - while (series['zoneGraph' + i]) { - series['zoneGraph' + i].attr(attribs); - i = i + 1; - } - } - } - }, - - /** - * Set the visibility of the graph - * - * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED, - * the visibility is toggled. - */ - setVisible: function (vis, redraw) { - var series = this, - chart = series.chart, - legendItem = series.legendItem, - showOrHide, - ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries, - oldVisibility = series.visible; - - // if called without an argument, toggle visibility - series.visible = vis = series.userOptions.visible = vis === UNDEFINED ? !oldVisibility : vis; - showOrHide = vis ? 'show' : 'hide'; - - // show or hide elements - each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker'], function (key) { - if (series[key]) { - series[key][showOrHide](); - } - }); - - - // hide tooltip (#1361) - if (chart.hoverSeries === series || (chart.hoverPoint && chart.hoverPoint.series) === series) { - series.onMouseOut(); - } - - - if (legendItem) { - chart.legend.colorizeItem(series, vis); - } - - - // rescale or adapt to resized chart - series.isDirty = true; - // in a stack, all other series are affected - if (series.options.stacking) { - each(chart.series, function (otherSeries) { - if (otherSeries.options.stacking && otherSeries.visible) { - otherSeries.isDirty = true; - } - }); - } - - // show or hide linked series - each(series.linkedSeries, function (otherSeries) { - otherSeries.setVisible(vis, false); - }); - - if (ignoreHiddenSeries) { - chart.isDirtyBox = true; - } - if (redraw !== false) { - chart.redraw(); - } - - fireEvent(series, showOrHide); - }, - - /** - * Show the graph - */ - show: function () { - this.setVisible(true); - }, - - /** - * Hide the graph - */ - hide: function () { - this.setVisible(false); - }, - - - /** - * Set the selected state of the graph - * - * @param selected {Boolean} True to select the series, false to unselect. If - * UNDEFINED, the selection state is toggled. - */ - select: function (selected) { - var series = this; - // if called without an argument, toggle - series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected; - - if (series.checkbox) { - series.checkbox.checked = selected; - } - - fireEvent(series, selected ? 'select' : 'unselect'); - }, - - drawTracker: TrackerMixin.drawTrackerGraph - }); - /* **************************************************************************** - * Start ordinal axis logic * - *****************************************************************************/ - - - wrap(Series.prototype, 'init', function (proceed) { - var series = this, - xAxis; - - // call the original function - proceed.apply(this, Array.prototype.slice.call(arguments, 1)); - - xAxis = series.xAxis; - - // Destroy the extended ordinal index on updated data - if (xAxis && xAxis.options.ordinal) { - addEvent(series, 'updatedData', function () { - delete xAxis.ordinalIndex; - }); - } - }); - - /** - * In an ordinal axis, there might be areas with dense consentrations of points, then large - * gaps between some. Creating equally distributed ticks over this entire range - * may lead to a huge number of ticks that will later be removed. So instead, break the - * positions up in segments, find the tick positions for each segment then concatenize them. - * This method is used from both data grouping logic and X axis tick position logic. - */ - wrap(Axis.prototype, 'getTimeTicks', function (proceed, normalizedInterval, min, max, startOfWeek, positions, closestDistance, findHigherRanks) { - - var start = 0, - end, - segmentPositions, - higherRanks = {}, - hasCrossedHigherRank, - info, - posLength, - outsideMax, - groupPositions = [], - lastGroupPosition = -Number.MAX_VALUE, - tickPixelIntervalOption = this.options.tickPixelInterval; - - // The positions are not always defined, for example for ordinal positions when data - // has regular interval (#1557, #2090) - if ((!this.options.ordinal && !this.options.breaks) || !positions || positions.length < 3 || min === UNDEFINED) { - return proceed.call(this, normalizedInterval, min, max, startOfWeek); - } - - // Analyze the positions array to split it into segments on gaps larger than 5 times - // the closest distance. The closest distance is already found at this point, so - // we reuse that instead of computing it again. - posLength = positions.length; - - for (end = 0; end < posLength; end++) { - - outsideMax = end && positions[end - 1] > max; - - if (positions[end] < min) { // Set the last position before min - start = end; - } - - if (end === posLength - 1 || positions[end + 1] - positions[end] > closestDistance * 5 || outsideMax) { - - // For each segment, calculate the tick positions from the getTimeTicks utility - // function. The interval will be the same regardless of how long the segment is. - if (positions[end] > lastGroupPosition) { // #1475 - - segmentPositions = proceed.call(this, normalizedInterval, positions[start], positions[end], startOfWeek); - - // Prevent duplicate groups, for example for multiple segments within one larger time frame (#1475) - while (segmentPositions.length && segmentPositions[0] <= lastGroupPosition) { - segmentPositions.shift(); - } - if (segmentPositions.length) { - lastGroupPosition = segmentPositions[segmentPositions.length - 1]; - } - - groupPositions = groupPositions.concat(segmentPositions); - } - // Set start of next segment - start = end + 1; - } - - if (outsideMax) { - break; - } - } - - // Get the grouping info from the last of the segments. The info is the same for - // all segments. - info = segmentPositions.info; - - // Optionally identify ticks with higher rank, for example when the ticks - // have crossed midnight. - if (findHigherRanks && info.unitRange <= timeUnits.hour) { - end = groupPositions.length - 1; - - // Compare points two by two - for (start = 1; start < end; start++) { - if (dateFormat('%d', groupPositions[start]) !== dateFormat('%d', groupPositions[start - 1])) { - higherRanks[groupPositions[start]] = 'day'; - hasCrossedHigherRank = true; - } - } - - // If the complete array has crossed midnight, we want to mark the first - // positions also as higher rank - if (hasCrossedHigherRank) { - higherRanks[groupPositions[0]] = 'day'; - } - info.higherRanks = higherRanks; - } - - // Save the info - groupPositions.info = info; - - - - // Don't show ticks within a gap in the ordinal axis, where the space between - // two points is greater than a portion of the tick pixel interval - if (findHigherRanks && defined(tickPixelIntervalOption)) { // check for squashed ticks - - var length = groupPositions.length, - i = length, - itemToRemove, - translated, - translatedArr = [], - lastTranslated, - medianDistance, - distance, - distances = []; - - // Find median pixel distance in order to keep a reasonably even distance between - // ticks (#748) - while (i--) { - translated = this.translate(groupPositions[i]); - if (lastTranslated) { - distances[i] = lastTranslated - translated; - } - translatedArr[i] = lastTranslated = translated; - } - distances.sort(); - medianDistance = distances[mathFloor(distances.length / 2)]; - if (medianDistance < tickPixelIntervalOption * 0.6) { - medianDistance = null; - } - - // Now loop over again and remove ticks where needed - i = groupPositions[length - 1] > max ? length - 1 : length; // #817 - lastTranslated = undefined; - while (i--) { - translated = translatedArr[i]; - distance = lastTranslated - translated; - - // Remove ticks that are closer than 0.6 times the pixel interval from the one to the right, - // but not if it is close to the median distance (#748). - if (lastTranslated && distance < tickPixelIntervalOption * 0.8 && - (medianDistance === null || distance < medianDistance * 0.8)) { - - // Is this a higher ranked position with a normal position to the right? - if (higherRanks[groupPositions[i]] && !higherRanks[groupPositions[i + 1]]) { - - // Yes: remove the lower ranked neighbour to the right - itemToRemove = i + 1; - lastTranslated = translated; // #709 - - } else { - - // No: remove this one - itemToRemove = i; - } - - groupPositions.splice(itemToRemove, 1); - - } else { - lastTranslated = translated; - } - } - } - return groupPositions; - }); - - // Extend the Axis prototype - extend(Axis.prototype, { - - /** - * Calculate the ordinal positions before tick positions are calculated. - */ - beforeSetTickPositions: function () { - var axis = this, - len, - ordinalPositions = [], - useOrdinal = false, - dist, - extremes = axis.getExtremes(), - min = extremes.min, - max = extremes.max, - minIndex, - maxIndex, - slope, - hasBreaks = axis.isXAxis && !!axis.options.breaks, - isOrdinal = axis.options.ordinal, - i; - - // apply the ordinal logic - if (isOrdinal || hasBreaks) { // #4167 YAxis is never ordinal ? - - each(axis.series, function (series, i) { - - if (series.visible !== false && (series.takeOrdinalPosition !== false || hasBreaks)) { - - // concatenate the processed X data into the existing positions, or the empty array - ordinalPositions = ordinalPositions.concat(series.processedXData); - len = ordinalPositions.length; - - // remove duplicates (#1588) - ordinalPositions.sort(function (a, b) { - return a - b; // without a custom function it is sorted as strings - }); - - if (len) { - i = len - 1; - while (i--) { - if (ordinalPositions[i] === ordinalPositions[i + 1]) { - ordinalPositions.splice(i, 1); - } - } - } - } - - }); - - // cache the length - len = ordinalPositions.length; - - // Check if we really need the overhead of mapping axis data against the ordinal positions. - // If the series consist of evenly spaced data any way, we don't need any ordinal logic. - if (len > 2) { // two points have equal distance by default - dist = ordinalPositions[1] - ordinalPositions[0]; - i = len - 1; - while (i-- && !useOrdinal) { - if (ordinalPositions[i + 1] - ordinalPositions[i] !== dist) { - useOrdinal = true; - } - } - - // When zooming in on a week, prevent axis padding for weekends even though the data within - // the week is evenly spaced. - if (!axis.options.keepOrdinalPadding && (ordinalPositions[0] - min > dist || max - ordinalPositions[ordinalPositions.length - 1] > dist)) { - useOrdinal = true; - } - } - - // Record the slope and offset to compute the linear values from the array index. - // Since the ordinal positions may exceed the current range, get the start and - // end positions within it (#719, #665b) - if (useOrdinal) { - - // Register - axis.ordinalPositions = ordinalPositions; - - // This relies on the ordinalPositions being set. Use mathMax and mathMin to prevent - // padding on either sides of the data. - minIndex = axis.val2lin(mathMax(min, ordinalPositions[0]), true); - maxIndex = mathMax(axis.val2lin(mathMin(max, ordinalPositions[ordinalPositions.length - 1]), true), 1); // #3339 - - // Set the slope and offset of the values compared to the indices in the ordinal positions - axis.ordinalSlope = slope = (max - min) / (maxIndex - minIndex); - axis.ordinalOffset = min - (minIndex * slope); - - } else { - axis.ordinalPositions = axis.ordinalSlope = axis.ordinalOffset = UNDEFINED; - } - } - axis.isOrdinal = isOrdinal && useOrdinal; // #3818, #4196, #4926 - axis.groupIntervalFactor = null; // reset for next run - }, - /** - * Translate from a linear axis value to the corresponding ordinal axis position. If there - * are no gaps in the ordinal axis this will be the same. The translated value is the value - * that the point would have if the axis were linear, using the same min and max. - * - * @param Number val The axis value - * @param Boolean toIndex Whether to return the index in the ordinalPositions or the new value - */ - val2lin: function (val, toIndex) { - var axis = this, - ordinalPositions = axis.ordinalPositions, - ret; - - if (!ordinalPositions) { - ret = val; - - } else { - - var ordinalLength = ordinalPositions.length, - i, - distance, - ordinalIndex; - - // first look for an exact match in the ordinalpositions array - i = ordinalLength; - while (i--) { - if (ordinalPositions[i] === val) { - ordinalIndex = i; - break; - } - } - - // if that failed, find the intermediate position between the two nearest values - i = ordinalLength - 1; - while (i--) { - if (val > ordinalPositions[i] || i === 0) { // interpolate - distance = (val - ordinalPositions[i]) / (ordinalPositions[i + 1] - ordinalPositions[i]); // something between 0 and 1 - ordinalIndex = i + distance; - break; - } - } - ret = toIndex ? - ordinalIndex : - axis.ordinalSlope * (ordinalIndex || 0) + axis.ordinalOffset; - } - return ret; - }, - /** - * Translate from linear (internal) to axis value - * - * @param Number val The linear abstracted value - * @param Boolean fromIndex Translate from an index in the ordinal positions rather than a value - */ - lin2val: function (val, fromIndex) { - var axis = this, - ordinalPositions = axis.ordinalPositions, - ret; - - if (!ordinalPositions) { // the visible range contains only equally spaced values - ret = val; - - } else { - - var ordinalSlope = axis.ordinalSlope, - ordinalOffset = axis.ordinalOffset, - i = ordinalPositions.length - 1, - linearEquivalentLeft, - linearEquivalentRight, - distance; - - - // Handle the case where we translate from the index directly, used only - // when panning an ordinal axis - if (fromIndex) { - - if (val < 0) { // out of range, in effect panning to the left - val = ordinalPositions[0]; - } else if (val > i) { // out of range, panning to the right - val = ordinalPositions[i]; - } else { // split it up - i = mathFloor(val); - distance = val - i; // the decimal - } - - // Loop down along the ordinal positions. When the linear equivalent of i matches - // an ordinal position, interpolate between the left and right values. - } else { - while (i--) { - linearEquivalentLeft = (ordinalSlope * i) + ordinalOffset; - if (val >= linearEquivalentLeft) { - linearEquivalentRight = (ordinalSlope * (i + 1)) + ordinalOffset; - distance = (val - linearEquivalentLeft) / (linearEquivalentRight - linearEquivalentLeft); // something between 0 and 1 - break; - } - } - } - - // If the index is within the range of the ordinal positions, return the associated - // or interpolated value. If not, just return the value - ret = distance !== UNDEFINED && ordinalPositions[i] !== UNDEFINED ? - ordinalPositions[i] + (distance ? distance * (ordinalPositions[i + 1] - ordinalPositions[i]) : 0) : - val; - } - return ret; - }, - /** - * Get the ordinal positions for the entire data set. This is necessary in chart panning - * because we need to find out what points or data groups are available outside the - * visible range. When a panning operation starts, if an index for the given grouping - * does not exists, it is created and cached. This index is deleted on updated data, so - * it will be regenerated the next time a panning operation starts. - */ - getExtendedPositions: function () { - var axis = this, - chart = axis.chart, - grouping = axis.series[0].currentDataGrouping, - ordinalIndex = axis.ordinalIndex, - key = grouping ? grouping.count + grouping.unitName : 'raw', - extremes = axis.getExtremes(), - fakeAxis, - fakeSeries; - - // If this is the first time, or the ordinal index is deleted by updatedData, - // create it. - if (!ordinalIndex) { - ordinalIndex = axis.ordinalIndex = {}; - } - - - if (!ordinalIndex[key]) { - - // Create a fake axis object where the extended ordinal positions are emulated - fakeAxis = { - series: [], - getExtremes: function () { - return { - min: extremes.dataMin, - max: extremes.dataMax - }; - }, - options: { - ordinal: true - }, - val2lin: Axis.prototype.val2lin // #2590 - }; - - // Add the fake series to hold the full data, then apply processData to it - each(axis.series, function (series) { - fakeSeries = { - xAxis: fakeAxis, - xData: series.xData, - chart: chart, - destroyGroupedData: noop - }; - fakeSeries.options = { - dataGrouping: grouping ? { - enabled: true, - forced: true, - approximation: 'open', // doesn't matter which, use the fastest - units: [[grouping.unitName, [grouping.count]]] - } : { - enabled: false - } - }; - series.processData.apply(fakeSeries); - - fakeAxis.series.push(fakeSeries); - }); - - // Run beforeSetTickPositions to compute the ordinalPositions - axis.beforeSetTickPositions.apply(fakeAxis); - - // Cache it - ordinalIndex[key] = fakeAxis.ordinalPositions; - } - return ordinalIndex[key]; - }, - - /** - * Find the factor to estimate how wide the plot area would have been if ordinal - * gaps were included. This value is used to compute an imagined plot width in order - * to establish the data grouping interval. - * - * A real world case is the intraday-candlestick - * example. Without this logic, it would show the correct data grouping when viewing - * a range within each day, but once moving the range to include the gap between two - * days, the interval would include the cut-away night hours and the data grouping - * would be wrong. So the below method tries to compensate by identifying the most - * common point interval, in this case days. - * - * An opposite case is presented in issue #718. We have a long array of daily data, - * then one point is appended one hour after the last point. We expect the data grouping - * not to change. - * - * In the future, if we find cases where this estimation doesn't work optimally, we - * might need to add a second pass to the data grouping logic, where we do another run - * with a greater interval if the number of data groups is more than a certain fraction - * of the desired group count. - */ - getGroupIntervalFactor: function (xMin, xMax, series) { - var i, - processedXData = series.processedXData, - len = processedXData.length, - distances = [], - median, - groupIntervalFactor = this.groupIntervalFactor; - - // Only do this computation for the first series, let the other inherit it (#2416) - if (!groupIntervalFactor) { - - // Register all the distances in an array - for (i = 0; i < len - 1; i++) { - distances[i] = processedXData[i + 1] - processedXData[i]; - } - - // Sort them and find the median - distances.sort(function (a, b) { - return a - b; - }); - median = distances[mathFloor(len / 2)]; - - // Compensate for series that don't extend through the entire axis extent. #1675. - xMin = mathMax(xMin, processedXData[0]); - xMax = mathMin(xMax, processedXData[len - 1]); - - this.groupIntervalFactor = groupIntervalFactor = (len * median) / (xMax - xMin); - } - - // Return the factor needed for data grouping - return groupIntervalFactor; - }, - - /** - * Make the tick intervals closer because the ordinal gaps make the ticks spread out or cluster - */ - postProcessTickInterval: function (tickInterval) { - // Problem: http://jsfiddle.net/highcharts/FQm4E/1/ - // This is a case where this algorithm doesn't work optimally. In this case, the - // tick labels are spread out per week, but all the gaps reside within weeks. So - // we have a situation where the labels are courser than the ordinal gaps, and - // thus the tick interval should not be altered - var ordinalSlope = this.ordinalSlope, - ret; - - - if (ordinalSlope) { - if (!this.options.breaks) { - ret = tickInterval / (ordinalSlope / this.closestPointRange); - } else { - ret = this.closestPointRange; - } - } else { - ret = tickInterval; - } - return ret; - } - }); - - // Extending the Chart.pan method for ordinal axes - wrap(Chart.prototype, 'pan', function (proceed, e) { - var chart = this, - xAxis = chart.xAxis[0], - chartX = e.chartX, - runBase = false; - - if (xAxis.options.ordinal && xAxis.series.length) { - - var mouseDownX = chart.mouseDownX, - extremes = xAxis.getExtremes(), - dataMax = extremes.dataMax, - min = extremes.min, - max = extremes.max, - trimmedRange, - hoverPoints = chart.hoverPoints, - closestPointRange = xAxis.closestPointRange, - pointPixelWidth = xAxis.translationSlope * (xAxis.ordinalSlope || closestPointRange), - movedUnits = (mouseDownX - chartX) / pointPixelWidth, // how many ordinal units did we move? - extendedAxis = { ordinalPositions: xAxis.getExtendedPositions() }, // get index of all the chart's points - ordinalPositions, - searchAxisLeft, - lin2val = xAxis.lin2val, - val2lin = xAxis.val2lin, - searchAxisRight; - - if (!extendedAxis.ordinalPositions) { // we have an ordinal axis, but the data is equally spaced - runBase = true; - - } else if (mathAbs(movedUnits) > 1) { - - // Remove active points for shared tooltip - if (hoverPoints) { - each(hoverPoints, function (point) { - point.setState(); - }); - } - - if (movedUnits < 0) { - searchAxisLeft = extendedAxis; - searchAxisRight = xAxis.ordinalPositions ? xAxis : extendedAxis; - } else { - searchAxisLeft = xAxis.ordinalPositions ? xAxis : extendedAxis; - searchAxisRight = extendedAxis; - } - - // In grouped data series, the last ordinal position represents the grouped data, which is - // to the left of the real data max. If we don't compensate for this, we will be allowed - // to pan grouped data series passed the right of the plot area. - ordinalPositions = searchAxisRight.ordinalPositions; - if (dataMax > ordinalPositions[ordinalPositions.length - 1]) { - ordinalPositions.push(dataMax); - } - - // Get the new min and max values by getting the ordinal index for the current extreme, - // then add the moved units and translate back to values. This happens on the - // extended ordinal positions if the new position is out of range, else it happens - // on the current x axis which is smaller and faster. - chart.fixedRange = max - min; - trimmedRange = xAxis.toFixedRange(null, null, - lin2val.apply(searchAxisLeft, [ - val2lin.apply(searchAxisLeft, [min, true]) + movedUnits, // the new index - true // translate from index - ]), - lin2val.apply(searchAxisRight, [ - val2lin.apply(searchAxisRight, [max, true]) + movedUnits, // the new index - true // translate from index - ]) - ); - - // Apply it if it is within the available data range - if (trimmedRange.min >= mathMin(extremes.dataMin, min) && trimmedRange.max <= mathMax(dataMax, max)) { - xAxis.setExtremes(trimmedRange.min, trimmedRange.max, true, false, { trigger: 'pan' }); - } - - chart.mouseDownX = chartX; // set new reference for next run - css(chart.container, { cursor: 'move' }); - } - - } else { - runBase = true; - } - - // revert to the linear chart.pan version - if (runBase) { - // call the original function - proceed.apply(this, Array.prototype.slice.call(arguments, 1)); - } - }); - - - - /** - * Extend getGraphPath by identifying gaps in the ordinal data so that we can draw a gap in the - * line or area - */ - Series.prototype.gappedPath = function () { - var gapSize = this.options.gapSize, - points = this.points.slice(), - i = points.length - 1; - - if (gapSize && i > 0) { // #5008 - - // extension for ordinal breaks - while (i--) { - if (points[i + 1].x - points[i].x > this.closestPointRange * gapSize) { - points.splice( // insert after this one - i + 1, - 0, - { isNull: true } - ); - } - } - } - - // Call base method - //return proceed.call(this, points, a, b); - return this.getGraphPath(points); - }; - - /* **************************************************************************** - * End ordinal axis logic * - *****************************************************************************/ - /** - * Highstock JS v4.2.5 (2016-05-06) - * Highcharts Broken Axis module - * - * License: www.highcharts.com/license - */ - - (function (factory) { - - factory(Highcharts); - - }(function (H) { - - 'use strict'; - - var pick = H.pick, - wrap = H.wrap, - each = H.each, - extend = H.extend, - fireEvent = H.fireEvent, - Axis = H.Axis, - Series = H.Series; - - function stripArguments() { - return Array.prototype.slice.call(arguments, 1); - } - - extend(Axis.prototype, { - isInBreak: function (brk, val) { - var ret, - repeat = brk.repeat || Infinity, - from = brk.from, - length = brk.to - brk.from, - test = (val >= from ? (val - from) % repeat : repeat - ((from - val) % repeat)); - - if (!brk.inclusive) { - ret = test < length && test !== 0; - } else { - ret = test <= length; - } - return ret; - }, - - isInAnyBreak: function (val, testKeep) { - - var breaks = this.options.breaks, - i = breaks && breaks.length, - inbrk, - keep, - ret; - - - if (i) { - - while (i--) { - if (this.isInBreak(breaks[i], val)) { - inbrk = true; - if (!keep) { - keep = pick(breaks[i].showPoints, this.isXAxis ? false : true); - } - } - } - - if (inbrk && testKeep) { - ret = inbrk && !keep; - } else { - ret = inbrk; - } - } - return ret; - } - }); - - wrap(Axis.prototype, 'setTickPositions', function (proceed) { - proceed.apply(this, Array.prototype.slice.call(arguments, 1)); - - if (this.options.breaks) { - var axis = this, - tickPositions = this.tickPositions, - info = this.tickPositions.info, - newPositions = [], - i; - - for (i = 0; i < tickPositions.length; i++) { - if (!axis.isInAnyBreak(tickPositions[i])) { - newPositions.push(tickPositions[i]); - } - } - - this.tickPositions = newPositions; - this.tickPositions.info = info; - } - }); - - wrap(Axis.prototype, 'init', function (proceed, chart, userOptions) { - // Force Axis to be not-ordinal when breaks are defined - if (userOptions.breaks && userOptions.breaks.length) { - userOptions.ordinal = false; - } - - proceed.call(this, chart, userOptions); - - if (this.options.breaks) { - - var axis = this; - - axis.isBroken = true; - - this.val2lin = function (val) { - var nval = val, - brk, - i; - - for (i = 0; i < axis.breakArray.length; i++) { - brk = axis.breakArray[i]; - if (brk.to <= val) { - nval -= brk.len; - } else if (brk.from >= val) { - break; - } else if (axis.isInBreak(brk, val)) { - nval -= (val - brk.from); - break; - } - } - - return nval; - }; - - this.lin2val = function (val) { - var nval = val, - brk, - i; - - for (i = 0; i < axis.breakArray.length; i++) { - brk = axis.breakArray[i]; - if (brk.from >= nval) { - break; - } else if (brk.to < nval) { - nval += brk.len; - } else if (axis.isInBreak(brk, nval)) { - nval += brk.len; - } - } - return nval; - }; - - this.setExtremes = function (newMin, newMax, redraw, animation, eventArguments) { - // If trying to set extremes inside a break, extend it to before and after the break ( #3857 ) - while (this.isInAnyBreak(newMin)) { - newMin -= this.closestPointRange; - } - while (this.isInAnyBreak(newMax)) { - newMax -= this.closestPointRange; - } - Axis.prototype.setExtremes.call(this, newMin, newMax, redraw, animation, eventArguments); - }; - - this.setAxisTranslation = function (saveOld) { - Axis.prototype.setAxisTranslation.call(this, saveOld); - - var breaks = axis.options.breaks, - breakArrayT = [], // Temporary one - breakArray = [], - length = 0, - inBrk, - repeat, - brk, - min = axis.userMin || axis.min, - max = axis.userMax || axis.max, - start, - i, - j; - - // Min & max check (#4247) - for (i in breaks) { - brk = breaks[i]; - repeat = brk.repeat || Infinity; - if (axis.isInBreak(brk, min)) { - min += (brk.to % repeat) - (min % repeat); - } - if (axis.isInBreak(brk, max)) { - max -= (max % repeat) - (brk.from % repeat); - } - } - - // Construct an array holding all breaks in the axis - for (i in breaks) { - brk = breaks[i]; - start = brk.from; - repeat = brk.repeat || Infinity; - - while (start - repeat > min) { - start -= repeat; - } - while (start < min) { - start += repeat; - } - - for (j = start; j < max; j += repeat) { - breakArrayT.push({ - value: j, - move: 'in' - }); - breakArrayT.push({ - value: j + (brk.to - brk.from), - move: 'out', - size: brk.breakSize - }); - } - } - - breakArrayT.sort(function (a, b) { - var ret; - if (a.value === b.value) { - ret = (a.move === 'in' ? 0 : 1) - (b.move === 'in' ? 0 : 1); - } else { - ret = a.value - b.value; - } - return ret; - }); - - // Simplify the breaks - inBrk = 0; - start = min; - - for (i in breakArrayT) { - brk = breakArrayT[i]; - inBrk += (brk.move === 'in' ? 1 : -1); - - if (inBrk === 1 && brk.move === 'in') { - start = brk.value; - } - if (inBrk === 0) { - breakArray.push({ - from: start, - to: brk.value, - len: brk.value - start - (brk.size || 0) - }); - length += brk.value - start - (brk.size || 0); - } - } - - axis.breakArray = breakArray; - - fireEvent(axis, 'afterBreaks'); - - axis.transA *= ((max - axis.min) / (max - min - length)); - - axis.min = min; - axis.max = max; - }; - } - }); - - wrap(Series.prototype, 'generatePoints', function (proceed) { - - proceed.apply(this, stripArguments(arguments)); - - var series = this, - xAxis = series.xAxis, - yAxis = series.yAxis, - points = series.points, - point, - i = points.length, - connectNulls = series.options.connectNulls, - nullGap; - - - if (xAxis && yAxis && (xAxis.options.breaks || yAxis.options.breaks)) { - while (i--) { - point = points[i]; - - nullGap = point.y === null && connectNulls === false; // respect nulls inside the break (#4275) - if (!nullGap && (xAxis.isInAnyBreak(point.x, true) || yAxis.isInAnyBreak(point.y, true))) { - points.splice(i, 1); - if (this.data[i]) { - this.data[i].destroyElements(); // removes the graphics for this point if they exist - } - } - } - } - - }); - - function drawPointsWrapped(proceed) { - proceed.apply(this); - this.drawBreaks(this.xAxis, ['x']); - this.drawBreaks(this.yAxis, pick(this.pointArrayMap, ['y'])); - } - - H.Series.prototype.drawBreaks = function (axis, keys) { - var series = this, - points = series.points, - breaks, - threshold, - eventName, - y; - - each(keys, function (key) { - breaks = axis.breakArray || []; - threshold = axis.isXAxis ? axis.min : pick(series.options.threshold, axis.min); - each(points, function (point) { - y = pick(point['stack' + key.toUpperCase()], point[key]); - each(breaks, function (brk) { - eventName = false; - - if ((threshold < brk.from && y > brk.to) || (threshold > brk.from && y < brk.from)) { - eventName = 'pointBreak'; - } else if ((threshold < brk.from && y > brk.from && y < brk.to) || (threshold > brk.from && y > brk.to && y < brk.from)) { // point falls inside the break - eventName = 'pointInBreak'; - } - if (eventName) { - fireEvent(axis, eventName, { point: point, brk: brk }); - } - }); - }); - }); - }; - - wrap(H.seriesTypes.column.prototype, 'drawPoints', drawPointsWrapped); - wrap(H.Series.prototype, 'drawPoints', drawPointsWrapped); - - })); - /* **************************************************************************** - * Start data grouping module * - ******************************************************************************/ - var DATA_GROUPING = 'dataGrouping', - seriesProto = Series.prototype, - baseProcessData = seriesProto.processData, - baseGeneratePoints = seriesProto.generatePoints, - baseDestroy = seriesProto.destroy, - - commonOptions = { - approximation: 'average', // average, open, high, low, close, sum - //enabled: null, // (true for stock charts, false for basic), - //forced: undefined, - groupPixelWidth: 2, - // the first one is the point or start value, the second is the start value if we're dealing with range, - // the third one is the end value if dealing with a range - dateTimeLabelFormats: { - millisecond: ['%A, %b %e, %H:%M:%S.%L', '%A, %b %e, %H:%M:%S.%L', '-%H:%M:%S.%L'], - second: ['%A, %b %e, %H:%M:%S', '%A, %b %e, %H:%M:%S', '-%H:%M:%S'], - minute: ['%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M'], - hour: ['%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M'], - day: ['%A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y'], - week: ['Week from %A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y'], - month: ['%B %Y', '%B', '-%B %Y'], - year: ['%Y', '%Y', '-%Y'] - } - // smoothed = false, // enable this for navigator series only - }, - - specificOptions = { // extends common options - line: {}, - spline: {}, - area: {}, - areaspline: {}, - column: { - approximation: 'sum', - groupPixelWidth: 10 - }, - arearange: { - approximation: 'range' - }, - areasplinerange: { - approximation: 'range' - }, - columnrange: { - approximation: 'range', - groupPixelWidth: 10 - }, - candlestick: { - approximation: 'ohlc', - groupPixelWidth: 10 - }, - ohlc: { - approximation: 'ohlc', - groupPixelWidth: 5 - } - }, - - // units are defined in a separate array to allow complete overriding in case of a user option - defaultDataGroupingUnits = [ - [ - 'millisecond', // unit name - [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples - ], [ - 'second', - [1, 2, 5, 10, 15, 30] - ], [ - 'minute', - [1, 2, 5, 10, 15, 30] - ], [ - 'hour', - [1, 2, 3, 4, 6, 8, 12] - ], [ - 'day', - [1] - ], [ - 'week', - [1] - ], [ - 'month', - [1, 3, 6] - ], [ - 'year', - null - ] - ], - - - /** - * Define the available approximation types. The data grouping approximations takes an array - * or numbers as the first parameter. In case of ohlc, four arrays are sent in as four parameters. - * Each array consists only of numbers. In case null values belong to the group, the property - * .hasNulls will be set to true on the array. - */ - approximations = { - sum: function (arr) { - var len = arr.length, - ret; - - // 1. it consists of nulls exclusively - if (!len && arr.hasNulls) { - ret = null; - // 2. it has a length and real values - } else if (len) { - ret = 0; - while (len--) { - ret += arr[len]; - } - } - // 3. it has zero length, so just return undefined - // => doNothing() - - return ret; - }, - average: function (arr) { - var len = arr.length, - ret = approximations.sum(arr); - - // If we have a number, return it divided by the length. If not, return - // null or undefined based on what the sum method finds. - if (isNumber(ret) && len) { - ret = ret / len; - } - - return ret; - }, - open: function (arr) { - return arr.length ? arr[0] : (arr.hasNulls ? null : UNDEFINED); - }, - high: function (arr) { - return arr.length ? arrayMax(arr) : (arr.hasNulls ? null : UNDEFINED); - }, - low: function (arr) { - return arr.length ? arrayMin(arr) : (arr.hasNulls ? null : UNDEFINED); - }, - close: function (arr) { - return arr.length ? arr[arr.length - 1] : (arr.hasNulls ? null : UNDEFINED); - }, - // ohlc and range are special cases where a multidimensional array is input and an array is output - ohlc: function (open, high, low, close) { - open = approximations.open(open); - high = approximations.high(high); - low = approximations.low(low); - close = approximations.close(close); - - if (isNumber(open) || isNumber(high) || isNumber(low) || isNumber(close)) { - return [open, high, low, close]; - } - // else, return is undefined - }, - range: function (low, high) { - low = approximations.low(low); - high = approximations.high(high); - - if (isNumber(low) || isNumber(high)) { - return [low, high]; - } - // else, return is undefined - } - }; - - - /** - * Takes parallel arrays of x and y data and groups the data into intervals defined by groupPositions, a collection - * of starting x values for each group. - */ - seriesProto.groupData = function (xData, yData, groupPositions, approximation) { - var series = this, - data = series.data, - dataOptions = series.options.data, - groupedXData = [], - groupedYData = [], - groupMap = [], - dataLength = xData.length, - pointX, - pointY, - groupedY, - handleYData = !!yData, // when grouping the fake extended axis for panning, we don't need to consider y - values = [[], [], [], []], - approximationFn = typeof approximation === 'function' ? approximation : approximations[approximation], - pointArrayMap = series.pointArrayMap, - pointArrayMapLength = pointArrayMap && pointArrayMap.length, - i, - start = 0; - - // Start with the first point within the X axis range (#2696) - for (i = 0; i <= dataLength; i++) { - if (xData[i] >= groupPositions[0]) { - break; - } - } - - for (i; i <= dataLength; i++) { - - // when a new group is entered, summarize and initiate the previous group - while ((groupPositions[1] !== UNDEFINED && xData[i] >= groupPositions[1]) || - i === dataLength) { // get the last group - - // get group x and y - pointX = groupPositions.shift(); - groupedY = approximationFn.apply(0, values); - - // push the grouped data - if (groupedY !== UNDEFINED) { - groupedXData.push(pointX); - groupedYData.push(groupedY); - groupMap.push({ start: start, length: values[0].length }); - } - - // reset the aggregate arrays - start = i; - values[0] = []; - values[1] = []; - values[2] = []; - values[3] = []; - - // don't loop beyond the last group - if (i === dataLength) { - break; - } - } - - // break out - if (i === dataLength) { - break; - } - - // for each raw data point, push it to an array that contains all values for this specific group - if (pointArrayMap) { - - var index = series.cropStart + i, - point = (data && data[index]) || series.pointClass.prototype.applyOptions.apply({ series: series }, [dataOptions[index]]), - j, - val; - - for (j = 0; j < pointArrayMapLength; j++) { - val = point[pointArrayMap[j]]; - if (isNumber(val)) { - values[j].push(val); - } else if (val === null) { - values[j].hasNulls = true; - } - } - - } else { - pointY = handleYData ? yData[i] : null; - - if (isNumber(pointY)) { - values[0].push(pointY); - } else if (pointY === null) { - values[0].hasNulls = true; - } - } - } - - return [groupedXData, groupedYData, groupMap]; - }; - - /** - * Extend the basic processData method, that crops the data to the current zoom - * range, with data grouping logic. - */ - seriesProto.processData = function () { - var series = this, - chart = series.chart, - options = series.options, - dataGroupingOptions = options[DATA_GROUPING], - groupingEnabled = series.allowDG !== false && dataGroupingOptions && pick(dataGroupingOptions.enabled, chart.options._stock), - hasGroupedData, - skip; - - // run base method - series.forceCrop = groupingEnabled; // #334 - series.groupPixelWidth = null; // #2110 - series.hasProcessed = true; // #2692 - - // skip if processData returns false or if grouping is disabled (in that order) - skip = baseProcessData.apply(series, arguments) === false || !groupingEnabled; - if (!skip) { - series.destroyGroupedData(); - - var i, - processedXData = series.processedXData, - processedYData = series.processedYData, - plotSizeX = chart.plotSizeX, - xAxis = series.xAxis, - ordinal = xAxis.options.ordinal, - groupPixelWidth = series.groupPixelWidth = xAxis.getGroupPixelWidth && xAxis.getGroupPixelWidth(); - - // Execute grouping if the amount of points is greater than the limit defined in groupPixelWidth - if (groupPixelWidth) { - hasGroupedData = true; - - series.points = null; // force recreation of point instances in series.translate - - var extremes = xAxis.getExtremes(), - xMin = extremes.min, - xMax = extremes.max, - groupIntervalFactor = (ordinal && xAxis.getGroupIntervalFactor(xMin, xMax, series)) || 1, - interval = (groupPixelWidth * (xMax - xMin) / plotSizeX) * groupIntervalFactor, - groupPositions = xAxis.getTimeTicks( - xAxis.normalizeTimeTickInterval(interval, dataGroupingOptions.units || defaultDataGroupingUnits), - Math.min(xMin, processedXData[0]), // Processed data may extend beyond axis (#4907) - Math.max(xMax, processedXData[processedXData.length - 1]), - xAxis.options.startOfWeek, - processedXData, - series.closestPointRange - ), - groupedData = seriesProto.groupData.apply(series, [processedXData, processedYData, groupPositions, dataGroupingOptions.approximation]), - groupedXData = groupedData[0], - groupedYData = groupedData[1]; - - // prevent the smoothed data to spill out left and right, and make - // sure data is not shifted to the left - if (dataGroupingOptions.smoothed) { - i = groupedXData.length - 1; - groupedXData[i] = Math.min(groupedXData[i], xMax); - while (i-- && i > 0) { - groupedXData[i] += interval / 2; - } - groupedXData[0] = Math.max(groupedXData[0], xMin); - } - - // record what data grouping values were used - series.currentDataGrouping = groupPositions.info; - series.closestPointRange = groupPositions.info.totalRange; - series.groupMap = groupedData[2]; - - // Make sure the X axis extends to show the first group (#2533) - if (defined(groupedXData[0]) && groupedXData[0] < xAxis.dataMin) { - if (xAxis.min === xAxis.dataMin) { - xAxis.min = groupedXData[0]; - } - xAxis.dataMin = groupedXData[0]; - } - - // set series props - series.processedXData = groupedXData; - series.processedYData = groupedYData; - } else { - series.currentDataGrouping = series.groupMap = null; - } - series.hasGroupedData = hasGroupedData; - } - }; - - /** - * Destroy the grouped data points. #622, #740 - */ - seriesProto.destroyGroupedData = function () { - - var groupedData = this.groupedData; - - // clear previous groups - each(groupedData || [], function (point, i) { - if (point) { - groupedData[i] = point.destroy ? point.destroy() : null; - } - }); - this.groupedData = null; - }; - - /** - * Override the generatePoints method by adding a reference to grouped data - */ - seriesProto.generatePoints = function () { - - baseGeneratePoints.apply(this); - - // record grouped data in order to let it be destroyed the next time processData runs - this.destroyGroupedData(); // #622 - this.groupedData = this.hasGroupedData ? this.points : null; - }; - - /** - * Extend the original method, make the tooltip's header reflect the grouped range - */ - wrap(Tooltip.prototype, 'tooltipFooterHeaderFormatter', function (proceed, point, isFooter) { - var tooltip = this, - series = point.series, - options = series.options, - tooltipOptions = series.tooltipOptions, - dataGroupingOptions = options.dataGrouping, - xDateFormat = tooltipOptions.xDateFormat, - xDateFormatEnd, - xAxis = series.xAxis, - currentDataGrouping, - dateTimeLabelFormats, - labelFormats, - formattedKey; - - // apply only to grouped series - if (xAxis && xAxis.options.type === 'datetime' && dataGroupingOptions && isNumber(point.key)) { - - // set variables - currentDataGrouping = series.currentDataGrouping; - dateTimeLabelFormats = dataGroupingOptions.dateTimeLabelFormats; - - // if we have grouped data, use the grouping information to get the right format - if (currentDataGrouping) { - labelFormats = dateTimeLabelFormats[currentDataGrouping.unitName]; - if (currentDataGrouping.count === 1) { - xDateFormat = labelFormats[0]; - } else { - xDateFormat = labelFormats[1]; - xDateFormatEnd = labelFormats[2]; - } - // if not grouped, and we don't have set the xDateFormat option, get the best fit, - // so if the least distance between points is one minute, show it, but if the - // least distance is one day, skip hours and minutes etc. - } else if (!xDateFormat && dateTimeLabelFormats) { - xDateFormat = tooltip.getXDateFormat(point, tooltipOptions, xAxis); - } - - // now format the key - formattedKey = dateFormat(xDateFormat, point.key); - if (xDateFormatEnd) { - formattedKey += dateFormat(xDateFormatEnd, point.key + currentDataGrouping.totalRange - 1); - } - - // return the replaced format - return format(tooltipOptions[(isFooter ? 'footer' : 'header') + 'Format'], { - point: extend(point, { key: formattedKey }), - series: series - }); - - } - - // else, fall back to the regular formatter - return proceed.call(tooltip, point, isFooter); - }); - - /** - * Extend the series destroyer - */ - seriesProto.destroy = function () { - var series = this, - groupedData = series.groupedData || [], - i = groupedData.length; - - while (i--) { - if (groupedData[i]) { - groupedData[i].destroy(); - } - } - baseDestroy.apply(series); - }; - - - // Handle default options for data grouping. This must be set at runtime because some series types are - // defined after this. - wrap(seriesProto, 'setOptions', function (proceed, itemOptions) { - - var options = proceed.call(this, itemOptions), - type = this.type, - plotOptions = this.chart.options.plotOptions, - defaultOptions = defaultPlotOptions[type].dataGrouping; - - if (specificOptions[type]) { // #1284 - if (!defaultOptions) { - defaultOptions = merge(commonOptions, specificOptions[type]); - } - - options.dataGrouping = merge( - defaultOptions, - plotOptions.series && plotOptions.series.dataGrouping, // #1228 - plotOptions[type].dataGrouping, // Set by the StockChart constructor - itemOptions.dataGrouping - ); - } - - if (this.chart.options._stock) { - this.requireSorting = true; - } - - return options; - }); - - - /** - * When resetting the scale reset the hasProccessed flag to avoid taking previous data grouping - * of neighbour series into accound when determining group pixel width (#2692). - */ - wrap(Axis.prototype, 'setScale', function (proceed) { - proceed.call(this); - each(this.series, function (series) { - series.hasProcessed = false; - }); - }); - - /** - * Get the data grouping pixel width based on the greatest defined individual width - * of the axis' series, and if whether one of the axes need grouping. - */ - Axis.prototype.getGroupPixelWidth = function () { - - var series = this.series, - len = series.length, - i, - groupPixelWidth = 0, - doGrouping = false, - dataLength, - dgOptions; - - // If multiple series are compared on the same x axis, give them the same - // group pixel width (#334) - i = len; - while (i--) { - dgOptions = series[i].options.dataGrouping; - if (dgOptions) { - groupPixelWidth = mathMax(groupPixelWidth, dgOptions.groupPixelWidth); - - } - } - - // If one of the series needs grouping, apply it to all (#1634) - i = len; - while (i--) { - dgOptions = series[i].options.dataGrouping; - - if (dgOptions && series[i].hasProcessed) { // #2692 - - dataLength = (series[i].processedXData || series[i].data).length; - - // Execute grouping if the amount of points is greater than the limit defined in groupPixelWidth - if (series[i].groupPixelWidth || dataLength > (this.chart.plotSizeX / groupPixelWidth) || (dataLength && dgOptions.forced)) { - doGrouping = true; - } - } - } - - return doGrouping ? groupPixelWidth : 0; - }; - - /** - * Force data grouping on all the axis' series. - */ - Axis.prototype.setDataGrouping = function (dataGrouping, redraw) { - var i; - - redraw = pick(redraw, true); - - if (!dataGrouping) { - dataGrouping = { - forced: false, - units: null - }; - } - - // Axis is instantiated, update all series - if (this instanceof Axis) { - i = this.series.length; - while (i--) { - this.series[i].update({ - dataGrouping: dataGrouping - }, false); - } - - // Axis not yet instanciated, alter series options - } else { - each(this.chart.options.series, function (seriesOptions) { - seriesOptions.dataGrouping = dataGrouping; - }, false); - } - - if (redraw) { - this.chart.redraw(); - } - }; - - - - /* **************************************************************************** - * End data grouping module * - ******************************************************************************/ - /* **************************************************************************** - * Start OHLC series code * - *****************************************************************************/ - - // 1 - Set default options - defaultPlotOptions.ohlc = merge(defaultPlotOptions.column, { - lineWidth: 1, - tooltip: { - pointFormat: '\u25CF {series.name}
' + - 'Open: {point.open}
' + - 'High: {point.high}
' + - 'Low: {point.low}
' + - 'Close: {point.close}
' - }, - states: { - hover: { - lineWidth: 3 - } - }, - threshold: null - //upColor: undefined - }); - - // 2 - Create the OHLCSeries object - var OHLCSeries = extendClass(seriesTypes.column, { - type: 'ohlc', - pointArrayMap: ['open', 'high', 'low', 'close'], // array point configs are mapped to this - toYData: function (point) { // return a plain array for speedy calculation - return [point.open, point.high, point.low, point.close]; - }, - pointValKey: 'high', - - pointAttrToOptions: { // mapping between SVG attributes and the corresponding options - stroke: 'color', - 'stroke-width': 'lineWidth' - }, - upColorProp: 'stroke', - - /** - * Postprocess mapping between options and SVG attributes - */ - getAttribs: function () { - seriesTypes.column.prototype.getAttribs.apply(this, arguments); - var series = this, - options = series.options, - stateOptions = options.states, - upColor = options.upColor || series.color, - seriesDownPointAttr = merge(series.pointAttr), - upColorProp = series.upColorProp; - - seriesDownPointAttr[''][upColorProp] = upColor; - seriesDownPointAttr.hover[upColorProp] = stateOptions.hover.upColor || upColor; - seriesDownPointAttr.select[upColorProp] = stateOptions.select.upColor || upColor; - - each(series.points, function (point) { - if (point.open < point.close && !point.options.color) { - point.pointAttr = seriesDownPointAttr; - } - }); - }, - - /** - * Translate data points from raw values x and y to plotX and plotY - */ - translate: function () { - var series = this, - yAxis = series.yAxis; - - seriesTypes.column.prototype.translate.apply(series); - - // do the translation - each(series.points, function (point) { - // the graphics - if (point.open !== null) { - point.plotOpen = yAxis.translate(point.open, 0, 1, 0, 1); - } - if (point.close !== null) { - point.plotClose = yAxis.translate(point.close, 0, 1, 0, 1); - } - - }); - }, - - /** - * Draw the data points - */ - drawPoints: function () { - var series = this, - points = series.points, - chart = series.chart, - pointAttr, - plotOpen, - plotClose, - crispCorr, - halfWidth, - path, - graphic, - crispX; - - - each(points, function (point) { - if (point.plotY !== UNDEFINED) { - - graphic = point.graphic; - pointAttr = point.pointAttr[point.selected ? 'selected' : ''] || series.pointAttr[NORMAL_STATE]; - - // crisp vector coordinates - crispCorr = (pointAttr['stroke-width'] % 2) / 2; - crispX = mathRound(point.plotX) - crispCorr; // #2596 - halfWidth = mathRound(point.shapeArgs.width / 2); - - // the vertical stem - path = [ - 'M', - crispX, mathRound(point.yBottom), - 'L', - crispX, mathRound(point.plotY) - ]; - - // open - if (point.open !== null) { - plotOpen = mathRound(point.plotOpen) + crispCorr; - path.push( - 'M', - crispX, - plotOpen, - 'L', - crispX - halfWidth, - plotOpen - ); - } - - // close - if (point.close !== null) { - plotClose = mathRound(point.plotClose) + crispCorr; - path.push( - 'M', - crispX, - plotClose, - 'L', - crispX + halfWidth, - plotClose - ); - } - - // create and/or update the graphic - if (graphic) { - graphic - .attr(pointAttr) // #3897 - .animate({ d: path }); - } else { - point.graphic = chart.renderer.path(path) - .attr(pointAttr) - .add(series.group); - } - - } - - - }); - - }, - - /** - * Disable animation - */ - animate: null - - - }); - seriesTypes.ohlc = OHLCSeries; - /* **************************************************************************** - * End OHLC series code * - *****************************************************************************/ - /* **************************************************************************** - * Start Candlestick series code * - *****************************************************************************/ - - // 1 - set default options - defaultPlotOptions.candlestick = merge(defaultPlotOptions.column, { - lineColor: 'black', - lineWidth: 1, - states: { - hover: { - lineWidth: 2 - } - }, - tooltip: defaultPlotOptions.ohlc.tooltip, - threshold: null, - upColor: 'white' - // upLineColor: null - }); - - // 2 - Create the CandlestickSeries object - var CandlestickSeries = extendClass(OHLCSeries, { - type: 'candlestick', - - /** - * One-to-one mapping from options to SVG attributes - */ - pointAttrToOptions: { // mapping between SVG attributes and the corresponding options - fill: 'color', - stroke: 'lineColor', - 'stroke-width': 'lineWidth' - }, - upColorProp: 'fill', - - /** - * Postprocess mapping between options and SVG attributes - */ - getAttribs: function () { - seriesTypes.ohlc.prototype.getAttribs.apply(this, arguments); - var series = this, - options = series.options, - stateOptions = options.states, - upLineColor = options.upLineColor || options.lineColor, - hoverStroke = stateOptions.hover.upLineColor || upLineColor, - selectStroke = stateOptions.select.upLineColor || upLineColor; - - // Add custom line color for points going up (close > open). - // Fill is handled by OHLCSeries' getAttribs. - each(series.points, function (point) { - if (point.open < point.close) { - - // If an individual line color is set, we need to merge the - // point attributes, because they are shared between all up - // points by inheritance from OHCLSeries. - if (point.lineColor) { - point.pointAttr = merge(point.pointAttr); - upLineColor = point.lineColor; - } - - point.pointAttr[''].stroke = upLineColor; - point.pointAttr.hover.stroke = hoverStroke; - point.pointAttr.select.stroke = selectStroke; - } - }); - }, - - /** - * Draw the data points - */ - drawPoints: function () { - var series = this, //state = series.state, - points = series.points, - chart = series.chart, - pointAttr, - seriesPointAttr = series.pointAttr[''], - plotOpen, - plotClose, - topBox, - bottomBox, - hasTopWhisker, - hasBottomWhisker, - crispCorr, - crispX, - graphic, - path, - halfWidth; - - - each(points, function (point) { - - graphic = point.graphic; - if (point.plotY !== UNDEFINED) { - - pointAttr = point.pointAttr[point.selected ? 'selected' : ''] || seriesPointAttr; - - // crisp vector coordinates - crispCorr = (pointAttr['stroke-width'] % 2) / 2; - crispX = mathRound(point.plotX) - crispCorr; // #2596 - plotOpen = point.plotOpen; - plotClose = point.plotClose; - topBox = math.min(plotOpen, plotClose); - bottomBox = math.max(plotOpen, plotClose); - halfWidth = mathRound(point.shapeArgs.width / 2); - hasTopWhisker = mathRound(topBox) !== mathRound(point.plotY); - hasBottomWhisker = bottomBox !== point.yBottom; - topBox = mathRound(topBox) + crispCorr; - bottomBox = mathRound(bottomBox) + crispCorr; - - // Create the path. Due to a bug in Chrome 49, the path is first instanciated - // with no values, then the values pushed. For unknown reasons, instanciated - // the path array with all the values would lead to a crash when updating - // frequently (#5193). - path = []; - path.push( - 'M', - crispX - halfWidth, bottomBox, - 'L', - crispX - halfWidth, topBox, - 'L', - crispX + halfWidth, topBox, - 'L', - crispX + halfWidth, bottomBox, - 'Z', // Use a close statement to ensure a nice rectangle #2602 - 'M', - crispX, topBox, - 'L', - crispX, hasTopWhisker ? mathRound(point.plotY) : topBox, // #460, #2094 - 'M', - crispX, bottomBox, - 'L', - crispX, hasBottomWhisker ? mathRound(point.yBottom) : bottomBox // #460, #2094 - ); - - if (graphic) { - graphic - .attr(pointAttr) // #3897 - .animate({ d: path }); - } else { - point.graphic = chart.renderer.path(path) - .attr(pointAttr) - .add(series.group) - .shadow(series.options.shadow); - } - - } - }); - - } - - - }); - - seriesTypes.candlestick = CandlestickSeries; - - /* **************************************************************************** - * End Candlestick series code * - *****************************************************************************/ - /* **************************************************************************** - * Start Flags series code * - *****************************************************************************/ - - var symbols = SVGRenderer.prototype.symbols; - - // 1 - set default options - defaultPlotOptions.flags = merge(defaultPlotOptions.column, { - fillColor: 'white', - lineWidth: 1, - pointRange: 0, // #673 - //radius: 2, - shape: 'flag', - stackDistance: 12, - states: { - hover: { - lineColor: 'black', - fillColor: '#FCFFC5' - } - }, - style: { - fontSize: '11px', - fontWeight: 'bold', - textAlign: 'center' - }, - tooltip: { - pointFormat: '{point.text}
' - }, - threshold: null, - y: -30 - }); - - // 2 - Create the CandlestickSeries object - seriesTypes.flags = extendClass(seriesTypes.column, { - type: 'flags', - sorted: false, - noSharedTooltip: true, - allowDG: false, - takeOrdinalPosition: false, // #1074 - trackerGroups: ['markerGroup'], - forceCrop: true, - /** - * Inherit the initialization from base Series - */ - init: Series.prototype.init, - - /** - * One-to-one mapping from options to SVG attributes - */ - pointAttrToOptions: { // mapping between SVG attributes and the corresponding options - fill: 'fillColor', - stroke: 'color', - 'stroke-width': 'lineWidth', - r: 'radius' - }, - - /** - * Extend the translate method by placing the point on the related series - */ - translate: function () { - - seriesTypes.column.prototype.translate.apply(this); - - var series = this, - options = series.options, - chart = series.chart, - points = series.points, - cursor = points.length - 1, - point, - lastPoint, - optionsOnSeries = options.onSeries, - onSeries = optionsOnSeries && chart.get(optionsOnSeries), - onKey = options.onKey || 'y', - step = onSeries && onSeries.options.step, - onData = onSeries && onSeries.points, - i = onData && onData.length, - xAxis = series.xAxis, - xAxisExt = xAxis.getExtremes(), - leftPoint, - lastX, - rightPoint, - currentDataGrouping; - - // relate to a master series - if (onSeries && onSeries.visible && i) { - currentDataGrouping = onSeries.currentDataGrouping; - lastX = onData[i - 1].x + (currentDataGrouping ? currentDataGrouping.totalRange : 0); // #2374 - - // sort the data points - points.sort(function (a, b) { - return (a.x - b.x); - }); - - onKey = 'plot' + onKey[0].toUpperCase() + onKey.substr(1); - while (i-- && points[cursor]) { - point = points[cursor]; - leftPoint = onData[i]; - if (leftPoint.x <= point.x && leftPoint[onKey] !== undefined) { - if (point.x <= lastX) { // #803 - - point.plotY = leftPoint[onKey]; - - // interpolate between points, #666 - if (leftPoint.x < point.x && !step) { - rightPoint = onData[i + 1]; - if (rightPoint && rightPoint[onKey] !== UNDEFINED) { - point.plotY += - ((point.x - leftPoint.x) / (rightPoint.x - leftPoint.x)) * // the distance ratio, between 0 and 1 - (rightPoint[onKey] - leftPoint[onKey]); // the y distance - } - } - } - cursor--; - i++; // check again for points in the same x position - if (cursor < 0) { - break; - } - } - } - } - - // Add plotY position and handle stacking - each(points, function (point, i) { - - var stackIndex; - - // Undefined plotY means the point is either on axis, outside series range or hidden series. - // If the series is outside the range of the x axis it should fall through with - // an undefined plotY, but then we must remove the shapeArgs (#847). - if (point.plotY === UNDEFINED) { - if (point.x >= xAxisExt.min && point.x <= xAxisExt.max) { // we're inside xAxis range - point.plotY = chart.chartHeight - xAxis.bottom - (xAxis.opposite ? xAxis.height : 0) + xAxis.offset - chart.plotTop; - } else { - point.shapeArgs = {}; // 847 - } - } - // if multiple flags appear at the same x, order them into a stack - lastPoint = points[i - 1]; - if (lastPoint && lastPoint.plotX === point.plotX) { - if (lastPoint.stackIndex === UNDEFINED) { - lastPoint.stackIndex = 0; - } - stackIndex = lastPoint.stackIndex + 1; - } - point.stackIndex = stackIndex; // #3639 - }); - - - }, - - /** - * Draw the markers - */ - drawPoints: function () { - var series = this, - pointAttr, - seriesPointAttr = series.pointAttr[''], - points = series.points, - chart = series.chart, - renderer = chart.renderer, - plotX, - plotY, - options = series.options, - optionsY = options.y, - shape, - i, - point, - graphic, - stackIndex, - anchorX, - anchorY, - outsideRight, - yAxis = series.yAxis; - - i = points.length; - while (i--) { - point = points[i]; - outsideRight = point.plotX > series.xAxis.len; - plotX = point.plotX; - if (plotX > 0) { // #3119 - plotX -= pick(point.lineWidth, options.lineWidth) % 2; // #4285 - } - stackIndex = point.stackIndex; - shape = point.options.shape || options.shape; - plotY = point.plotY; - if (plotY !== UNDEFINED) { - plotY = point.plotY + optionsY - (stackIndex !== UNDEFINED && stackIndex * options.stackDistance); - } - anchorX = stackIndex ? UNDEFINED : point.plotX; // skip connectors for higher level stacked points - anchorY = stackIndex ? UNDEFINED : point.plotY; - - graphic = point.graphic; - - // only draw the point if y is defined and the flag is within the visible area - if (plotY !== UNDEFINED && plotX >= 0 && !outsideRight) { - // shortcuts - pointAttr = point.pointAttr[point.selected ? 'select' : ''] || seriesPointAttr; - if (graphic) { // update - graphic.attr({ - x: plotX, - y: plotY, - r: pointAttr.r, - anchorX: anchorX, - anchorY: anchorY - }); - } else { - graphic = point.graphic = renderer.label( - point.options.title || options.title || 'A', - plotX, - plotY, - shape, - anchorX, - anchorY, - options.useHTML - ) - .css(merge(options.style, point.style)) - .attr(pointAttr) - .attr({ - align: shape === 'flag' ? 'left' : 'center', - width: options.width, - height: options.height - }) - .add(series.markerGroup) - .shadow(options.shadow); - - } - - // Set the tooltip anchor position - point.tooltipPos = chart.inverted ? [yAxis.len + yAxis.pos - chart.plotLeft - plotY, series.xAxis.len - plotX] : [plotX, plotY]; - - } else if (graphic) { - point.graphic = graphic.destroy(); - } - - } - - }, - - /** - * Extend the column trackers with listeners to expand and contract stacks - */ - drawTracker: function () { - var series = this, - points = series.points; - - TrackerMixin.drawTrackerPoint.apply(this); - - // Bring each stacked flag up on mouse over, this allows readability of vertically - // stacked elements as well as tight points on the x axis. #1924. - each(points, function (point) { - var graphic = point.graphic; - if (graphic) { - addEvent(graphic.element, 'mouseover', function () { - - // Raise this point - if (point.stackIndex > 0 && !point.raised) { - point._y = graphic.y; - graphic.attr({ - y: point._y - 8 - }); - point.raised = true; - } - - // Revert other raised points - each(points, function (otherPoint) { - if (otherPoint !== point && otherPoint.raised && otherPoint.graphic) { - otherPoint.graphic.attr({ - y: otherPoint._y - }); - otherPoint.raised = false; - } - }); - }); - } - }); - }, - - /** - * Disable animation - */ - animate: noop, - buildKDTree: noop, - setClip: noop - - }); - - // create the flag icon with anchor - symbols.flag = function (x, y, w, h, options) { - var anchorX = (options && options.anchorX) || x, - anchorY = (options && options.anchorY) || y; - - return [ - 'M', anchorX, anchorY, - 'L', x, y + h, - x, y, - x + w, y, - x + w, y + h, - x, y + h, - 'Z' - ]; - }; - - // create the circlepin and squarepin icons with anchor - each(['circle', 'square'], function (shape) { - symbols[shape + 'pin'] = function (x, y, w, h, options) { - - var anchorX = options && options.anchorX, - anchorY = options && options.anchorY, - path, - labelTopOrBottomY; - - // For single-letter flags, make sure circular flags are not taller than their width - if (shape === 'circle' && h > w) { - x -= mathRound((h - w) / 2); - w = h; - } - - path = symbols[shape](x, y, w, h); - - if (anchorX && anchorY) { - // if the label is below the anchor, draw the connecting line from the top edge of the label - // otherwise start drawing from the bottom edge - labelTopOrBottomY = (y > anchorY) ? y : y + h; - path.push('M', anchorX, labelTopOrBottomY, 'L', anchorX, anchorY); - } - - return path; - }; - }); - - // The symbol callbacks are generated on the SVGRenderer object in all browsers. Even - // VML browsers need this in order to generate shapes in export. Now share - // them with the VMLRenderer. - if (Renderer === Highcharts.VMLRenderer) { - each(['flag', 'circlepin', 'squarepin'], function (shape) { - VMLRenderer.prototype.symbols[shape] = symbols[shape]; - }); - } - - /* **************************************************************************** - * End Flags series code * - *****************************************************************************/ - /* **************************************************************************** - * Start Scroller code * - *****************************************************************************/ - var units = [].concat(defaultDataGroupingUnits), // copy - defaultSeriesType, - - // Finding the min or max of a set of variables where we don't know if they are defined, - // is a pattern that is repeated several places in Highcharts. Consider making this - // a global utility method. - numExt = function (extreme) { - var numbers = grep(arguments, function (n) { - return isNumber(n); - }); - if (numbers.length) { - return Math[extreme].apply(0, numbers); - } - }; - - // add more resolution to units - units[4] = ['day', [1, 2, 3, 4]]; // allow more days - units[5] = ['week', [1, 2, 3]]; // allow more weeks - - defaultSeriesType = seriesTypes.areaspline === UNDEFINED ? 'line' : 'areaspline'; - - extend(defaultOptions, { - navigator: { - //enabled: true, - handles: { - backgroundColor: '#ebe7e8', - borderColor: '#b2b1b6' - }, - height: 40, - margin: 25, - maskFill: 'rgba(128,179,236,0.3)', - maskInside: true, - outlineColor: '#b2b1b6', - outlineWidth: 1, - series: { - type: defaultSeriesType, - color: '#4572A7', - compare: null, - fillOpacity: 0.05, - dataGrouping: { - approximation: 'average', - enabled: true, - groupPixelWidth: 2, - smoothed: true, - units: units - }, - dataLabels: { - enabled: false, - zIndex: 2 // #1839 - }, - id: PREFIX + 'navigator-series', - lineColor: null, // Allow color setting while disallowing default candlestick setting (#4602) - lineWidth: 1, - marker: { - enabled: false - }, - pointRange: 0, - shadow: false, - threshold: null - }, - //top: undefined, - xAxis: { - tickWidth: 0, - lineWidth: 0, - gridLineColor: '#EEE', - gridLineWidth: 1, - tickPixelInterval: 200, - labels: { - align: 'left', - style: { - color: '#888' - }, - x: 3, - y: -4 - }, - crosshair: false - }, - yAxis: { - gridLineWidth: 0, - startOnTick: false, - endOnTick: false, - minPadding: 0.1, - maxPadding: 0.1, - labels: { - enabled: false - }, - crosshair: false, - title: { - text: null - }, - tickWidth: 0 - } - }, - scrollbar: { - //enabled: true - height: isTouchDevice ? 20 : 14, - barBackgroundColor: '#bfc8d1', - barBorderRadius: 0, - barBorderWidth: 1, - barBorderColor: '#bfc8d1', - buttonArrowColor: '#666', - buttonBackgroundColor: '#ebe7e8', - buttonBorderColor: '#bbb', - buttonBorderRadius: 0, - buttonBorderWidth: 1, - minWidth: 6, - rifleColor: '#666', - trackBackgroundColor: '#eeeeee', - trackBorderColor: '#eeeeee', - trackBorderWidth: 1, - // trackBorderRadius: 0 - liveRedraw: hasSVG && !isTouchDevice - } - }); - - /** - * The Scroller class - * @param {Object} chart - */ - function Scroller(chart) { - var chartOptions = chart.options, - navigatorOptions = chartOptions.navigator, - navigatorEnabled = navigatorOptions.enabled, - scrollbarOptions = chartOptions.scrollbar, - scrollbarEnabled = scrollbarOptions.enabled, - height = navigatorEnabled ? navigatorOptions.height : 0, - scrollbarHeight = scrollbarEnabled ? scrollbarOptions.height : 0; - - - this.handles = []; - this.scrollbarButtons = []; - this.elementsToDestroy = []; // Array containing the elements to destroy when Scroller is destroyed - - this.chart = chart; - this.setBaseSeries(); - - this.height = height; - this.scrollbarHeight = scrollbarHeight; - this.scrollbarEnabled = scrollbarEnabled; - this.navigatorEnabled = navigatorEnabled; - this.navigatorOptions = navigatorOptions; - this.scrollbarOptions = scrollbarOptions; - this.outlineHeight = height + scrollbarHeight; - - // Run scroller - this.init(); - } - - Scroller.prototype = { - /** - * Draw one of the handles on the side of the zoomed range in the navigator - * @param {Number} x The x center for the handle - * @param {Number} index 0 for left and 1 for right - */ - drawHandle: function (x, index) { - var scroller = this, - chart = scroller.chart, - renderer = chart.renderer, - elementsToDestroy = scroller.elementsToDestroy, - handles = scroller.handles, - handlesOptions = scroller.navigatorOptions.handles, - attr = { - fill: handlesOptions.backgroundColor, - stroke: handlesOptions.borderColor, - 'stroke-width': 1 - }, - tempElem; - - // create the elements - if (!scroller.rendered) { - // the group - handles[index] = renderer.g('navigator-handle-' + ['left', 'right'][index]) - .css({ cursor: 'ew-resize' }) - .attr({ zIndex: 10 - index }) // zIndex = 3 for right handle, 4 for left / 10 - #2908 - .add(); - - // the rectangle - tempElem = renderer.rect(-4.5, 0, 9, 16, 0, 1) - .attr(attr) - .add(handles[index]); - elementsToDestroy.push(tempElem); - - // the rifles - tempElem = renderer - .path([ - 'M', - -1.5, 4, - 'L', - -1.5, 12, - 'M', - 0.5, 4, - 'L', - 0.5, 12 - ]).attr(attr) - .add(handles[index]); - elementsToDestroy.push(tempElem); - } - - // Place it - handles[index][scroller.rendered ? 'animate' : 'attr']({ - translateX: scroller.scrollerLeft + scroller.scrollbarHeight + parseInt(x, 10), - translateY: scroller.top + scroller.height / 2 - 8 - }); - }, - - /** - * Draw the scrollbar buttons with arrows - * @param {Number} index 0 is left, 1 is right - */ - drawScrollbarButton: function (index) { - var scroller = this, - chart = scroller.chart, - renderer = chart.renderer, - elementsToDestroy = scroller.elementsToDestroy, - scrollbarButtons = scroller.scrollbarButtons, - scrollbarHeight = scroller.scrollbarHeight, - scrollbarOptions = scroller.scrollbarOptions, - tempElem; - - if (!scroller.rendered) { - scrollbarButtons[index] = renderer.g().add(scroller.scrollbarGroup); - - tempElem = renderer.rect( - -0.5, - -0.5, - scrollbarHeight + 1, // +1 to compensate for crispifying in rect method - scrollbarHeight + 1, - scrollbarOptions.buttonBorderRadius, - scrollbarOptions.buttonBorderWidth - ).attr({ - stroke: scrollbarOptions.buttonBorderColor, - 'stroke-width': scrollbarOptions.buttonBorderWidth, - fill: scrollbarOptions.buttonBackgroundColor - }).add(scrollbarButtons[index]); - elementsToDestroy.push(tempElem); - - tempElem = renderer - .path([ - 'M', - scrollbarHeight / 2 + (index ? -1 : 1), scrollbarHeight / 2 - 3, - 'L', - scrollbarHeight / 2 + (index ? -1 : 1), scrollbarHeight / 2 + 3, - scrollbarHeight / 2 + (index ? 2 : -2), scrollbarHeight / 2 - ]).attr({ - fill: scrollbarOptions.buttonArrowColor - }).add(scrollbarButtons[index]); - elementsToDestroy.push(tempElem); - } - - // adjust the right side button to the varying length of the scroll track - if (index) { - scrollbarButtons[index].attr({ - translateX: scroller.scrollerWidth - scrollbarHeight - }); - } - }, - - /** - * Render the navigator and scroll bar - * @param {Number} min X axis value minimum - * @param {Number} max X axis value maximum - * @param {Number} pxMin Pixel value minimum - * @param {Number} pxMax Pixel value maximum - */ - render: function (min, max, pxMin, pxMax) { - var scroller = this, - chart = scroller.chart, - renderer = chart.renderer, - navigatorLeft, - navigatorWidth, - scrollerLeft, - scrollerWidth, - scrollbarGroup = scroller.scrollbarGroup, - navigatorGroup = scroller.navigatorGroup, - scrollbar = scroller.scrollbar, - xAxis = scroller.xAxis, - scrollbarTrack = scroller.scrollbarTrack, - scrollbarHeight = scroller.scrollbarHeight, - scrollbarEnabled = scroller.scrollbarEnabled, - navigatorOptions = scroller.navigatorOptions, - scrollbarOptions = scroller.scrollbarOptions, - scrollbarMinWidth = scrollbarOptions.minWidth, - height = scroller.height, - top = scroller.top, - navigatorEnabled = scroller.navigatorEnabled, - outlineWidth = navigatorOptions.outlineWidth, - halfOutline = outlineWidth / 2, - zoomedMin, - zoomedMax, - range, - scrX, - scrWidth, - scrollbarPad = 0, - outlineHeight = scroller.outlineHeight, - barBorderRadius = scrollbarOptions.barBorderRadius, - strokeWidth, - scrollbarStrokeWidth = scrollbarOptions.barBorderWidth, - centerBarX, - outlineTop = top + halfOutline, - rendered = scroller.rendered, - verb; - - // Don't render the navigator until we have data (#486, #4202, #5172). Don't redraw while moving the handles (#4703). - if (!isNumber(min) || !isNumber(max) || (scroller.hasDragged && !defined(pxMin))) { - return; - } - - scroller.navigatorLeft = navigatorLeft = pick( - xAxis.left, - chart.plotLeft + scrollbarHeight // in case of scrollbar only, without navigator - ); - scroller.navigatorWidth = navigatorWidth = pick(xAxis.len, chart.plotWidth - 2 * scrollbarHeight); - scroller.scrollerLeft = scrollerLeft = navigatorLeft - scrollbarHeight; - scroller.scrollerWidth = scrollerWidth = scrollerWidth = navigatorWidth + 2 * scrollbarHeight; - - // Get the pixel position of the handles - pxMin = pick(pxMin, xAxis.translate(min)); - pxMax = pick(pxMax, xAxis.translate(max)); - if (!isNumber(pxMin) || mathAbs(pxMin) === Infinity) { // Verify (#1851, #2238) - pxMin = 0; - pxMax = scrollerWidth; - } - - // Are we below the minRange? (#2618) - if (xAxis.translate(pxMax, true) - xAxis.translate(pxMin, true) < chart.xAxis[0].minRange) { - return; - } - - - // handles are allowed to cross, but never exceed the plot area - scroller.zoomedMax = mathMin(mathMax(pxMin, pxMax, 0), navigatorWidth); - scroller.zoomedMin = mathMin(mathMax(scroller.fixedWidth ? scroller.zoomedMax - scroller.fixedWidth : mathMin(pxMin, pxMax), 0), navigatorWidth); - scroller.range = scroller.zoomedMax - scroller.zoomedMin; - zoomedMax = mathRound(scroller.zoomedMax); - zoomedMin = mathRound(scroller.zoomedMin); - range = zoomedMax - zoomedMin; - - if (!rendered) { - - if (navigatorEnabled) { - - // draw the navigator group - scroller.navigatorGroup = navigatorGroup = renderer.g('navigator') - .attr({ - zIndex: 3 - }) - .add(); - - scroller.leftShade = renderer.rect() - .attr({ - fill: navigatorOptions.maskFill - }).add(navigatorGroup); - - if (navigatorOptions.maskInside) { - scroller.leftShade.css({ cursor: 'ew-resize' }); - } else { - scroller.rightShade = renderer.rect() - .attr({ - fill: navigatorOptions.maskFill - }).add(navigatorGroup); - } - - - scroller.outline = renderer.path() - .attr({ - 'stroke-width': outlineWidth, - stroke: navigatorOptions.outlineColor - }) - .add(navigatorGroup); - } - - if (scrollbarEnabled) { - - // draw the scrollbar group - scroller.scrollbarGroup = scrollbarGroup = renderer.g('scrollbar').add(); - - // the scrollbar track - strokeWidth = scrollbarOptions.trackBorderWidth; - scroller.scrollbarTrack = scrollbarTrack = renderer.rect().attr({ - x: 0, - y: -strokeWidth % 2 / 2, - fill: scrollbarOptions.trackBackgroundColor, - stroke: scrollbarOptions.trackBorderColor, - 'stroke-width': strokeWidth, - r: scrollbarOptions.trackBorderRadius || 0, - height: scrollbarHeight - }).add(scrollbarGroup); - - // the scrollbar itself - scroller.scrollbar = scrollbar = renderer.rect() - .attr({ - y: -scrollbarStrokeWidth % 2 / 2, - height: scrollbarHeight, - fill: scrollbarOptions.barBackgroundColor, - stroke: scrollbarOptions.barBorderColor, - 'stroke-width': scrollbarStrokeWidth, - r: barBorderRadius - }) - .add(scrollbarGroup); - - scroller.scrollbarRifles = renderer.path() - .attr({ - stroke: scrollbarOptions.rifleColor, - 'stroke-width': 1 - }) - .add(scrollbarGroup); - } - } - - // place elements - verb = rendered ? 'animate' : 'attr'; - if (navigatorEnabled) { - scroller.leftShade[verb](navigatorOptions.maskInside ? { - x: navigatorLeft + zoomedMin, - y: top, - width: zoomedMax - zoomedMin, - height: height - } : { - x: navigatorLeft, - y: top, - width: zoomedMin, - height: height - }); - if (scroller.rightShade) { - scroller.rightShade[verb]({ - x: navigatorLeft + zoomedMax, - y: top, - width: navigatorWidth - zoomedMax, - height: height - }); - } - - scroller.outline[verb]({ d: [ - M, - scrollerLeft, outlineTop, // left - L, - navigatorLeft + zoomedMin - halfOutline, outlineTop, // upper left of zoomed range - navigatorLeft + zoomedMin - halfOutline, outlineTop + outlineHeight, // lower left of z.r. - L, - navigatorLeft + zoomedMax - halfOutline, outlineTop + outlineHeight, // lower right of z.r. - L, - navigatorLeft + zoomedMax - halfOutline, outlineTop, // upper right of z.r. - scrollerLeft + scrollerWidth, outlineTop // right - ].concat(navigatorOptions.maskInside ? [ - M, - navigatorLeft + zoomedMin + halfOutline, outlineTop, // upper left of zoomed range - L, - navigatorLeft + zoomedMax - halfOutline, outlineTop // upper right of z.r. - ] : []) }); - // draw handles - scroller.drawHandle(zoomedMin + halfOutline, 0); - scroller.drawHandle(zoomedMax + halfOutline, 1); - } - - // draw the scrollbar - if (scrollbarEnabled && scrollbarGroup) { - - // draw the buttons - scroller.drawScrollbarButton(0); - scroller.drawScrollbarButton(1); - - scrollbarGroup[verb]({ - translateX: scrollerLeft, - translateY: mathRound(outlineTop + height) - }); - - scrollbarTrack[verb]({ - width: scrollerWidth - }); - - // prevent the scrollbar from drawing to small (#1246) - scrX = scrollbarHeight + zoomedMin; - scrWidth = range - scrollbarStrokeWidth; - if (scrWidth < scrollbarMinWidth) { - scrollbarPad = (scrollbarMinWidth - scrWidth) / 2; - scrWidth = scrollbarMinWidth; - scrX -= scrollbarPad; - } - scroller.scrollbarPad = scrollbarPad; - scrollbar[verb]({ - x: mathFloor(scrX) + (scrollbarStrokeWidth % 2 / 2), - width: scrWidth - }); - - centerBarX = scrollbarHeight + zoomedMin + range / 2 - 0.5; - - scroller.scrollbarRifles - .attr({ - visibility: range > 12 ? VISIBLE : HIDDEN - })[verb]({ - d: [ - M, - centerBarX - 3, scrollbarHeight / 4, - L, - centerBarX - 3, 2 * scrollbarHeight / 3, - M, - centerBarX, scrollbarHeight / 4, - L, - centerBarX, 2 * scrollbarHeight / 3, - M, - centerBarX + 3, scrollbarHeight / 4, - L, - centerBarX + 3, 2 * scrollbarHeight / 3 - ] - }); - } - - scroller.scrollbarPad = scrollbarPad; - scroller.rendered = true; - }, - - /** - * Set up the mouse and touch events for the navigator and scrollbar - */ - addEvents: function () { - var chart = this.chart, - container = chart.container, - mouseDownHandler = this.mouseDownHandler, - mouseMoveHandler = this.mouseMoveHandler, - mouseUpHandler = this.mouseUpHandler, - _events; - - // Mouse events - _events = [ - [container, 'mousedown', mouseDownHandler], - [container, 'mousemove', mouseMoveHandler], - [doc, 'mouseup', mouseUpHandler] - ]; - - // Touch events - if (hasTouch) { - _events.push( - [container, 'touchstart', mouseDownHandler], - [container, 'touchmove', mouseMoveHandler], - [doc, 'touchend', mouseUpHandler] - ); - } - - // Add them all - each(_events, function (args) { - addEvent.apply(null, args); - }); - this._events = _events; - - // Data events - if (this.series) { - addEvent(this.series.xAxis, 'foundExtremes', function () { - chart.scroller.modifyNavigatorAxisExtremes(); - }); - } - addEvent(chart, 'redraw', function () { - // Move the scrollbar after redraw, like after data updata even if axes don't redraw - var scroller = this.scroller, - xAxis; - if (scroller) { - xAxis = scroller.baseSeries.xAxis; - if (xAxis) { - scroller.render(xAxis.min, xAxis.max); - } - } - }); - }, - - /** - * Removes the event handlers attached previously with addEvents. - */ - removeEvents: function () { - - each(this._events, function (args) { - removeEvent.apply(null, args); - }); - this._events = UNDEFINED; - if (this.navigatorEnabled && this.baseSeries) { - removeEvent(this.baseSeries, 'updatedData', this.updatedDataHandler); - } - }, - - /** - * Initiate the Scroller object - */ - init: function () { - var scroller = this, - chart = scroller.chart, - xAxis, - yAxis, - scrollbarHeight = scroller.scrollbarHeight, - navigatorOptions = scroller.navigatorOptions, - height = scroller.height, - top = scroller.top, - dragOffset, - baseSeries = scroller.baseSeries; - - /** - * Event handler for the mouse down event. - */ - scroller.mouseDownHandler = function (e) { - e = chart.pointer.normalize(e); - - var zoomedMin = scroller.zoomedMin, - zoomedMax = scroller.zoomedMax, - top = scroller.top, - scrollbarHeight = scroller.scrollbarHeight, - scrollerLeft = scroller.scrollerLeft, - scrollerWidth = scroller.scrollerWidth, - navigatorLeft = scroller.navigatorLeft, - navigatorWidth = scroller.navigatorWidth, - scrollbarPad = scroller.scrollbarPad, - range = scroller.range, - chartX = e.chartX, - chartY = e.chartY, - baseXAxis = chart.xAxis[0], - fixedMax, - ext, - handleSensitivity = isTouchDevice ? 10 : 7, - left, - isOnNavigator; - - if (chartY > top && chartY < top + height + scrollbarHeight) { // we're vertically inside the navigator - isOnNavigator = !scroller.scrollbarEnabled || chartY < top + height; - - // grab the left handle - if (isOnNavigator && math.abs(chartX - zoomedMin - navigatorLeft) < handleSensitivity) { - scroller.grabbedLeft = true; - scroller.otherHandlePos = zoomedMax; - scroller.fixedExtreme = baseXAxis.max; - chart.fixedRange = null; - - // grab the right handle - } else if (isOnNavigator && math.abs(chartX - zoomedMax - navigatorLeft) < handleSensitivity) { - scroller.grabbedRight = true; - scroller.otherHandlePos = zoomedMin; - scroller.fixedExtreme = baseXAxis.min; - chart.fixedRange = null; - - // grab the zoomed range - } else if (chartX > navigatorLeft + zoomedMin - scrollbarPad && chartX < navigatorLeft + zoomedMax + scrollbarPad) { - scroller.grabbedCenter = chartX; - scroller.fixedWidth = range; - - dragOffset = chartX - zoomedMin; - - - // shift the range by clicking on shaded areas, scrollbar track or scrollbar buttons - } else if (chartX > scrollerLeft && chartX < scrollerLeft + scrollerWidth) { - - // Center around the clicked point - if (isOnNavigator) { - left = chartX - navigatorLeft - range / 2; - - // Click on scrollbar - } else { - - // Click left scrollbar button - if (chartX < navigatorLeft) { - left = zoomedMin - range * 0.2; - - // Click right scrollbar button - } else if (chartX > scrollerLeft + scrollerWidth - scrollbarHeight) { - left = zoomedMin + range * 0.2; - - // Click on scrollbar track, shift the scrollbar by one range - } else { - left = chartX < navigatorLeft + zoomedMin ? // on the left - zoomedMin - range : - zoomedMax; - } - } - if (left < 0) { - left = 0; - } else if (left + range >= navigatorWidth) { - left = navigatorWidth - range; - fixedMax = scroller.getUnionExtremes().dataMax; // #2293, #3543 - } - if (left !== zoomedMin) { // it has actually moved - scroller.fixedWidth = range; // #1370 - - ext = xAxis.toFixedRange(left, left + range, null, fixedMax); - baseXAxis.setExtremes( - ext.min, - ext.max, - true, - false, - { trigger: 'navigator' } - ); - } - } - - } - }; - - /** - * Event handler for the mouse move event. - */ - scroller.mouseMoveHandler = function (e) { - var scrollbarHeight = scroller.scrollbarHeight, - navigatorLeft = scroller.navigatorLeft, - navigatorWidth = scroller.navigatorWidth, - scrollerLeft = scroller.scrollerLeft, - scrollerWidth = scroller.scrollerWidth, - range = scroller.range, - chartX, - hasDragged; - - // In iOS, a mousemove event with e.pageX === 0 is fired when holding the finger - // down in the center of the scrollbar. This should be ignored. - if (!e.touches || e.touches[0].pageX !== 0) { // #4696, scrollbar failed on Android - - e = chart.pointer.normalize(e); - chartX = e.chartX; - - // validation for handle dragging - if (chartX < navigatorLeft) { - chartX = navigatorLeft; - } else if (chartX > scrollerLeft + scrollerWidth - scrollbarHeight) { - chartX = scrollerLeft + scrollerWidth - scrollbarHeight; - } - - // drag left handle - if (scroller.grabbedLeft) { - hasDragged = true; - scroller.render(0, 0, chartX - navigatorLeft, scroller.otherHandlePos); - - // drag right handle - } else if (scroller.grabbedRight) { - hasDragged = true; - scroller.render(0, 0, scroller.otherHandlePos, chartX - navigatorLeft); - - // drag scrollbar or open area in navigator - } else if (scroller.grabbedCenter) { - - hasDragged = true; - if (chartX < dragOffset) { // outside left - chartX = dragOffset; - } else if (chartX > navigatorWidth + dragOffset - range) { // outside right - chartX = navigatorWidth + dragOffset - range; - } - - scroller.render(0, 0, chartX - dragOffset, chartX - dragOffset + range); - - } - if (hasDragged && scroller.scrollbarOptions.liveRedraw) { - setTimeout(function () { - scroller.mouseUpHandler(e); - }, 0); - } - scroller.hasDragged = hasDragged; - } - }; - - /** - * Event handler for the mouse up event. - */ - scroller.mouseUpHandler = function (e) { - var ext, - fixedMin, - fixedMax; - - if (scroller.hasDragged) { - // When dragging one handle, make sure the other one doesn't change - if (scroller.zoomedMin === scroller.otherHandlePos) { - fixedMin = scroller.fixedExtreme; - } else if (scroller.zoomedMax === scroller.otherHandlePos) { - fixedMax = scroller.fixedExtreme; - } - - // Snap to right edge (#4076) - if (scroller.zoomedMax === scroller.navigatorWidth) { - fixedMax = scroller.getUnionExtremes().dataMax; - } - - ext = xAxis.toFixedRange(scroller.zoomedMin, scroller.zoomedMax, fixedMin, fixedMax); - if (defined(ext.min)) { - chart.xAxis[0].setExtremes( - ext.min, - ext.max, - true, - false, - { - trigger: 'navigator', - triggerOp: 'navigator-drag', - DOMEvent: e // #1838 - } - ); - } - } - - if (e.type !== 'mousemove') { - scroller.grabbedLeft = scroller.grabbedRight = scroller.grabbedCenter = scroller.fixedWidth = - scroller.fixedExtreme = scroller.otherHandlePos = scroller.hasDragged = dragOffset = null; - } - - }; - - - - var xAxisIndex = chart.xAxis.length, - yAxisIndex = chart.yAxis.length; - - // make room below the chart - chart.extraBottomMargin = scroller.outlineHeight + navigatorOptions.margin; - - if (scroller.navigatorEnabled) { - // an x axis is required for scrollbar also - scroller.xAxis = xAxis = new Axis(chart, merge({ - // inherit base xAxis' break and ordinal options - breaks: baseSeries && baseSeries.xAxis.options.breaks, - ordinal: baseSeries && baseSeries.xAxis.options.ordinal - }, navigatorOptions.xAxis, { - id: 'navigator-x-axis', - isX: true, - type: 'datetime', - index: xAxisIndex, - height: height, - offset: 0, - offsetLeft: scrollbarHeight, - offsetRight: -scrollbarHeight, - keepOrdinalPadding: true, // #2436 - startOnTick: false, - endOnTick: false, - minPadding: 0, - maxPadding: 0, - zoomEnabled: false - })); - - scroller.yAxis = yAxis = new Axis(chart, merge(navigatorOptions.yAxis, { - id: 'navigator-y-axis', - alignTicks: false, - height: height, - offset: 0, - index: yAxisIndex, - zoomEnabled: false - })); - - // If we have a base series, initialize the navigator series - if (baseSeries || navigatorOptions.series.data) { - scroller.addBaseSeries(); - - // If not, set up an event to listen for added series - } else if (chart.series.length === 0) { - - wrap(chart, 'redraw', function (proceed, animation) { - // We've got one, now add it as base and reset chart.redraw - if (chart.series.length > 0 && !scroller.series) { - scroller.setBaseSeries(); - chart.redraw = proceed; // reset - } - proceed.call(chart, animation); - }); - } - - - // in case of scrollbar only, fake an x axis to get translation - } else { - scroller.xAxis = xAxis = { - translate: function (value, reverse) { - var axis = chart.xAxis[0], - ext = axis.getExtremes(), - scrollTrackWidth = chart.plotWidth - 2 * scrollbarHeight, - min = numExt('min', axis.options.min, ext.dataMin), - valueRange = numExt('max', axis.options.max, ext.dataMax) - min; - - return reverse ? - // from pixel to value - (value * valueRange / scrollTrackWidth) + min : - // from value to pixel - scrollTrackWidth * (value - min) / valueRange; - }, - toFixedRange: Axis.prototype.toFixedRange - }; - } - - // Respond to updated data in the base series. - // Abort if lazy-loading data from the server. - if (baseSeries && baseSeries.xAxis && this.navigatorOptions.adaptToUpdatedData !== false) { - addEvent(baseSeries, 'updatedData', this.updatedDataHandler); - - addEvent(baseSeries.xAxis, 'foundExtremes', function () { - if (baseSeries.xAxis) { - this.chart.scroller.modifyBaseAxisExtremes(); - } - }); - - // Survive Series.update() - baseSeries.userOptions.events = extend(baseSeries.userOptions.event, { updatedData: this.updatedDataHandler }); - - } - - - /** - * For stock charts, extend the Chart.getMargins method so that we can set the final top position - * of the navigator once the height of the chart, including the legend, is determined. #367. - */ - wrap(chart, 'getMargins', function (proceed) { - - var legend = this.legend, - legendOptions = legend.options; - - proceed.apply(this, [].slice.call(arguments, 1)); - - // Compute the top position - scroller.top = top = scroller.navigatorOptions.top || - this.chartHeight - scroller.height - scroller.scrollbarHeight - this.spacing[2] - - (legendOptions.verticalAlign === 'bottom' && legendOptions.enabled && !legendOptions.floating ? - legend.legendHeight + pick(legendOptions.margin, 10) : 0); - - if (xAxis && yAxis) { // false if navigator is disabled (#904) - - xAxis.options.top = yAxis.options.top = top; - - xAxis.setAxisSize(); - yAxis.setAxisSize(); - } - }); - - - scroller.addEvents(); - }, - - /** - * Get the union data extremes of the chart - the outer data extremes of the base - * X axis and the navigator axis. - */ - getUnionExtremes: function (returnFalseOnNoBaseSeries) { - var baseAxis = this.chart.xAxis[0], - navAxis = this.xAxis, - navAxisOptions = navAxis.options, - baseAxisOptions = baseAxis.options, - ret; - - if (!returnFalseOnNoBaseSeries || baseAxis.dataMin !== null) { - ret = { - dataMin: pick( // #4053 - navAxisOptions && navAxisOptions.min, - numExt( - 'min', - baseAxisOptions.min, - baseAxis.dataMin, - navAxis.dataMin, - navAxis.min - ) - ), - dataMax: pick( - navAxisOptions && navAxisOptions.max, - numExt( - 'max', - baseAxisOptions.max, - baseAxis.dataMax, - navAxis.dataMax, - navAxis.max - ) - ) - }; - } - return ret; - }, - - /** - * Set the base series. With a bit of modification we should be able to make - * this an API method to be called from the outside - */ - setBaseSeries: function (baseSeriesOption) { - var chart = this.chart; - - baseSeriesOption = baseSeriesOption || chart.options.navigator.baseSeries; - - // If we're resetting, remove the existing series - if (this.series) { - this.series.remove(); - } - - // Set the new base series - this.baseSeries = chart.series[baseSeriesOption] || - (typeof baseSeriesOption === 'string' && chart.get(baseSeriesOption)) || - chart.series[0]; - - // When run after render, this.xAxis already exists - if (this.xAxis) { - this.addBaseSeries(); - } - }, - - addBaseSeries: function () { - var baseSeries = this.baseSeries, - baseOptions = baseSeries ? baseSeries.options : {}, - baseData = baseOptions.data, - mergedNavSeriesOptions, - navigatorSeriesOptions = this.navigatorOptions.series, - navigatorData; - - // remove it to prevent merging one by one - navigatorData = navigatorSeriesOptions.data; - this.hasNavigatorData = !!navigatorData; - - // Merge the series options - mergedNavSeriesOptions = merge(baseOptions, navigatorSeriesOptions, { - enableMouseTracking: false, - group: 'nav', // for columns - padXAxis: false, - xAxis: 'navigator-x-axis', - yAxis: 'navigator-y-axis', - name: 'Navigator', - showInLegend: false, - stacking: false, // We only allow one series anyway (#4823) - isInternal: true, - visible: true - }); - - // Set the data. Do a slice to avoid mutating the navigator options from base series (#4923). - mergedNavSeriesOptions.data = navigatorData || baseData.slice(0); - - // Add the series - this.series = this.chart.initSeries(mergedNavSeriesOptions); - - }, - - /** - * Set the scroller x axis extremes to reflect the total. The navigator extremes - * should always be the extremes of the union of all series in the chart as - * well as the navigator series. - */ - modifyNavigatorAxisExtremes: function () { - var xAxis = this.xAxis, - unionExtremes; - - if (xAxis.getExtremes) { - unionExtremes = this.getUnionExtremes(true); - - if (unionExtremes && (unionExtremes.dataMin !== xAxis.min || unionExtremes.dataMax !== xAxis.max)) { - xAxis.min = unionExtremes.dataMin; - xAxis.max = unionExtremes.dataMax; - } - } - }, - - /** - * Hook to modify the base axis extremes with information from the Navigator - */ - modifyBaseAxisExtremes: function () { - var baseSeries = this.baseSeries, - baseXAxis = baseSeries.xAxis, - baseExtremes = baseXAxis.getExtremes(), - baseMin = baseExtremes.min, - baseMax = baseExtremes.max, - baseDataMin = baseExtremes.dataMin, - baseDataMax = baseExtremes.dataMax, - range = baseMax - baseMin, - stickToMin = this.stickToMin, - stickToMax = this.stickToMax, - newMax, - newMin, - navigatorSeries = this.series, - hasSetExtremes = !!baseXAxis.setExtremes; - - // If the zoomed range is already at the min, move it to the right as new data - // comes in - if (stickToMin) { - newMin = baseDataMin; - newMax = newMin + range; - } - - // If the zoomed range is already at the max, move it to the right as new data - // comes in - if (stickToMax) { - newMax = baseDataMax; - if (!stickToMin) { // if stickToMin is true, the new min value is set above - newMin = mathMax(newMax - range, navigatorSeries ? navigatorSeries.xData[0] : -Number.MAX_VALUE); - } - } - - // Update the extremes - if (hasSetExtremes && (stickToMin || stickToMax)) { - if (isNumber(newMin)) { - baseXAxis.min = baseXAxis.userMin = newMin; - baseXAxis.max = baseXAxis.userMax = newMax; - } - } - - // Reset - this.stickToMin = this.stickToMax = null; - }, - - /** - * Handler for updated data on the base series. When data is modified, the navigator series - * must reflect it. This is called from the Chart.redraw function before axis and series - * extremes are computed. - */ - updatedDataHandler: function () { - var scroller = this.chart.scroller, - baseSeries = scroller.baseSeries, - navigatorSeries = scroller.series; - - // Detect whether the zoomed area should stick to the minimum or maximum. If the current - // axis minimum falls outside the new updated dataset, we must adjust. - scroller.stickToMin = baseSeries.xAxis.min <= baseSeries.xData[0]; - // If the scrollbar is scrolled all the way to the right, keep right as new data - // comes in. - scroller.stickToMax = scroller.zoomedMax >= scroller.navigatorWidth; - - // Set the navigator series data to the new data of the base series - if (navigatorSeries && !scroller.hasNavigatorData) { - navigatorSeries.options.pointStart = baseSeries.xData[0]; - navigatorSeries.setData(baseSeries.options.data, false); - - // When adding points, shift it. A more fail-safe and lean procedure may be to extend the three - // cases of updating data (addPoint, update, removePoint) directly so that this operation - // on the base series reflects directly on the navigator series. - if (navigatorSeries.graph && baseSeries.graph) { - navigatorSeries.graph.shift = baseSeries.graph.shift; - } - } - }, - - /** - * Destroys allocated elements. - */ - destroy: function () { - var scroller = this; - - // Disconnect events added in addEvents - scroller.removeEvents(); - - // Destroy properties - each([scroller.xAxis, scroller.yAxis, scroller.leftShade, scroller.rightShade, scroller.outline, scroller.scrollbarTrack, scroller.scrollbarRifles, scroller.scrollbarGroup, scroller.scrollbar], function (prop) { - if (prop && prop.destroy) { - prop.destroy(); - } - }); - scroller.xAxis = scroller.yAxis = scroller.leftShade = scroller.rightShade = scroller.outline = scroller.scrollbarTrack = scroller.scrollbarRifles = scroller.scrollbarGroup = scroller.scrollbar = null; - - // Destroy elements in collection - each([scroller.scrollbarButtons, scroller.handles, scroller.elementsToDestroy], function (coll) { - destroyObjectProperties(coll); - }); - } - }; - - Highcharts.Scroller = Scroller; - - - /** - * For Stock charts, override selection zooming with some special features because - * X axis zooming is already allowed by the Navigator and Range selector. - */ - wrap(Axis.prototype, 'zoom', function (proceed, newMin, newMax) { - var chart = this.chart, - chartOptions = chart.options, - zoomType = chartOptions.chart.zoomType, - previousZoom, - navigator = chartOptions.navigator, - rangeSelector = chartOptions.rangeSelector, - ret; - - if (this.isXAxis && ((navigator && navigator.enabled) || - (rangeSelector && rangeSelector.enabled))) { - - // For x only zooming, fool the chart.zoom method not to create the zoom button - // because the property already exists - if (zoomType === 'x') { - chart.resetZoomButton = 'blocked'; - - // For y only zooming, ignore the X axis completely - } else if (zoomType === 'y') { - ret = false; - - // For xy zooming, record the state of the zoom before zoom selection, then when - // the reset button is pressed, revert to this state - } else if (zoomType === 'xy') { - previousZoom = this.previousZoom; - if (defined(newMin)) { - this.previousZoom = [this.min, this.max]; - } else if (previousZoom) { - newMin = previousZoom[0]; - newMax = previousZoom[1]; - delete this.previousZoom; - } - } - - } - return ret !== UNDEFINED ? ret : proceed.call(this, newMin, newMax); - }); - - // Initialize scroller for stock charts - wrap(Chart.prototype, 'init', function (proceed, options, callback) { - - addEvent(this, 'beforeRender', function () { - var options = this.options; - if (options.navigator.enabled || options.scrollbar.enabled) { - this.scroller = new Scroller(this); - } - }); - - proceed.call(this, options, callback); - - }); - - // Pick up badly formatted point options to addPoint - wrap(Series.prototype, 'addPoint', function (proceed, options, redraw, shift, animation) { - var turboThreshold = this.options.turboThreshold; - if (turboThreshold && this.xData.length > turboThreshold && isObject(options) && !isArray(options) && this.chart.scroller) { - error(20, true); - } - proceed.call(this, options, redraw, shift, animation); - }); - - /* **************************************************************************** - * End Scroller code * - *****************************************************************************/ - /* **************************************************************************** - * Start Range Selector code * - *****************************************************************************/ - extend(defaultOptions, { - rangeSelector: { - // allButtonsEnabled: false, - // enabled: true, - // buttons: {Object} - // buttonSpacing: 0, - buttonTheme: { - width: 28, - height: 18, - fill: '#f7f7f7', - padding: 2, - r: 0, - 'stroke-width': 0, - style: { - color: '#444', - cursor: 'pointer', - fontWeight: 'normal' - }, - zIndex: 7, // #484, #852 - states: { - hover: { - fill: '#e7e7e7' - }, - select: { - fill: '#e7f0f9', - style: { - color: 'black', - fontWeight: 'bold' - } - } - } - }, - height: 35, // reserved space for buttons and input - inputPosition: { - align: 'right' - }, - // inputDateFormat: '%b %e, %Y', - // inputEditDateFormat: '%Y-%m-%d', - // inputEnabled: true, - // inputStyle: {}, - labelStyle: { - color: '#666' - } - // selected: undefined - } - }); - defaultOptions.lang = merge(defaultOptions.lang, { - rangeSelectorZoom: 'Zoom', - rangeSelectorFrom: 'From', - rangeSelectorTo: 'To' - }); - - /** - * The object constructor for the range selector - * @param {Object} chart - */ - function RangeSelector(chart) { - - // Run RangeSelector - this.init(chart); - } - - RangeSelector.prototype = { - /** - * The method to run when one of the buttons in the range selectors is clicked - * @param {Number} i The index of the button - * @param {Object} rangeOptions - * @param {Boolean} redraw - */ - clickButton: function (i, redraw) { - var rangeSelector = this, - selected = rangeSelector.selected, - chart = rangeSelector.chart, - buttons = rangeSelector.buttons, - rangeOptions = rangeSelector.buttonOptions[i], - baseAxis = chart.xAxis[0], - unionExtremes = (chart.scroller && chart.scroller.getUnionExtremes()) || baseAxis || {}, - dataMin = unionExtremes.dataMin, - dataMax = unionExtremes.dataMax, - newMin, - newMax = baseAxis && mathRound(mathMin(baseAxis.max, pick(dataMax, baseAxis.max))), // #1568 - now, - type = rangeOptions.type, - baseXAxisOptions, - range = rangeOptions._range, - rangeMin, - year, - minSetting, - rangeSetting, - ctx, - dataGrouping = rangeOptions.dataGrouping; - - if (dataMin === null || dataMax === null || // chart has no data, base series is removed - i === rangeSelector.selected) { // same button is clicked twice - return; - } - - // Set the fixed range before range is altered - chart.fixedRange = range; - - // Apply dataGrouping associated to button - if (dataGrouping) { - this.forcedDataGrouping = true; - Axis.prototype.setDataGrouping.call(baseAxis || { chart: this.chart }, dataGrouping, false); - } - - // Apply range - if (type === 'month' || type === 'year') { - if (!baseAxis) { - // This is set to the user options and picked up later when the axis is instantiated - // so that we know the min and max. - range = rangeOptions; - } else { - ctx = { - range: rangeOptions, - max: newMax, - dataMin: dataMin, - dataMax: dataMax - }; - newMin = baseAxis.minFromRange.call(ctx); - if (isNumber(ctx.newMax)) { - newMax = ctx.newMax; - } - } - - // Fixed times like minutes, hours, days - } else if (range) { - newMin = mathMax(newMax - range, dataMin); - newMax = mathMin(newMin + range, dataMax); - - } else if (type === 'ytd') { - - // On user clicks on the buttons, or a delayed action running from the beforeRender - // event (below), the baseAxis is defined. - if (baseAxis) { - - // When "ytd" is the pre-selected button for the initial view, its calculation - // is delayed and rerun in the beforeRender event (below). When the series - // are initialized, but before the chart is rendered, we have access to the xData - // array (#942). - if (dataMax === UNDEFINED) { - dataMin = Number.MAX_VALUE; - dataMax = Number.MIN_VALUE; - each(chart.series, function (series) { - var xData = series.xData; // reassign it to the last item - dataMin = mathMin(xData[0], dataMin); - dataMax = mathMax(xData[xData.length - 1], dataMax); - }); - redraw = false; - } - now = new Date(dataMax); - year = now.getFullYear(); - newMin = rangeMin = mathMax(dataMin || 0, Date.UTC(year, 0, 1)); - now = now.getTime(); - newMax = mathMin(dataMax || now, now); - - // "ytd" is pre-selected. We don't yet have access to processed point and extremes data - // (things like pointStart and pointInterval are missing), so we delay the process (#942) - } else { - addEvent(chart, 'beforeRender', function () { - rangeSelector.clickButton(i); - }); - return; - } - } else if (type === 'all' && baseAxis) { - newMin = dataMin; - newMax = dataMax; - } - - // Deselect previous button - if (buttons[selected]) { - buttons[selected].setState(0); - } - // Select this button - if (buttons[i]) { - buttons[i].setState(2); - rangeSelector.lastSelected = i; - } - - // Update the chart - if (!baseAxis) { - // Axis not yet instanciated. Temporarily set min and range - // options and remove them on chart load (#4317). - baseXAxisOptions = chart.options.xAxis[0]; - rangeSetting = baseXAxisOptions.range; - baseXAxisOptions.range = range; - minSetting = baseXAxisOptions.min; - baseXAxisOptions.min = rangeMin; - rangeSelector.setSelected(i); - addEvent(chart, 'load', function resetMinAndRange() { - baseXAxisOptions.range = rangeSetting; - baseXAxisOptions.min = minSetting; - }); - } else { - // Existing axis object. Set extremes after render time. - baseAxis.setExtremes( - newMin, - newMax, - pick(redraw, 1), - 0, - { - trigger: 'rangeSelectorButton', - rangeSelectorButton: rangeOptions - } - ); - rangeSelector.setSelected(i); - } - }, - - /** - * Set the selected option. This method only sets the internal flag, it doesn't - * update the buttons or the actual zoomed range. - */ - setSelected: function (selected) { - this.selected = this.options.selected = selected; - }, - - /** - * The default buttons for pre-selecting time frames - */ - defaultButtons: [{ - type: 'month', - count: 1, - text: '1m' - }, { - type: 'month', - count: 3, - text: '3m' - }, { - type: 'month', - count: 6, - text: '6m' - }, { - type: 'ytd', - text: 'YTD' - }, { - type: 'year', - count: 1, - text: '1y' - }, { - type: 'all', - text: 'All' - }], - - /** - * Initialize the range selector - */ - init: function (chart) { - - var rangeSelector = this, - options = chart.options.rangeSelector, - buttonOptions = options.buttons || [].concat(rangeSelector.defaultButtons), - selectedOption = options.selected, - blurInputs = rangeSelector.blurInputs = function () { - var minInput = rangeSelector.minInput, - maxInput = rangeSelector.maxInput; - if (minInput && minInput.blur) { //#3274 in some case blur is not defined - fireEvent(minInput, 'blur'); //#3274 - } - if (maxInput && maxInput.blur) { //#3274 in some case blur is not defined - fireEvent(maxInput, 'blur'); //#3274 - } - }; - - rangeSelector.chart = chart; - rangeSelector.options = options; - rangeSelector.buttons = []; - - chart.extraTopMargin = options.height; - rangeSelector.buttonOptions = buttonOptions; - - addEvent(chart.container, 'mousedown', blurInputs); - addEvent(chart, 'resize', blurInputs); - - // Extend the buttonOptions with actual range - each(buttonOptions, rangeSelector.computeButtonRange); - - // zoomed range based on a pre-selected button index - if (selectedOption !== UNDEFINED && buttonOptions[selectedOption]) { - this.clickButton(selectedOption, false); - } - - - addEvent(chart, 'load', function () { - // If a data grouping is applied to the current button, release it when extremes change - addEvent(chart.xAxis[0], 'setExtremes', function (e) { - if (this.max - this.min !== chart.fixedRange && e.trigger !== 'rangeSelectorButton' && - e.trigger !== 'updatedData' && rangeSelector.forcedDataGrouping) { - this.setDataGrouping(false, false); - } - }); - // Normalize the pressed button whenever a new range is selected - addEvent(chart.xAxis[0], 'afterSetExtremes', function () { - rangeSelector.updateButtonStates(true); - }); - }); - }, - - /** - * Dynamically update the range selector buttons after a new range has been set - */ - updateButtonStates: function (updating) { - var rangeSelector = this, - chart = this.chart, - baseAxis = chart.xAxis[0], - unionExtremes = (chart.scroller && chart.scroller.getUnionExtremes()) || baseAxis, - dataMin = unionExtremes.dataMin, - dataMax = unionExtremes.dataMax, - selected = rangeSelector.selected, - allButtonsEnabled = rangeSelector.options.allButtonsEnabled, - buttons = rangeSelector.buttons; - - if (updating && chart.fixedRange !== mathRound(baseAxis.max - baseAxis.min)) { - if (buttons[selected]) { - buttons[selected].setState(0); - } - rangeSelector.setSelected(null); - } - - each(rangeSelector.buttonOptions, function (rangeOptions, i) { - var actualRange = mathRound(baseAxis.max - baseAxis.min), - range = rangeOptions._range, - type = rangeOptions.type, - count = rangeOptions.count || 1, - // Disable buttons where the range exceeds what is allowed in the current view - isTooGreatRange = range > dataMax - dataMin, - // Disable buttons where the range is smaller than the minimum range - isTooSmallRange = range < baseAxis.minRange, - // Disable the All button if we're already showing all - isAllButAlreadyShowingAll = rangeOptions.type === 'all' && baseAxis.max - baseAxis.min >= dataMax - dataMin && - buttons[i].state !== 2, - // Disable the YTD button if the complete range is within the same year - isYTDButNotAvailable = rangeOptions.type === 'ytd' && dateFormat('%Y', dataMin) === dateFormat('%Y', dataMax), - // Set a button on export - isSelectedForExport = chart.renderer.forExport && i === selected, - - isSameRange = range === actualRange, - - hasNoData = !baseAxis.hasVisibleSeries; - - // Months and years have a variable range so we check the extremes - if ((type === 'month' || type === 'year') && (actualRange >= { month: 28, year: 365 }[type] * 24 * 36e5 * count) && - (actualRange <= { month: 31, year: 366 }[type] * 24 * 36e5 * count)) { - isSameRange = true; - } - // The new zoom area happens to match the range for a button - mark it selected. - // This happens when scrolling across an ordinal gap. It can be seen in the intraday - // demos when selecting 1h and scroll across the night gap. - if (isSelectedForExport || (isSameRange && i !== selected) && i === rangeSelector.lastSelected) { - rangeSelector.setSelected(i); - buttons[i].setState(2); - - } else if (!allButtonsEnabled && (isTooGreatRange || isTooSmallRange || isAllButAlreadyShowingAll || isYTDButNotAvailable || hasNoData)) { - buttons[i].setState(3); - - } else if (buttons[i].state === 3) { - buttons[i].setState(0); - } - }); - }, - - /** - * Compute and cache the range for an individual button - */ - computeButtonRange: function (rangeOptions) { - var type = rangeOptions.type, - count = rangeOptions.count || 1, - - // these time intervals have a fixed number of milliseconds, as opposed - // to month, ytd and year - fixedTimes = { - millisecond: 1, - second: 1000, - minute: 60 * 1000, - hour: 3600 * 1000, - day: 24 * 3600 * 1000, - week: 7 * 24 * 3600 * 1000 - }; - - // Store the range on the button object - if (fixedTimes[type]) { - rangeOptions._range = fixedTimes[type] * count; - } else if (type === 'month' || type === 'year') { - rangeOptions._range = { month: 30, year: 365 }[type] * 24 * 36e5 * count; - } - }, - - /** - * Set the internal and displayed value of a HTML input for the dates - * @param {String} name - * @param {Number} time - */ - setInputValue: function (name, time) { - var options = this.chart.options.rangeSelector; - - if (defined(time)) { - this[name + 'Input'].HCTime = time; - } - - this[name + 'Input'].value = dateFormat( - options.inputEditDateFormat || '%Y-%m-%d', - this[name + 'Input'].HCTime - ); - this[name + 'DateBox'].attr({ - text: dateFormat(options.inputDateFormat || '%b %e, %Y', this[name + 'Input'].HCTime) - }); - }, - - showInput: function (name) { - var inputGroup = this.inputGroup, - dateBox = this[name + 'DateBox']; - - css(this[name + 'Input'], { - left: (inputGroup.translateX + dateBox.x) + PX, - top: inputGroup.translateY + PX, - width: (dateBox.width - 2) + PX, - height: (dateBox.height - 2) + PX, - border: '2px solid silver' - }); - }, - - hideInput: function (name) { - css(this[name + 'Input'], { - border: 0, - width: '1px', - height: '1px' - }); - this.setInputValue(name); - }, - - /** - * Draw either the 'from' or the 'to' HTML input box of the range selector - * @param {Object} name - */ - drawInput: function (name) { - var rangeSelector = this, - chart = rangeSelector.chart, - chartStyle = chart.renderer.style, - renderer = chart.renderer, - options = chart.options.rangeSelector, - lang = defaultOptions.lang, - div = rangeSelector.div, - isMin = name === 'min', - input, - label, - dateBox, - inputGroup = this.inputGroup; - - function updateExtremes() { - var inputValue = input.value, - value = (options.inputDateParser || Date.parse)(inputValue), - xAxis = chart.xAxis[0], - dataMin = xAxis.dataMin, - dataMax = xAxis.dataMax; - if (value !== input.previousValue) { - input.previousValue = value; - // If the value isn't parsed directly to a value by the browser's Date.parse method, - // like YYYY-MM-DD in IE, try parsing it a different way - if (!isNumber(value)) { - value = inputValue.split('-'); - value = Date.UTC(pInt(value[0]), pInt(value[1]) - 1, pInt(value[2])); - } - - if (isNumber(value)) { - - // Correct for timezone offset (#433) - if (!defaultOptions.global.useUTC) { - value = value + new Date().getTimezoneOffset() * 60 * 1000; - } - - // Validate the extremes. If it goes beyound the data min or max, use the - // actual data extreme (#2438). - if (isMin) { - if (value > rangeSelector.maxInput.HCTime) { - value = UNDEFINED; - } else if (value < dataMin) { - value = dataMin; - } - } else { - if (value < rangeSelector.minInput.HCTime) { - value = UNDEFINED; - } else if (value > dataMax) { - value = dataMax; - } - } - - // Set the extremes - if (value !== UNDEFINED) { - chart.xAxis[0].setExtremes( - isMin ? value : xAxis.min, - isMin ? xAxis.max : value, - UNDEFINED, - UNDEFINED, - { trigger: 'rangeSelectorInput' } - ); - } - } - } - } - - // Create the text label - this[name + 'Label'] = label = renderer.label(lang[isMin ? 'rangeSelectorFrom' : 'rangeSelectorTo'], this.inputGroup.offset) - .attr({ - padding: 2 - }) - .css(merge(chartStyle, options.labelStyle)) - .add(inputGroup); - inputGroup.offset += label.width + 5; - - // Create an SVG label that shows updated date ranges and and records click events that - // bring in the HTML input. - this[name + 'DateBox'] = dateBox = renderer.label('', inputGroup.offset) - .attr({ - padding: 2, - width: options.inputBoxWidth || 90, - height: options.inputBoxHeight || 17, - stroke: options.inputBoxBorderColor || 'silver', - 'stroke-width': 1 - }) - .css(merge({ - textAlign: 'center', - color: '#444' - }, chartStyle, options.inputStyle)) - .on('click', function () { - rangeSelector.showInput(name); // If it is already focused, the onfocus event doesn't fire (#3713) - rangeSelector[name + 'Input'].focus(); - }) - .add(inputGroup); - inputGroup.offset += dateBox.width + (isMin ? 10 : 0); - - - // Create the HTML input element. This is rendered as 1x1 pixel then set to the right size - // when focused. - this[name + 'Input'] = input = createElement('input', { - name: name, - className: PREFIX + 'range-selector', - type: 'text' - }, extend({ - position: ABSOLUTE, - border: 0, - width: '1px', // Chrome needs a pixel to see it - height: '1px', - padding: 0, - textAlign: 'center', - fontSize: chartStyle.fontSize, - fontFamily: chartStyle.fontFamily, - left: '-9em', // #4798 - top: chart.plotTop + PX // prevent jump on focus in Firefox - }, options.inputStyle), div); - - // Blow up the input box - input.onfocus = function () { - rangeSelector.showInput(name); - }; - // Hide away the input box - input.onblur = function () { - rangeSelector.hideInput(name); - }; - - // handle changes in the input boxes - input.onchange = updateExtremes; - - input.onkeypress = function (event) { - // IE does not fire onchange on enter - if (event.keyCode === 13) { - updateExtremes(); - } - }; - }, - - /** - * Get the position of the range selector buttons and inputs. This can be overridden from outside for custom positioning. - */ - getPosition: function () { - var chart = this.chart, - options = chart.options.rangeSelector, - buttonTop = pick((options.buttonPosition || {}).y, chart.plotTop - chart.axisOffset[0] - options.height); - - return { - buttonTop: buttonTop, - inputTop: buttonTop - 10 - }; - }, - - /** - * Render the range selector including the buttons and the inputs. The first time render - * is called, the elements are created and positioned. On subsequent calls, they are - * moved and updated. - * @param {Number} min X axis minimum - * @param {Number} max X axis maximum - */ - render: function (min, max) { - - var rangeSelector = this, - chart = rangeSelector.chart, - renderer = chart.renderer, - container = chart.container, - chartOptions = chart.options, - navButtonOptions = chartOptions.exporting && chartOptions.exporting.enabled !== false && - chartOptions.navigation && chartOptions.navigation.buttonOptions, - options = chartOptions.rangeSelector, - buttons = rangeSelector.buttons, - lang = defaultOptions.lang, - div = rangeSelector.div, - inputGroup = rangeSelector.inputGroup, - buttonTheme = options.buttonTheme, - buttonPosition = options.buttonPosition || {}, - inputEnabled = options.inputEnabled, - states = buttonTheme && buttonTheme.states, - plotLeft = chart.plotLeft, - buttonLeft, - pos = this.getPosition(), - buttonGroup = rangeSelector.group, - buttonBBox, - rendered = rangeSelector.rendered; - - - // create the elements - if (!rendered) { - - rangeSelector.group = buttonGroup = renderer.g('range-selector-buttons').add(); - - rangeSelector.zoomText = renderer.text(lang.rangeSelectorZoom, pick(buttonPosition.x, plotLeft), 15) - .css(options.labelStyle) - .add(buttonGroup); - - // button starting position - buttonLeft = pick(buttonPosition.x, plotLeft) + rangeSelector.zoomText.getBBox().width + 5; - - each(rangeSelector.buttonOptions, function (rangeOptions, i) { - buttons[i] = renderer.button( - rangeOptions.text, - buttonLeft, - 0, - function () { - rangeSelector.clickButton(i); - rangeSelector.isActive = true; - }, - buttonTheme, - states && states.hover, - states && states.select, - states && states.disabled - ) - .css({ - textAlign: 'center' - }) - .add(buttonGroup); - - // increase button position for the next button - buttonLeft += buttons[i].width + pick(options.buttonSpacing, 5); - - if (rangeSelector.selected === i) { - buttons[i].setState(2); - } - }); - - rangeSelector.updateButtonStates(); - - // first create a wrapper outside the container in order to make - // the inputs work and make export correct - if (inputEnabled !== false) { - rangeSelector.div = div = createElement('div', null, { - position: 'relative', - height: 0, - zIndex: 1 // above container - }); - - container.parentNode.insertBefore(div, container); - - // Create the group to keep the inputs - rangeSelector.inputGroup = inputGroup = renderer.g('input-group') - .add(); - inputGroup.offset = 0; - - rangeSelector.drawInput('min'); - rangeSelector.drawInput('max'); - } - } - - // Set or update the group position - buttonGroup[rendered ? 'animate' : 'attr']({ - translateY: pos.buttonTop - }); - - if (inputEnabled !== false) { - - // Update the alignment to the updated spacing box - inputGroup.align(extend({ - y: pos.inputTop, - width: inputGroup.offset, - // Detect collision with the exporting buttons - x: navButtonOptions && (pos.inputTop < (navButtonOptions.y || 0) + navButtonOptions.height - chart.spacing[0]) ? - -40 : 0 - }, options.inputPosition), true, chart.spacingBox); - - // Hide if overlapping - inputEnabled is null or undefined - if (!defined(inputEnabled)) { - buttonBBox = buttonGroup.getBBox(); - inputGroup[inputGroup.translateX < buttonBBox.x + buttonBBox.width + 10 ? 'hide' : 'show'](); - } - - // Set or reset the input values - rangeSelector.setInputValue('min', min); - rangeSelector.setInputValue('max', max); - } - - rangeSelector.rendered = true; - }, - - /** - * Destroys allocated elements. - */ - destroy: function () { - var minInput = this.minInput, - maxInput = this.maxInput, - chart = this.chart, - blurInputs = this.blurInputs, - key; - - removeEvent(chart.container, 'mousedown', blurInputs); - removeEvent(chart, 'resize', blurInputs); - - // Destroy elements in collections - destroyObjectProperties(this.buttons); - - // Clear input element events - if (minInput) { - minInput.onfocus = minInput.onblur = minInput.onchange = null; - } - if (maxInput) { - maxInput.onfocus = maxInput.onblur = maxInput.onchange = null; - } - - // Destroy HTML and SVG elements - for (key in this) { - if (this[key] && key !== 'chart') { - if (this[key].destroy) { // SVGElement - this[key].destroy(); - } else if (this[key].nodeType) { // HTML element - discardElement(this[key]); - } - } - this[key] = null; - } - } - }; - - /** - * Add logic to normalize the zoomed range in order to preserve the pressed state of range selector buttons - */ - Axis.prototype.toFixedRange = function (pxMin, pxMax, fixedMin, fixedMax) { - var fixedRange = this.chart && this.chart.fixedRange, - newMin = pick(fixedMin, this.translate(pxMin, true)), - newMax = pick(fixedMax, this.translate(pxMax, true)), - changeRatio = fixedRange && (newMax - newMin) / fixedRange; - - // If the difference between the fixed range and the actual requested range is - // too great, the user is dragging across an ordinal gap, and we need to release - // the range selector button. - if (changeRatio > 0.7 && changeRatio < 1.3) { - if (fixedMax) { - newMin = newMax - fixedRange; - } else { - newMax = newMin + fixedRange; - } - } - if (!isNumber(newMin)) { // #1195 - newMin = newMax = undefined; - } - - return { - min: newMin, - max: newMax - }; - }; - - Axis.prototype.minFromRange = function () { - var rangeOptions = this.range, - type = rangeOptions.type, - timeName = { month: 'Month', year: 'FullYear' }[type], - min, - max = this.max, - dataMin, - range, - // Get the true range from a start date - getTrueRange = function (base, count) { - var date = new Date(base); - date['set' + timeName](date['get' + timeName]() + count); - return date.getTime() - base; - }; - - if (isNumber(rangeOptions)) { - min = this.max - rangeOptions; - range = rangeOptions; - } else { - min = max + getTrueRange(max, -rangeOptions.count); - } - - dataMin = pick(this.dataMin, Number.MIN_VALUE); - if (!isNumber(min)) { - min = dataMin; - } - if (min <= dataMin) { - min = dataMin; - if (range === undefined) { // #4501 - range = getTrueRange(min, rangeOptions.count); - } - this.newMax = mathMin(min + range, this.dataMax); - } - if (!isNumber(max)) { - min = undefined; - } - return min; - - }; - - // Initialize scroller for stock charts - wrap(Chart.prototype, 'init', function (proceed, options, callback) { - - addEvent(this, 'init', function () { - if (this.options.rangeSelector.enabled) { - this.rangeSelector = new RangeSelector(this); - } - }); - - proceed.call(this, options, callback); - - }); - - - Highcharts.RangeSelector = RangeSelector; - - /* **************************************************************************** - * End Range Selector code * - *****************************************************************************/ - - - - Chart.prototype.callbacks.push(function (chart) { - var extremes, - scroller = chart.scroller, - rangeSelector = chart.rangeSelector; - - function renderRangeSelector() { - extremes = chart.xAxis[0].getExtremes(); - if (isNumber(extremes.min)) { - rangeSelector.render(extremes.min, extremes.max); - } - } - - function afterSetExtremesHandlerRangeSelector(e) { - rangeSelector.render(e.min, e.max); - } - - function destroyEvents() { - if (rangeSelector) { - removeEvent(chart, 'resize', renderRangeSelector); - removeEvent(chart.xAxis[0], 'afterSetExtremes', afterSetExtremesHandlerRangeSelector); - } - } - - // initiate the scroller - if (scroller) { - extremes = chart.xAxis[0].getExtremes(); - scroller.render(extremes.min, extremes.max); - } - if (rangeSelector) { - // redraw the scroller on setExtremes - addEvent(chart.xAxis[0], 'afterSetExtremes', afterSetExtremesHandlerRangeSelector); - - // redraw the scroller chart resize - addEvent(chart, 'resize', renderRangeSelector); - - // do it now - renderRangeSelector(); - } - - // Remove resize/afterSetExtremes at chart destroy - addEvent(chart, 'destroy', destroyEvents); - }); - /** - * A wrapper for Chart with all the default values for a Stock chart - */ - Highcharts.StockChart = Highcharts.stockChart = function (a, b, c) { - var hasRenderToArg = isString(a) || a.nodeName, - options = arguments[hasRenderToArg ? 1 : 0], - seriesOptions = options.series, // to increase performance, don't merge the data - opposite, - - // Always disable startOnTick:true on the main axis when the navigator is enabled (#1090) - navigatorEnabled = pick(options.navigator && options.navigator.enabled, true), - disableStartOnTick = navigatorEnabled ? { - startOnTick: false, - endOnTick: false - } : null, - - lineOptions = { - - marker: { - enabled: false, - radius: 2 - } - // gapSize: 0 - }, - columnOptions = { - shadow: false, - borderWidth: 0 - }; - - // apply X axis options to both single and multi y axes - options.xAxis = map(splat(options.xAxis || {}), function (xAxisOptions) { - return merge( - { // defaults - minPadding: 0, - maxPadding: 0, - ordinal: true, - title: { - text: null - }, - labels: { - overflow: 'justify' - }, - showLastLabel: true - }, xAxisOptions, // user options - { // forced options - type: 'datetime', - categories: null - }, - disableStartOnTick - ); - }); - - // apply Y axis options to both single and multi y axes - options.yAxis = map(splat(options.yAxis || {}), function (yAxisOptions) { - opposite = pick(yAxisOptions.opposite, true); - return merge({ // defaults - labels: { - y: -2 - }, - opposite: opposite, - showLastLabel: false, - title: { - text: null - } - }, yAxisOptions // user options - ); - }); - - options.series = null; - - options = merge( - { - chart: { - panning: true, - pinchType: 'x' - }, - navigator: { - enabled: true - }, - scrollbar: { - enabled: true - }, - rangeSelector: { - enabled: true - }, - title: { - text: null, - style: { - fontSize: '16px' - } - }, - tooltip: { - shared: true, - crosshairs: true - }, - legend: { - enabled: false - }, - - plotOptions: { - line: lineOptions, - spline: lineOptions, - area: lineOptions, - areaspline: lineOptions, - arearange: lineOptions, - areasplinerange: lineOptions, - column: columnOptions, - columnrange: columnOptions, - candlestick: columnOptions, - ohlc: columnOptions - } - - }, - options, // user's options - - { // forced options - _stock: true, // internal flag - chart: { - inverted: false - } - } - ); - - options.series = seriesOptions; - - return hasRenderToArg ? - new Chart(a, options, c) : - new Chart(options, b); - }; - - // Implement the pinchType option - wrap(Pointer.prototype, 'init', function (proceed, chart, options) { - - var pinchType = options.chart.pinchType || ''; - - proceed.call(this, chart, options); - - // Pinch status - this.pinchX = this.pinchHor = pinchType.indexOf('x') !== -1; - this.pinchY = this.pinchVert = pinchType.indexOf('y') !== -1; - this.hasZoom = this.hasZoom || this.pinchHor || this.pinchVert; - }); - - // Override the automatic label alignment so that the first Y axis' labels - // are drawn on top of the grid line, and subsequent axes are drawn outside - wrap(Axis.prototype, 'autoLabelAlign', function (proceed) { - var chart = this.chart, - options = this.options, - panes = chart._labelPanes = chart._labelPanes || {}, - key, - labelOptions = this.options.labels; - if (this.chart.options._stock && this.coll === 'yAxis') { - key = options.top + ',' + options.height; - if (!panes[key] && labelOptions.enabled) { // do it only for the first Y axis of each pane - if (labelOptions.x === 15) { // default - labelOptions.x = 0; - } - if (labelOptions.align === undefined) { - labelOptions.align = 'right'; - } - panes[key] = 1; - return 'right'; - } - } - return proceed.call(this, [].slice.call(arguments, 1)); - }); - - // Override getPlotLinePath to allow for multipane charts - wrap(Axis.prototype, 'getPlotLinePath', function (proceed, value, lineWidth, old, force, translatedValue) { - var axis = this, - series = (this.isLinked && !this.series ? this.linkedParent.series : this.series), - chart = axis.chart, - renderer = chart.renderer, - axisLeft = axis.left, - axisTop = axis.top, - x1, - y1, - x2, - y2, - result = [], - axes = [], //#3416 need a default array - axes2, - uniqueAxes, - transVal; - - // Ignore in case of color Axis. #3360, #3524 - if (axis.coll === 'colorAxis') { - return proceed.apply(this, [].slice.call(arguments, 1)); - } - - // Get the related axes based on series - axes = (axis.isXAxis ? - (defined(axis.options.yAxis) ? - [chart.yAxis[axis.options.yAxis]] : - map(series, function (s) { - return s.yAxis; - }) - ) : - (defined(axis.options.xAxis) ? - [chart.xAxis[axis.options.xAxis]] : - map(series, function (s) { - return s.xAxis; - }) - ) - ); - - // Get the related axes based options.*Axis setting #2810 - axes2 = (axis.isXAxis ? chart.yAxis : chart.xAxis); - each(axes2, function (A) { - if (defined(A.options.id) ? A.options.id.indexOf('navigator') === -1 : true) { - var a = (A.isXAxis ? 'yAxis' : 'xAxis'), - rax = (defined(A.options[a]) ? chart[a][A.options[a]] : chart[a][0]); - - if (axis === rax) { - axes.push(A); - } - } - }); - - - // Remove duplicates in the axes array. If there are no axes in the axes array, - // we are adding an axis without data, so we need to populate this with grid - // lines (#2796). - uniqueAxes = axes.length ? [] : [axis.isXAxis ? chart.yAxis[0] : chart.xAxis[0]]; //#3742 - each(axes, function (axis2) { - if (inArray(axis2, uniqueAxes) === -1) { - uniqueAxes.push(axis2); - } - }); - - transVal = pick(translatedValue, axis.translate(value, null, null, old)); - if (isNumber(transVal)) { - if (axis.horiz) { - each(uniqueAxes, function (axis2) { - var skip; - - y1 = axis2.pos; - y2 = y1 + axis2.len; - x1 = x2 = mathRound(transVal + axis.transB); - - if (x1 < axisLeft || x1 > axisLeft + axis.width) { // outside plot area - if (force) { - x1 = x2 = mathMin(mathMax(axisLeft, x1), axisLeft + axis.width); - } else { - skip = true; - } - } - if (!skip) { - result.push('M', x1, y1, 'L', x2, y2); - } - }); - } else { - each(uniqueAxes, function (axis2) { - var skip; - - x1 = axis2.pos; - x2 = x1 + axis2.len; - y1 = y2 = mathRound(axisTop + axis.height - transVal); - - if (y1 < axisTop || y1 > axisTop + axis.height) { // outside plot area - if (force) { - y1 = y2 = mathMin(mathMax(axisTop, y1), axis.top + axis.height); - } else { - skip = true; - } - } - if (!skip) { - result.push('M', x1, y1, 'L', x2, y2); - } - }); - } - } - return result.length > 0 ? - renderer.crispPolyLine(result, lineWidth || 1) : - null; //#3557 getPlotLinePath in regular Highcharts also returns null - }); - - // Override getPlotBandPath to allow for multipane charts - Axis.prototype.getPlotBandPath = function (from, to) { - var toPath = this.getPlotLinePath(to, null, null, true), - path = this.getPlotLinePath(from, null, null, true), - result = [], - i; - - if (path && toPath && path.toString() !== toPath.toString()) { - // Go over each subpath - for (i = 0; i < path.length; i += 6) { - result.push('M', path[i + 1], path[i + 2], 'L', path[i + 4], path[i + 5], toPath[i + 4], toPath[i + 5], toPath[i + 1], toPath[i + 2]); - } - } else { // outside the axis area - result = null; - } - - return result; - }; - - // Function to crisp a line with multiple segments - SVGRenderer.prototype.crispPolyLine = function (points, width) { - // points format: [M, 0, 0, L, 100, 0] - // normalize to a crisp line - var i; - for (i = 0; i < points.length; i = i + 6) { - if (points[i + 1] === points[i + 4]) { - // Substract due to #1129. Now bottom and left axis gridlines behave the same. - points[i + 1] = points[i + 4] = mathRound(points[i + 1]) - (width % 2 / 2); - } - if (points[i + 2] === points[i + 5]) { - points[i + 2] = points[i + 5] = mathRound(points[i + 2]) + (width % 2 / 2); - } - } - return points; - }; - if (Renderer === Highcharts.VMLRenderer) { - VMLRenderer.prototype.crispPolyLine = SVGRenderer.prototype.crispPolyLine; - } - - - // Wrapper to hide the label - wrap(Axis.prototype, 'hideCrosshair', function (proceed, i) { - proceed.call(this, i); - - if (this.crossLabel) { - this.crossLabel = this.crossLabel.hide(); - } - }); - - // Wrapper to draw the label - wrap(Axis.prototype, 'drawCrosshair', function (proceed, e, point) { - // Draw the crosshair - proceed.call(this, e, point); - - // Check if the label has to be drawn - if (!defined(this.crosshair.label) || !this.crosshair.label.enabled) { - return; - } - - var chart = this.chart, - options = this.options.crosshair.label, // the label's options - horiz = this.horiz, // axis orientation - opposite = this.opposite, // axis position - left = this.left, // left position - top = this.top, // top position - crossLabel = this.crossLabel, // reference to the svgElement - posx, - posy, - crossBox, - formatOption = options.format, - formatFormat = '', - limit, - align, - tickInside = this.options.tickPosition === 'inside', - snap = this.crosshair.snap !== false, - value; - - align = (horiz ? 'center' : opposite ? (this.labelAlign === 'right' ? 'right' : 'left') : (this.labelAlign === 'left' ? 'left' : 'center')); - - // If the label does not exist yet, create it. - if (!crossLabel) { - crossLabel = this.crossLabel = chart.renderer.label(null, null, null, options.shape || 'callout') - .attr({ - align: options.align || align, - zIndex: 12, - fill: options.backgroundColor || (this.series[0] && this.series[0].color) || 'gray', - padding: pick(options.padding, 8), - stroke: options.borderColor || '', - 'stroke-width': options.borderWidth || 0, - r: pick(options.borderRadius, 3) - }) - .css(extend({ - color: 'white', - fontWeight: 'normal', - fontSize: '11px', - textAlign: 'center' - }, options.style)) - .add(); - } - - if (horiz) { - posx = snap ? point.plotX + left : e.chartX; - posy = top + (opposite ? 0 : this.height); - } else { - posx = opposite ? this.width + left : 0; - posy = snap ? point.plotY + top : e.chartY; - } - - if (!formatOption && !options.formatter) { - if (this.isDatetimeAxis) { - formatFormat = '%b %d, %Y'; - } - formatOption = '{value' + (formatFormat ? ':' + formatFormat : '') + '}'; - } - - // Show the label - value = snap ? point[this.isXAxis ? 'x' : 'y'] : this.toValue(horiz ? e.chartX : e.chartY); - crossLabel.attr({ - text: formatOption ? format(formatOption, { value: value }) : options.formatter.call(this, value), - anchorX: horiz ? posx : (this.opposite ? 0 : chart.chartWidth), - anchorY: horiz ? (this.opposite ? chart.chartHeight : 0) : posy, - x: posx, - y: posy, - visibility: VISIBLE - }); - crossBox = crossLabel.getBBox(); - - // now it is placed we can correct its position - if (horiz) { - if ((tickInside && !opposite) || (!tickInside && opposite)) { - posy = crossLabel.y - crossBox.height; - } - } else { - posy = crossLabel.y - (crossBox.height / 2); - } - - // check the edges - if (horiz) { - limit = { - left: left - crossBox.x, - right: left + this.width - crossBox.x - }; - } else { - limit = { - left: this.labelAlign === 'left' ? left : 0, - right: this.labelAlign === 'right' ? left + this.width : chart.chartWidth - }; - } - - // left edge - if (crossLabel.translateX < limit.left) { - posx += limit.left - crossLabel.translateX; - } - // right edge - if (crossLabel.translateX + crossBox.width >= limit.right) { - posx -= crossLabel.translateX + crossBox.width - limit.right; - } - - // show the crosslabel - crossLabel.attr({ x: posx, y: posy, visibility: 'visible' }); - }); - - /* **************************************************************************** - * Start value compare logic * - *****************************************************************************/ - - var seriesInit = seriesProto.init, - seriesProcessData = seriesProto.processData, - pointTooltipFormatter = Point.prototype.tooltipFormatter; - - /** - * Extend series.init by adding a method to modify the y value used for plotting - * on the y axis. This method is called both from the axis when finding dataMin - * and dataMax, and from the series.translate method. - */ - seriesProto.init = function () { - - // Call base method - seriesInit.apply(this, arguments); - - // Set comparison mode - this.setCompare(this.options.compare); - }; - - /** - * The setCompare method can be called also from the outside after render time - */ - seriesProto.setCompare = function (compare) { - - // Set or unset the modifyValue method - this.modifyValue = (compare === 'value' || compare === 'percent') ? function (value, point) { - var compareValue = this.compareValue; - - if (value !== UNDEFINED) { // #2601 - - // get the modified value - value = compare === 'value' ? - value - compareValue : // compare value - value = 100 * (value / compareValue) - 100; // compare percent - - // record for tooltip etc. - if (point) { - point.change = value; - } - - } - - return value; - } : null; - - // Mark dirty - if (this.chart.hasRendered) { - this.isDirty = true; - } - - }; - - /** - * Extend series.processData by finding the first y value in the plot area, - * used for comparing the following values - */ - seriesProto.processData = function () { - var series = this, - i, - keyIndex = -1, - processedXData, - processedYData, - length, - compareValue; - - // call base method - seriesProcessData.apply(this, arguments); - - if (series.xAxis && series.processedYData) { // not pies - - // local variables - processedXData = series.processedXData; - processedYData = series.processedYData; - length = processedYData.length; - - // For series with more than one value (range, OHLC etc), compare against - // the pointValKey (#4922) - if (series.pointArrayMap) { - keyIndex = inArray(series.pointValKey || 'y', series.pointArrayMap); - } - - // find the first value for comparison - for (i = 0; i < length; i++) { - compareValue = keyIndex > -1 ? - processedYData[i][keyIndex] : - processedYData[i]; - if (isNumber(compareValue) && processedXData[i] >= series.xAxis.min && compareValue !== 0) { - series.compareValue = compareValue; - break; - } - } - } - }; - - /** - * Modify series extremes - */ - wrap(seriesProto, 'getExtremes', function (proceed) { - var extremes; - - proceed.apply(this, [].slice.call(arguments, 1)); - - if (this.modifyValue) { - extremes = [this.modifyValue(this.dataMin), this.modifyValue(this.dataMax)]; - this.dataMin = arrayMin(extremes); - this.dataMax = arrayMax(extremes); - } - }); - - /** - * Add a utility method, setCompare, to the Y axis - */ - Axis.prototype.setCompare = function (compare, redraw) { - if (!this.isXAxis) { - each(this.series, function (series) { - series.setCompare(compare); - }); - if (pick(redraw, true)) { - this.chart.redraw(); - } - } - }; - - /** - * Extend the tooltip formatter by adding support for the point.change variable - * as well as the changeDecimals option - */ - Point.prototype.tooltipFormatter = function (pointFormat) { - var point = this; - - pointFormat = pointFormat.replace( - '{point.change}', - (point.change > 0 ? '+' : '') + Highcharts.numberFormat(point.change, pick(point.series.tooltipOptions.changeDecimals, 2)) - ); - - return pointTooltipFormatter.apply(this, [pointFormat]); - }; - - /* **************************************************************************** - * End value compare logic * - *****************************************************************************/ - - - /** - * Extend the Series prototype to create a separate series clip box. This is related - * to using multiple panes, and a future pane logic should incorporate this feature (#2754). - */ - wrap(Series.prototype, 'render', function (proceed) { - // Only do this on stock charts (#2939), and only if the series type handles clipping - // in the animate method (#2975). - if (this.chart.options._stock && this.xAxis) { - - // First render, initial clip box - if (!this.clipBox && this.animate) { - this.clipBox = merge(this.chart.clipBox); - this.clipBox.width = this.xAxis.len; - this.clipBox.height = this.yAxis.len; - - // On redrawing, resizing etc, update the clip rectangle - } else if (this.chart[this.sharedClipKey]) { - stop(this.chart[this.sharedClipKey]); // #2998 - this.chart[this.sharedClipKey].attr({ - width: this.xAxis.len, - height: this.yAxis.len - }); - } - } - proceed.call(this); - }); - - // global variables - extend(Highcharts, { - - // Constructors - Color: Color, - Point: Point, - Tick: Tick, - Renderer: Renderer, - SVGElement: SVGElement, - SVGRenderer: SVGRenderer, - - // Various - arrayMin: arrayMin, - arrayMax: arrayMax, - charts: charts, - correctFloat: correctFloat, - dateFormat: dateFormat, - error: error, - format: format, - pathAnim: pathAnim, - getOptions: getOptions, - hasBidiBug: hasBidiBug, - isTouchDevice: isTouchDevice, - setOptions: setOptions, - addEvent: addEvent, - removeEvent: removeEvent, - createElement: createElement, - discardElement: discardElement, - css: css, - each: each, - map: map, - merge: merge, - splat: splat, - stableSort: stableSort, - extendClass: extendClass, - pInt: pInt, - svg: hasSVG, - canvas: useCanVG, - vml: !hasSVG && !useCanVG, - product: PRODUCT, - version: VERSION - }); - - return Highcharts; -})); diff --git a/ui/site/gulpfile.js b/ui/site/gulpfile.js index cbf2ead985..efa37bf8ae 100644 --- a/ui/site/gulpfile.js +++ b/ui/site/gulpfile.js @@ -58,6 +58,15 @@ const jqueryBarRating = () => gulp.src([ cwdbase: true }).pipe(gulp.dest('../../public/vendor/bar-rating/')); +const highcharts = () => gulp.src([ + 'highcharts.js', + 'highcharts-more.js', + 'highstock.js' +], { + cwd: path.dirname(require.resolve('highcharts/package.json')), + cwdbase: true +}).pipe(gulp.dest('../../public/vendor/highcharts-4.2.5/')); + const stockfishJs = () => gulp.src([ require.resolve('stockfish.js/stockfish.wasm.js'), require.resolve('stockfish.js/stockfish.wasm'), @@ -153,7 +162,7 @@ const tasks = [ gitSha, jqueryFill, ab, standalonesJs, userMod, clas, stockfishWasm, stockfishMvWasm, stockfishJs, deps, - hopscotch, jqueryBarRating + hopscotch, jqueryBarRating, highcharts ]; const dev = gulp.series(tasks.concat([devSource])); diff --git a/ui/site/package.json b/ui/site/package.json index 5cb1bea43c..cb414be4d9 100644 --- a/ui/site/package.json +++ b/ui/site/package.json @@ -22,6 +22,7 @@ "watchify": "^3" }, "dependencies": { + "highcharts": "=4.2.5", "hopscotch": "^0.3.1", "jquery-bar-rating": "^1.2.2", "stockfish-mv.wasm": "^0.2.0", diff --git a/yarn.lock b/yarn.lock index 22ad344bd2..f26be7993e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2233,6 +2233,11 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" +highcharts@=4.2.5: + version "4.2.5" + resolved "https://registry.yarnpkg.com/highcharts/-/highcharts-4.2.5.tgz#f18bbda5ca326fdff7d816c94814fe8509cec969" + integrity sha1-8Yu9pcoyb9/32BbJSBT+hQnOyWk= + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"