From a11dda9042763fe34762df0b16ae08640bc9722b Mon Sep 17 00:00:00 2001 From: Yuecai Liu <38887641+luky116@users.noreply.github.com> Date: Fri, 12 May 2023 20:37:51 +0800 Subject: [PATCH] fix: missing codis fe page files --- .gitignore | 3 + .../assets/node_modules/bootstrap/README.md | 2 +- .../lib/adapters/standalone-framework.js | 18 + .../lib/adapters/standalone-framework.src.js | 635 + .../highcharts/lib/highcharts-3d.js | 48 + .../highcharts/lib/highcharts-3d.src.js | 1711 ++ .../highcharts/lib/highcharts-more.js | 55 + .../highcharts/lib/highcharts-more.src.js | 2666 ++ .../node_modules/highcharts/lib/highcharts.js | 334 + .../highcharts/lib/highcharts.src.js | 19072 ++++++++++++ .../node_modules/highcharts/lib/highmaps.js | 361 + .../highcharts/lib/highmaps.src.js | 20049 +++++++++++++ .../node_modules/highcharts/lib/highstock.js | 424 + .../highcharts/lib/highstock.src.js | 23953 ++++++++++++++++ .../highcharts/lib/modules/boost.js | 12 + .../highcharts/lib/modules/boost.src.js | 591 + .../highcharts/lib/modules/broken-axis.js | 7 + .../highcharts/lib/modules/broken-axis.src.js | 327 + .../highcharts/lib/modules/canvas-tools.js | 133 + .../lib/modules/canvas-tools.src.js | 3114 ++ .../highcharts/lib/modules/data.js | 26 + .../highcharts/lib/modules/data.src.js | 957 + .../highcharts/lib/modules/drilldown.js | 17 + .../highcharts/lib/modules/drilldown.src.js | 717 + .../highcharts/lib/modules/exporting.js | 24 + .../highcharts/lib/modules/exporting.src.js | 780 + .../highcharts/lib/modules/funnel.js | 13 + .../highcharts/lib/modules/funnel.src.js | 322 + .../highcharts/lib/modules/heatmap.js | 23 + .../highcharts/lib/modules/heatmap.src.js | 711 + .../highcharts/lib/modules/map.js | 57 + .../highcharts/lib/modules/map.src.js | 2277 ++ .../lib/modules/no-data-to-display.js | 12 + .../lib/modules/no-data-to-display.src.js | 143 + .../lib/modules/offline-exporting.js | 14 + .../lib/modules/offline-exporting.src.js | 280 + .../highcharts/lib/modules/solid-gauge.js | 14 + .../highcharts/lib/modules/solid-gauge.src.js | 273 + .../highcharts/lib/modules/treemap.js | 30 + .../highcharts/lib/modules/treemap.src.js | 868 + .../highcharts/lib/themes/dark-blue.js | 254 + .../highcharts/lib/themes/dark-green.js | 255 + .../highcharts/lib/themes/dark-unica.js | 213 + .../highcharts/lib/themes/gray.js | 257 + .../highcharts/lib/themes/grid-light.js | 74 + .../highcharts/lib/themes/grid.js | 103 + .../highcharts/lib/themes/sand-signika.js | 104 + .../highcharts/lib/themes/skies.js | 89 + .../node_modules/sha256/lib/nodecrypto.js | 36 + .../assets/node_modules/sha256/lib/sha256.js | 153 + 50 files changed, 82610 insertions(+), 1 deletion(-) create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/adapters/standalone-framework.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/adapters/standalone-framework.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts-3d.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts-3d.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts-more.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts-more.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/highmaps.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/highmaps.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/highstock.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/highstock.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/boost.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/boost.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/broken-axis.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/broken-axis.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/canvas-tools.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/canvas-tools.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/data.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/data.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/drilldown.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/drilldown.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/exporting.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/exporting.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/funnel.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/funnel.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/heatmap.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/heatmap.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/map.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/map.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/no-data-to-display.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/no-data-to-display.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/offline-exporting.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/offline-exporting.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/solid-gauge.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/solid-gauge.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/treemap.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/modules/treemap.src.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/themes/dark-blue.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/themes/dark-green.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/themes/dark-unica.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/themes/gray.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/themes/grid-light.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/themes/grid.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/themes/sand-signika.js create mode 100644 codis/cmd/fe/assets/node_modules/highcharts/lib/themes/skies.js create mode 100644 codis/cmd/fe/assets/node_modules/sha256/lib/nodecrypto.js create mode 100644 codis/cmd/fe/assets/node_modules/sha256/lib/sha256.js diff --git a/.gitignore b/.gitignore index 6447601812..15efa9bb27 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,6 @@ deps #develop container .devcontainer +# include codis fe javascript lib files +!codis/cmd/fe/assets/** + diff --git a/codis/cmd/fe/assets/node_modules/bootstrap/README.md b/codis/cmd/fe/assets/node_modules/bootstrap/README.md index d1099b3825..c2a887b73c 100644 --- a/codis/cmd/fe/assets/node_modules/bootstrap/README.md +++ b/codis/cmd/fe/assets/node_modules/bootstrap/README.md @@ -95,7 +95,7 @@ Documentation for v2.3.2 has been made available for the time being at -1&&(f.splice(k,1),this.HCEvents[c]=f),this.removeEventListener? +a(this,c,i):this.attachEvent&&d(this,c,i)):(b(this,c),this.HCEvents[c]=[])):(b(this),this.HCEvents={})},c.trigger=function(a,b){var d=this.HCEvents[a]||[],c=d.length,h,g,j;g=function(){b.defaultPrevented=!0};for(h=0;h=b.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();a=this.options.curAnim[this.prop]=!0;for(i in b.curAnim)b.curAnim[i]!==!0&&(a=!1);a&&b.complete&&b.complete.call(e);b=!1}else e=c-this.startTime,this.state=e/b.duration,this.pos=b.easing(e,0,1,b.duration),this.now=this.start+(this.end- +this.start)*this.pos,this.update(),b=!0;return b}};this.animate=function(a,d,b){var e,i="",f,g,h;if(typeof b!=="object"||b===null)e=arguments,b={duration:e[2],easing:e[3],complete:e[4]};if(typeof b.duration!=="number")b.duration=400;b.easing=Math[b.easing]||Math.easeInOutSine;b.curAnim={};for(f in d)b.curAnim[f]=d[f];for(h in d)g=new r(a,b,h),f=null,h==="d"?(g.paths=c.init(a,a.d,d.d),g.toD=d.d,e=0,f=1):a.attr?e=a.attr(h):(e=parseFloat(o(a,h))||0,h!=="opacity"&&(i="px")),f||(f=d[h]),f.match&&f.match("px")&& +(f=f.replace(/px/g,"")),g.custom(e,f,i)}},addAnimSetter:function(c,a){q[c]=a},getScript:function(c,a){var d=n.getElementsByTagName("head")[0],b=n.createElement("script");b.type="text/javascript";b.src=c;b.onload=a;d.appendChild(b)},inArray:function(c,a){return a.indexOf?a.indexOf(c):t.indexOf.call(a,c)},adapterRun:function(c,a){return parseInt(o(c,a),10)},grep:function(c,a){return t.filter.call(c,a)},map:function(c,a){for(var d=[],b=0,e=c.length;b -1) { + events.splice(index, 1); + this.HCEvents[name] = events; + } + if (this.removeEventListener) { + removeOneEvent(this, name, fn); + } else if (this.attachEvent) { + IERemoveOneEvent(this, name, fn); + } + } else { + removeAllEvents(this, name); + this.HCEvents[name] = []; + } + } else { + removeAllEvents(this); + this.HCEvents = {}; + } + }; + + obj.trigger = function (name, args) { + var events = this.HCEvents[name] || [], + target = this, + len = events.length, + i, + preventDefault, + fn; + + // Attach a simple preventDefault function to skip default handler if called + preventDefault = function () { + args.defaultPrevented = true; + }; + + for (i = 0; i < len; i++) { + fn = events[i]; + + // args is never null here + if (args.stopped) { + return; + } + + args.preventDefault = preventDefault; + args.target = target; + + // 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 (!args.type) { + args.type = name; + } + + + + // If the event handler return false, prevent the default handler from executing + if (fn.call(this, args) === false) { + args.preventDefault(); + } + } + }; + } + + return obj; +} + + +HighchartsAdapter = { + + /** + * Initialize the adapter. This is run once as Highcharts is first run. + */ + init: function (pathAnim) { + + /** + * Compatibility section to add support for legacy IE. This can be removed if old IE + * support is not needed. + */ + if (!doc.defaultView) { + _getStyle = function (el, prop) { + var val; + if (el.style[prop]) { + return el.style[prop]; + } + if (prop === 'opacity') { + prop = 'filter'; + } + + 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 : val; + }; + this.adapterRun = function (elem, method) { + var alias = { width: 'clientWidth', height: 'clientHeight' }[method]; + + if (alias) { + elem.style.zoom = 1; + return elem[alias] - 2 * parseInt(_getStyle(elem, 'padding'), 10); + } + }; + } + + if (!Array.prototype.forEach) { + this.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) { + this.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) { + this.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 --- + + + /** + * Start of animation specific code + */ + Fx = function (elem, options, prop) { + this.options = options; + this.elem = elem; + this.prop = prop; + }; + Fx.prototype = { + + update: function () { + var styles, + paths = this.paths, + elem = this.elem, + elemelem = elem.element, + prop; // if destroyed, it is null + + // Animation setter defined from outside + if (animSetters[this.prop]) { + animSetters[this.prop](this); + + // Animating a path definition on SVGElement + } else if (paths && elemelem) { + elem.attr('d', pathAnim.step(paths[0], paths[1], this.now, this.toD)); + + // Other animations on SVGElement + } else if (elem.attr) { + if (elemelem) { + elem.attr(this.prop, this.now); + } + + // HTML styles, raw HTML content like container size + } else { + styles = {}; + styles[this.prop] = this.now + this.unit; + for (prop in styles) { + elem.style[prop] = styles[prop]; + } + } + + if (this.options.step) { + this.options.step.call(this.elem, this.now, this); + } + + }, + custom: function (from, to, unit) { + var self = this, + t = function (gotoEnd) { + return self.step(gotoEnd); + }, + i; + + this.startTime = +new Date(); + this.start = from; + this.end = to; + this.unit = unit; + this.now = this.start; + this.pos = this.state = 0; + + t.elem = this.elem; + + if (t() && timers.push(t) === 1) { + t.timerId = setInterval(function () { + + for (i = 0; i < timers.length; i++) { + if (!timers[i]()) { + timers.splice(i--, 1); + } + } + + if (!timers.length) { + clearInterval(t.timerId); + } + }, 13); + } + }, + + step: function (gotoEnd) { + var t = +new Date(), + ret, + done, + options = this.options, + elem = this.elem, + i; + + if (elem.attr && !elem.element) { // #2616, element including flag is destroyed + ret = false; + + } else if (gotoEnd || t >= options.duration + this.startTime) { + this.now = this.end; + this.pos = this.state = 1; + this.update(); + + this.options.curAnim[this.prop] = true; + + done = true; + for (i in options.curAnim) { + if (options.curAnim[i] !== true) { + done = false; + } + } + + if (done) { + if (options.complete) { + options.complete.call(elem); + } + } + ret = false; + + } else { + var n = t - this.startTime; + this.state = n / options.duration; + this.pos = options.easing(n, 0, 1, options.duration); + this.now = this.start + ((this.end - this.start) * this.pos); + this.update(); + ret = true; + } + return ret; + } + }; + + /** + * The adapter animate method + */ + this.animate = function (el, prop, opt) { + var start, + unit = '', + end, + fx, + args, + name, + key, + PX = 'px'; + + if (typeof opt !== 'object' || opt === null) { + args = arguments; + opt = { + duration: args[2], + easing: args[3], + complete: args[4] + }; + } + if (typeof opt.duration !== 'number') { + opt.duration = 400; + } + opt.easing = Math[opt.easing] || Math.easeInOutSine; + opt.curAnim = {}; + for (key in prop) { + opt.curAnim[key] = prop[key]; + } + + for (name in prop) { + fx = new Fx(el, opt, name); + end = null; + + if (name === 'd') { + fx.paths = pathAnim.init( + el, + el.d, + prop.d + ); + fx.toD = prop.d; + start = 0; + end = 1; + } else if (el.attr) { + start = el.attr(name); + } else { + start = parseFloat(_getStyle(el, name)) || 0; + if (name !== 'opacity') { + unit = PX; + } + } + + if (!end) { + end = prop[name]; + } + if (end.match && end.match(PX)) { + end = end.replace(/px/g, ''); // #4351 + } + fx.custom(start, end, unit); + } + }; + }, + + /** + * Add an animation setter for a specific property + */ + addAnimSetter: function (prop, fn) { + animSetters[prop] = fn; + }, + + /** + * Downloads a script and executes a callback when done. + * @param {String} scriptLocation + * @param {Function} callback + */ + getScript: function (scriptLocation, callback) { + // We cannot assume that Assets class from mootools-more is available so instead insert a script tag to download script. + var head = doc.getElementsByTagName('head')[0], + script = doc.createElement('script'); + + script.type = 'text/javascript'; + script.src = scriptLocation; + script.onload = callback; + + head.appendChild(script); + }, + + /** + * Return the index of an item in an array, or -1 if not found + */ + inArray: function (item, arr) { + return arr.indexOf ? arr.indexOf(item) : emptyArray.indexOf.call(arr, item); + }, + + + /** + * A direct link to adapter methods + */ + adapterRun: function (elem, method) { + return parseInt(_getStyle(elem, method), 10); + }, + + /** + * Filter an array + */ + grep: function (elements, callback) { + return emptyArray.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. Loosely based on jQuery's offset method. + */ + offset: function (el) { + var docElem = document.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) + }; + }, + + /** + * Add an event listener + */ + addEvent: function (el, type, fn) { + augment(el).bind(type, fn); + }, + + /** + * Remove event added with addEvent + */ + removeEvent: function (el, type, fn) { + augment(el).unbind(type, fn); + }, + + /** + * Fire an event on a custom object + */ + fireEvent: function (el, type, eventArguments, defaultFunction) { + var e, + key; + + if (doc.createEvent && (el.dispatchEvent || el.fireEvent)) { + e = doc.createEvent('Events'); + e.initEvent(type, true, true); + e.target = el; + + for (key in eventArguments) { + e[key] = eventArguments[key]; + } + + if (el.dispatchEvent) { + el.dispatchEvent(e); + } else { + el.fireEvent(type, e); + } + + } else if (el.HCExtended === true) { + eventArguments = eventArguments || {}; + el.trigger(type, eventArguments); + } + + if (eventArguments && eventArguments.defaultPrevented) { + defaultFunction = null; + } + + if (defaultFunction) { + defaultFunction(eventArguments); + } + }, + + washMouseEvent: function (e) { + return e; + }, + + + /** + * Stop running animation + */ + stop: function (el) { + + var i = timers.length, + timer; + + // Remove timers related to this element (#4519) + while (i--) { + timer = timers[i]; + if (timer.elem === el) { + timers.splice(i, 1); + } + } + }, + + /** + * Utility for iterating over an array. Parameters are reversed compared to jQuery. + * @param {Array} arr + * @param {Function} fn + */ + each: function (arr, fn) { // modern browsers + return Array.prototype.forEach.call(arr, fn); + } +}; + return HighchartsAdapter; +})); diff --git a/codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts-3d.js b/codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts-3d.js new file mode 100644 index 0000000000..5a63b3d9b8 --- /dev/null +++ b/codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts-3d.js @@ -0,0 +1,48 @@ +/* + Highcharts JS v4.1.10 (2015-12-07) + + 3D features for Highcharts JS + + @license: www.highcharts.com/license +*/ +(function(c){typeof module==="object"&&module.exports?module.exports=c:c(Highcharts)})(function(c){function r(d,b,a){var e,g,f=b.options.chart.options3d,c=!1;a?(c=b.inverted,a=b.plotWidth/2,b=b.plotHeight/2,e=f.depth/2,g=u(f.depth,1)*u(f.viewDistance,0)):(a=b.plotLeft+b.plotWidth/2,b=b.plotTop+b.plotHeight/2,e=f.depth/2,g=u(f.depth,1)*u(f.viewDistance,0));var j=[],k=a,i=b,l=e,w=g,a=A*(c?f.beta:-f.beta),f=A*(c?-f.alpha:f.alpha),o=p(a),q=m(a),n=p(f),y=m(f),x,s,z,t,v,C;B(d,function(a){x=(c?a.y:a.x)- +k;s=(c?a.x:a.y)-i;z=(a.z||0)-l;t=q*x-o*z;v=-o*n*x+y*s-q*n*z;C=o*y*x+n*s+q*y*z;w>0&&wc&&f-c>o/2+1.0E-4?(k=k.concat(q(d, +b,a,e,c,c+o/2,h,j)),k=k.concat(q(d,b,a,e,c+o/2,f,h,j))):fo/2+1.0E-4?(k=k.concat(q(d,b,a,e,c,c-o/2,h,j)),k=k.concat(q(d,b,a,e,c-o/2,f,h,j))):(k=f-c,k=["C",d+a*m(c)-a*E*k*p(c)+h,b+e*p(c)+e*E*k*m(c)+j,d+a*m(f)+a*E*k*p(f)+h,b+e*p(f)-e*E*k*m(f)+j,d+a*m(f)+h,b+e*p(f)+j]);return k}function J(d){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;c.each(this.data,function(b){if(b.y!==null)b=b.pointAttr,this.borderColor=c.pick(a.edgeColor,b[""].fill),b[""].stroke=this.borderColor,b.hover.stroke=c.pick(e.hover.edgeColor,this.borderColor),b.select.stroke=c.pick(e.select.edgeColor,this.borderColor)})}d.apply(this,[].slice.call(arguments,1))}var B=c.each,M=c.extend,N=c.inArray,G=c.merge,u=c.pick,K=c.wrap,o=Math.PI,A=o/180,p=Math.sin,m=Math.cos,L=Math.round; +c.perspective=r;var E=4*(Math.sqrt(2)-1)/3/(o/2);c.SVGRenderer.prototype.toLinePath=function(d,b){var a=[];c.each(d,function(b){a.push("L",b.x,b.y)});d.length&&(a[0]="M",b&&a.push("Z"));return a};c.SVGRenderer.prototype.cuboid=function(d){var b=this.g(),d=this.cuboidPath(d);b.front=this.path(d[0]).attr({zIndex:d[3],"stroke-linejoin":"round"}).add(b);b.top=this.path(d[1]).attr({zIndex:d[4],"stroke-linejoin":"round"}).add(b);b.side=this.path(d[2]).attr({zIndex:d[5],"stroke-linejoin":"round"}).add(b); +b.fillSetter=function(a){var b=c.Color(a).brighten(0.1).get(),d=c.Color(a).brighten(-0.1).get();this.front.attr({fill:a});this.top.attr({fill:b});this.side.attr({fill:d});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){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]})):c.SVGElement.prototype.attr.call(this,a);return this};b.animate=function(a,b,d){D(a.x)&&D(a.y)?(a=this.renderer.cuboidPath(a),this.front.attr({zIndex:a[3]}).animate({d:a[0]},b,d),this.top.attr({zIndex:a[4]}).animate({d:a[1]},b,d),this.side.attr({zIndex:a[5]}).animate({d:a[2]},b,d)):a.opacity?(this.front.animate(a,b,d),this.top.animate(a,b,d),this.side.animate(a,b,d)):c.SVGElement.prototype.animate.call(this,a,b,d);return this};b.destroy=function(){this.front.destroy();this.top.destroy(); +this.side.destroy();return null};b.attr({zIndex:-d[3]});return b};c.SVGRenderer.prototype.cuboidPath=function(d){function b(a){return i[a]}var a=d.x,e=d.y,g=d.z,f=d.height,h=d.width,j=d.depth,k=c.map,i=[{x:a,y:e,z:g},{x:a+h,y:e,z:g},{x:a+h,y:e+f,z:g},{x:a,y:e+f,z:g},{x:a,y:e+f,z:g+j},{x:a+h,y:e+f,z:g+j},{x:a+h,y:e,z:g+j},{x:a,y:e,z:g+j}],i=r(i,c.charts[this.chartIndex],d.insidePlotArea),e=function(a,d){a=k(a,b);d=k(d,b);return I(a)<0?a:I(d)<0?d:[]},d=e([3,2,1,0],[7,6,5,4]),a=e([1,6,7,0],[4,5,2,3]), +e=e([1,2,5,6],[0,7,4,3]);return[this.toLinePath(d,!0),this.toLinePath(a,!0),this.toLinePath(e,!0),F(d),F(a),F(e)]};c.SVGRenderer.prototype.arc3d=function(d){function b(a){var b=!1,d={},e;for(e in a)N(e,g)!==-1&&(d[e]=a[e],delete a[e],b=!0);return b?d:!1}var a=this.g(),e=a.renderer,g="x,y,r,innerR,start,end".split(","),d=G(d);d.alpha*=A;d.beta*=A;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 d=a.renderer.arc3dPath(b),e=d.zTop*100;a.attribs=b;a.top.attr({d:d.top,zIndex:d.zTop});a.inn.attr({d:d.inn,zIndex:d.zInn});a.out.attr({d:d.out,zIndex:d.zOut});a.side1.attr({d:d.side1,zIndex:d.zSide1});a.side2.attr({d:d.side2,zIndex:d.zSide2});a.zIndex=e;a.attr({zIndex:e});b.center&&(a.top.setRadialReference(b.center),delete b.center)};a.setPaths(d);a.fillSetter=function(a){var b=c.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};B(["opacity","translateX","translateY","visibility"],function(b){a[b+"Setter"]=function(b,d){a[d]=b;B(["out","inn","side1","side2","top"],function(e){a[e].attr(d,b)})}});K(a,"attr",function(d,e,c){var g;if(typeof e==="object"&&(g=b(e)))M(a.attribs,g),a.setPaths(a.attribs);return d.call(this,e,c)});K(a,"animate",function(a,d,e,c){var g,l=this.attribs,m;delete d.center;delete d.z;delete d.depth; +delete d.alpha;delete d.beta;if(e=u(e,this.renderer.globalAnimation))if(typeof e!=="object"&&(e={}),d=G(d),g=b(d))m=g,e.step=function(a,b){function d(a){return l[a]+(u(m[a],l[a])-l[a])*b.pos}b.elem.setPaths(G(l,{x:d("x"),y:d("y"),r:d("r"),innerR:d("innerR"),start:d("start"),end:d("end")}))};return a.call(this,d,e,c)});a.destroy=function(){this.top.destroy();this.out.destroy();this.inn.destroy();this.side1.destroy();this.side2.destroy();c.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};c.SVGRenderer.prototype.arc3dPath=function(d){function b(a){a%=2*o;a>o&&(a=2*o-a);return a}var a=d.x,e=d.y,c=d.start,f=d.end-1.0E-5,h=d.r,j=d.innerR,k=d.depth,i=d.alpha,l=d.beta,w=m(c),u=p(c),d=m(f),r=p(f),n=h*m(l);h*=m(i);var y=j*m(l),x=j*m(i),j=k*p(l),s=k*p(i),k=["M",a+n*w,e+h*u],k=k.concat(q(a,e,n,h,c,f,0,0)),k=k.concat(["L", +a+y*d,e+x*r]),k=k.concat(q(a,e,y,x,f,c,0,0)),k=k.concat(["Z"]),z=l>0?o/2:0,l=i>0?0:o/2,z=c>-z?c:f>-z?-z:c,t=fv&&co-l&&c0?4:-1}).css({stroke:f.color}).add()):(c={x:l+(b.yAxis[0].opposite?0:-g.size),y:m+(b.xAxis[0].opposite?-f.size:0),z:j,width:i+g.size,height:k+f.size,depth:h.size,insidePlotArea:!1},this.backFrame?this.backFrame.animate(c):this.backFrame=a.cuboid(c).attr({fill:h.color,zIndex:-3}).css({stroke:h.color}).add(),b={x:l+(b.yAxis[0].opposite?i:-g.size),y:m+(b.xAxis[0].opposite?-f.size:0),z:0,width:g.size,height:k+f.size,depth:j,insidePlotArea:!1},this.sideFrame?this.sideFrame.animate(b): +this.sideFrame=a.cuboid(b).attr({fill:g.color,zIndex:-2}).css({stroke:g.color}).add())}});c.wrap(c.Axis.prototype,"getPlotLinePath",function(d){var b=d.apply(this,[].slice.call(arguments,1));if(!this.chart.is3d())return b;if(b===null)return b;var a=this.chart,c=a.options.chart.options3d,a=this.isZAxis?a.plotWidth:c.depth,c=this.opposite;this.horiz&&(c=!c);b=[this.swapZ({x:b[1],y:b[2],z:c?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:c?0:a})];b=r(b, +this.chart,!1);return b=this.chart.renderer.toLinePath(b,!1)});c.wrap(c.Axis.prototype,"getLinePath",function(d){return this.chart.is3d()?[]:d.apply(this,[].slice.call(arguments,1))});c.wrap(c.Axis.prototype,"getPlotBandPath",function(d){if(!this.chart.is3d())return d.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});c.wrap(c.Tick.prototype,"getMarkPath", +function(d){var b=d.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=r(b,this.axis.chart,!1);return b=["M",b[0].x,b[0].y,"L",b[1].x,b[1].y]});c.wrap(c.Tick.prototype,"getLabelPosition",function(d){var b=d.apply(this,[].slice.call(arguments,1));if(!this.axis.chart.is3d())return b;var a=r([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});c.wrap(c.Tick.prototype,"handleOverflow",function(d,b){if(this.axis.chart.is3d())b=b.old;return d.call(this,b)});c.wrap(c.Axis.prototype,"getTitlePosition",function(d){var b=this.chart.is3d(),a,c;if(b)c=this.axisTitleMargin,this.axisTitleMargin=0;a=d.apply(this,[].slice.call(arguments,1));if(b)a=r([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)*c,this.axisTitleMargin=c;return a});c.wrap(c.Axis.prototype,"drawCrosshair", +function(d){var b=arguments;this.chart.is3d()&&b[2]&&(b[2]={plotX:b[2].plotXold||b[2].plotX,plotY:b[2].plotYold||b[2].plotY});d.apply(this,[].slice.call(b,1))});c.Axis.prototype.swapZ=function(d,b){if(this.isZAxis){var a=b?0:this.chart.plotLeft,c=this.chart;return{x:a+(c.yAxis[0].opposite?d.z:c.xAxis[0].width-d.z),y:d.y,z:d.x-a}}return d};var H=c.ZAxis=function(){this.isZAxis=!0;this.init.apply(this,arguments)};c.extend(H.prototype,c.Axis.prototype);c.extend(H.prototype,{setOptions:function(d){d= +c.merge({offset:0,lineWidth:0},d);c.Axis.prototype.setOptions.call(this,d);this.coll="zAxis"},setAxisSize:function(){c.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 d=this,b=d.chart;d.hasVisibleSeries=!1;d.dataMin=d.dataMax=d.ignoreMinPadding=d.ignoreMaxPadding=null;d.buildStacks&&d.buildStacks();c.each(d.series,function(a){if(a.visible||!b.options.chart.ignoreHiddenSeries)if(d.hasVisibleSeries= +!0,a=a.zData,a.length)d.dataMin=Math.min(u(d.dataMin,a[0]),Math.min.apply(null,a)),d.dataMax=Math.max(u(d.dataMax,a[0]),Math.max.apply(null,a))})}});c.wrap(c.Chart.prototype,"getAxes",function(d){var b=this,a=this.options,a=a.zAxis=c.splat(a.zAxis||{});d.call(this);if(b.is3d())this.zAxis=[],c.each(a,function(a,d){a.index=d;a.isX=!0;(new H(b,a)).setScale()})});c.wrap(c.seriesTypes.column.prototype,"translate",function(d){d.apply(this,[].slice.call(arguments,1));if(this.chart.is3d()){var b=this.chart, +a=this.options,e=a.depth||25,g=(a.stacking?a.stack||0:this._i)*(e+(a.groupZPadding||1));a.grouping!==!1&&(g=0);g+=a.groupZPadding||1;c.each(this.data,function(a){if(a.y!==null){var d=a.shapeArgs,c=a.tooltipPos;a.shapeType="cuboid";d.z=g;d.depth=e;d.insidePlotArea=!0;c=r([{x:c[0],y:c[1],z:g}],b,!1)[0];a.tooltipPos=[c.x,c.y]}});this.z=g}});c.wrap(c.seriesTypes.column.prototype,"animate",function(d){if(this.chart.is3d()){var b=arguments[1],a=this.yAxis,e=this,g=this.yAxis.reversed;if(c.svg)b?c.each(e.data, +function(b){if(b.y!==null&&(b.height=b.shapeArgs.height,b.shapey=b.shapeArgs.y,b.shapeArgs.height=1,!g))b.shapeArgs.y=b.stackY?b.plotY+a.translate(b.stackY):b.plotY+(b.negative?-b.height:b.height)}):(c.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 d.apply(this,[].slice.call(arguments,1))});c.wrap(c.seriesTypes.column.prototype,"init",function(d){d.apply(this, +[].slice.call(arguments,1));if(this.chart.is3d()){var b=this.options,a=b.grouping,c=b.stacking,g=u(this.yAxis.options.reversedStacks,!0),f=0;if(a===void 0||a){a=this.chart.retrieveStacks(c);f=b.stack||0;for(c=0;c=a.min&&f<=a.max:!1,e.push({x:g.plotX,y:g.plotY,z:g.plotZ});b=r(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(c.VMLRenderer)c.setOptions({animate:!1}),c.VMLRenderer.prototype.cuboid=c.SVGRenderer.prototype.cuboid,c.VMLRenderer.prototype.cuboidPath= +c.SVGRenderer.prototype.cuboidPath,c.VMLRenderer.prototype.toLinePath=c.SVGRenderer.prototype.toLinePath,c.VMLRenderer.prototype.createElement3D=c.SVGRenderer.prototype.createElement3D,c.VMLRenderer.prototype.arc3d=function(d){d=c.SVGRenderer.prototype.arc3d.call(this,d);d.css({zIndex:d.zIndex});return d},c.VMLRenderer.prototype.arc3dPath=c.SVGRenderer.prototype.arc3dPath,c.wrap(c.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/codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts-3d.src.js b/codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts-3d.src.js new file mode 100644 index 0000000000..43dd4416f6 --- /dev/null +++ b/codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts-3d.src.js @@ -0,0 +1,1711 @@ +// ==ClosureCompiler== +// @compilation_level SIMPLE_OPTIMIZATIONS + +/** + * @license Highcharts JS v4.1.10 (2015-12-07) + * + * 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 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; + + 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 + xe; + py = py + ye; + pz = pz + 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 { + Highcharts.SVGElement.prototype.attr.call(this, args); + } + + 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); + } 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[3] }); + + 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; + } else { + ret = []; + } + 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)]; + }; + + ////// 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 = pick(animation, this.renderer.globalAnimation); + + if (animation) { + if (typeof animation !== 'object') { + animation = {}; + } + + 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 + }; + + 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, + 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.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, false)[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 ? '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']; + } + + 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/codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts-more.js b/codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts-more.js new file mode 100644 index 0000000000..c4e94eaacb --- /dev/null +++ b/codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts-more.js @@ -0,0 +1,55 @@ +/* + Highcharts JS v4.1.10 (2015-12-07) + + (c) 2009-2014 Torstein Honsi + + License: www.highcharts.com/license +*/ +(function(m){typeof module==="object"&&module.exports?module.exports=m:m(Highcharts)})(function(m){function K(a,b,c){this.init(a,b,c)}var P=m.arrayMin,Q=m.arrayMax,s=m.each,H=m.extend,t=m.merge,R=m.map,p=m.pick,A=m.pInt,o=m.getOptions().plotOptions,i=m.seriesTypes,u=m.extendClass,L=m.splat,r=m.wrap,M=m.Axis,z=m.Tick,I=m.Point,S=m.Pointer,T=m.CenteredSeriesMixin,B=m.TrackerMixin,w=m.Series,x=Math,E=x.round,C=x.floor,N=x.max,U=m.Color,v=function(){};H(K.prototype,{init:function(a,b,c){var d=this,e= +d.defaultOptions;d.chart=b;d.options=a=t(e,b.angular?{background:{}}:void 0,a);(a=a.background)&&s([].concat(L(a)).reverse(),function(a){var b=a.backgroundColor,j=c.userOptions,a=t(d.defaultBackgroundOptions,a);if(b)a.backgroundColor=b;a.color=a.backgroundColor;c.options.plotBands.unshift(a);j.plotBands=j.plotBands||[];j.plotBands!==c.options.plotBands&&j.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 G=M.prototype,z=z.prototype,V={getOffset:v,redraw:function(){this.isDirty=!1},render:function(){this.isDirty=!1},setScale:v,setCategories:v,setTitle:v},O={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=t(this.defaultOptions,this.defaultRadialOptions,a);if(!a.plotBands)a.plotBands=[]},getOffset:function(){G.getOffset.call(this); +this.chart.axisOffset[this.side]=0;this.center=this.pane.center=T.getCenter.call(this.pane)},getLinePath:function(a,b){var c=this.center,b=p(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(){G.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(){G.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]*p(this.sector,1)/2}},getPosition:function(a, +b){return this.postTranslate(this.isCircular?this.translate(a):0,p(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,e=this.startAngleRad,f=d[2]/2,h=[p(c.outerRadius,"100%"),c.innerRadius,p(c.thickness,10)],j=/%$/,n,g=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),g||(h[0]=this.translate(a),h[1]=this.translate(b)),h=R(h,function(a){j.test(a)&&(a=A(a,10)*f/100);return a}),c.shape==="circle"||!g?(a=-Math.PI/2,b=Math.PI*1.5,n=!0):(a=e+this.translate(a),b=e+this.translate(b)),d=this.chart.renderer.symbols.arc(this.left+d[0],this.top+d[1],h[0],h[0],{start:Math.min(a,b),end:Math.max(a,b),innerR:p(h[1],h[0]-h[2]),open:n}));return d},getPlotLinePath:function(a, +b){var c=this,d=c.center,e=c.chart,f=c.getPosition(a),h,j,n;c.isCircular?n=["M",d[0]+e.plotLeft,d[1]+e.plotTop,"L",f.x,f.y]:c.options.gridLineInterpolation==="circle"?(a=c.translate(a))&&(n=c.getLinePath(0,a)):(s(e.xAxis,function(a){a.pane===c.pane&&(h=a)}),n=[],a=c.translate(a),d=h.tickPositions,h.autoConnect&&(d=d.concat([d[0]])),b&&(d=[].concat(d).reverse()),s(d,function(f,b){j=h.getPosition(f,a);n.push(b?"L":"M",j.x,j.y)}));return n},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)}}};r(G,"init",function(a,b,c){var k;var d=b.angular,e=b.polar,f=c.isX,h=d&&f,j,n;n=b.options;var g=c.pane||0;if(d){if(H(this,h?V:O),j=!f)this.defaultRadialOptions=this.defaultRadialGaugeOptions}else if(e)H(this,O),this.defaultRadialOptions=(j=f)?this.defaultRadialXOptions:t(this.defaultYAxisOptions,this.defaultRadialYOptions);a.call(this,b,c);if(!h&&(d||e)){a=this.options;if(!b.panes)b.panes= +[];this.pane=(k=b.panes[g]=b.panes[g]||new K(L(n.pane)[g],b,this),g=k);g=g.options;b.inverted=!1;n.chart.zoomType=null;this.startAngleRad=b=(g.startAngle-90)*Math.PI/180;this.endAngleRad=n=(p(g.endAngle,g.startAngle+360)-90)*Math.PI/180;this.offset=a.offset||0;if((this.isCircular=j)&&c.max===void 0&&n-b===2*Math.PI)this.autoConnect=!0}});r(z,"getPosition",function(a,b,c,d,e){var f=this.axis;return f.getPosition?f.getPosition(c):a.call(this,b,c,d,e)});r(z,"getLabelPosition",function(a,b,c,d,e,f,h, +j,n){var g=this.axis,l=f.y,k=20,i=f.align,y=(g.translate(this.pos)+g.startAngleRad+Math.PI/2)/Math.PI*180%360;g.isRadial?(a=g.getPosition(this.pos,g.center[2]/2+p(f.distance,-25)),f.rotation==="auto"?d.attr({rotation:y}):l===null&&(l=g.chart.renderer.fontMetrics(d.styles.fontSize).b-d.getBBox().height/2),i===null&&(g.isCircular?(this.label.getBBox().width>g.len*g.tickInterval/(g.max-g.min)&&(k=0),i=y>k&&y<180-k?"left":y>180+k&&y<360-k?"right":"center"):i="center",d.attr({align:i})),a.x+=f.x,a.y+= +l):a=a.call(this,b,c,d,e,f,h,j,n);return a});r(z,"getMarkPath",function(a,b,c,d,e,f,h){var j=this.axis;j.isRadial?(a=j.getPosition(this.pos,j.center[2]/2+d),b=["M",b,c,"L",a.x,a.y]):b=a.call(this,b,c,d,e,f,h);return b});o.arearange=t(o.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=u(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},getSegments:function(){var a=this;s(a.points,function(b){if(!a.options.connectNulls&&(b.low===null||b.high===null))b.y= +null;else if(b.low===null&&b.high!==null)b.y=b.high});w.prototype.getSegments.call(this)},translate:function(){var a=this,b=a.yAxis;i.area.prototype.translate.apply(a);s(a.points,function(a){var d=a.low,e=a.high,f=a.plotY;e===null&&d===null?a.y=null:d===null?(a.plotLow=a.plotY=null,a.plotHigh=b.translate(e,0,1,0,1)):e===null?(a.plotLow=f,a.plotHigh=null):(a.plotLow=f,a.plotHigh=b.translate(e,0,1,0,1))});this.chart.polar&&s(this.points,function(b){a.highToXY(b)})},getSegmentPath:function(a){var b, +c=[],d=a.length,e=w.prototype.getSegmentPath,f,h;h=this.options;var j=h.step;for(b=m.grep(a,function(a){return a.plotLow!==null});d--;)f=a[d],f.plotHigh!==null&&c.push({plotX:f.plotHighX||f.plotX,plotY:f.plotHigh});a=e.call(this,b);if(j)j===!0&&(j="left"),h.step={left:"right",center:"center",right:"left"}[j];c=e.call(this,c);h.step=j;h=[].concat(a,c);this.chart.polar||(c[0]="L");this.areaPath=this.areaPath.concat(a,c);return h},drawDataLabels:function(){var a=this.data,b=a.length,c,d=[],e=w.prototype, +f=this.options.dataLabels,h=f.align,j=f.verticalAlign,n=f.inside,g,l,k=this.chart.inverted;if(f.enabled||this._hasPointLabels){for(c=b;c--;)if(g=a[c]){l=n?g.plotHighg.plotLow;g.y=g.high;g._plotY=g.plotY;g.plotY=g.plotHigh;d[c]=g.dataLabel;g.dataLabel=g.dataLabelUpper;g.below=l;if(k){if(!h)f.align=l?"right":"left"}else if(!j)f.verticalAlign=l?"top":"bottom";f.x=f.xHigh;f.y=f.yHigh}e.drawDataLabels&&e.drawDataLabels.apply(this,arguments);for(c=b;c--;)if(g=a[c]){l=n?g.plotHigh< +g.plotLow:g.plotHigh>g.plotLow;g.dataLabelUpper=g.dataLabel;g.dataLabel=d[c];g.y=g.low;g.plotY=g._plotY;g.below=!l;if(k){if(!h)f.align=l?"left":"right"}else if(!j)f.verticalAlign=l?"bottom":"top";f.x=f.xLow;f.y=f.yLow}e.drawDataLabels&&e.drawDataLabels.apply(this,arguments)}f.align=h;f.verticalAlign=j},alignDataLabel:function(){i.column.prototype.alignDataLabel.apply(this,arguments)},setStackedPoints:v,getSymbol:v,drawPoints:v});o.areasplinerange=t(o.arearange);i.areasplinerange=u(i.arearange,{type:"areasplinerange", +getPointSpline:i.spline.prototype.getPointSpline});(function(){var a=i.column.prototype;o.columnrange=t(o.column,o.arearange,{lineWidth:1,pointRange:null});i.columnrange=u(i.arearange,{type:"columnrange",translate:function(){var b=this,c=b.yAxis,d;a.translate.apply(b);s(b.points,function(a){var f=a.shapeArgs,h=b.options.minPointLength,j;a.tooltipPos=null;a.plotHigh=d=c.translate(a.high,0,1,0,1);a.plotLow=a.plotY;j=d;a=a.plotY-d;Math.abs(a)\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=u(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:v,translate:function(){var a=this.yAxis,b=this.pointArrayMap;i.column.prototype.translate.apply(this);s(this.points,function(c){s(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,e,f,h,j,n,g,l,k,i,y,m,J,o,t,r,v,u,w,x,B,A,z=a.doQuartiles!==!1,F,D=a.options.whiskerLength;s(a.points,function(q){k=q.graphic;B=q.shapeArgs;y={};o={};r={};A=q.color||a.color;if(q.plotY!==void 0)if(d= +q.pointAttr[q.selected?"selected":""],v=B.width,u=C(B.x),w=u+v,x=E(v/2),e=C(z?q.q1Plot:q.lowPlot),f=C(z?q.q3Plot:q.lowPlot),h=C(q.highPlot),j=C(q.lowPlot),y.stroke=q.stemColor||b.stemColor||A,y["stroke-width"]=p(q.stemWidth,b.stemWidth,b.lineWidth),y.dashstyle=q.stemDashStyle||b.stemDashStyle,o.stroke=q.whiskerColor||b.whiskerColor||A,o["stroke-width"]=p(q.whiskerWidth,b.whiskerWidth,b.lineWidth),r.stroke=q.medianColor||b.medianColor||A,r["stroke-width"]=p(q.medianWidth,b.medianWidth,b.lineWidth), +g=y["stroke-width"]%2/2,l=u+x+g,i=["M",l,f,"L",l,h,"M",l,e,"L",l,j],z&&(g=d["stroke-width"]%2/2,l=C(l)+g,e=C(e)+g,f=C(f)+g,u+=g,w+=g,m=["M",u,f,"L",u,e,"L",w,e,"L",w,f,"L",u,f,"z"]),D&&(g=o["stroke-width"]%2/2,h+=g,j+=g,F=/%$/.test(D)?x*parseFloat(D)/100:D/2,J=["M",l-F,h,"L",l+F,h,"M",l-F,j,"L",l+F,j]),g=r["stroke-width"]%2/2,n=E(q.medianPlot)+g,t=["M",u,n,"L",w,n],k)q.stem.animate({d:i}),D&&q.whiskers.animate({d:J}),z&&q.box.animate({d:m}),q.medianShape.animate({d:t});else{q.graphic=k=c.g().add(a.group); +q.stem=c.path(i).attr(y).add(k);if(D)q.whiskers=c.path(J).attr(o).add(k);if(z)q.box=c.path(m).attr(d).add(k);q.medianShape=c.path(t).attr(r).add(k)}})},setStackedPoints:v});o.errorbar=t(o.boxplot,{color:"#000000",grouping:!1,linkedTo:":previous",tooltip:{pointFormat:'\u25cf {series.name}: {point.low} - {point.high}
'},whiskerWidth:null});i.errorbar=u(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:v,getColumnMetrics:function(){return this.linkedParent&&this.linkedParent.columnMetrics||i.column.prototype.getColumnMetrics.call(this)}});o.waterfall=t(o.column,{lineWidth:1,lineColor:"#333",dashStyle:"dot",borderColor:"#333",dataLabels:{inside:!0},states:{hover:{lineWidthPlus:0}}});i.waterfall=u(i.column,{type:"waterfall",upColorProp:"fill",pointValKey:"y",translate:function(){var a=this.options, +b=this.yAxis,c,d,e,f,h,j,n,g,l,k=a.threshold,m=a.stacking;i.column.prototype.translate.apply(this);n=g=k;d=this.points;for(c=0,a=d.length;c0?b.translate(n,0,1)-f.y:b.translate(n,0,1)-b.translate(n-j,0,1);n+=j}f.height<0&&(f.y+=f.height,f.height*=-1);e.plotY=f.y=E(f.y)-this.borderWidth%2/2;f.height=N(E(f.height),0.001);e.yBottom=f.y+f.height;f=e.plotY+(e.negative?f.height:0);this.chart.inverted?e.tooltipPos[0]=b.len-f:e.tooltipPos[1]=f}},processData:function(a){var b=this.yData,c=this.options.data,d,e=b.length,f,h,j,n,g,l;h=f=j=n=this.options.threshold|| +0;for(l=0;l0?(f.pointAttr=e,f.color=d):f.pointAttr=a.pointAttr})},getGraphPath:function(){var a=this.data,b=a.length,c=E(this.options.lineWidth+this.borderWidth)%2/2,d=[],e,f,h;for(h=1;h0?(h-a)/i:0.5,l&&h>=0&&(h=Math.sqrt(h)), +h=x.ceil(c+h*(d-c))/2),n.push(h);this.radii=n},animate:function(a){var b=this.options.animation;if(!a)s(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,e=this.radii;i.scatter.prototype.translate.call(this);for(a=b.length;a--;)c=b[a],d=e?e[a]:0,typeof d==="number"&&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:v,applyZones:v});M.prototype.beforePadding=function(){var a=this,b=this.len,c=this.chart,d=0,e=b,f=this.isXAxis,h=f?"xData":"yData",j=this.min,n= +{},g=x.min(c.plotWidth,c.plotHeight),l=Number.MAX_VALUE,k=-Number.MAX_VALUE,i=this.max-j,m=b/i,o=[];s(this.series,function(b){var h=b.options;if(b.bubblePadding&&(b.visible||!c.options.chart.ignoreHiddenSeries))if(a.allowZoomOutside=!0,o.push(b),f)s(["minSize","maxSize"],function(a){var b=h[a],f=/%$/.test(b),b=A(b);n[a]=f?g*b/100:b}),b.minPxSize=n.minSize,b.maxPxSize=n.maxSize,b=b.zData,b.length&&(l=p(h.zMin,x.min(l,x.max(P(b),h.displayNegative===!1?h.zThreshold:-Number.MAX_VALUE))),k=p(h.zMax,x.max(k, +Q(b))))});s(o,function(a){var b=a[h],c=b.length,g;f&&a.getRadii(l,k,a.minPxSize,a.maxPxSize);if(i>0)for(;c--;)typeof b[c]==="number"&&(g=a.radii[c],d=Math.min((b[c]-j)*m-g,d),e=Math.max((b[c]-j)*m+g,e))});o.length&&i>0&&!this.isLog&&(e-=b,m*=(b+d-e)/b,s([["min","userMin",d],["max","userMax",e]],function(b){p(a.options[b[0]],a[b[1]])===void 0&&(a[b[0]]+=b[2]/m)}))};(function(){function a(a,b,c){a.call(this,b,c);if(this.chart.polar)this.closeSegment=function(a){var b=this.xAxis.center;a.push("L",b[0], +b[1])},this.closedStacks=!0}function b(a,b){var c=this.chart,d=this.options.animation,g=this.group,e=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},g.attr(c),e&&e.attr(c)):(c={translateX:i,translateY:m,scaleX:1,scaleY:1},g.animate(c,d),e&&e.animate(c,d),this.animate=null)}else a.call(this,b)}var c=w.prototype,d=S.prototype,e;c.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)})};r(c,"buildKDTree",function(a){if(this.chart.polar)this.kdByAngle?this.searchPoint=this.searchPointByAngle:this.kdDimensions=2;a.apply(this)});c.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.area&&r(i.area.prototype,"init",a);i.areaspline&&r(i.areaspline.prototype,"init",a);i.spline&&r(i.spline.prototype,"getPointSpline",function(a,b,c,d){var g,e,k,i,m,o,p;if(this.chart.polar){g=c.plotX;e=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,o=k.plotY,i=(1.5*g+i)/2.5,m=(1.5*e+m)/2.5,k=(1.5*g+b)/2.5,p=(1.5*e+o)/2.5,b=Math.sqrt(Math.pow(i- +g,2)+Math.pow(m-e,2)),o=Math.sqrt(Math.pow(k-g,2)+Math.pow(p-e,2)),i=Math.atan2(m-e,i-g),m=Math.atan2(p-e,k-g),p=Math.PI/2+(i+m)/2,Math.abs(i-p)>Math.PI/2&&(p-=Math.PI),i=g+Math.cos(p)*b,m=e+Math.sin(p)*b,k=g+Math.cos(Math.PI+p)*o,p=e+Math.sin(Math.PI+p)*o,c.rightContX=k,c.rightContY=p;d?(c=["C",a.rightContX||a.plotX,a.rightContY||a.plotY,i||g,m||e,g,e],a.rightContX=a.rightContY=null):c=["M",g,e]}else c=a.call(this,b,c,d);return c});r(c,"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])}});r(c,"getSegmentPath",function(a,b){var c=this.points;if(this.chart.polar&&this.options.connectEnds!==!1&&b[b.length-1]===c[c.length-1]&&c[0].y!==null)this.connectEnds=!0,b=[].concat(b,[c[0]]);return a.call(this,b)});r(c,"animate",b);if(i.column)e=i.column.prototype,r(e,"animate",b),r(e,"translate",function(a){var b=this.xAxis,c=this.yAxis.len,d=b.center,e=b.startAngleRad,i= +this.chart.renderer,k,m;this.preventPostTranslate=!0;a.call(this);if(b.isRadial){b=this.points;for(m=b.length;m--;)k=b[m],a=k.barX+e,k.shapeType="path",k.shapeArgs={d:i.symbols.arc(d[0],d[1],c-k.plotY,null,{start:a,end:a+k.pointWidth,innerR:c-p(k.yBottom,c)})},this.toXY(k),k.tooltipPos=[k.plotX,k.plotY],k.ttBelow=k.plotY>d[1]}}),r(e,"alignDataLabel",function(a,b,d,e,g,i){if(this.chart.polar){a=b.rectPlotX/Math.PI*180;if(e.align===null)e.align=a>20&&a<160?"left":a>200&&a<340?"right":"center";if(e.verticalAlign=== +null)e.verticalAlign=a<45||a>315?"bottom":a>135&&a<225?"top":"middle";c.alignDataLabel.call(this,b,d,e,g,i)}else a.call(this,b,d,e,g,i)});r(d,"getCoordinates",function(a,b){var c=this.chart,d={xAxis:[],yAxis:[]};c.polar?s(c.axes,function(a){var e=a.isXAxis,f=a.center,i=b.chartX-f[0]-c.plotLeft,f=b.chartY-f[1]-c.plotTop;d[e?"xAxis":"yAxis"].push({axis:a,value:a.translate(e?Math.PI-Math.atan2(i,f):Math.sqrt(Math.pow(i,2)+Math.pow(f,2)),!0)})}):d=a.call(this,b);return d})})()}); diff --git a/codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts-more.src.js b/codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts-more.src.js new file mode 100644 index 0000000000..9c81b7a651 --- /dev/null +++ b/codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts-more.src.js @@ -0,0 +1,2666 @@ +// ==ClosureCompiler== +// @compilation_level SIMPLE_OPTIMIZATIONS + +/** + * @license Highcharts JS v4.1.10 (2015-12-07) + * + * (c) 2009-2014 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, + merge = Highcharts.merge, + map = Highcharts.map, + pick = Highcharts.pick, + pInt = Highcharts.pInt, + 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); + + } + + // 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; + + + // Disable certain features on angular and polar axes + chart.inverted = false; + chartOptions.chart.zoomType = null; + + // 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; + } + } + + }); + + /** + * 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; + }, + + /** + * Extend getSegments to force null points if the higher value is null. #1703. + */ + getSegments: function () { + var series = this; + + each(series.points, function (point) { + if (!series.options.connectNulls && (point.low === null || point.high === null)) { + point.y = null; + } else if (point.low === null && point.high !== null) { + point.y = point.high; + } + }); + Series.prototype.getSegments.call(this); + }, + + /** + * 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.y = null; + } else if (low === null) { + point.plotLow = point.plotY = null; + point.plotHigh = yAxis.translate(high, 0, 1, 0, 1); + } else if (high === null) { + point.plotLow = plotY; + point.plotHigh = null; + } 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 + */ + getSegmentPath: function (segment) { + + var lowSegment, + highSegment = [], + i = segment.length, + baseGetSegmentPath = Series.prototype.getSegmentPath, + point, + linePath, + lowerPath, + options = this.options, + step = options.step, + higherPath; + + // Remove nulls from low segment + lowSegment = Highcharts.grep(segment, function (point) { + return point.plotLow !== null; + }); + + // Make a segment with plotX and plotY for the top values + while (i--) { + point = segment[i]; + if (point.plotHigh !== null) { + highSegment.push({ + plotX: point.plotHighX || point.plotX, // plotHighX is for polar charts + plotY: point.plotHigh + }); + } + } + + // Get the paths + lowerPath = baseGetSegmentPath.call(this, lowSegment); + if (step) { + if (step === true) { + step = 'left'; + } + options.step = { left: 'right', center: 'center', right: 'left' }[step]; // swap for reading in getSegmentPath + } + higherPath = baseGetSegmentPath.call(this, highSegment); + 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) { + higherPath[0] = 'L'; // this probably doesn't work for spline + } + this.areaPath = this.areaPath.concat(lowerPath, higherPath); + + 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, + 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.tooltipPos = null; // don't inherit from column + point.plotHigh = plotHigh = yAxis.translate(point.high, 0, 1, 0, 1); + point.plotLow = point.plotY; + + // adjust shape + y = plotHigh; + height = 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; + } + + shapeArgs.height = height; + shapeArgs.y = y; + }); + }, + directTouch: true, + trackerGroups: ['group', 'dataLabelsGroup'], + drawGraph: noop, + crispCol: colProto.crispCol, + pointAttrToOptions: colProto.pointAttrToOptions, + drawPoints: colProto.drawPoints, + drawTracker: colProto.drawTracker, + animate: colProto.animate, + getColumnMetrics: colProto.getColumnMetrics + }); + }()); + + /* + * 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, + 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 (overshoot && typeof overshoot === 'number') { + 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}
' + // docs + '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}
' // docs + }, + 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, + threshold = options.threshold, + stacking = options.stacking, + tooltipY; + + // run column series translate + seriesTypes.column.prototype.translate.apply(this); + + 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 = yValue; + } else if (point.isIntermediateSum) { + point.y = 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; // #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; + 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; + + // Correct tooltip placement (#3014) + tooltipY = point.plotY + (point.negative ? shapeArgs.height : 0); + 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] = sum; + } else if (y === 'intermediateSum' || point.isIntermediateSum) { + yData[i] = 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 (typeof radius === 'number' && 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 (typeof data[i] === 'number') { + 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; + } + }; + + /** + * Add some special init logic to areas and areasplines + */ + function initArea(proceed, chart, options) { + proceed.call(this, chart, options); + if (this.chart.polar) { + + /** + * Overridden method to close a segment path. While in a cartesian plane the area + * goes down to the threshold, in the polar chart it goes to the center. + */ + this.closeSegment = function (path) { + var center = this.xAxis.center; + path.push( + 'L', + center[0], + center[1] + ); + }; + + // Instead of complicated logic to draw an area around the inner area in a stack, + // just draw it behind + this.closedStacks = true; + } + } + + + if (seriesTypes.area) { + wrap(seriesTypes.area.prototype, 'init', initArea); + } + if (seriesTypes.areaspline) { + wrap(seriesTypes.areaspline.prototype, 'init', initArea); + } + + 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, 'getSegmentPath', function (proceed, segment) { + + var points = this.points; + + // Connect the path + if (this.chart.polar && this.options.connectEnds !== false && + segment[segment.length - 1] === points[points.length - 1] && points[0].y !== null) { + this.connectEnds = true; // re-used in splines + segment = [].concat(segment, [points[0]]); + } + + // Run uber method + return proceed.call(this, segment); + + }); + + + 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; + /** + * 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, + len = this.yAxis.len, + center = xAxis.center, + startAngleRad = xAxis.startAngleRad, + renderer = this.chart.renderer, + 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: renderer.symbols.arc( + center[0], + center[1], + len - point.plotY, + null, + { + start: start, + end: start + point.pointWidth, + innerR: len - pick(point.yBottom, len) + } + ) + }; + // Provide correct plotX, plotY for tooltip + this.toXY(point); + point.tooltipPos = [point.plotX, point.plotY]; + point.ttBelow = point.plotY > 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/codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts.js b/codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts.js new file mode 100644 index 0000000000..12ff5d87b9 --- /dev/null +++ b/codis/cmd/fe/assets/node_modules/highcharts/lib/highcharts.js @@ -0,0 +1,334 @@ +/* + Highcharts JS v4.1.10 (2015-12-07) + + (c) 2009-2014 Torstein Honsi + + License: www.highcharts.com/license +*/ +(function(Aa,Y){typeof module==="object"&&module.exports?module.exports=Aa.document?Y(Aa):function(B){return Y(B)}:Aa.Highcharts=Y()})(typeof window!=="undefined"?window:this,function(Aa){function Y(a,b){var c="Highcharts error #"+a+": www.highcharts.com/errors/"+a;if(b)throw Error(c);K.console&&console.log(c)}function B(){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=Pa(f,e)}k.push(e);a=a.slice(c+1);c=(d=!d)?"}":"{"}k.push(a);return k.join("")}function rb(a){return U.pow(10,S(U.log(a)/U.LN10))}function sb(a,b,c,d,e){var f,g=a,c=p(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 jb(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){kb||(kb=$(Ka));a&&kb.appendChild(a);kb.innerHTML=""}function ga(a,b){return parseFloat(a.toPrecision(b||14))}function Ta(a, +b){b.renderer.globalAnimation=p(a,b.animation)}function Pb(a){return{init:function(b){var c=a.fx;a.extend(a.easing,{easeOutQuad:function(a,b,c,g,h){return-g*(b/=h)*(b-2)+c}});a.each(["cur","_default","width","height","opacity"],function(b,e){var f=c.step,g;e==="cur"?f=c.prototype:e==="_default"&&a.Tween&&(f=a.Tween.propHooks[e],e="set");(g=f[e])&&(f[e]=function(a){var c,f;f=b?a:this;if(f.prop!=="align")return c=f.elem,c.attr?c.attr(f.prop.replace("strokeWidth","stroke-width"),e==="cur"?void 0:f.now): +g.apply(this,arguments)})});Ua(a.cssHooks.opacity,"get",function(a,b,c){return b.attr?b.opacity||0:a.call(this,b,c)});this.addAnimSetter("d",function(a){var c=a.elem,f;if(!a.started)f=b.init(c,c.d,c.toD),a.start=f[0],a.end=f[1],a.started=!0;c.attr("d",b.step(a.start,a.end,a.pos,c.toD))});this.each=Array.prototype.forEach?function(a,b){return Array.prototype.forEach.call(a,b)}:function(a,b){var c,g=a.length;for(c=0;c3?c.length%3:0;return e+(g?c.substr(0,g)+d:"")+c.substr(g).replace(/(\d{3})(?=\d)/g, +"$1"+d)+(f?b+P(a-c).toFixed(f).slice(2):"")};Ab={init: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,i,k=function(a){for(g=a.length;g--;)a[g]==="M"&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&(k(b),k(c));a.isArea&&(h=b.splice(b.length-6,6),i=c.splice(c.length-6,6));if(d<=c.length/f&&b.length===c.length)for(;d--;)c=[].concat(c).splice(0,f).concat(c);a.shift=0;if(b.length)for(a=c.length;b.length{point.key}
',pointFormat:'\u25cf {series.name}: {point.y}
', +shadow:!0,snap:Aa?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 ba=L.plotOptions,fa=ba.line;Db();ea.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[A(a[1]), +A(a[2]),A(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[A(a[1],16),A(a[2],16),A(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[A(a[1]),A(a[2]),A(a[3]),1]}}],init:function(a){var b,c,d,e;if((this.input=a)&&a.stops)this.stops=Ma(a.stops,function(a){return new ea(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=B(b),d.stops=[].concat(d.stops),o(this.stops,function(b,c){d.stops[c]=[d.stops[c][0],b.get(a)]})):d=c&&!isNaN(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(qa(a)&&a!==0)for(b=0;b<3;b++)c[b]+=A(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}};M.prototype={opacity:1,textProps:"direction,fontSize,fontWeight,fontFamily,fontStyle,color,lineHeight,width,textDecoration,textOverflow,textShadow".split(","),init:function(a,b){this.element=b==="span"?$(b):C.createElementNS(Ea,b);this.renderer=a},animate:function(a,b,c){b=p(b,this.renderer.globalAnimation,!0);Wa(this);if(b){b=B(b,{});if(c)b.complete=c;gb(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,s,r=[],p; +a.linearGradient?f="linearGradient":a.radialGradient&&(f="radialGradient");if(f){g=a[f];i=d.gradients;j=a.stops;n=c.radialReference;Ha(g)&&(a[f]=g={x1:g[0],y1:g[1],x2:g[2],y2:g[3],gradientUnits:"userSpaceOnUse"});f==="radialGradient"&&n&&!q(g.gradientUnits)&&(h=g,g=B(g,d.getRadialAttr(n,h),{gradientUnits:"userSpaceOnUse"}));for(s in g)s!=="id"&&r.push(s,g[s]);for(s in j)r.push(j[s]);r=r.join(",");i[r]?n=i[r].attr("id"):(g.id=n="highcharts-"+zb++,i[r]=k=d.createElement(f).attr(g).add(d.defs),k.radAttr= +h,k.stops=[],o(j,function(a){a[1].indexOf("rgba")===0?(e=ea(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)}));p="url("+d.url+"#"+n+")";c.setAttribute(b,p);c.gradient=r;a.toString=function(){return p}}},applyTextShadow:function(a){var b=this.element,c,d=a.indexOf("contrast")!==-1,e={},f=this.renderer.forExport,g=f||b.style.textShadow!==x&&!sa;if(d)e.textShadow=a=a.replace(/contrast/g,this.renderer.getContrast(b.style.fill)); +if(nb||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);O(g,{"class":"highcharts-text-shadow",fill:e,stroke:e,"stroke-opacity":1/v(A(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!==x&&(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||(this[d+"Setter"]||this._defaultSetter).call(this, +b,d,e);this.shadows&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(d)&&this.updateShadows(d,b)}if(this.doTransform)this.updateTransform(),this.doTransform=!1}c&&c();return g},updateShadows:function(a,b){for(var c=this.shadows,d=c.length;d--;)c[d].setAttribute(a,a==="height"?Math.max(b-(c[d].cutHeight||0),0):a==="d"?this.d:b)},addClass:function(a){var b=this.element,c=O(b,"class")||"";c.indexOf(a)===-1&&O(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]=p(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=a.strokeWidth||this.strokeWidth||0;d=y(e)%2/2;a.x=S(a.x||this.x||0)+d;a.y=S(a.y||this.y||0)+d;a.width=S((a.width||this.width||0)-2*d);a.height=S((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"&&A(a.width)||this.textWidth;b&&(a=t(b,c));this.styles=a;e&&(ia||!Z&&this.renderer.forExport)&&delete a.width;if(sa&&!Z)J(this.element,a);else{b=function(a,b){return"-"+b.toLowerCase()};for(f in a)g+=f.replace(/([A-Z])/g,b)+":"+a[f]+";";O(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=xa.now();a.preventDefault();b.call(d,a)},d.onclick=function(a){(ya.indexOf("Android")===-1||xa.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)+")");(q(c)||q(d))&&a.push("scale("+p(c,1)+" "+p(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||Ba(c))this.alignTo=d=c||"renderer",la(f,this),f.push(this),c=null}else a=this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=p(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 k,j=h.style,l,m=d.cache,n=d.cacheKeys,s;f=p(b,this.rotation);g=f*ha;e!==x&&(s=["",f||0,i&&i.fontSize,h.style.width].join(","),s=e===""||Rb.test(e)?"num:"+e.toString().length+s:e+s); +s&&!a&&(c=m[s]);if(!c){if(h.namespaceURI===Ea||d.forExport){try{l=this.fakeTS&&function(a){o(h.querySelectorAll(".highcharts-text-shadow"),function(b){b.style.display=a})},La&&j.textShadow?(k=j.textShadow,j.textShadow=""):l&&l("none"),c=h.getBBox?t({},h.getBBox()):{width:h.offsetWidth,height:h.offsetHeight},k?j.textShadow=k: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(sa&&i&&i.fontSize==="11px"&&e.toPrecision(3)==="16.9")c.height= +e=14;if(f)c.width=P(e*aa(g))+P(d*T(g)),c.height=P(e*T(g))+P(d*aa(g))}if(s){for(;n.length>250;)delete m[n.shift()];m[s]||n.push(s);m[s]=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;Wa(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;q(a)&&(g.setAttribute(b,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;i< +d.length&&!h;i++)if(e=d[i],f=O(e,"zIndex"),e!==g&&(A(f)>a||!q(a)&&q(f)))c.insertBefore(g,e),h=!0;h||c.appendChild(g)}return h},_defaultSetter:function(a,b,c){c.setAttribute(b,a)}};M.prototype.yGetter=M.prototype.xGetter;M.prototype.translateXSetter=M.prototype.translateYSetter=M.prototype.rotationSetter=M.prototype.verticalAlignSetter=M.prototype.scaleXSetter=M.prototype.scaleYSetter=function(a,b){this[b]=a;this.doTransform=!0};M.prototype["stroke-widthSetter"]=M.prototype.strokeSetter=function(a, +b,c){this[b]=a;if(this.stroke&&this["stroke-width"])this.strokeWidth=this["stroke-width"],M.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 za=function(){this.init.apply(this,arguments)};za.prototype={Element:M,init:function(a,b,c,d,e,f){var g=location,h,d=this.createElement("svg").attr({version:"1.1"}).css(this.getStyle(d)); +h=d.element;a.appendChild(h);a.innerHTML.indexOf("xmlns")===-1&&O(h,"xmlns",Ea);this.isSVG=!0;this.box=h;this.boxWrapper=d;this.alignedObjects=[];this.url=(La||nb)&&C.getElementsByTagName("base").length?g.href.replace(/#.*?$/,"").replace(/([\('\)])/g,"\\$1").replace(/ /g,"%20"):"";this.createElement("desc").add().element.appendChild(C.createTextNode("Created with Highcharts 4.1.10"));this.defs=this.createElement("defs").add();this.allowHTML=f;this.forExport=e;this.gradients={};this.cache={};this.cacheKeys= +[];this.setSize(b,c,!1);var i;if(La&&a.getBoundingClientRect)this.subPixelFix=b=function(){J(a,{left:0,top:0});i=a.getBoundingClientRect();J(a,{left:ta(i.left)-i.left+"px",top:ta(i.top)-i.top+"px"})},b(),H(K,"resize",b)},getStyle:function(a){return this.style=t({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&&W(K,"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=p(a.textStr,"").toString(),f=e.indexOf("<")!==-1,g=b.childNodes,h,i,k=O(b,"x"),j=a.styles,l=a.textWidth, +m=j&&j.lineHeight,n=j&&j.textShadow,s=j&&j.textOverflow==="ellipsis",r=g.length,X=l&&!a.added&&this.box,z=function(a){return m?A(m):c.fontMetrics(/(px|em)$/.test(a&&a.style.fontSize)?a.style.fontSize:j&&j.fontSize||c.style.fontSize||12,a).h},w=function(a){return a.replace(/</g,"<").replace(/>/g,">")};r--;)b.removeChild(g[r]);!f&&!n&&!s&&e.indexOf(" ")===-1?b.appendChild(C.createTextNode(w(e))):(h=/<.*style="([^"]+)".*>/,i=/<.*href="(http[^"]+)".*>/,X&&X.appendChild(b),e=f?e.replace(/<(b|strong)>/g, +'').replace(/<(i|em)>/g,'').replace(//g,"").split(//g):[e],e[e.length-1]===""&&e.pop(),o(e,function(e,f){var g,m=0,e=e.replace(//g,"|||");g=e.split("|||");o(g,function(e){if(e!==""||g.length===1){var n={},r=C.createElementNS(Ea,"tspan"),p;h.test(e)&&(p=e.match(h)[1].replace(/(;| |^)color([ :])/,"$1fill$2"),O(r,"style",p));i.test(e)&&!d&& +(O(r,"onclick",'location.href="'+e.match(i)[1]+'"'),J(r,{cursor:"pointer"}));e=w(e.replace(/<(.|\n)*?>/g,"")||" ");if(e!==" "){r.appendChild(C.createTextNode(e));if(m)n.dx=0;else if(f&&k!==null)n.x=k;O(r,n);b.appendChild(r);!m&&f&&(!Z&&d&&J(r,{display:"block"}),O(r,"dy",z(r)));if(l){for(var n=e.replace(/([^\^])-/g,"$1- ").split(" "),X=g.length>1||f||n.length>1&&j.whiteSpace!=="nowrap",o,D,q,v=[],x=z(r),t=1,y=a.rotation,u=e,B=u.length;(X||s)&&(n.length||v.length);)a.rotation=0,o=a.getBBox(!0),q=o.width, +!Z&&c.forExport&&(q=c.measureSpanWidth(r.firstChild.data,a.styles)),o=q>l,D===void 0&&(D=o),s&&D?(B/=2,u===""||!o&&B<0.5?n=[]:(o&&(D=!0),u=e.substring(0,u.length+(o?-1:1)*ta(B)),n=[u+(l>3?"\u2026":"")],r.removeChild(r.firstChild))):!o||n.length===1?(n=v,v=[],n.length&&(t++,r=C.createElementNS(Ea,"tspan"),O(r,{dy:x,x:k}),p&&O(r,"style",p),b.appendChild(r)),q>l&&(l=q)):(r.removeChild(r.firstChild),v.unshift(n.pop())),n.length&&r.appendChild(C.createTextNode(n.join(" ").replace(/- /g,"-")));D&&a.attr("title", +a.textStr);a.rotation=y}m++}}})}),X&&X.removeChild(b),n&&a.applyTextShadow&&a.applyTextShadow(n))},getContrast:function(a){a=ea(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,s,r,p,a={x1:0,y1:0,x2:0,y2:1},e=B({"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=B(e,{stroke:"#68A",fill:{linearGradient:a, +stops:[[0,"#FFF"],[1,"#ACF"]]}},f);s=f.style;delete f.style;g=B(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#9BD"],[1,"#CDF"]]}},g);r=g.style;delete g.style;h=B(e,{style:{color:"#CCC"}},h);p=h.style;delete h.style;H(k.element,sa?"mouseover":"mouseenter",function(){j!==3&&k.attr(f).css(s)});H(k.element,sa?"mouseout":"mouseleave",function(){j!==3&&(l=[e,f,g][j],m=[n,s,r][j],k.attr(l).css(m))});k.setState=function(a){(k.state=j=a)?a===2?k.attr(g).css(r):a===3&&k.attr(h).css(p):k.attr(e).css(n)}; +return k.on("click",function(a){j!==3&&d.call(k,a)}).attr(e).css(t({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"};Ha(a)?b.d=a:da(a)&&t(b,a);return this.createElement("path").attr(b)},circle:function(a,b,c){a=da(a)?a:{x:a,y:b,r:c};b=this.createElement("circle");b.xSetter=function(a){this.element.setAttribute("cx",a)};b.ySetter=function(a){this.element.setAttribute("cy",a)}; +return b.attr(a)},arc:function(a,b,c,d,e,f){if(da(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=da(a)?a.r:e,g=this.createElement("rect"),a=da(a)?a:a===x?{}:{x:a,y:b,width:v(c,0),height:v(d,0)};if(f!==x)a.strokeWidth=f,a=g.crisp(a);if(e)a.r=e;g.rSetter=function(a){O(this.element,{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[p(c,!0)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return q(a)?b.attr({"class":"highcharts-"+a}):b},image:function(a,b,c,d,e){var f={preserveAspectRatio:"none"};arguments.length>1&&t(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,h=this.symbols[a],h=h&&h(y(b),y(c),d,e,f),i=/^url\((.*?)\)$/,k,j;if(h)g=this.path(h),t(g,{symbolName:a,x:b,y:c,width:d,height:e}),f&&t(g,f);else if(i.test(a))j=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(i)[1],a=Kb[k]||f&&f.width&&f.height&&[f.width,f.height],g=this.image(k).attr({x:b,y:c}),g.isImg=!0,a?j(g,a):(g.attr({width:0,height:0}),$("img",{onload:function(){this.width===0&&(J(this,{position:"absolute", +top:"-999em"}),document.body.appendChild(this));j(g,Kb[k]=[this.width,this.height]);this.parentNode&&this.parentNode.removeChild(this)},src:k}));return g},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=T(f),k=aa(f),j=T(g),g=aa(g),e=e.end-fc&&e>b+g&&eb+g&&ed&&h>a+g&&ha+g&&hm&&/[ \-]/.test(b.textContent||b.innerText))J(b, +{width:m+"px",display:"block",whiteSpace:k&&k.whiteSpace||"normal"}),i=m;this.getSpanCorrection(i,l,h,j,g)}J(b,{left:e+(this.xCorr||0)+"px",top:f+(this.yCorr||0)+"px"});if(nb)l=b.offsetHeight;this.cTT=n}}else this.alignOnAdd=!0},setSpanRotation:function(a,b,c){var d={},e=sa?"-ms-transform":nb?"-webkit-transform":La?"MozTransform":Jb?"-o-transform":"";d[e]=d.transform="rotate("+a+"deg)";d[e+(La?"Origin":"-origin")]=d.transformOrigin=b*100+"% "+c+"px";J(this.element,d)},getSpanCorrection:function(a, +b,c){this.xCorr=-a*c;this.yCorr=-b}});t(za.prototype,{html:function(a,b,c){var d=this.createElement("span"),e=d.element,f=d.renderer;d.textSetter=function(a){a!==e.innerHTML&&delete this.bBox;e.innerHTML=this.textStr=a;d.htmlUpdateTransform()};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(f.isSVG)d.add=function(a){var b,c=f.box.parentNode,k=[];if(this.parentGroup=a){if(b=a.div,!b){for(;a;)k.push(a),a=a.parentGroup;o(k.reverse(),function(a){var d,e=O(a.element,"class");e&&(e={className:e});b=a.div=a.div||$(Ka,e,{position:"absolute",left:(a.translateX||0)+"px",top:(a.translateY||0)+"px"},b||c);d=b.style;t(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}});o(["opacity", +"visibility"],function(b){Ua(a,b+"Setter",function(a,b,c,e){a.call(this,b,c,e);d[c]=b})})})}}else b=c;b.appendChild(e);d.added=!0;d.alignOnAdd&&d.htmlUpdateTransform();return d};return d}});var Q;if(!Z&&!ia){Q={init:function(a,b){var c=["<",b,' filled="f" stroked="f"'],d=["position: ","absolute",";"],e=b===Ka;(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=$(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:M.prototype.htmlUpdateTransform,setSpanRotation:function(){var a=this.rotation,b=T(a*ha),c=aa(a*ha);J(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?T(d*ha):1,g=d?aa(d*ha):0,h=p(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)),J(this.element,{textAlign:e}))},pathToVML:function(a){for(var b=a.length,c=[];b--;)if(qa(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,la(c,b),c.push(b),b.destroyClip=function(){la(c,b)},a=a.getCSS(b)):(b.destroyClip&&b.destroyClip(),a={clip:mb?"inherit":"rect(auto)"});return b.css(a)},css:M.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&&Sa(a)},destroy:function(){this.destroyClip&&this.destroyClip(); +return M.prototype.destroy.apply(this)},on:function(a,b){this.element["on"+a]=function(){var a=K.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]=A(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,s;j&&typeof j.value!=="string"&&(j="x");m=j;if(a){n=p(a.width,3);s=(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=$(g.prepVML(k),null,{left:A(i.left)+p(a.offsetX,1),top:A(i.top)+p(a.offsetY,1)});if(c)h.cutOff=l+1;k=[''];$(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:ua,setAttr:function(a,b){mb?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]||$(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))},opacitySetter:ua,rotationSetter:function(a,b,c){c=c.style;this[b]=c[b]=a;c.left=-y(aa(a*ha)+1)+"px";c.top=y(T(a*ha))+"px"},strokeSetter:function(a,b,c){this.setAttr("strokecolor",this.renderer.color(a,c,b))},"stroke-widthSetter":function(a,b,c){c.stroked=!!a;this[b]=a;qa(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,mb||(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}};u.VMLElement=Q=ma(M,Q);Q.prototype.ySetter=Q.prototype.widthSetter=Q.prototype.heightSetter=Q.prototype.xSetter;var Na={Element:Q,isIE8:ya.indexOf("MSIE 8.0")>-1,init:function(a, +b,c,d){var e;this.alignedObjects=[];d=this.createElement(Ka).css(t(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.setSize(b,c,!1);if(!C.namespaces.hcv){C.namespaces.add("hcv","urn:schemas-microsoft-com:vml");try{C.createStyleSheet().cssText="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}catch(f){C.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=da(a);return t(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&&mb&&c==="DIV"&&t(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,k="none";a&&a.linearGradient?i="gradient":a&&a.radialGradient&&(i="pattern");if(i){var j,l,m=a.linearGradient||a.radialGradient,n,s,r,p,z,w="",a=a.stops,D,q=[],ca=function(){h=[''];$(e.prepVML(h),null,null,b)};n=a[0];D=a[a.length-1];n[0]>0&&a.unshift([0,n[1]]);D[0]<1&&a.push([1,D[1]]);o(a,function(a,b){g.test(a[1])?(f=ea(a[1]),j=f.get("rgb"),l=f.get("a")):(j=a[1],l=1);q.push(a[0]*100+"% "+j);b?(r=l,p=j):(s=l,z=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-U.atan((m-a)/(n-c))*180/na)+'"',ca();else{var k=m.r,v=k*2,u=k*2,x=m.cx,t=m.cy,y=b.radialReference,B,k=function(){y&&(B= +d.getBBox(),x+=(y[0]-B.x)/B.width-0.5,t+=(y[1]-B.y)/B.height-0.5,v*=y[2]/B.width,u*=y[2]/B.height);w='src="'+L.global.VMLRadialGradientURL+'" size="'+v+","+u+'" origin="0.5,0.5" position="'+x+","+t+'" color2="'+z+'" ';ca()};d.added?k():d.onAdd=k;k=p}else k=j}else if(g.test(a)&&b.tagName!=="IMG")f=ea(a),h=["<",c,' opacity="',f.get("a"),'"/>'],$(this.prepVML(h),null,null,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):za.prototype.createElement.call(this,a)},invertChild:function(a,b){var c=this,d=b.style,e=a.tagName==="IMG"&&a.style;J(a,{flip:"x", +left:A(d.width)-(e?A(e.top):1),top:A(d.height)-(e?A(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=T(f),i=aa(f),k=T(g),j=aa(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 za.prototype.symbols[!q(e)||!e.r?"square":"callout"].call(0,a,b,c,d,e)}}};u.VMLRenderer=Q=function(){this.init.apply(this,arguments)};Q.prototype=B(za.prototype,Na);bb=Q}za.prototype.measureSpanWidth=function(a,b){var c=C.createElement("span"),d;d=C.createTextNode(a);c.appendChild(d);J(c,b);this.box.appendChild(c);d=c.offsetWidth;Sa(c);return d};var Nb;if(ia)u.CanVGRenderer=Q=function(){Ea="http://www.w3.org/1999/xhtml"},Q.prototype.symbols= +{},Nb=function(){function a(){var a=b.length,d;for(d=0;d0&&c+i*k>e&&(m=y((d-c)/T(h*ha)));else if(d=c+(1-i)*k,c-i*ke&&(j=e-a.x+j*i,l=-1),j=E(b.slotWidth,j),jj||b.autoRotation&&g.styles.width)m=j;if(m){n.width=m;if(!b.options.labels.style.textOverflow)n.textOverflow="ellipsis";g.css(n)}}, +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;q(n)||(n=i.side===2?m.y+8:n=T(c.rotation*ha)*(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: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,k=this.pos,j=e.labels,l=this.gridLine,m=h?h+"Grid":"grid",n=h?h+"Tick":"tick",s=e[m+"LineWidth"],r=e[m+"LineColor"],o=e[m+"LineDashStyle"],z=e[n+ +"Length"],m=p(e[n+"Width"],!h&&d.isXAxis?1:0),w=e[n+"Color"],D=e[n+"Position"],n=this.mark,q=j.step,ca=!0,v=d.tickmarkOffset,u=this.getPosition(g,k,v,b),t=u.x,u=u.y,y=g&&t===d.pos+d.len||!g&&u===d.pos?-1:1,c=p(c,1);this.isActive=!0;if(s){k=d.getPlotLinePath(k+v,s*y,b,!0);if(l===x){l={stroke:r,"stroke-width":s};if(o)l.dashstyle=o;if(!h)l.zIndex=1;if(b)l.opacity=0;this.gridLine=l=s?f.path(k).attr(l).add(d.gridGroup):null}if(!b&&l&&k)l[this.isNew?"attr":"animate"]({d:k,opacity:c})}if(m&&z)D==="inside"&& +(z=-z),d.opposite&&(z=-z),h=this.getMarkPath(t,u,z,m*y,g,f),n?n.animate({d:h,opacity:c}):this.mark=f.path(h).attr({stroke:w,"stroke-width":m,opacity:c}).add(d.axisGroup);if(i&&!isNaN(t))i.xy=u=this.getLabelPosition(t,u,i,g,j,v,a,q),this.isFirst&&!this.isLast&&!p(e.showFirstLabel,1)||this.isLast&&!this.isFirst&&!p(e.showLastLabel,1)?ca=!1:g&&!d.isRadial&&!j.step&&!j.rotation&&!b&&c!==0&&this.handleOverflow(u),q&&a%q&&(ca=!1),ca&&!isNaN(u.y)?(u.opacity=c,i[this.isNew?"attr":"animate"](u),this.isNew= +!1):i.attr("y",-9999)},destroy:function(){Ra(this,this.axis)}};u.PlotLineOrBand=function(a,b){this.axis=a;if(b)this.options=b,this.id=b.id};u.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=q(i)&&q(h),j=d.value,l=d.dashStyle,m=a.svgElem,n=[],s,r=d.color,p=d.zIndex,o=d.events,w={},D=b.chart.renderer;b.isLog&&(i=Ca(i),h=Ca(h),j=Ca(j));if(g){if(n=b.getPlotLinePath(j,g),w={stroke:r,"stroke-width":g},l)w.dashstyle=l}else if(k){n= +b.getPlotBandPath(i,h,d);if(r)w.fill=r;if(d.borderWidth)w.stroke=d.borderColor,w["stroke-width"]=d.borderWidth}else return;if(q(p))w.zIndex=p;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=D.path(n).attr(w).add(),o))for(s in d=function(b){m.on(b,function(c){o[b].apply(a,[c])})},o)d(s);if(e&&q(e.text)&&n&&n.length&&b.width>0&&b.height>0&&!n.flat){e=B({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);if(!f){w={align:e.textAlign||e.align,rotation:e.rotation};if(q(p))w.zIndex=p;a.label=f=D.text(e.text,0,0,e.useHTML).attr(w).css(e.style).add()}b=[n[1],n[4],k?n[6]:n[1]];k=[n[2],n[5],k?n[7]:n[2]];n=Qa(b);c=Qa(k);f.align(e,!1,{x:n,y:c,width:Da(b)-n,height:Da(k)-c});f.show()}else f&&f.hide();return a},destroy:function(){la(this.axis.plotLinesAndBands,this);delete this.axis;Ra(this)}};var ja=u.Axis=function(){this.init.apply(this,arguments)};ja.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,y:15},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,y:3},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0,title:{rotation:270,text:"Values"},stackLabels:{enabled:!1,formatter:function(){return u.numberFormat(this.total,-1)},style:B(ba.line.dataLabels.style,{color:"#000000"})}},defaultLeftAxisOptions:{labels:{x:-15,y:null},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:15, +y:null},title:{rotation:90}},defaultBottomAxisOptions:{labels:{autoRotation:[-45],x:0,y:null},title:{rotation:0}},defaultTopAxisOptions:{labels:{autoRotation:[-45],x:0,y:-15},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=q(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=p(d.crosshair,ra(a.options.tooltip.crosshairs)[c?0:1],!1);var f,d=this.options.events;Fa(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===x)this.reversed=!0;this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;for(f in d)H(this,f,d[f]);if(this.isLog)this.val2lin= +Ca,this.lin2val=ka},setOptions:function(a){this.options=B(this.defaultOptions,this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],B(L[this.coll],a))},defaultLabelFormatter:function(){var a=this.axis,b=this.value,c=a.categories,d=this.dateTimeLabelFormat,e=L.lang.numericSymbols,f=e&&e.length,g,h=a.options.labels.format,a=a.isLog?b:a.tickInterval;if(h)g=Ja(h,this);else if(c)g=b;else if(d)g= +Pa(d,b);else if(f&&a>=1E3)for(;f--&&g===x;)c=Math.pow(1E3,f+1),a>=c&&b*10%c===0&&e[f]!==null&&(g=u.numberFormat(b/c,-1)+e[f]);g===x&&(g=P(b)>=1E4?u.numberFormat(b,-1):u.numberFormat(b,-1,x,""));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)a.dataMin=E(p(a.dataMin,d[0]),Qa(d)),a.dataMax=v(p(a.dataMax,d[0]),Da(d))}else{c.getExtremes();f=c.dataMax;c=c.dataMin;if(q(c)&&q(f))a.dataMin=E(p(a.dataMin,c),c),a.dataMax=v(p(a.dataMax,f),f);if(q(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.doPostTranslate||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+(qa(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=E(v(b,a),c):m=!0;return a},e=p(e,this.translate(a,null,null,c)),a=c=y(e+i);i=k=y(j-e-i);isNaN(e)?m=!0: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));return m&&!d?null:f.renderer.crispLine(["M",a,i,"L",c,k],b||1)},getLinearTickPositions:function(a,b,c){var d,e=ga(S(b/a)*a),f=ga(ta(c/a)*a),g=[];if(b===c&&qa(b))return[b];for(b=e;b<=f;){g.push(b);b=ga(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===x&&!this.isLog)q(a.min)||q(a.max)?this.minRange=null:(o(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===x||h=n?(r=n,k=0):b.dataMax<=n&&(X=n,i=0)),b.min=p(z,r,b.dataMin),b.max=p(w,X,b.dataMax));if(e)!a&& +E(b.min,p(b.dataMin,b.min))<=0&&Y(10,1),b.min=ga(Ca(b.min),15),b.max=ga(Ca(b.max),15);if(b.range&&q(b.max))b.userMin=b.min=z=v(b.min,b.minFromRange()),b.userMax=w=b.max,b.range=null;b.beforePadding&&b.beforePadding();b.adjustForMinRange();if(!m&&!b.axisPointRange&&!b.usePercentage&&!h&&q(b.min)&&q(b.max)&&(c=b.max-b.min))!q(z)&&k&&(b.min-=c*k),!q(w)&&i&&(b.max+=c*i);if(qa(d.floor))b.min=v(b.min,d.floor);if(qa(d.ceiling))b.max=E(b.max,d.ceiling);if(s&&q(b.dataMin))if(n=n||0,!q(z)&&b.min= +n)b.min=n;else if(!q(w)&&b.max>n&&b.dataMax<=n)b.max=n;b.tickInterval=b.min===b.max||b.min===void 0||b.max===void 0?1:h&&!j&&l===b.linkedParent.options.tickPixelInterval?j=b.linkedParent.tickInterval:p(j,this.tickAmount?(b.max-b.min)/v(this.tickAmount-1,1):void 0,m?1:(b.max-b.min)*l/v(b.len,l));g&&!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&&!j)b.tickInterval=v(b.pointRange,b.tickInterval);a=p(d.minTickInterval,b.isDatetimeAxis&&b.closestPointRange);if(!j&&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&&q(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;b?this.min=d:this.min-f>d&&a.shift();c?this.max= +e:this.max+fc&&(this.tickInterval*=2,this.setTickPositions());if(q(d)){for(a=c=b.length;a--;)(d===3&&a%2===1||d<=2&&a>0&&a=e&&(b=e));this.displayBtn=a!==x||b!==x;this.setExtremes(a, +b,!1,x,{trigger:"zoom"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,d=this.horiz,e=p(b.width,a.plotWidth-c+(b.offsetRight||0)),f=p(b.height,a.plotHeight),g=p(b.top,a.plotTop),b=p(b.left,a.plotLeft+c),c=/%$/;c.test(f)&&(f=parseFloat(f)/100*a.plotHeight);c.test(g)&&(g=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=v(d?e:f,0);this.pos=d?b:g},getExtremes:function(){var a= +this.isLog;return{min:a?ga(ka(this.min)):this.min,max:a?ga(ka(this.max)):this.max,dataMin:this.dataMin,dataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}},getThreshold:function(a){var b=this.isLog,c=b?ka(this.min):this.min,b=b?ka(this.max):this.max;a===null?a=b<0?b:c:c>a?a=c:b15&&a<165?"right":a>195&&a<345?"left":"center"},unsquish:function(){var a=this.ticks,b=this.options.labels, +c=this.horiz,d=this.tickInterval,e=d,f=this.len/(((this.categories?1:0)+this.max-this.min)/d),g,h=b.rotation,i=this.chart.renderer.fontMetrics(b.style.fontSize,a[0]&&a[0].label),k,j=Number.MAX_VALUE,l,m=function(a){a/=f||1;a=a>1?ta(a):1;return a*d};c?(l=!b.staggerLines&&!b.step&&(q(h)?[h]:f=-90&&a<=90)k=m(P(i.h/aa(ha*a))),b=k+P(a/360),bm)m=a.labelLength}),m>i&&m>h.h?k.rotation=this.labelRotation:this.labelRotation=0;else if(g&&(l={width:i+"px"},!j)){l.textOverflow="clip";for(n=c.length;!f&&n--;)if(s=c[n],i=d[s].label)if(i.styles.textOverflow==="ellipsis"&&i.css({textOverflow:"clip"}),i.getBBox().height>this.len/c.length-(h.h-h.f)||d[s].labelLength>g)i.specCss={textOverflow:"ellipsis"}}if(k.rotation&&(l={width:(m>a.chartHeight*0.5?a.chartHeight*0.33:a.chartHeight)+"px"},!j))l.textOverflow="ellipsis"; +this.labelAlign=k.align=e.align||this.autoLabelAlign(this.labelRotation);o(c,function(a){var b=(a=d[a])&&a.label;if(b)b.attr(k),l&&b.css(B(l,b.specCss)),delete b.specCss,a.rotation=k.rotation});this.tickRotCorr=b.rotCorr(h.b,this.labelRotation||0,this.side!==0)},hasData:function(){return this.hasVisibleSeries||q(this.min)&&q(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,s=d.title,r=d.labels,X=0,z=a.opposite,w=b.axisOffset,b=b.clipOffset,D=[-1,1,1,-1][h],u,ca=a.axisParent;k=a.hasData();a.showAxis=j=k||p(d.showEmpty,!0);a.staggerLines=a.horiz&&r.staggerLines;if(!a.axisGroup)a.gridGroup=c.g("grid").attr({zIndex:d.gridZIndex||1}).add(ca),a.axisGroup=c.g("axis").attr({zIndex:d.zIndex||2}).add(ca),a.labelGroup=c.g("axis-labels").attr({zIndex:r.zIndex||7}).addClass("highcharts-"+a.coll.toLowerCase()+"-labels").add(ca);if(k||a.isLinked){if(o(e,function(b){f[b]? +f[b].addLabel():f[b]=new Va(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){X=v(f[a].getLabelSize(),X)}),a.staggerLines)X*=a.staggerLines,a.labelOffset=X*(a.opposite?-1:1)}else for(u in f)f[u].destroy(),delete f[u];if(s&&s.text&&s.enabled!==!1){if(!a.axisTitle)a.axisTitle=c.text(s.text,0,0,s.useHTML).attr({zIndex:7,rotation:s.rotation||0,align:s.textAlign||{low:z?"right":"left",middle:"center",high:z?"left": +"right"}[s.align]}).addClass("highcharts-"+this.coll.toLowerCase()+"-title").css(s.style).add(a.axisGroup),a.axisTitle.isNew=!0;if(j)l=a.axisTitle.getBBox()[g?"height":"width"],m=s.offset,n=q(m)?0:p(s.margin,g?5:10);a.axisTitle[j?"show":"hide"](!0)}a.offset=D*p(d.offset,w[h]);a.tickRotCorr=a.tickRotCorr||{x:0,y:0};c=h===2?a.tickRotCorr.y:0;g=Math.abs(X)+n+(X&&D*(g?p(r.y,a.tickRotCorr.y+8):r.x)-c);a.axisTitleMargin=p(m,g);w[h]=v(w[h],a.axisTitleMargin+l+D*a.offset,g);d=d.offset?0:S(d.lineWidth/2)* +2;b[i]=v(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=A(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.isLinked,g=a.tickPositions,h=a.axisTitle,i=a.ticks,k=a.minorTicks,j=a.alternateBands,l=d.stackLabels,m=d.alternateGridColor,n=a.tickmarkOffset,s=d.lineWidth,r,p=b.hasRendered&&q(a.oldMin)&&!isNaN(a.oldMin),z=a.showAxis, +w=c.globalAnimation,D,v;a.labelEdge.length=0;a.overlap=!1;o([i,k,j],function(a){for(var b in a)a[b].isActive=!1});if(a.hasData()||f){a.minorTickInterval&&!a.categories&&o(a.getMinorTickPositions(),function(b){k[b]||(k[b]=new Va(a,b,"minor"));p&&k[b].isNew&&k[b].render(null,!0);k[b].render(null,!1,1)});if(g.length&&(o(g,function(b,c){if(!f||b>=a.min&&b<=a.max)i[b]||(i[b]=new Va(a,b)),p&&i[b].isNew&&i[b].render(c,!0,0.1),i[b].render(c)}),n&&(a.min===0||a.single)))i[-1]||(i[-1]=new Va(a,-1,null,!0)), +i[-1].render(-1);m&&o(g,function(c,d){v=g[d+1]!==x?g[d+1]+n:a.max-n;if(d%2===0&&c=F.second?0:j*S(i.getMilliseconds()/j));if(k>=F.second)i[Fb](k>=F.minute?0:j*S(i.getSeconds()/j));if(k>=F.minute)i[Gb](k>=F.hour?0:j*S(i[tb]()/j));if(k>=F.hour)i[Hb](k>=F.day?0:j*S(i[ub]()/j));if(k>=F.day)i[wb](k>=F.month?1:j*S(i[Za]()/j));k>=F.month&&(i[xb](k>=F.year?0:j*S(i[$a]()/j)),h=i[ab]());k>=F.year&&(h-=h%j,i[yb](h));if(k===F.week)i[wb](i[Za]()- +i[vb]()+p(d,1));b=1;if(qb||ib)i=i.getTime(),i=new xa(i+Ya(i));h=i[ab]();for(var d=i.getTime(),l=i[$a](),m=i[Za](),n=(F.day+(g?Ya(i):i.getTimezoneOffset()*6E4))%F.day;d=0.5)a=y(a),g=this.getLinearTickPositions(a,b,c);else if(a>=0.08)for(var f=S(b),h,i,k,j,l,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||j<=c)&&j!==x&&g.push(j),j>c&&(l=!0),j=k}else if(b=ka(b),c=ka(c),a=e[d?"minorTickInterval":"tickInterval"],a=p(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)),g=Ma(this.getLinearTickPositions(a,b,c),Ca),!d)this._minorAutoInterval=a/5;if(!d)this.tickInterval=a;return g};var Ob=u.Tooltip=function(){this.init.apply(this,arguments)};Ob.prototype={init:function(a,b){var c=b.borderWidth,d=b.style,e=A(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});ia||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&&(P(a-f.x)>1||P(b-f.y)>1),h=e.followPointer||e.len>1;t(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:h?x:g?(2*f.anchorX+c)/3:c,anchorY:h?x: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=p(a,this.options.hideDelay,500);if(!this.isHidden)this.hideTimer=Oa(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=ra(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===x&&(b=d.pointer.normalize(b)),c=[b.chartX-d.plotLeft,b.chartY- +f]);c||(o(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 Ma(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],k=["x",d.chartWidth,a,c.plotX+d.plotLeft,d.plotLeft,d.plotLeft+d.plotWidth], +j=p(c.ttBelow,d.inverted&&!c.negative||!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},s=function(){l.apply(0,i)!==!1?m.apply(0,k)===!1&&!h&&(n(!0),s()):h?f.x=f.y=0:(n(!0),s())};(d.inverted||this.len>1)&&n();s();return f},defaultFormatter:function(a){var b= +this.points||ra(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=ra(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");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=p(i.tooltipOptions.distance,16);k===!1?this.hide():(this.isHidden&&(Wa(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);G(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(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=Pa("%m-%d %H:%M:%S.%L",a.x);for(f in F){if(e===F.week&&+Pa("%w", +a.x)===c.options.startOfWeek&&h.substr(6)==="00:00:00.000"){f="week";break}if(F[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"&&qa(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 Ja(c,{point:a,series:d})},bodyFormatter:function(a){return Ma(a,function(a){var c=a.series.tooltipOptions;return(c.pointFormatter||a.point.tooltipFormatter).call(a.point,c.pointFormat)})}};var oa;cb=C.documentElement.ontouchstart!==x;var Xa=u.Pointer=function(a,b){this.init(a,b)};Xa.prototype={init:function(a,b){var c=b.chart,d=c.events,e=ia?"":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(u.Tooltip&&b.tooltip.enabled)a.tooltip=new Ob(a,b.tooltip),this.followTouchMove=p(b.tooltip.followTouchMove,!0);this.setDOMEvents()},normalize:function(a,b){var c,d,a=a||window.event,a=Mb(a);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=Lb(this.chart.container);d.pageX===x?(c=v(a.x,a.clientX-b.left),d=a.y):(c=d.pageX- +b.left,d=d.pageY-b.top);return t(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,k,j,l,m=[],n,s;if(!e&&!g)for(h=0;hh+k&&(d=h+k),ei+j&&(e=i+j),this.hasDragged=Math.sqrt(Math.pow(n-d,2)+Math.pow(s-e,2)),this.hasDragged>10){l=b.isInsidePlot(n-h,s-i);if(b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&l&&!r&& +!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:P(d),x:(d>0?0:d)+n}));m&&g&&(d=e-s,m.attr({height:P(d),y:(d>0?0:d)+s}));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={xAxis:[],yAxis:[],originalEvent:a.originalEvent||a},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)o(c.axes,function(c){if(c.zoomEnabled&&q(c.min)&&(d||b[{xAxis:"zoomX",yAxis:"zoomY"}[c.coll]])){var f=c.horiz,n=a.type==="touchend"?c.minPixelPadding:0,s=c.toValue((f?g:h)+n),f=c.toValue((f?g+i:h+k)-n);e[c.coll].push({axis:c,min:E(s,f),max:v(s,f)});j=!0}}),j&&G(c,"selection",e,function(a){c.zoom(t(a,d?{animation:!1}:null))});this.selectionMarker=this.selectionMarker.destroy();d&&this.scaleGroups()}if(c)J(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){V[oa]&&V[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(){var a= +V[oa];if(a)a.pointer.reset(),a.pointer.chartPosition=null},onContainerMouseMove:function(a){var b=this.chart;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=O(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&&!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);a.originalEvent=a;b.cancelClick||(c&&this.inClass(a.target,"highcharts-tracker")?(G(c.series,"click",t(a,{point:c})),b.hoverPoint&&c.firePointEvent("click",a)):(t(a,this.getCoordinates(a)),b.isInsidePlot(a.chartX- +d,a.chartY-e)&&G(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)};H(b,"mouseleave",a.onContainerMouseLeave);db===1&&H(C,"mouseup",a.onDocumentMouseUp);if(cb)b.ontouchstart=function(b){a.onContainerTouchStart(b)},b.ontouchmove=function(b){a.onContainerTouchMove(b)},db===1&&H(C,"touchend",a.onDocumentTouchEnd)},destroy:function(){var a; +W(this.chart.container,"mouseleave",this.onContainerMouseLeave);db||(W(C,"mouseup",this.onDocumentMouseUp),W(C,"touchend",this.onDocumentTouchEnd));clearInterval(this.tooltipTimeout);for(a in this)this[a]=null}};t(u.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")],s,r,p=h||1,o=i.inverted,w=i.bounds[a?"h":"v"],D=b.length===1,q=b[0][l],v=c[0][l],u=!D&&b[1][l],t=!D&&c[1][l],y,c=function(){!D&&P(q-u)>20&&(p=h||P(v-t)/P(q-u));r=(n-v)/p+q;s=i["plot"+(a?"Width":"Height")]/p};c();b=r;bw.max&&(b=w.max-s,y=!0);y?(v-=0.8*(v-g[k][0]),D||(t-=0.8*(t-g[k][1])),c()):g[k]=[v,t];o||(f[k]=r-n,f[m]=s);f=o?1/p:p;e[m]=s;e[k]=b;d[o?a?"scaleY":"scaleX":"scale"+j]=p;d["translate"+ +j]=f*n+(v-f*q)},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();Ma(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(p(a.options.min,a.dataMin)),f=a.toPixels(p(a.options.max,a.dataMax)),g=E(e,f),e=v(e,f);b.min=E(a.pos,g-d);b.max=v(a.pos+a.len,e+d)}}),b.res=!0;else if(d.length){if(!i)b.selectionMarker=i=t({destroy:ua,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;oa=c.index;a.touches.length===1?(a=this.normalize(a),c.isInsidePlot(a.chartX-c.plotLeft,a.chartY-c.plotTop)&&!c.openMenu?(b&&this.runPointActions(a),this.pinch(a)):b&&this.reset()):a.touches.length===2&&this.pinch(a)},onContainerTouchStart:function(a){this.touch(a,!0)},onContainerTouchMove:function(a){this.touch(a)},onDocumentTouchEnd:function(a){V[oa]&&V[oa].pointer.drop(a)}});if(K.PointerEvent||K.MSPointerEvent){var va={},Bb=!!K.PointerEvent,Sb=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},Cb=function(a,b,c,d){a=a.originalEvent||a;if((a.pointerType==="touch"||a.pointerType===a.MSPOINTER_TYPE_TOUCH)&&V[oa])d(a),d=V[oa].pointer,d[b]({type:c,target:a.currentTarget,preventDefault:ua,touches:Sb()})};t(Xa.prototype,{onContainerPointerDown:function(a){Cb(a,"onContainerTouchStart","touchstart",function(a){va[a.pointerId]={pageX:a.pageX,pageY:a.pageY,target:a.currentTarget}})},onContainerPointerMove:function(a){Cb(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){Cb(a,"onDocumentTouchEnd","touchend",function(a){delete va[a.pointerId]})},batchMSEvents:function(a){a(this.chart.container,Bb?"pointerdown":"MSPointerDown",this.onContainerPointerDown);a(this.chart.container,Bb?"pointermove":"MSPointerMove",this.onContainerPointerMove);a(C,Bb?"pointerup":"MSPointerUp",this.onDocumentPointerUp)}}); +Ua(Xa.prototype,"init",function(a,b,c){a.call(this,b,c);this.hasZoom&&J(b.container,{"-ms-touch-action":"none","touch-action":"none"})});Ua(Xa.prototype,"setDOMEvents",function(a){a.apply(this);(this.hasZoom||this.followTouchMove)&&this.batchMSEvents(H)});Ua(Xa.prototype,"destroy",function(a){this.batchMSEvents(W);a.call(this)})}var pb=u.Legend=function(a,b){this.init(a,b)};pb.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= +B(d,b.itemHiddenStyle),c.itemMarginTop=e,c.padding=d=p(b.padding,8),c.initialItemX=d,c.initialItemY=d-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.symbolWidth=p(b.symbolWidth,16),c.pages=[],c.render(),H(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!==x&&(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;o(["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;if(b)c=b.translateY,o(this.allItems,function(e){var f=e.checkbox,g;f&&(g=c+f.y+(a||0)+3,J(f,{left:b.translateX+e.checkboxOffset+f.x-20+"px",top:g+"px",display:g>c-6&&g(m||b.chartWidth-2*k-r-d.x))this.itemX=r,this.itemY+=s+this.lastLineHeight+n,this.lastLineHeight= +0;this.maxItemWidth=v(this.maxItemWidth,f);this.lastItemY=s+this.itemY+n;this.lastLineHeight=v(g,this.lastLineHeight);a._legendItemPos=[this.itemX,this.itemY];e?this.itemX+=f:(this.itemY+=s+g+n,this.lastLineHeight=g);this.offsetWidth=m||v((e?this.itemX-r-j:f)+k,this.offsetWidth)},getAllItems:function(){var a=[];o(this.chart.series,function(b){var c=b.options;if(p(c.showInLegend,!q(c.linkedTo)?x:!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)&&!q(a[g])&&(c[ob[g]]=v(c[ob[g]],c.legend[(g+1)%2?"legendHeight":"legendWidth"]+[1,-1,-1,1][g]*d[g%2?"x":"y"]+p(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();jb(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;o(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;o(e,function(b){a.positionItem(b)});f&&d.align(t({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=p(k.animation,!0),l=k.arrowSize||12,m=this.nav,n=this.pages,s=this.padding,r,q=this.allItems,z=function(a){i.attr({height:a});if(b.contentGroup.div)b.contentGroup.div.style.clip="rect("+s+"px,9999px,"+(s+a)+"px,0)"};e.layout==="horizontal"&&(f/=2);g&&(f=E(f,g));n.length=0;if(a>f){this.clipHeight=h=v(f-20-this.titleHeight-s,0);this.currentPage= +p(this.currentPage,1);this.fullHeight=a;o(q,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===q.length-1&&c+d-n[e-1]>h&&n.push(c);c!==r&&(r=c)});if(!i)i=b.clipRect=d.clipRect(0,s,9999,0),b.contentGroup.clip(i);z(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)z(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!==x&&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)}};Na=u.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;d=a.symbolWidth;var 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=d=e.symbol(this.symbol,d/2-b,a-b,2*b,2*b).add(f),d.isMarker=!0}};(/Trident\/7\.0/.test(ya)||La)&&Ua(pb.prototype, +"positionItem",function(a,b){var c=this,d=function(){b._legendItemPos&&a.call(c,b)};d();setTimeout(d)});Q=u.Chart=function(){this.init.apply(this,arguments)};Q.prototype={callbacks:[],init:function(a,b){var c,d=a.series;a.series=null;c=B(L,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=V.length;V.push(f);db++;d.reflow!==!1&&H(f,"load",function(){f.initReflow()});if(e)for(g in e)H(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=ia?!1:p(d.animation,!0);f.pointCount=f.colorCounter=f.symbolCounter=0;f.firstRender()},initSeries:function(a){var b=this.options.chart;(b=N[a.type||b.type||b.defaultSeriesType])||Y(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(),p=[];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;o(c,function(a){a.isDirty&&a.options.legendType==="point"&&(a.updateTotals&&a.updateTotals(),f=!0)});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&&(k=!0)}),o(b,function(a){var b=a.min+","+a.max;if(a.extKey!==b)a.extKey=b,p.push(function(){G(a,"afterSetExtremes",t(a.eventArgs,a.getExtremes()));delete a.eventArgs});(k||g)&&a.redraw()}));k&&this.drawChartBox();o(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});d&&d.reset(!0);m.draw();G(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),Sa(b),delete this.renderToClone):(c&&c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=b=this.renderTo.cloneNode(0),J(b,{position:"absolute",top:"-9999px",display:"block"}),b.style.setProperty&&b.style.setProperty("display","block","important"),C.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options,c=b.chart,d,e,f;this.renderTo= +a=c.renderTo;f="highcharts-"+zb++;if(Ba(a))this.renderTo=a=C.getElementById(a);a||Y(13,!0);d=A(O(a,"data-highcharts-chart"));!isNaN(d)&&V[d]&&V[d].hasRendered&&V[d].destroy();O(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=$(Ka,{className:"highcharts-container"+(c.className?" "+c.className:""),id:f},t({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 (u[c.renderer]||bb)(a,d,e,c.style,c.forExport,b.exporting&&b.exporting.allowHTML);ia&&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&&!q(c[0]))this.plotTop=v(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(ob,function(d,e){q(c[e])||(a[d]+=b[e])});a.setChartSize()},reflow:function(a){var b=this,c=b.options.chart,d=b.renderTo,e=c.width||eb(d,"width"),f=c.height||eb(d,"height"),c=a?a.target:K;if(!b.hasUserSize&& +!b.isPrinting&&e&&f&&(c===K||c===C)){if(e!==b.containerWidth||f!==b.containerHeight)clearTimeout(b.reflowTimeout),b.reflowTimeout=Oa(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)};H(K,"resize",b);H(a,"destroy",function(){W(K,"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(q(a))d.chartWidth=e=v(0,y(a)),d.hasUserSize=!!e;if(q(b))d.chartHeight=f=v(0,y(b));a=g.globalAnimation;(a?gb:J)(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;G(d,"resize");a=g.globalAnimation;Oa(function(){d&&G(d,"endResize",null,function(){d.isResizing-=1})},a=== +!1?0:a&&a.duration||500)},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=y(this.plotLeft);this.plotTop=k=y(this.plotTop);this.plotWidth=j=v(0,y(d-i-this.marginRight));this.plotHeight=l=v(0,y(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*S(this.plotBorderWidth/2);b=ta(v(d,h[3])/2);c=ta(v(d,h[0])/2);this.clipBox={x:b,y:c,width:S(this.plotSizeX-v(d,h[1])/2-b),height:v(0,S(this.plotSizeY-v(d,h[2])/2-c))};a||o(this.axes,function(a){a.setAxisSize();a.setAxisTranslation()})},resetMargins:function(){var a=this;o(ob,function(b,c){a[b]=p(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,p=this.plotLeft,o=this.plotTop,q=this.plotWidth,z=this.plotHeight,w=this.plotBox,v=this.clipRect,u=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(p,o,q,z,0).attr({fill:j}).add().shadow(a.plotShadow);if(l)h?h.animate(w):this.plotBGImage=b.image(l,p,o,q,z).add();v?v.animate({width:u.width,height:u.height}):this.clipRect=b.clipRect(u);if(m)g?g.animate(g.crisp({x:p,y:o,width:q,height:z,strokeWidth:-m})):this.plotBorder=b.rect(p,o,q,z,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=N[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&&e--;)(c=N[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(Ba(d)&&(d=d===":previous"?a.series[b.index-1]:a.get(d)))d.linkedSeries.push(b),b.linkedParent= +d,b.visible=p(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=t(b.style,c.style),e=A(d.left)+a.plotLeft,f=A(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 pb(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)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;G(a,"destroy");V[a.index]=x;db--;a.renderTo.removeAttribute("data-highcharts-chart");W(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="",W(d),f&&Sa(d);for(e in a)delete a[e]},isReadyToRender:function(){var a=this;return!Z&&K==K.top&&C.readyState!=="complete"||ia&&!K.canvg?(ia?Nb.push(function(){a.firstRender()},a.options.global.canvasToolsURL): +C.attachEvent("onreadystatechange",function(){C.detachEvent("onreadystatechange",a.firstRender);C.readyState==="complete"&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options,c=a.callback;if(a.isReadyToRender()){a.getContainer();G(a,"init");a.resetMargins();a.setChartSize();a.propFromSeries();a.getAxes();o(b.series||[],function(b){a.initSeries(b)});a.linkSeries();G(a,"beforeRender");if(u.Pointer)a.pointer=new Xa(a,b);a.render();a.renderer.draw();c&&c.apply(a,[a]);o(a.callbacks, +function(b){a.index!==x&&b.apply(a,[a])});G(a,"load");a.cloneRenderTo(!0)}},splashArray:function(a,b){var c=b[a],c=da(c)?c:[c,c,c,c];return[p(b[a+"Top"],c[0]),p(b[a+"Right"],c[1]),p(b[a+"Bottom"],c[2]),p(b[a+"Left"],c[3])]}};var Tb=u.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=[p(e[0],"50%"),p(e[1],"50%"),a.size||"100%",a.innerSize||0],f=E(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}},Ga=function(){};Ga.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=Ga.prototype.optionsToObject.call(this,a);t(this,a);this.options=this.options?t(this.options,a):a;if(d)this.y=this[d];if(this.x===x&&c)this.x=b===x?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(typeof a==="number"||a===null)b[e[0]]=a;else if(Ha(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(;ha+1&&b.push(d.slice(a+1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments=b},setOptions:function(a){var b=this.chart,c=b.options.plotOptions,b=b.userOptions||{},d=b.plotOptions||{},e=c[this.type]; +this.userOptions=a;c=B(e,c.series,a);this.tooltipOptions=B(L.tooltip,L.plotOptions[this.type].tooltip,b.tooltip,d.series&&d.series.tooltip,d[this.type]&&d[this.type].tooltip,a.tooltip);e.marker===null&&delete c.marker;this.zoneAxis=c.zoneAxis;a=this.zones=(c.zones||[]).slice();if((c.negativeColor||c.negativeFillColor)&&!c.zones)a.push({value:c[this.zoneAxis+"Threshold"]||c.threshold||0,color:c.negativeColor,fillColor:c.negativeFillColor});a.length&&q(a[a.length-1].value)&&a.push({color:this.color, +fillColor:this.fillColor});return c},getCyclic:function(a,b,c){var d=this.userOptions,e="_"+a+"Index",f=a+"Counter";b||(q(d[e])?b=d[e]:(d[e]=b=this.chart[f]%c.length,this.chart[f]+=1),b=c[b]);this[a]=b},getColor:function(){this.options.colorByPoint?this.options.color=null:this.getCyclic("color",this.options.color||ba[this.type].color,this.chart.options.colors)},getSymbol:function(){var a=this.options.marker;this.getCyclic("symbol",a.symbol,this.chart.options.symbols);if(/^url/.test(this.symbol))a.radius= +0},drawLegendSymbol:Na.drawLineMarker,setData:function(a,b,c,d){var e=this,f=e.points,g=f&&f.length||0,h,i=e.options,k=e.chart,j=null,l=e.xAxis,m=l&&!!l.categories,n=i.turboThreshold,s=this.xData,r=this.yData,v=(h=e.pointArrayMap)&&h.length,a=a||[];h=a.length;b=p(b,!0);if(d!==!1&&h&&g===h&&!e.cropped&&!e.hasGroupedData&&e.visible)o(a,function(a,b){f[b].update&&a!==i.data[b]&&f[b].update(a,!1,null,!1)});else{e.xIncrement=null;e.colorCounter=0;o(this.parallelArrays,function(a){e[a+"Data"].length=0}); +if(n&&h>n){for(c=0;j===null&&ci||this.forceCrop))if(b[d-1]m)b=[],c=[];else if(b[0]m)e=this.cropData(this.xData,this.yData,l,m),b=e.xData,c=e.yData,e=e.start,f=!0;for(i=b.length-1;i>=0;i--)d=b[i]-b[i-1],d>0&&(g===x||d=c){f=v(0,i-h);break}for(c=i;c< +e;c++)if(a[c]>d){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=Qa(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,k=i==="between"||qa(i),j=a.threshold,l=a.startFromThreshold?j:0,m,n,s,o,u=Number.MAX_VALUE,a=0;a=0&&n<=e.len&&m>=0&&m<=c.len;z.clientX=k?c.translate(w,0,0,0,1):m;z.negative=z.y<(j||0);z.category=d&&d[z.x]!==x?d[z.x]:z.x;a&&(u=E(u,P(m-s)));s=m}this.closestPointRangePx=u;this.getSegments()},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&&!da(c))c=ba[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();G(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,s,o,q=this.markerGroup,v=p(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=S(g.plotX),e=g.plotY,j=g.graphic,n=g.marker||{},s=!!g.marker,a=v&&n.enabled===x|| +n.enabled,o=g.isInside,a&&e!==x&&!isNaN(e)&&g.y!==null)if(a=g.pointAttr[g.selected?"select":""]||m,h=a.r,i=p(n.symbol,this.symbol),k=i.indexOf("url")===0,j)j[o?"show":"hide"](!0).animate(t({x:d-h,y:e-h},j.symbolName?{width:2*h,height:2*h}:{}));else{if(o&&(h>0||k))g.graphic=c.renderer.symbol(i,d-h,e-h,2*h,2*h,s?n:l).attr(a).add(q)}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]=p(a[g],b[f], +c[f],d[f]);return h},getAttribs:function(){var a=this,b=a.options,c=ba[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color,h=a.options.negativeColor;f={stroke:g,fill:g};var i=a.points||[],k,j,l=[],m=a.pointAttrToOptions;k=a.hasPointSpecificOptions;var n=c.lineColor,s=c.fillColor;j=b.turboThreshold;var r=a.zones,v=a.zoneAxis||"y",z;b.marker?(e.radius=e.radius||c.radius+e.radiusPlus,e.lineWidth=e.lineWidth||c.lineWidth+e.lineWidthPlus):(e.color=e.color||ea(e.color||g).brighten(e.brightness).get(), +e.negativeColor=e.negativeColor||ea(e.negativeColor||h).brighten(e.brightness).get());l[""]=a.convertAttribs(c,f);o(["hover","select"],function(b){l[b]=a.convertAttribs(d[b],l[""])});a.pointAttr=l;g=i.length;if(!j||g=f.value;)f=r[++k];j.color=j.fillColor=p(f.color,a.color)}k=b.colorByPoint||j.color;if(j.options)for(z in m)q(c[m[z]])&&(k=!0);if(k){c=c||{};k=[];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"]||ea(j.color).brighten(f.brightness||e.brightness).get();f={color:j.color};if(!s)f.fillColor=j.color;if(!n)f.lineColor=j.color;c.hasOwnProperty("color")&&!c.color&&delete c.color;k[""]=a.convertAttribs(t(f,c),l[""]);k.hover=a.convertAttribs(d.hover,l.hover,k[""]);k.select=a.convertAttribs(d.select,l.select,k[""])}else k=l;j.pointAttr= +k}},destroy:function(){var a=this,b=a.chart,c=/AppleWebKit\/533/.test(ya),d,e=a.data||[],f,g,h;G(a,"destroy");W(a);o(a.axisTypes||[],function(b){if(h=a[b])la(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 M&&!a[g].survive&&(d=c&&g==="group"?"hide":"destroy",a[g][d]());if(b.hoverSeries===a)b.hoverSeries=null;la(b.series,a);for(g in a)delete a[g]}, +getSegmentPath:function(a){var b=this,c=[],d=b.options.step;o(a,function(e,f){var g=e.plotX,h=e.plotY,i;b.getPointSpline?c.push.apply(c,b.getPointSpline(a,e,f)):(c.push(f?"L":"M"),d&&f&&(i=a[f-1],d==="right"?c.push(i.plotX,h,"L"):d==="center"?c.push((i.plotX+g)/2,i.plotY,"L",(i.plotX+g)/2,h,"L"):c.push(g,i.plotY,"L")),c.push(e.plotX,e.plotY))});return c},getGraphPath:function(){var a=this,b=[],c,d=[];o(a.segments,function(e){c=a.getSegmentPath(e);e.length>1?b=b.concat(c):d.push(e[0])});a.singlePoints= +d;return a.graphPath=b},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.getGraphPath(),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 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=v(b.chartWidth,b.chartHeight),l=this[(this.zoneAxis||"y")+"Axis"],m,n=l.reversed,s=b.inverted,r=l.horiz,q,z,w,u=!1;if(d.length&&(i||k)&&l.min!==x)i&&i.hide(),k&&k.hide(),m=l.getExtremes(),o(d,function(d,o){e=n?r?b.plotWidth:0:r?0:l.toPixels(m.min);e=E(v(p(f,e),0),j);f=E(v(y(l.toPixels(p(d.value, +m.max),!0)),0),j);u&&(e=f=l.toPixels(m.max));q=Math.abs(e-f);z=E(e,f);w=v(e,f);if(l.isXAxis){if(h={x:s?w:z,y:0,width:q,height:j},!r)h.x=b.plotHeight-h.x}else if(h={x:0,y:s?w:z,width:j,height:q},r)h.y=b.plotWidth-h.y;b.inverted&&c.isVML&&(h=l.isXAxis?{x:0,y:n?z:w,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]),k&&a["zoneArea"+o].clip(g[o]));u=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)H(c,"resize",a),H(b,"destroy",function(){W(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({visibility:c,zIndex:d||0.1}).add(e),f.addClass("highcharts-series-"+this.index));f[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=(c=d.animation)&&!!a.animate&&b.renderer.isSVG&&p(c.duration,500)||0,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=Oa(function(){a.afterAnimate()},e);a.isDirty=a.isDirtyData=!1;a.hasRendered=!0},redraw:function(){var a=this.chart,b=this.isDirtyData,c=this.isDirty, +d=this.group,e=this.xAxis,f=this.yAxis;d&&(a.inverted&&d.attr({width:a.plotWidth,height:a.plotHeight}),d.animate({translateX:p(e&&e.left,a.plotLeft),translateY:p(f&&f.top,a.plotTop)}));this.translate();this.render();b&&G(this,"updatedData");(c||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;Oa(function(){var d=fb(b.points||[],function(a){return a.y!==null});b.kdTree=a(d,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,p,o=l;p=q(a[e])&&q(l[e])?Math.pow(a[e]- +l[e],2):null;n=q(a[f])&&q(l[f])?Math.pow(a[f]-l[f],2):null;n=(p||0)+(n||0);l.dist=q(n)?Math.sqrt(n):Number.MAX_VALUE;l.distX=q(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],k+1,j),o=n[g]0&&this.singleStacks===!1&&(q.points[u][0]=q.points[this.index+","+t+",0"][0]);e==="percent"?(r=r?i:k,j&&m[r]&&m[r][t]?(r=m[r][t],q.total=r.total=v(r.total,q.total)+P(y)||0):q.total=ga(q.total+(P(y)||0))):q.total=ga(q.total+(y||0));q.cum=p(q.cum,g)+(y||0);q.points[u].push(q.cum);c[w]=q.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;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]=ga(h[0]*i),h[1]=ga(h[1]*i),a.stackedYData[g]=h[1]})};R.prototype.getStackIndicator=function(a,b,c){!q(a)||a.x!==b?a={x:b,index:0}:a.index++;a.key=[c,b,a.index].join(",");return a};t(Q.prototype,{addSeries:function(a,b,c){var d,e=this;a&&(b=p(b,!0),G(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;new ja(this,B(a,{index:this[e].length,isX:b}));f[e]=ra(f[e]||{});f[e].push(a);p(c,!0)&&this.redraw(d)},showLoading:function(a){var b=this,c=b.options,d=b.loadingDiv,e=c.loading,f=function(){d&&J(d,{left:b.plotLeft+"px",top:b.plotTop+"px",width:b.plotWidth+"px",height:b.plotHeight+"px"})};if(!d)b.loadingDiv=d=$(Ka,{className:"highcharts-loading"}, +t(e.style,{zIndex:10,display:"none"}),b.container),b.loadingSpan=$("span",null,e.labelStyle,d),H(b,"redraw",f);b.loadingSpan.innerHTML=a||c.lang.loading;if(!b.loadingShown)J(d,{opacity:0,display:""}),gb(d,{opacity:e.style.opacity},{duration:e.showDuration||0}),b.loadingShown=!0;f()},hideLoading:function(){var a=this.options,b=this.loadingDiv;b&&gb(b,{opacity:0},{duration:a.loading.hideDuration||100,complete:function(){J(b,{display:"none"})}});this.loadingShown=!1}});t(Ga.prototype,{update:function(a, +b,c,d){function e(){f.applyOptions(a);if(f.y===null&&h)f.graphic=h.destroy();if(da(a)&&!Ha(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]=da(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=p(b,!0);d===!1?e():f.firePointEvent("update",{options:a},e)},remove:function(a,b){this.series.removePoint(Fa(this,this.series.data),a,b)}});t(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,s=e.xData;Ta(d,k);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=p(b,!0);i={series:e};e.pointClass.prototype.applyOptions.apply(i,[a]);m=i.x;d=s.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(){e.length===g.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=p(b,!0);f?f.firePointEvent("remove",null,i):i()},remove:function(a,b){var c=this,d=c.chart,a=p(a,!0);if(!c.isRemoving)c.isRemoving=!0,G(c,"remove",null,function(){c.destroy();d.isDirtyLegend= +d.isDirtyBox=!0;d.linkSeries();a&&d.redraw(b)});c.isRemoving=!1},update:function(a,b){var c=this,d=this.chart,e=this.userOptions,f=this.type,g=N[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=B(e,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data},a);this.remove(!1);for(i in g)this[i]=x;t(this,N[a.type||f].prototype);o(h,function(a){c[a]=h[a]});this.init(d,a);d.linkSeries(); +p(b,!0)&&d.redraw(!1)}});t(ja.prototype,{update:function(a,b){var c=this.chart,a=c.options[this.coll][this.options.index]=B(this.userOptions,a);this.destroy(!0);this._addedPlotLB=this.chart._labelPanes=x;this.init(c,t(a,{events:x}));c.isDirtyBox=!0;p(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);la(b.axes,this);la(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;p(a,!0)&&b.redraw()},setTitle:function(a,b){this.update({title:a},b)},setCategories:function(a,b){this.update({categories:a},b)}});var wa=ma(R);N.line=wa;ba.area=B(fa,{softThreshold:!1,threshold:0});var pa=ma(R,{type:"area",singleStacks:!1,getSegments:function(){var a=this,b=[],c=[],d=[],e=this.xAxis,f=this.yAxis,g=f.stacks[this.stackKey],h={},i,k,j=this.points,l=this.options.connectNulls,m,n,p;if(this.options.stacking&&!this.cropped){for(n=0;n=0;d--)g=p(a[d].yBottom,f),da&&i>e?(i=v(a,e),j=2*e-i):ig&&j>e?(j=v(g,e),i=2*e-j):j0.5;b=Math.round(b)+f;d-=b;g&&(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=p(c.borderWidth,a.closestPointRange*a.xAxis.transA<2?0:1),e=a.yAxis,f=a.translatedThreshold=e.getThreshold(c.threshold),g=p(c.minPointLength,5),h=a.getColumnMetrics(),i=h.width, +k=a.barW=v(i,1+2*d),j=a.pointXOffset=h.offset;b.inverted&&(f-=0.5);c.pointPadding&&(k=ta(k));R.prototype.translate.apply(a);o(a.points,function(c){var d=E(p(c.yBottom,f),9E4),h=999+P(d),h=E(v(-h,c.plotY),e.len+h),o=c.plotX+j,q=k,u=E(h,d),t,w=v(h,d)-u;P(w)g?d-g:f-(t?g:0));c.barX=o;c.pointWidth=i;c.tooltipPos=b.inverted?[e.len+e.pos-b.plotLeft-h,a.xAxis.len-o-q/2,w]:[o+q/2,h+e.pos-b.plotTop,w];c.shapeType="rect";c.shapeArgs=a.crispCol(o, +u,q,w)})},getSymbol:ua,drawLegendSymbol:Na.drawRectangle,drawGraph:ua,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.plotY,k=h.graphic;if(i!==x&&!isNaN(i)&&h.y!==null)f=h.shapeArgs,i=q(a.borderWidth)?{"stroke-width":a.borderWidth}:{},g=h.pointAttr[h.selected?"select":""]||a.pointAttr[""],k?(Wa(k),k.attr(i).attr(g)[b.pointCount\u25cf {series.name}
',pointFormat:"x: {point.x}
y: {point.y}
"}});pa=ma(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)}});N.scatter=pa;ba.pie=B(fa,{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}});fa={type:"pie", +isCartesian:!1,pointClass:ma(Ga,{init:function(){Ga.prototype.init.apply(this,arguments);var a=this,b;a.name=p(a.name,"Slice");b=function(b){a.slice(b.type==="select")};H(a,"select",b);H(a,"unselect",b);return a},setVisible:function(a,b){var c=this,d=c.series,e=d.chart,f=d.options.ignoreHiddenPoint,b=p(b,f);if(a!==c.visible){c.visible=c.options.visible=a=a===x?!c.visible:a;d.options.data[Fa(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;Ta(c,d.chart);p(b,!0);this.sliced=this.options.sliced=a=q(a)?a:!this.sliced;d.options.data[Fa(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(){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=na/180*(i-90),i=(this.endAngleRad=na/180*(p(c.endAngle,i+360)-90))-k,j=this.points,l=c.dataLabels.distance,c=c.ignoreHiddenPoint,m,n=j.length,o;if(!a)this.center=a=this.getCenter();this.getX=function(b,c){h=U.asin(E((b-a[1])/(a[2]/2+l),1));return a[0]+(c?-1:1)*T(h)*(a[2]/2+l)};for(m=0;m1.5*na? +h-=2*na:h<-na/2&&(h+=2*na);o.slicedTranslation={translateX:y(T(h)*d),translateY:y(aa(h)*d)};f=T(h)*a[2]/2;g=aa(h)*a[2]/2;o.tooltipPos=[a[0]+f*0.7,a[1]+g*0.7];o.half=h<-na/2||h>na/2?1:0;o.angle=h;e=E(e,l/2);o.labelPos=[a[0]+f+T(h)*l,a[1]+g+aa(h)*l,a[0]+f+T(h)*e,a[1]+g+aa(h)*e,a[0]+f,a[1]+g,l<0?"center":o.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(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(t(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:ua,sortByAngle:function(a,b){a.sort(function(a,d){return a.angle!==void 0&&(d.angle-a.angle)*b})},drawLegendSymbol:Na.drawRectangle,getCenter:Tb.getCenter,getSymbol:ua};fa=ma(R,fa);N.pie=fa;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=a.chart.renderer;if(d.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(d),k=a.plotGroup("dataLabelsGroup","data-labels",d.defer?"hidden":"visible", +d.zIndex||6),p(d.defer,!0)&&(k.attr({opacity:+h}),h||H(a,"afterAnimate",function(){a.visible&&k.show();k[b.animation?"animate":"attr"]({opacity:1},{duration:200})})),g=d,o(e,function(e){var h,n=e.dataLabel,o,r,v=e.connector,u=!0,w,y={};f=e.dlOptions||e.options&&e.options.dataLabels;h=p(f&&f.enabled,g.enabled)&&e.y!==null;if(n&&!h)e.dataLabel=n.destroy();else if(h){d=B(g,f);w=d.style;h=d.rotation;o=e.getLabelConfig();i=d.format?Ja(d.format,o):d.formatter.call(o,d);w.color=p(d.color,w.color,a.color, +"black");if(n)if(q(i))n.attr({text:i}),u=!1;else{if(e.dataLabel=n=n.destroy(),v)e.connector=v.destroy()}else if(q(i)){n={fill:d.backgroundColor,stroke:d.borderColor,"stroke-width":d.borderWidth,r:d.borderRadius||0,rotation:h,padding:d.padding,zIndex:1};if(w.color==="contrast")y.color=d.inside||d.distance<0||b.stacking?j.getContrast(e.color||a.color):"#000000";if(c)y.cursor=c;for(r in n)n[r]===x&&delete n[r];n=e.dataLabel=j[h?"text":"label"](i,0,-9999,d.shape,null,null,d.useHTML).attr(n).css(t(w,y)).add(k).shadow(d.shadow)}n&& +a.alignDataLabel(e,n,d,null,u)}})};R.prototype.alignDataLabel=function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=p(a.plotX,-9999),i=p(a.plotY,-9999),k=b.getBBox(),j=f.renderer.fontMetrics(c.style.fontSize).b,l=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));if(l)d=t({x:g?f.plotWidth-i:h,y:y(g?f.plotHeight-h:i),width:0,height:0},d),t(c,{width:k.width,height:k.height}),c.rotation?(a=f.renderer.rotCorr(j,c.rotation),b[e?"attr":"animate"]({x:d.x+ +c.x+d.width/2+a.x,y:d.y+c.y+d.height/2}).attr({align:c.align})):(b.align(c,null,d),g=b.alignAttr,p(c.overflow,"justify")==="justify"?this.justifyDataLabel(b,c,g,k,d,e):p(c.crop,!0)&&(l=f.isInsidePlot(g.x,g.y)&&f.isInsidePlot(g.x+k.width,g.y+k.height)),c.shape&&b.attr({anchorX:a.plotX,anchorY:a.plotY}));if(!l)Wa(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(N.pie)N.pie.prototype.drawDataLabels=function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=p(e.connectorPadding,10),g=p(e.connectorWidth,1),h=d.plotWidth,i=d.plotHeight,k,j,l=p(e.softConnector, +!0),m=e.distance,n=a.center,q=n[2]/2,r=n[1],u=m>0,t,w,x,B=[[],[]],C,A,F,J,I,G=[0,0,0,0],N=function(a,b){return b.y-a.y};if(a.visible&&(e.enabled||a._hasPointLabels)){R.prototype.drawDataLabels.apply(a);o(b,function(a){a.dataLabel&&a.visible&&B[a.half].push(a)});for(J=2;J--;){var H=[],O=[],K=B[J],M=K.length,L;if(M){a.sortByAngle(K,J-0.5);for(I=b=0;!b&&K[I];)b=K[I]&&K[I].dataLabel&&(K[I].dataLabel.getBBox().height||21),I++;if(m>0){w=E(r+q+m,d.plotHeight);for(I=v(0,r-q-m);I<=w;I+=b)H.push(I);w=H.length; +if(M>w){c=[].concat(K);c.sort(N);for(I=M;I--;)c[I].rank=I;for(I=M;I--;)K[I].rank>=w&&K.splice(I,1);M=K.length}for(I=0;I0){if(w=O.pop(),L=w.i,A=w.y,c>A&&H[L+1]!==null||ch-f&&(G[1]=v(y(C+w-h+f),G[1])),A-b/2<0?G[0]=v(y(-A+b/2),G[0]):A+b/2>i&&(G[2]=v(y(A+b/2-i),G[2]))}}}if(Da(G)===0||this.verifyDataLabelOverflow(G))this.placeDataLabels(),u&&g&&o(this.points,function(b){k=b.connector;x=b.labelPos; +if((t=b.dataLabel)&&t._pos&&b.visible)F=t._attr.visibility,C=t.connX,A=t.connY,j=l?["M",C+(x[6]==="left"?5:-5),A,"C",C,A,2*x[2]-x[4],2*x[3]-x[5],x[2],x[3],"L",x[4],x[5]]:["M",C+(x[6]==="left"?5:-5),A,"L",x[2],x[3],"L",x[4],x[5]],k?(k.animate({d:j}),k.attr("visibility",F)):b.connector=k=a.chart.renderer.path(j).attr({"stroke-width":g,stroke:e.connectorColor||b.color||"#606060",visibility:F}).add(a.dataLabelsGroup);else if(k)b.connector=k.destroy()})}},N.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})})},N.pie.prototype.alignDataLabel=ua,N.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=v(b[2]-v(a[1],a[3]),e):(f=v(b[2]-a[1]-a[3],e),b[0]+=(a[3]-a[1])/2);d[1]!==null?f=v(E(f,b[2]-v(a[0],a[2])),e):(f=v(E(f,b[2]-a[0]-a[2]),e),b[1]+=(a[0]-a[2])/2);fp(this.translatedThreshold,g.yAxis.len)),k=p(c.inside,!!this.options.stacking);if(h){d=B(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=p(c.align,!f||k?"center":i?"right":"left");c.verticalAlign=p(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;for(e=0;el.x+(k.width-n)||m.x+(j.width-n)l.y+(k.height-n)||m.y+(j.height-n)d;if(h.series.length&&(i||l>E(j.dataMin,j.min))&& +(!i||k
/g, '') + .split(//g); + + } else { + lines = [textStr]; + } + + + // remove empty line at end + if (lines[lines.length - 1] === '') { + lines.pop(); + } + + + // build the lines + each(lines, function (line, lineNo) { + var spans, spanNo = 0; + + line = line.replace(//g, '|||'); + spans = line.split('|||'); + + each(spans, function (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, + wasTooLong, + 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 { + if (tooLong) { + wasTooLong = true; + } + 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, '-'))); + } + } + if (wasTooLong) { + wrapper.attr('title', wrapper.textStr); + } + wrapper.rotation = rotation; + } + + spanNo++; + } + } + }); + }); + 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(finalPos, 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'); + + wrapper.xSetter = function (value) { + this.element.setAttribute('cx', value); + }; + wrapper.ySetter = function (value) { + this.element.setAttribute('cy', 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) { + attribs.strokeWidth = strokeWidth; + attribs = wrapper.crisp(attribs); + } + + if (r) { + attribs.r = r; + } + + wrapper.rSetter = function (value) { + attr(this.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 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' + }); + document.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); + } + }, + src: imageSrc + }); + } + } + + 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: x }); + } + } + }; + + // 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 width, + rotation = wrapper.rotation, + baseline, + textWidth = pInt(wrapper.textWidth), + 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); + } + + width = pick(wrapper.elemWidth, elem.offsetWidth); + + // Update textWidth + if (width > textWidth && /[ \-]/.test(elem.textContent || elem.innerText)) { // #983, #1254 + css(elem, { + width: textWidth + PX, + display: 'block', + whiteSpace: (styles && styles.whiteSpace) || 'normal' // #3331 + }); + width = textWidth; + } + + wrapper.getSpanCorrection(width, 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; + + // Text setter + wrapper.textSetter = function (value) { + if (value !== element.innerHTML) { + delete this.bBox; + } + element.innerHTML = this.textStr = value; + wrapper.htmlUpdateTransform(); + }; + + // 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 (renderer.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 + }, 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; + } + }); + + // 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(parentGroup, prop + 'Setter', function (proceed, value, key, elem) { + proceed.call(this, value, key, elem); + htmlGroupStyle[key] = value; + }); + }); + }); + + } + } 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)); + } + }, + 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)); + }, + '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; + } + }; + 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.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); + + markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>']; + createElement(this.prepVML(markup), null, null, 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; + + 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) { + Highcharts.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(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.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) { + slotWidth = xy.x + slotWidth * (1 - factor) - leftBound; + } else if (rightPos > rightBound) { + slotWidth = rightBound - xy.x + slotWidth * factor; + goRight = -1; + } + + slotWidth = mathMin(axis.slotWidth, slotWidth); // #4177 + if (slotWidth < axis.slotWidth && axis.labelAlign === 'center') { + xy.x += goRight * (axis.slotWidth - slotWidth - xCorrection * (axis.slotWidth - mathMin(labelWidth, slotWidth))); + } + // 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 > slotWidth || (axis.autoRotation && label.styles.width)) { + textWidth = slotWidth; + } + + // 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)) { + yOffset = axis.side === 2 ? + rotCorr.y + 8 : + // #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'], + tickLength = options[tickPrefix + 'Length'], + tickWidth = pick(options[tickPrefix + 'Width'], !type && axis.isXAxis ? 1 : 0), // X axis defaults to 1 + tickColor = options[tickPrefix + 'Color'], + tickPosition = options[tickPrefix + 'Position'], + 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 (tickWidth && tickLength) { + + // negate the length + if (tickPosition === 'inside') { + tickLength = -tickLength; + } + if (axis.opposite) { + tickLength = -tickLength; + } + + markPath = tick.getMarkPath(x, y, tickLength, tickWidth * 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': tickWidth, + opacity: opacity + }).add(axis.axisGroup); + } + } + + // the label is created on init - now move it into place + if (label && !isNaN(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 && !isNaN(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, + xs, + ys, + x, + y, + color = options.color, + zIndex = options.zIndex, + events = options.events, + attribs = {}, + renderer = axis.chart.renderer; + + // 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 + if (defined(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); + + // add the SVG element + if (!label) { + attribs = { + align: optionsLabel.textAlign || optionsLabel.align, + rotation: optionsLabel.rotation + }; + if (defined(zIndex)) { + 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(); + + } else if (label) { // move out of sight + label.hide(); + } + + // chainable + return plotLine; + }, + + /** + * 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: 15 + /*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, + y: 3 + }, + 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, + y: null + }, + title: { + rotation: 270 + } + }, + + /** + * These options extend the defaultOptions for right axes + */ + defaultRightAxisOptions: { + labels: { + x: 15, + y: null + }, + title: { + rotation: 90 + } + }, + + /** + * These options extend the defaultOptions for bottom axes + */ + defaultBottomAxisOptions: { + labels: { + autoRotation: [-45], + x: 0, + y: null // based on font size + // overflow: undefined, + // staggerLines: null + }, + title: { + rotation: 0 + } + }, + /** + * These options extend the defaultOptions for top axes + */ + defaultTopAxisOptions: { + labels: { + autoRotation: [-45], + x: 0, + y: -15 + // 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 = log2lin; + axis.lin2val = 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) { + axis.dataMin = mathMin(pick(axis.dataMin, xData[0]), arrayMin(xData)); + 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.doPostTranslate || (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 (isNaN(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; + }, + + /** + * 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 { + // Find the closestPointRange across all series + each(axis.series, function (series) { + var seriesClosest = series.closestPointRange; + if (!series.noSharedTooltip && defined(seriesClosest)) { + closestPointRange = defined(closestPointRange) ? + mathMin(closestPointRange, seriesClosest) : + seriesClosest; + } + }); + + 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, + 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 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 if (this.min - minPointOffset > roundedMin) { + tickPositions.shift(); + } + + if (endOnTick) { + this.max = roundedMax; + } else if (this.max + minPointOffset < roundedMax) { + 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 + if (percentRegex.test(height)) { + height = parseFloat(height) / 100 * chart.plotHeight; + } + if (percentRegex.test(top)) { + top = 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; + + 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, + 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; + }, + + /** + * 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 chart = this.chart, + ticks = this.ticks, + 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 = chart.renderer.fontMetrics(labelOptions.style.fontSize, ticks[0] && ticks[0].label), + 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; + }, + + renderUnsquish: function () { + var chart = this.chart, + renderer = chart.renderer, + tickPositions = this.tickPositions, + ticks = this.ticks, + labelOptions = this.options.labels, + horiz = this.horiz, + margin = chart.margin, + slotCount = this.categories ? tickPositions.length : tickPositions.length - 1, + slotWidth = this.slotWidth = (horiz && (labelOptions.step || 0) < 2 && !labelOptions.rotation && // #4415 + ((this.staggerLines || 1) * chart.plotWidth) / slotCount) || + (!horiz && ((margin[3] && (margin[3] - chart.spacing[3])) || chart.chartWidth * 0.33)), // #1580, #1931, + innerWidth = mathMax(1, mathRound(slotWidth - 2 * (labelOptions.padding || 5))), + attr = {}, + labelMetrics = renderer.fontMetrics(labelOptions.style.fontSize, ticks[0] && ticks[0].label), + 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' }); + } + if (label.getBBox().height > this.len / tickPositions.length - (labelMetrics.h - labelMetrics.f) || + ticks[pos].labelLength > slotWidth) { // #4678 + 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 = attr.align = labelOptions.align || this.autoLabelAlign(this.labelRotation); + + // 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, + axisParent = axis.axisParent, // Used in color axis + lineHeightCorrection; + + // 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) { + axis.axisTitle = renderer.text( + axisTitleOptions.text, + 0, + 0, + axisTitleOptions.useHTML + ) + .attr({ + zIndex: 7, + rotation: axisTitleOptions.rotation || 0, + align: + axisTitleOptions.textAlign || + { + low: opposite ? 'right' : 'left', + middle: 'center', + high: opposite ? 'left' : 'right' + }[axisTitleOptions.align] + }) + .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 + lineHeightCorrection = side === 2 ? axis.tickRotCorr.y : 0; + labelOffsetPadded = Math.abs(labelOffset) + titleMargin + + (labelOffset && (directionFactor * (horiz ? pick(labelOptions.y, axis.tickRotCorr.y + 8) : labelOptions.x) - lineHeightCorrection)); + axis.axisTitleMargin = pick(titleOffsetOption, labelOffsetPadded); + + axisOffset[side] = mathMax( + axisOffset[side], + axis.axisTitleMargin + titleOffset + directionFactor * axis.offset, + labelOffsetPadded // #3027 + ); + + // 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, + 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 && defined(axis.oldMin) && !isNaN(axis.oldMin), + showAxis = axis.showAxis, + globalAnimation = 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 = globalAnimation ? globalAnimation.duration || 500 : 0, + 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) || + // Not on this axis (#4095, #2888) + (point && point.series && point.series[this.coll] !== this) + ) { + 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 = { + '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](), + 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 (!useUTC && (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, + // 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; + };/** + * 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 = pick(point.ttBelow, (chart.inverted && !point.negative) || (!chart.inverted && point.negative)), + /** + * 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.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; + + // common IE normalizing + e = e || window.event; + + // Framework specific normalizing (#1165) + e = washMouseEvent(e); + + // More IE normalizing, needs to go after washMouseEvent + 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, // #4511 + anchor, + noSharedTooltip, + stickToHoverSeries, + directTouch, + pointDistance, + 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) { + pointDistance = !shared && p.series.kdDimensions === 1 ? p.dist : p.distX; // #4645 + + if (p && typeof pointDistance === 'number' && pointDistance < distance) { + distance = pointDistance; + kdpoint = p; + } + }); + } + + // Refresh tooltip for kdpoint if new hover point or tooltip was hidden // #3926, #4200 + if (kdpoint && (kdpoint !== this.prevKDPoint || (tooltip && tooltip.isHidden))) { + // Draw tooltip if necessary + if (shared && !kdpoint.series.noSharedTooltip) { + i = kdpoints.length; + while (i--) { + if (kdpoints[i].clientX !== kdpoint.clientX || kdpoints[i].series.noSharedTooltip) { + kdpoints.splice(i, 1); + } + } + 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)); + }); + } else { + if (tooltip) { + tooltip.refresh(kdpoint, e); + } + if (!hoverSeries || !hoverSeries.directTouch) { // #4448 + kdpoint.onMouseOver(e); + } + } + this.prevKDPoint = kdpoint; + + // 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 + each(chart.axes, function (axis) { + axis.drawCrosshair(e, pick(kdpoint, hoverPoint)); + }); + + + }, + + + + /** + * 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; + + // Narrow in allowMove + allowMove = allowMove && tooltip && tooltipPoints; + + // Check if the points have moved outside the plot area (#1003, #4736) + if (allowMove) { + each(splat(tooltipPoints), function (point) { + if (point.plotX === undefined) { + allowMove = false; + } + }); + } + + // Just move the tooltip, #349 + if (allowMove) { + tooltip.refresh(tooltipPoints); + if (hoverPoint) { // #2500 + hoverPoint.setState(hoverPoint.state, true); + each(chart.axes, function (axis) { + if (pick(axis.options.crosshair && axis.options.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 = { + xAxis: [], + yAxis: [], + originalEvent: e.originalEvent || e + }, + 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 () { + var chart = charts[hoverChartIndex]; + if (chart) { + 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; + + 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 && !series.options.stickyTracking && + !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); + e.originalEvent = e; // #3913 + + 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; + + 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); + } + + 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; + e = e.originalEvent || e; + 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; + + if (alignAttr) { + translateY = alignAttr.translateY; + each(this.allItems, function (item) { + var checkbox = item.checkbox, + top; + + if (checkbox) { + top = (translateY + 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); + } + + // Colorize the items + legend.colorizeItem(item, item.visible); + + // add the HTML checkbox on top + if (showCheckbox) { + legend.createCheckboxForItem(item); + } + } + + // 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) { + + 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 + ) + .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 {Object} options + * @param {Function} callback Function to run when the chart has loaded + */ + var Chart = Highcharts.Chart = function () { + this.init.apply(this, arguments); + }; + + Chart.prototype = { + + /** + * Hook for modules + */ + callbacks: [], + + /** + * 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; + } + } + }); + + // 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'); // jQuery breaks this when calling it from addEvent. Overwrites 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, + autoWidth = this.spacingBox.width - 44; // 44 makes room for default context button + + if (title) { + title + .css({ width: (titleOptions.width || autoWidth) + 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 || autoWidth) + 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 from jQuery (#824) + if (!defined(widthOption)) { + chart.containerWidth = adapterRun(renderTo, 'width'); + } + if (!defined(heightOption)) { + chart.containerHeight = adapterRun(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, + indexAttrName = 'data-highcharts-chart', + oldChartIndex, + Ren, + containerId; + + chart.renderTo = renderTo = optionsChart.renderTo; + containerId = PREFIX + idCounter++; + + 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 (!isNaN(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 || adapterRun(renderTo, 'width'), + height = optionsChart.height || adapterRun(renderTo, 'height'), + target = e ? e.target : win; // #805 - MooTools doesn't supply e + + // 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 + globalAnimation = renderer.globalAnimation; // Reassign it before using it, it may have changed since the top of this function. + syncTimeout(function () { + if (chart) { + fireEvent(chart, 'endResize', null, function () { + chart.isResizing -= 1; + }); + } + }, globalAnimation === false ? 0 : ((globalAnimation && globalAnimation.duration) || 500)); + }, + + /** + * 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.animate( + plotBorder.crisp({ x: plotLeft, y: plotTop, width: plotWidth, height: plotHeight, strokeWidth: -plotBorderWidth }) //#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) { + 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, + callback = chart.callback; + + // 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(); + // run callbacks + if (callback) { + callback.apply(chart, [chart]); + } + each(chart.callbacks, function (fn) { + if (chart.index !== UNDEFINED) { // Chart destroyed in its own callback (#3600) + fn.apply(chart, [chart]); + } + }); + + // Fire the load event + fireEvent(chart, 'load'); + + // If the chart was rendered outside the top container, put it back in (#3679) + chart.cloneRenderTo(true); + + }, + + /** + * 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]; + } + + // 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 (typeof options === 'number' || 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. Must not be renamed to fireEvent, as this + * causes a name clash in MooTools + * @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 = typeof i === 'number' ? + // 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 === 'month' || pointIntervalUnit === 'year') { + date = new Date(xIncrement); + date = (pointIntervalUnit === 'month') ? + +date[setMonth](date[getMonth]() + pointInterval) : + +date[setFullYear](date[getFullYear]() + pointInterval); + pointInterval = date - xIncrement; + } + + this.xIncrement = xIncrement + pointInterval; + return xIncrement; + }, + + /** + * Divide the series data into segments divided by null values. + */ + getSegments: function () { + var series = this, + lastNull = -1, + segments = [], + i, + points = series.points, + pointsLength = points.length; + + if (pointsLength) { // no action required for [] + + // if connect nulls, just remove null points + if (series.options.connectNulls) { + i = pointsLength; + while (i--) { + if (points[i].y === null) { + points.splice(i, 1); + } + } + if (points.length) { + segments = [points]; + } + + // else, split on null points + } else { + each(points, function (point, i) { + if (point.y === null) { + if (i > lastNull + 1) { + segments.push(points.slice(lastNull + 1, i)); + } + lastNull = i; + } else if (i === pointsLength - 1) { // last value + segments.push(points.slice(lastNull + 1, i + 1)); + } + }); + } + } + + // register it + series.segments = segments; + }, + + /** + * 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 = data; + //series.zData = zData; + + // 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') { // docs: legendType now supported on more series types (at least column and pie) + 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, + 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 + for (i = processedXData.length - 1; i >= 0; i--) { + distance = processedXData[i] - processedXData[i - 1]; + + if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) { + closestPointRange = distance; + + // Unsorted data is not supported by the line tooltip, as well as data grouping and + // navigation in Stock charts (#725) and width calculation of columns (#1900) + } else if (distance < 0 && series.requireSorting) { + error(15); + } + } + + // Record the properties + series.cropped = cropped; // undefined or true + series.cropStart = cropStart; + series.processedXData = processedXData; + series.processedYData = processedYData; + + 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].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 = 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 && 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) + if (i) { + closestPointRangePx = mathMin(closestPointRangePx, mathAbs(plotX - lastPlotX)); + } + lastPlotX = plotX; + + } + + series.closestPointRangePx = closestPointRangePx; + + // now that we have the cropped data, build the segments + series.getSegments(); + }, + + /** + * 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 && plotY !== UNDEFINED && !isNaN(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 + .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', + 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; + } + + if (zones.length) { + j = 0; + threshold = zones[j]; + while (point[zoneAxis] >= threshold.value) { + threshold = zones[++j]; + } + + point.color = point.fillColor = 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; + } + 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]; + } + }, + + /** + * Return the graph path of a segment + */ + getSegmentPath: function (segment) { + var series = this, + segmentPath = [], + step = series.options.step; + + // build the segment line + each(segment, function (point, i) { + + var plotX = point.plotX, + plotY = point.plotY, + lastPoint; + + if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object + segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i)); + + } else { + + // moveTo or lineTo + segmentPath.push(i ? L : M); + + // step line? + if (step && i) { + lastPoint = segment[i - 1]; + if (step === 'right') { + segmentPath.push( + lastPoint.plotX, + plotY, + L + ); + + } else if (step === 'center') { + segmentPath.push( + (lastPoint.plotX + plotX) / 2, + lastPoint.plotY, + L, + (lastPoint.plotX + plotX) / 2, + plotY, + L + ); + + } else { + segmentPath.push( + plotX, + lastPoint.plotY, + L + ); + } + } + + // normal line to next point + segmentPath.push( + point.plotX, + point.plotY + ); + } + }); + + return segmentPath; + }, + + /** + * Get the graph path + */ + getGraphPath: function () { + var series = this, + graphPath = [], + segmentPath, + singlePoints = []; // used in drawTracker + + // Divide into segments and build graph and area paths + each(series.segments, function (segment) { + + segmentPath = series.getSegmentPath(segment); + + // add the segment to the graph, or a single point for tracking + if (segment.length > 1) { + graphPath = graphPath.concat(segmentPath); + } else { + singlePoints.push(segment[0]); + } + }); + + // Record it for use in drawGraph and drawTracker, and return graphPath + series.singlePoints = singlePoints; + 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.getGraphPath(), + 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({ + visibility: visibility, + zIndex: zIndex || 0.1 // IE8 needs this + }) + .add(parent); + + group.addClass('highcharts-series-' + this.index); + } + + // Place it on first and subsequent (redraw) calls + group[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 = options.animation, + // Animation doesn't work in IE8 quirks when the group div is hidden, + // and looks bad in other oldIE + animDuration = (animation && !!series.animate && chart.renderer.isSVG && pick(animation.duration, 500)) || 0, + 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, + wasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after + wasDirty = series.isDirty, + 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 (wasDirtyData) { + fireEvent(series, 'updatedData'); + } + if (wasDirty || wasDirtyData) { // #3945 recalculate the kdtree when dirty + delete this.kdTree; // #3868 recalculate the kdtree with dirty data + } + }, + + /** + * 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() { + var points = grep(series.points || [], function (point) { // #4390 + return point.y !== null; + }); + + series.kdTree = _kdtree(points, 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; + + // 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 series = this.series, + reversedStacks = pick(this.options.reversedStacks, true), + i = series.length; + if (!this.isXAxis) { + this.usePercentage = false; + while (i--) { + series[reversedStacks ? i : series.length - i - 1].setStackedPoints(); + } + // Loop up again to compute percent stack + if (this.usePercentage) { + for (i = 0; i < series.length; i++) { + series[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]; + stack.points[pointKey] = [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); + + 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; + + new Axis(this, merge(options, { // eslint-disable-line no-new + index: this[key].length, + isX: isX + })); + + // Push the new axis options to the chart options + chartOptions[key] = splat(chartOptions[key] || {}); + chartOptions[key].push(options); + + 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]) ? 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 (data.length === points.length) { + 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; + redraw = pick(redraw, true); + + if (!series.isRemoving) { /* prevent triggering native event in jQuery + (calling the remove function from the remove event) */ + series.isRemoving = true; + + // 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 (redraw) { + chart.redraw(animation); + } + }); + + } + series.isRemoving = false; + }, + + /** + * 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, + /** + * For stacks, don't split segments on null values. Instead, draw null values with + * no marker. Also insert dummy points for any X position that exists in other series + * in the stack. + */ + getSegments: function () { + var series = this, + segments = [], + segment = [], + keys = [], + xAxis = this.xAxis, + yAxis = this.yAxis, + stack = yAxis.stacks[this.stackKey], + pointMap = {}, + plotX, + plotY, + points = this.points, + connectNulls = this.options.connectNulls, + stackIndicator, + i, + x; + + if (this.options.stacking && !this.cropped) { // cropped causes artefacts in Stock, and perf issue + // 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; + }); + + each(keys, function (x) { + var threshold = null, + stackPoint, + skip = connectNulls && (!pointMap[x] || pointMap[x].y === null); // #1836 + + if (!skip) { + + // The point exists, push it to the segment + if (pointMap[x]) { + segment.push(pointMap[x]); + + // 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) + for (i = series.index; i <= yAxis.series.length; i++) { + stackIndicator = series.getStackIndicator(null, x, i); + stackPoint = stack[x].points[stackIndicator.key]; + if (stackPoint) { + threshold = stackPoint[1]; + break; + } + } + + plotX = xAxis.translate(x); + plotY = yAxis.getThreshold(threshold); + segment.push({ + y: null, + plotX: plotX, + clientX: plotX, + plotY: plotY, + yBottom: plotY, + onMouseOver: noop + }); + } + } + }); + + if (segment.length) { + segments.push(segment); + } + + } else { + Series.prototype.getSegments.call(this); + segments = this.segments; + } + + this.segments = segments; + }, + + /** + * Extend the base Series getSegmentPath method by adding the path for the area. + * This path is pushed to the series.areaPath property. + */ + getSegmentPath: function (segment) { + + var segmentPath = Series.prototype.getSegmentPath.call(this, segment), // call base method + areaSegmentPath = [].concat(segmentPath), // work on a copy for the area path + i, + options = this.options, + segLength = segmentPath.length, + translatedThreshold = this.yAxis.getThreshold(options.threshold), // #2181 + yBottom; + + if (segLength === 3) { // for animation from 1 to two points + areaSegmentPath.push(L, segmentPath[1], segmentPath[2]); + } + if (options.stacking && !this.closedStacks) { + + // Follow stack back. Later, implement areaspline. A general solution could be to + // reverse the entire graphPath of the previous series, though may be hard with + // splines and with series with different extremes + for (i = segment.length - 1; i >= 0; i--) { + + yBottom = pick(segment[i].yBottom, translatedThreshold); + + // step line? + if (i < segment.length - 1 && options.step) { + areaSegmentPath.push(segment[i + 1].plotX, yBottom); + } + + areaSegmentPath.push(segment[i].plotX, yBottom); + } + + } else { // follow zero line back + this.closeSegment(areaSegmentPath, segment, translatedThreshold); + } + this.areaPath = this.areaPath.concat(areaSegmentPath); + return segmentPath; + }, + + /** + * Extendable method to close the segment path of an area. This is overridden in polar + * charts. + */ + closeSegment: function (path, segment, translatedThreshold) { + path.push( + L, + segment[segment.length - 1].plotX, + translatedThreshold, + L, + segment[0].plotX, + translatedThreshold + ); + }, + + /** + * 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]; + + // Create or update the area + if (area) { // update + area.animate({ d: areaPath }); + + } else { // create + series[areaKey] = series.chart.renderer.path(areaPath) + .attr({ + fill: pick( + prop[2], + Color(prop[1]).setOpacity(pick(options.fillOpacity, 0.75)).get() + ), + zIndex: 0 // #1069 + }).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 (segment, 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 = segment[i - 1], + nextPoint = segment[i + 1], + leftContX, + leftContY, + rightContX, + rightContY, + ret; + + // find control points + if (lastPoint && nextPoint) { + + var lastX = lastPoint.plotX, + lastY = lastPoint.plotY, + nextX = nextPoint.plotX, + nextY = nextPoint.plotY, + correction; + + 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 + 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(); + } + */ + + // 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 + } + 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', + closedStacks: true, // instead of following the previous graph back, follow the threshold back + + // Mix in methods from the area series + getSegmentPath: areaProto.getSegmentPath, + closeSegment: areaProto.closeSegment, + 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 = {}, + columnIndex, + 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; + 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) { + 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 (plotY !== UNDEFINED && !isNaN(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.scaleY = 1; + attr[inverted ? 'translateX' : 'translateY'] = yAxis.pos; + series.group.animate(attr, series.options.animation); + + // 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, + 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', + options.defer ? HIDDEN : VISIBLE, + options.zIndex || 6 + ); + + if (pick(options.defer, true)) { + 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, + 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; + + 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 (options.rotation) { // Fancy box alignment isn't supported for rotated text + rotCorr = chart.renderer.rotCorr(baseline, options.rotation); // #3723 + dataLabel[isNew ? 'attr' : 'animate']({ + x: alignTo.x + options.x + alignTo.width / 2 + rotCorr.x, + y: alignTo.y + options.y + alignTo.height / 2 + }) + .attr({ // #3003 + align: options.align + }); + } else { + dataLabel.align(options, null, alignTo); + alignAttr = dataLabel.alignAttr; + + // Handle justify or crop + if (pick(options.overflow, 'justify') === 'justify') { + this.justifyDataLabel(dataLabel, options, alignAttr, bBox, alignTo, isNew); + + } else if (pick(options.crop, true)) { + // Now check that the data label is within the plot area + 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) { + 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); + + // arrange points for detection collision + each(data, function (point) { + if (point.dataLabel && point.visible) { // #407, #2510 + halves[point.half].push(point); + } + }); + + /* 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 (!isNaN(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); + each(this.points, function (point) { + if (point.dataLabel) { + point.dataLabel._pos = null; // reset + } + }); + + 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, + 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; + padding = 2 * (label1.box ? 0 : label1.padding); // Substract the padding if no background or border (#4333) + isIntersecting = intersectRect( + pos1.x, + pos1.y, + label1.width - padding, + label1.height - padding, + pos2.x, + pos2.y, + 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 }, + singlePoints = series.singlePoints, + singlePoint, + 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 mousePos = e[isX ? 'chartX' : 'chartY'], + axis = chart[isX ? 'xAxis' : 'yAxis'][0], + startPos = chart[isX ? 'mouseDownX' : 'mouseDownY'], + halfPointRange = (axis.pointRange || 0) / 2, + extremes = axis.getExtremes(), + newMin = axis.toValue(startPos - mousePos, true) + halfPointRange, + newMax = axis.toValue(startPos + chart[isX ? 'plotWidth' : 'plotHeight'] - 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[isX ? 'mouseDownX' : 'mouseDownY'] = 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 defalut 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(hasSVG ? { + fill: point.color || series.color, + 'fill-opacity': haloOptions.opacity + } : { + // Old IE doesn't take fill-opacity + fill: Color(point.color || series.color).setOpacity(haloOptions.opacity).get() + }, + 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, + 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/codis/cmd/fe/assets/node_modules/highcharts/lib/highmaps.js b/codis/cmd/fe/assets/node_modules/highcharts/lib/highmaps.js new file mode 100644 index 0000000000..746544e871 --- /dev/null +++ b/codis/cmd/fe/assets/node_modules/highcharts/lib/highmaps.js @@ -0,0 +1,361 @@ +/* + Highmaps JS v1.1.10 (2015-12-07) + + (c) 2011-2014 Torstein Honsi + + License: www.highcharts.com/license +*/ +(function(ya,O){typeof module==="object"&&module.exports?module.exports=ya.document?O(ya):function(z){return O(z)}:ya.Highcharts=O()})(typeof window!=="undefined"?window:this,function(ya){function O(a,b){var c="Highcharts error #"+a+": www.highcharts.com/errors/"+a;if(b)throw Error(c);L.console&&console.log(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=La(f,e)}j.push(e);a=a.slice(c+1);c=(d=!d)?"}":"{"}j.push(a);return j.join("")}function tb(a,b,c,d,e){var f,g=a,c=p(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 cb(a,b){var c=a.length,d,e;for(e=0;ec&&(c=a[b]);return c}function db(a,b){for(var c in a)a[c]&&a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Ma(a){eb||(eb=ba(Ea));a&&eb.appendChild(a);eb.innerHTML=""}function va(a,b){return parseFloat(a.toPrecision(b||14))}function Ta(a,b){b.renderer.globalAnimation=p(a,b.animation)}function Mb(a){return{init:function(b){var c= +a.fx;a.extend(a.easing,{easeOutQuad:function(a,b,c,g,h){return-g*(b/=h)*(b-2)+c}});a.each(["cur","_default","width","height","opacity"],function(b,e){var f=c.step,g;e==="cur"?f=c.prototype:e==="_default"&&a.Tween&&(f=a.Tween.propHooks[e],e="set");(g=f[e])&&(f[e]=function(a){var c,f;f=b?a:this;if(f.prop!=="align")return c=f.elem,c.attr?c.attr(f.prop.replace("strokeWidth","stroke-width"),e==="cur"?void 0:f.now):g.apply(this,arguments)})});ca(a.cssHooks.opacity,"get",function(a,b,c){return b.attr?b.opacity|| +0:a.call(this,b,c)});this.addAnimSetter("d",function(a){var c=a.elem,f;if(!a.started)f=b.init(c,c.d,c.toD),a.start=f[0],a.end=f[1],a.started=!0;c.attr("d",b.step(a.start,a.end,a.pos,c.toD))});this.each=Array.prototype.forEach?function(a,b){return Array.prototype.forEach.call(a,b)}:function(a,b){var c,g=a.length;for(c=0;ci,f=b[d][1]>i,e!==f&&h<(b[d][0]-b[c][0])*(i-b[c][1])/(b[d][1]-b[c][1])+b[c][0]&&(g=!g);return g}function Db(a,b,c,d,e,f,g,h){return["M",a+e,b,"L",a+c-f,b,"C",a+c- +f/2,b,a+c,b+f/2,a+c,b+f,"L",a+c,b+d-g,"C",a+c,b+d-g/2,a+c-g/2,b+d,a+c-g,b+d,"L",a+h,b+d,"C",a+h/2,b+d,a,b+d-h/2,a,b+d-h,"L",a,b+e,"C",a,b+e/2,a+e/2,b,a+e,b,"Z"]}var v,L=ya||window,A=L.document,I=Math,x=I.round,ga=I.floor,sa=I.ceil,w=I.max,E=I.min,Q=I.abs,ha=I.cos,na=I.sin,ob=I.PI,ia=ob*2/360,wa=navigator.userAgent,Eb=L.opera,ra=/(msie|trident|edge)/i.test(wa)&&!Eb,fb=A.documentMode===8,gb=!ra&&/AppleWebKit/.test(wa),Ga=/Firefox/.test(wa),ya=/(Mobile|Android|Windows Phone)/.test(wa),Aa="http://www.w3.org/2000/svg", +aa=!!A.createElementNS&&!!A.createElementNS(Aa,"svg").createSVGRect,Nb=Ga&&parseInt(wa.split("Firefox/")[1],10)<4,ja=!aa&&!ra&&!!A.createElement("canvas").getContext,Oa,Ua,Fb={},pb=0,eb,N,La,qb,hb,T=function(){},W=[],Va=0,Ea="div",Ob=/^[0-9]+$/,ib=["plotTop","marginRight","marginBottom","plotLeft"],Fa,vb,lb,wb,xb,yb,zb,mb,nb,Ab,Bb,B={},s;s=L.Highcharts?O(16,!0):function(a){s.loadAdapter(a);return s};s.seriesTypes=B;var q=s.extend=function(a,b){var c;a||(a={});for(c in b)a[c]=b[c];return a},p=s.pick= +function(){var a=arguments,b,c,d=a.length;for(b=0;b3?c.length%3:0;return e+(g?c.substr(0,g)+d:"")+c.substr(g).replace(/(\d{3})(?=\d)/g,"$1"+d)+(f?b+Q(a-c).toFixed(f).slice(2):"")};qb={init: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,i,j=function(a){for(g=a.length;g--;)a[g]==="M"&&a.splice(g+1,0,a[g+ +1],a[g+2],a[g+1],a[g+2])};e&&(j(b),j(c));a.isArea&&(h=b.splice(b.length-6,6),i=c.splice(c.length-6,6));if(d<=c.length/f&&b.length===c.length)for(;d--;)c=[].concat(c).splice(0,f).concat(c);a.shift=0;if(b.length)for(a=c.length;b.length{point.key}
',pointFormat:'\u25cf {series.name}: {point.y}
',shadow:!0,snap:ya?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 U=N.plotOptions,Ya=U.line;ub();K.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=Ha(a.stops,function(a){return new K(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&&!isNaN(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(pa(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}};P.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(Aa,b);this.renderer=a},animate:function(a,b,c){b=p(b,this.renderer.globalAnimation,!0);Qa(this);if(b){b=z(b,{});if(c)b.complete=c;Xa(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,u,t=[],p;a.linearGradient?f="linearGradient":a.radialGradient&&(f="radialGradient");if(f){g=a[f];i=d.gradients;k=a.stops;n=c.radialReference;Ba(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=z(g,d.getRadialAttr(n,h),{gradientUnits:"userSpaceOnUse"}));for(u in g)u!=="id"&&t.push(u,g[u]);for(u in k)t.push(k[u]);t=t.join(",");i[t]?n=i[t].attr("id"):(g.id=n="highcharts-"+pb++,i[t]=j=d.createElement(f).attr(g).add(d.defs),j.radAttr=h,j.stops=[],o(k,function(a){a[1].indexOf("rgba")===0?(e=K(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)})); +p="url("+d.url+"#"+n+")";c.setAttribute(b,p);c.gradient=t;a.toString=function(){return p}}},applyTextShadow:function(a){var b=this.element,c,d=a.indexOf("contrast")!==-1,e={},f=this.renderer.forExport,g=f||b.style.textShadow!==v&&!ra;if(d)e.textShadow=a=a.replace(/contrast/g,this.renderer.getContrast(b.style.fill));if(gb||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);J(g,{"class":"highcharts-text-shadow",fill:e,stroke:e,"stroke-opacity":1/w(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!==v&&(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||(this[d+"Setter"]||this._defaultSetter).call(this,b,d,e);this.shadows&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(d)&&this.updateShadows(d,b)}if(this.doTransform)this.updateTransform(),this.doTransform=!1}c&&c(); +return g},updateShadows:function(a,b){for(var c=this.shadows,d=c.length;d--;)c[d].setAttribute(a,a==="height"?Math.max(b-(c[d].cutHeight||0),0):a==="d"?this.d:b)},addClass:function(a){var b=this.element,c=J(b,"class")||"";c.indexOf(a)===-1&&J(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]=p(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=a.strokeWidth||this.strokeWidth||0;d=x(e)%2/2;a.x=ga(a.x||this.x||0)+d;a.y=ga(a.y||this.y||0)+d;a.width=ga((a.width||this.width||0)-2*d);a.height=ga((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=q(b,c));this.styles=a;e&&(ja||!aa&&this.renderer.forExport)&&delete a.width;if(ra&&!aa)G(this.element,a);else{b=function(a,b){return"-"+b.toLowerCase()};for(f in a)g+=f.replace(/([A-Z])/g,b)+":"+a[f]+";";J(d,"style",g)}e&&this.added&&this.renderer.buildText(this)}return this},on:function(a,b){var c=this,d=c.element;Ua&&a==="click"?(d.ontouchstart=function(a){c.touchEventFired=Fa.now();a.preventDefault();b.call(d,a)},d.onclick= +function(a){(wa.indexOf("Android")===-1||Fa.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("+p(c,1)+" "+p(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||za(c))this.alignTo=d=c||"renderer",ua(f,this),f.push(this),c=null}else a=this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=p(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"]=x(f);if(e==="bottom"||e==="middle")g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]|| +1);h[b?"translateY":"y"]=x(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,u;f=p(b,this.rotation);g=f*ia;e!==v&&(u=["",f||0,i&&i.fontSize,h.style.width].join(","),u=e===""||Ob.test(e)?"num:"+e.toString().length+u:e+u);u&&!a&&(c=m[u]);if(!c){if(h.namespaceURI===Aa||d.forExport){try{l=this.fakeTS&&function(a){o(h.querySelectorAll(".highcharts-text-shadow"), +function(b){b.style.display=a})},Ga&&k.textShadow?(j=k.textShadow,k.textShadow=""):l&&l("none"),c=h.getBBox?q({},h.getBBox()):{width:h.offsetWidth,height:h.offsetHeight},j?k.textShadow=j:l&&l("")}catch(t){}if(!c||c.width<0)c={width:0,height:0}}else c=this.htmlGetBBox();if(d.isSVG){d=c.width;e=c.height;if(ra&&i&&i.fontSize==="11px"&&e.toPrecision(3)==="16.9")c.height=e=14;if(f)c.width=Q(e*na(g))+Q(d*ha(g)),c.height=Q(e*ha(g))+Q(d*na(g))}if(u){for(;n.length>250;)delete m[n.shift()];m[u]||n.push(u); +m[u]=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;Qa(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;r(a)&&(g.setAttribute(b,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)}};P.prototype.yGetter=P.prototype.xGetter;P.prototype.translateXSetter=P.prototype.translateYSetter=P.prototype.rotationSetter=P.prototype.verticalAlignSetter=P.prototype.scaleXSetter=P.prototype.scaleYSetter=function(a,b){this[b]=a;this.doTransform=!0};P.prototype["stroke-widthSetter"]=P.prototype.strokeSetter=function(a,b,c){this[b]=a;if(this.stroke&&this["stroke-width"])this.strokeWidth=this["stroke-width"],P.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 ka=function(){this.init.apply(this,arguments)};ka.prototype={Element:P,init:function(a,b,c,d,e,f){var g=location,h,d=this.createElement("svg").attr({version:"1.1"}).css(this.getStyle(d));h=d.element;a.appendChild(h);a.innerHTML.indexOf("xmlns")===-1&&J(h,"xmlns",Aa);this.isSVG=!0;this.box=h;this.boxWrapper=d;this.alignedObjects= +[];this.url=(Ga||gb)&&A.getElementsByTagName("base").length?g.href.replace(/#.*?$/,"").replace(/([\('\)])/g,"\\$1").replace(/ /g,"%20"):"";this.createElement("desc").add().element.appendChild(A.createTextNode("Created with Highmaps 1.1.10"));this.defs=this.createElement("defs").add();this.allowHTML=f;this.forExport=e;this.gradients={};this.cache={};this.cacheKeys=[];this.setSize(b,c,!1);var i;if(Ga&&a.getBoundingClientRect)this.subPixelFix=b=function(){G(a,{left:0,top:0});i=a.getBoundingClientRect(); +G(a,{left:sa(i.left)-i.left+"px",top:sa(i.top)-i.top+"px"})},b(),M(L,"resize",b)},getStyle:function(a){return this.style=q({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();db(this.gradients||{});this.gradients=null;if(a)this.defs=a.destroy();this.subPixelFix&&X(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=p(a.textStr,"").toString(),f=e.indexOf("<")!==-1,g=b.childNodes,h,i,j=J(b,"x"),k=a.styles,l=a.textWidth,m=k&&k.lineHeight,n=k&&k.textShadow,u=k&&k.textOverflow==="ellipsis",t=g.length,R=l&&!a.added&&this.box,y=function(a){return m?C(m):c.fontMetrics(/(px|em)$/.test(a&& +a.style.fontSize)?a.style.fontSize:k&&k.fontSize||c.style.fontSize||12,a).h},D=function(a){return a.replace(/</g,"<").replace(/>/g,">")};t--;)b.removeChild(g[t]);!f&&!n&&!u&&e.indexOf(" ")===-1?b.appendChild(A.createTextNode(D(e))):(h=/<.*style="([^"]+)".*>/,i=/<.*href="(http[^"]+)".*>/,R&&R.appendChild(b),e=f?e.replace(/<(b|strong)>/g,'').replace(/<(i|em)>/g,'').replace(/
/g,"").split(//g): +[e],e[e.length-1]===""&&e.pop(),o(e,function(e,f){var g,m=0,e=e.replace(//g,"|||");g=e.split("|||");o(g,function(e){if(e!==""||g.length===1){var n={},t=A.createElementNS(Aa,"tspan"),o;h.test(e)&&(o=e.match(h)[1].replace(/(;| |^)color([ :])/,"$1fill$2"),J(t,"style",o));i.test(e)&&!d&&(J(t,"onclick",'location.href="'+e.match(i)[1]+'"'),G(t,{cursor:"pointer"}));e=D(e.replace(/<(.|\n)*?>/g,"")||" ");if(e!==" "){t.appendChild(A.createTextNode(e));if(m)n.dx=0; +else if(f&&j!==null)n.x=j;J(t,n);b.appendChild(t);!m&&f&&(!aa&&d&&G(t,{display:"block"}),J(t,"dy",y(t)));if(l){for(var n=e.replace(/([^\^])-/g,"$1- ").split(" "),p=g.length>1||f||n.length>1&&k.whiteSpace!=="nowrap",R,da,r,v=[],q=y(t),w=1,s=a.rotation,x=e,z=x.length;(p||u)&&(n.length||v.length);)a.rotation=0,R=a.getBBox(!0),r=R.width,!aa&&c.forExport&&(r=c.measureSpanWidth(t.firstChild.data,a.styles)),R=r>l,da===void 0&&(da=R),u&&da?(z/=2,x===""||!R&&z<0.5?n=[]:(R&&(da=!0),x=e.substring(0,x.length+ +(R?-1:1)*sa(z)),n=[x+(l>3?"\u2026":"")],t.removeChild(t.firstChild))):!R||n.length===1?(n=v,v=[],n.length&&(w++,t=A.createElementNS(Aa,"tspan"),J(t,{dy:q,x:j}),o&&J(t,"style",o),b.appendChild(t)),r>l&&(l=r)):(t.removeChild(t.firstChild),v.unshift(n.pop())),n.length&&t.appendChild(A.createTextNode(n.join(" ").replace(/- /g,"-")));da&&a.attr("title",a.textStr);a.rotation=s}m++}}})}),R&&R.removeChild(b),n&&a.applyTextShadow&&a.applyTextShadow(n))},getContrast:function(a){a=K(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,u,t,o,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);u=f.style;delete f.style;g=z(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#9BD"],[1,"#CDF"]]}}, +g);t=g.style;delete g.style;h=z(e,{style:{color:"#CCC"}},h);o=h.style;delete h.style;M(j.element,ra?"mouseover":"mouseenter",function(){k!==3&&j.attr(f).css(u)});M(j.element,ra?"mouseout":"mouseleave",function(){k!==3&&(l=[e,f,g][k],m=[n,u,t][k],j.attr(l).css(m))});j.setState=function(a){(j.state=k=a)?a===2?j.attr(g).css(t):a===3&&j.attr(h).css(o):j.attr(e).css(n)};return j.on("click",function(a){k!==3&&d.call(j,a)}).attr(e).css(q({cursor:"default"},n))},crispLine:function(a,b){a[1]===a[4]&&(a[1]= +a[4]=x(a[1])-b%2/2);a[2]===a[5]&&(a[2]=a[5]=x(a[2])+b%2/2);return a},path:function(a){var b={fill:"none"};Ba(a)?b.d=a:fa(a)&&q(b,a);return this.createElement("path").attr(b)},circle:function(a,b,c){a=fa(a)?a:{x:a,y:b,r:c};b=this.createElement("circle");b.xSetter=function(a){this.element.setAttribute("cx",a)};b.ySetter=function(a){this.element.setAttribute("cy",a)};return b.attr(a)},arc:function(a,b,c,d,e,f){if(fa(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=fa(a)?a.r:e,g=this.createElement("rect"),a=fa(a)?a:a===v?{}:{x:a,y:b,width:w(c,0),height:w(d,0)};if(f!==v)a.strokeWidth=f,a=g.crisp(a);if(e)a.r=e;g.rSetter=function(a){J(this.element,{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[p(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&&q(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,h=this.symbols[a],h=h&&h(x(b),x(c),d,e,f),i=/^url\((.*?)\)$/,j,k;if(h)g=this.path(h),q(g,{symbolName:a,x:b,y:c,width:d,height:e}), +f&&q(g,f);else if(i.test(a))k=function(a,b){a.element&&(a.attr({width:b[0],height:b[1]}),a.alignByTranslate||a.translate(x((d-b[0])/2),x((e-b[1])/2)))},j=a.match(i)[1],a=Fb[j]||f&&f.width&&f.height&&[f.width,f.height],g=this.image(j).attr({x:b,y:c}),g.isImg=!0,a?k(g,a):(g.attr({width:0,height:0}),ba("img",{onload:function(){this.width===0&&(G(this,{position:"absolute",top:"-999em"}),document.body.appendChild(this));k(g,Fb[j]=[this.width,this.height]);this.parentNode&&this.parentNode.removeChild(this)}, +src:j}));return g},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=ha(f),j=na(f),k=ha(g),g=na(g),e=e.end-fc&&e>b+g&&eb+g&&ed&&h>a+g&&ha+g&&hm&&/[ \-]/.test(b.textContent||b.innerText))G(b,{width:m+"px",display:"block",whiteSpace:j&&j.whiteSpace||"normal"}),i=m;this.getSpanCorrection(i,l,h,k,g)}G(b,{left:e+(this.xCorr||0)+ +"px",top:f+(this.yCorr||0)+"px"});if(gb)l=b.offsetHeight;this.cTT=n}}else this.alignOnAdd=!0},setSpanRotation:function(a,b,c){var d={},e=ra?"-ms-transform":gb?"-webkit-transform":Ga?"MozTransform":Eb?"-o-transform":"";d[e]=d.transform="rotate("+a+"deg)";d[e+(Ga?"Origin":"-origin")]=d.transformOrigin=b*100+"% "+c+"px";G(this.element,d)},getSpanCorrection:function(a,b,c){this.xCorr=-a*c;this.yCorr=-b}});q(ka.prototype,{html:function(a,b,c){var d=this.createElement("span"),e=d.element,f=d.renderer;d.textSetter= +function(a){a!==e.innerHTML&&delete this.bBox;e.innerHTML=this.textStr=a;d.htmlUpdateTransform()};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:x(b),y:x(c)}).css({position:"absolute",fontFamily:this.style.fontFamily,fontSize:this.style.fontSize});e.style.whiteSpace="nowrap";d.css=d.htmlCss;if(f.isSVG)d.add=function(a){var b,c=f.box.parentNode,j=[];if(this.parentGroup=a){if(b=a.div,!b){for(;a;)j.push(a), +a=a.parentGroup;o(j.reverse(),function(a){var d,e=J(a.element,"class");e&&(e={className:e});b=a.div=a.div||ba(Ea,e,{position:"absolute",left:(a.translateX||0)+"px",top:(a.translateY||0)+"px"},b||c);d=b.style;q(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}});o(["opacity","visibility"],function(b){ca(a,b+"Setter",function(a,b,c,e){a.call(this,b,c,e);d[c]=b})})})}}else b=c;b.appendChild(e);d.added=!0;d.alignOnAdd&& +d.htmlUpdateTransform();return d};return d}});var Za,V;if(!aa&&!ja)V={init:function(a,b){var c=["<",b,' filled="f" stroked="f"'],d=["position: ","absolute",";"],e=b===Ea;(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:P.prototype.htmlUpdateTransform,setSpanRotation:function(){var a=this.rotation,b=ha(a*ia),c=na(a*ia);G(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?ha(d*ia):1,g= +d?na(d*ia):0,h=p(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)),G(this.element,{textAlign:e}))},pathToVML:function(a){for(var b=a.length,c=[];b--;)if(pa(a[b]))c[b]=x(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,ua(c,b),c.push(b),b.destroyClip=function(){ua(c,b)},a=a.getCSS(b)):(b.destroyClip&&b.destroyClip(),a={clip:fb?"inherit":"rect(auto)"});return b.css(a)},css:P.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&&Ma(a)},destroy:function(){this.destroyClip&&this.destroyClip();return P.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]=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,j,k=f.path,l,m,n,u;k&&typeof k.value!=="string"&&(k="x");m=k;if(a){n=p(a.width,3);u=(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=ba(g.prepVML(j),null,{left:C(i.left)+p(a.offsetX,1),top:C(i.top)+p(a.offsetY,1)});if(c)h.cutOff=l+1;j=[''];ba(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:T,setAttr:function(a,b){fb?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))},opacitySetter:T,rotationSetter:function(a,b,c){c= +c.style;this[b]=c[b]=a;c.left=-x(na(a*ia)+1)+"px";c.top=x(ha(a*ia))+"px"},strokeSetter:function(a,b,c){this.setAttr("strokecolor",this.renderer.color(a,c,b))},"stroke-widthSetter":function(a,b,c){c.stroked=!!a;this[b]=a;pa(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,fb||(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}},s.VMLElement=V=$(P,V),V.prototype.ySetter=V.prototype.widthSetter=V.prototype.heightSetter=V.prototype.xSetter,V={Element:V,isIE8:wa.indexOf("MSIE 8.0")>-1,init:function(a,b,c,d){var e;this.alignedObjects=[];d=this.createElement(Ea).css(q(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.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=fa(a);return q(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("+x(a?e:d)+"px,"+x(a?f:b)+"px,"+x(a?b:f)+"px,"+x(a?d:e)+"px)"};!a&&fb&&c==="DIV"&&q(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,u,t,p,y,D="",a=a.stops,da,r=[],ea=function(){h=[''];ba(e.prepVML(h),null,null,b)};n=a[0];da=a[a.length-1];n[0]>0&&a.unshift([0, +n[1]]);da[0]<1&&a.push([1,da[1]]);o(a,function(a,b){g.test(a[1])?(f=K(a[1]),k=f.get("rgb"),l=f.get("a")):(k=a[1],l=1);r.push(a[0]*100+"% "+k);b?(t=l,p=k):(u=l,y=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,D='angle="'+(90-I.atan((m-a)/(n-c))*180/ob)+'"',ea();else{var j=m.r,v=j*2,w=j*2,q=m.cx,s=m.cy,x=b.radialReference,z,j=function(){x&&(z=d.getBBox(),q+=(x[0]-z.x)/z.width-0.5,s+=(x[1]-z.y)/z.height-0.5,v*=x[2]/z.width,w*=x[2]/z.height);D='src="'+ +N.global.VMLRadialGradientURL+'" size="'+v+","+w+'" origin="0.5,0.5" position="'+q+","+s+'" color2="'+y+'" ';ea()};d.added?j():d.onAdd=j;j=p}else j=k}else if(g.test(a)&&b.tagName!=="IMG")f=K(a),h=["<",c,' opacity="',f.get("a"),'"/>'],ba(this.prepVML(h),null,null,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):ka.prototype.createElement.call(this,a)},invertChild:function(a,b){var c=this,d=b.style,e=a.tagName==="IMG"&&a.style;G(a,{flip:"x",left:C(d.width)-(e?C(e.top):1),top:C(d.height)-(e?C(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=ha(f),i=na(f),j=ha(g),k=na(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 ka.prototype.symbols[!r(e)||!e.r?"square":"callout"].call(0, +a,b,c,d,e)}}},s.VMLRenderer=Za=function(){this.init.apply(this,arguments)},Za.prototype=z(ka.prototype,V),Oa=Za;ka.prototype.measureSpanWidth=function(a,b){var c=A.createElement("span"),d;d=A.createTextNode(a);c.appendChild(d);G(c,b);this.box.appendChild(c);d=c.offsetWidth;Ma(c);return d};var Ib;if(ja)s.CanVGRenderer=V=function(){Aa="http://www.w3.org/1999/xhtml"},V.prototype.symbols={},Ib=function(){function a(){var a=b.length,d;for(d=0;d0&&c+i*j>e&&(m=x((d-c)/ha(h*ia)));else if(d=c+(1-i)*j,c-i*je&&(k=e-a.x+k*i,l=-1),k=E(b.slotWidth,k),kk||b.autoRotation&&g.styles.width)m=k;if(m){n.width=m;if(!b.options.labels.style.textOverflow)n.textOverflow="ellipsis";g.css(n)}},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;r(n)||(n=i.side===2?m.y+8:n=ha(c.rotation*ia)*(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:x(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",u=e[m+"LineWidth"],t=e[m+"LineColor"],o=e[m+"LineDashStyle"],y=e[n+"Length"],m=p(e[n+"Width"],!h&&d.isXAxis?1:0),D=e[n+"Color"],da=e[n+"Position"],n=this.mark,r=k.step,ea=!0,w= +d.tickmarkOffset,q=this.getPosition(g,j,w,b),s=q.x,q=q.y,x=g&&s===d.pos+d.len||!g&&q===d.pos?-1:1,c=p(c,1);this.isActive=!0;if(u){j=d.getPlotLinePath(j+w,u*x,b,!0);if(l===v){l={stroke:t,"stroke-width":u};if(o)l.dashstyle=o;if(!h)l.zIndex=1;if(b)l.opacity=0;this.gridLine=l=u?f.path(j).attr(l).add(d.gridGroup):null}if(!b&&l&&j)l[this.isNew?"attr":"animate"]({d:j,opacity:c})}if(m&&y)da==="inside"&&(y=-y),d.opposite&&(y=-y),h=this.getMarkPath(s,q,y,m*x,g,f),n?n.animate({d:h,opacity:c}):this.mark=f.path(h).attr({stroke:D, +"stroke-width":m,opacity:c}).add(d.axisGroup);if(i&&!isNaN(s))i.xy=q=this.getLabelPosition(s,q,i,g,k,w,a,r),this.isFirst&&!this.isLast&&!p(e.showFirstLabel,1)||this.isLast&&!this.isFirst&&!p(e.showLastLabel,1)?ea=!1:g&&!d.isRadial&&!k.step&&!k.rotation&&!b&&c!==0&&this.handleOverflow(q),r&&a%r&&(ea=!1),ea&&!isNaN(q.y)?(q.opacity=c,i[this.isNew?"attr":"animate"](q),this.isNew=!1):i.attr("y",-9999)},destroy:function(){db(this,this.axis)}};var Y=s.Axis=function(){this.init.apply(this,arguments)};Y.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,y:15},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,y:3},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0,title:{rotation:270,text:"Values"},stackLabels:{enabled:!1,formatter:function(){return s.numberFormat(this.total,-1)},style:z(U.line.dataLabels.style,{color:"#000000"})}}, +defaultLeftAxisOptions:{labels:{x:-15,y:null},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:15,y:null},title:{rotation:90}},defaultBottomAxisOptions:{labels:{autoRotation:[-45],x:0,y:null},title:{rotation:0}},defaultTopAxisOptions:{labels:{autoRotation:[-45],x:0,y:-15},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=p(d.crosshair,qa(a.options.tooltip.crosshairs)[c?0:1],!1);var f,d=this.options.events;Pa(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===v)this.reversed=!0;this.removePlotLine= +this.removePlotBand=this.removePlotBandOrLine;for(f in d)M(this,f,d[f]);if(this.isLog)this.val2lin=Ra,this.lin2val=ma},setOptions:function(a){this.options=z(this.defaultOptions,this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],z(N[this.coll],a))},defaultLabelFormatter:function(){var a=this.axis,b=this.value,c=a.categories,d=this.dateTimeLabelFormat,e=N.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=La(d,b);else if(f&&a>=1E3)for(;f--&&g===v;)c=Math.pow(1E3,f+1),a>=c&&b*10%c===0&&e[f]!==null&&(g=s.numberFormat(b/c,-1)+e[f]);g===v&&(g=Q(b)>=1E4?s.numberFormat(b,-1):s.numberFormat(b,-1,v,""));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)a.dataMin=E(p(a.dataMin,d[0]),Sa(d)),a.dataMax=w(p(a.dataMax,d[0]),Da(d))}else{c.getExtremes();f=c.dataMax;c=c.dataMin;if(r(c)&&r(f))a.dataMin=E(p(a.dataMin,c),c),a.dataMax=w(p(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,j=d?g.oldTransA: +g.transA,d=d?g.oldMin:g.min,k=g.minPixelPadding,e=(g.doPostTranslate||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+(pa(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=E(w(b,a),c):m=!0;return a},e=p(e,this.translate(a,null,null,c)),a=c=x(e+i);i=j=x(k-e-i);isNaN(e)?m=!0: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));return m&&!d?null:f.renderer.crispLine(["M",a,i,"L",c,j],b||1)},getLinearTickPositions:function(a,b,c){var d,e=va(ga(b/ +a)*a),f=va(sa(c/a)*a),g=[];if(b===c&&pa(b))return[b];for(b=e;b<=f;){g.push(b);b=va(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===v&&!this.isLog)r(a.min)||r(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===v||h=n?(t=n,j=0):b.dataMax<=n&&(R=n, +i=0)),b.min=p(y,t,b.dataMin),b.max=p(D,R,b.dataMax));if(e)!a&&E(b.min,p(b.dataMin,b.min))<=0&&O(10,1),b.min=va(Ra(b.min),15),b.max=va(Ra(b.max),15);if(b.range&&r(b.max))b.userMin=b.min=y=w(b.min,b.minFromRange()),b.userMax=D=b.max,b.range=null;b.beforePadding&&b.beforePadding();b.adjustForMinRange();if(!m&&!b.axisPointRange&&!b.usePercentage&&!h&&r(b.min)&&r(b.max)&&(c=b.max-b.min))!r(y)&&j&&(b.min-=c*j),!r(D)&&i&&(b.max+=c*i);if(pa(d.floor))b.min=w(b.min,d.floor);if(pa(d.ceiling))b.max=E(b.max,d.ceiling); +if(u&&r(b.dataMin))if(n=n||0,!r(y)&&b.min=n)b.min=n;else if(!r(D)&&b.max>n&&b.dataMax<=n)b.max=n;b.tickInterval=b.min===b.max||b.min===void 0||b.max===void 0?1:h&&!k&&l===b.linkedParent.options.tickPixelInterval?k=b.linkedParent.tickInterval:p(k,this.tickAmount?(b.max-b.min)/w(this.tickAmount-1,1):void 0,m?1:(b.max-b.min)*l/w(b.len,l));g&&!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&&!k)b.tickInterval=w(b.pointRange,b.tickInterval);a=p(d.minTickInterval,b.isDatetimeAxis&&b.closestPointRange);if(!k&&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;b?this.min=d:this.min-f>d&&a.shift();c?this.max=e: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!==v||b!==v;this.setExtremes(a,b,!1,v,{trigger:"zoom"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,d=this.horiz,e=p(b.width,a.plotWidth-c+(b.offsetRight||0)),f=p(b.height,a.plotHeight),g=p(b.top,a.plotTop),b=p(b.left,a.plotLeft+c),c=/%$/;c.test(f)&&(f=parseFloat(f)/100*a.plotHeight);c.test(g)&&(g=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;return{min:a?va(ma(this.min)):this.min,max:a?va(ma(this.max)):this.max,dataMin:this.dataMin,dataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}},getThreshold:function(a){var b=this.isLog,c=b?ma(this.min):this.min,b=b?ma(this.max):this.max;a===null?a=b<0?b:c:c>a?a=c:b15&&a<165?"right":a>195&&a<345?"left":"center"},unsquish:function(){var a=this.ticks,b=this.options.labels,c=this.horiz,d=this.tickInterval,e=d,f=this.len/(((this.categories?1:0)+this.max-this.min)/d),g,h=b.rotation,i=this.chart.renderer.fontMetrics(b.style.fontSize,a[0]&&a[0].label),j,k=Number.MAX_VALUE,l,m=function(a){a/=f||1;a=a>1?sa(a):1;return a*d};c?(l=!b.staggerLines&&!b.step&&(r(h)?[h]:f=-90&&a<=90)j=m(Q(i.h/na(ia*a))),b=j+Q(a/360),bm)m=a.labelLength}),m>i&&m>h.h?j.rotation=this.labelRotation:this.labelRotation=0;else if(g&&(l={width:i+"px"},!k)){l.textOverflow="clip";for(n=c.length;!f&&n--;)if(u=c[n],i=d[u].label)if(i.styles.textOverflow==="ellipsis"&&i.css({textOverflow:"clip"}),i.getBBox().height>this.len/c.length-(h.h-h.f)||d[u].labelLength> +g)i.specCss={textOverflow:"ellipsis"}}if(j.rotation&&(l={width:(m>a.chartHeight*0.5?a.chartHeight*0.33:a.chartHeight)+"px"},!k))l.textOverflow="ellipsis";this.labelAlign=j.align=e.align||this.autoLabelAlign(this.labelRotation);o(c,function(a){var b=(a=d[a])&&a.label;if(b)b.attr(j),l&&b.css(z(l,b.specCss)),delete b.specCss,a.rotation=j.rotation});this.tickRotCorr=b.rotCorr(h.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,j,k,l=0,m,n=0,u=d.title,t=d.labels,R=0,y=a.opposite,D=b.axisOffset,b=b.clipOffset,q=[-1,1,1,-1][h],v,ea=a.axisParent;j=a.hasData();a.showAxis=k=j||p(d.showEmpty,!0);a.staggerLines=a.horiz&&t.staggerLines;if(!a.axisGroup)a.gridGroup=c.g("grid").attr({zIndex:d.gridZIndex||1}).add(ea),a.axisGroup=c.g("axis").attr({zIndex:d.zIndex||2}).add(ea),a.labelGroup=c.g("axis-labels").attr({zIndex:t.zIndex|| +7}).addClass("highcharts-"+a.coll.toLowerCase()+"-labels").add(ea);if(j||a.isLinked){if(o(e,function(b){f[b]?f[b].addLabel():f[b]=new Na(a,b)}),a.renderUnsquish(),t.reserveSpace!==!1&&(h===0||h===2||{1:"left",3:"right"}[h]===a.labelAlign||a.labelAlign==="center")&&o(e,function(a){R=w(f[a].getLabelSize(),R)}),a.staggerLines)R*=a.staggerLines,a.labelOffset=R*(a.opposite?-1:1)}else for(v in f)f[v].destroy(),delete f[v];if(u&&u.text&&u.enabled!==!1){if(!a.axisTitle)a.axisTitle=c.text(u.text,0,0,u.useHTML).attr({zIndex:7, +rotation:u.rotation||0,align:u.textAlign||{low:y?"right":"left",middle:"center",high:y?"left":"right"}[u.align]}).addClass("highcharts-"+this.coll.toLowerCase()+"-title").css(u.style).add(a.axisGroup),a.axisTitle.isNew=!0;if(k)l=a.axisTitle.getBBox()[g?"height":"width"],m=u.offset,n=r(m)?0:p(u.margin,g?5:10);a.axisTitle[k?"show":"hide"](!0)}a.offset=q*p(d.offset,D[h]);a.tickRotCorr=a.tickRotCorr||{x:0,y:0};c=h===2?a.tickRotCorr.y:0;g=Math.abs(R)+n+(R&&q*(g?p(t.y,a.tickRotCorr.y+8):t.x)-c);a.axisTitleMargin= +p(m,g);D[h]=w(D[h],a.axisTitleMargin+l+q*a.offset,g);d=d.offset?0:ga(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=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?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.isLinked,g=a.tickPositions,h=a.axisTitle,i=a.ticks,j=a.minorTicks,k=a.alternateBands,l=d.stackLabels,m=d.alternateGridColor,n=a.tickmarkOffset, +u=d.lineWidth,t,p=b.hasRendered&&r(a.oldMin)&&!isNaN(a.oldMin),y=a.showAxis,D=c.globalAnimation,q,w;a.labelEdge.length=0;a.overlap=!1;o([i,j,k],function(a){for(var b in a)a[b].isActive=!1});if(a.hasData()||f){a.minorTickInterval&&!a.categories&&o(a.getMinorTickPositions(),function(b){j[b]||(j[b]=new Na(a,b,"minor"));p&&j[b].isNew&&j[b].render(null,!0);j[b].render(null,!1,1)});if(g.length&&(o(g,function(b,c){if(!f||b>=a.min&&b<=a.max)i[b]||(i[b]=new Na(a,b)),p&&i[b].isNew&&i[b].render(c,!0,0.1),i[b].render(c)}), +n&&(a.min===0||a.single)))i[-1]||(i[-1]=new Na(a,-1,null,!0)),i[-1].render(-1);m&&o(g,function(c,d){w=g[d+1]!==v?g[d+1]+n:a.max-n;if(d%2===0&&c=0.5)a=x(a),g=this.getLinearTickPositions(a,b,c);else if(a>=0.08)for(var f=ga(b),h, +i,j,k,l,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||k<=c)&&k!==v&&g.push(k),k>c&&(l=!0),k=j}else if(b=ma(b),c=ma(c),a=e[d?"minorTickInterval":"tickInterval"],a=p(a==="auto"?null:a,this._minorAutoInterval,(c-b)*(e.tickPixelInterval/(d?5:1))/((d?f/this.tickPositions.length:f)||1)),a=tb(a,null,I.pow(10,ga(I.log(a)/I.LN10))),g=Ha(this.getLinearTickPositions(a,b,c),Ra),!d)this._minorAutoInterval=a/5;if(!d)this.tickInterval= +a;return g};var Jb=s.Tooltip=function(){this.init.apply(this,arguments)};Jb.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});ja||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;q(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:h?v:g?(2*f.anchorX+c)/3:c,anchorY:h?v: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=p(a,this.options.hideDelay,500);if(!this.isHidden)this.hideTimer=Ja(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=qa(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===v&&(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 Ha(c,x)},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=p(c.ttBelow,d.inverted&&!c.negative||!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},u=function(){l.apply(0,i)!==!1?m.apply(0,j)===!1&&!h&&(n(!0),u()):h?f.x=f.y=0:(n(!0),u())};(d.inverted||this.len>1)&&n();u();return f},defaultFormatter:function(a){var b=this.points||qa(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=qa(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=p(i.tooltipOptions.distance,16);j===!1?this.hide():(this.isHidden&&(Qa(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);F(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(x(c.x),x(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=La("%m-%d %H:%M:%S.%L",a.x);for(f in hb){if(e===hb.week&&+La("%w",a.x)===c.options.startOfWeek&&h.substr(6)==="00:00:00.000"){f="week";break}if(hb[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"&&pa(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 Ha(a,function(a){var c=a.series.tooltipOptions; +return(c.pointFormatter||a.point.tooltipFormatter).call(a.point,c.pointFormat)})}};var oa;Ua=A.documentElement.ontouchstart!==v;var xa=s.Pointer=function(a,b){this.init(a,b)};xa.prototype={init:function(a,b){var c=b.chart,d=c.events,e=ja?"":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(s.Tooltip&& +b.tooltip.enabled)a.tooltip=new Jb(a,b.tooltip),this.followTouchMove=p(b.tooltip.followTouchMove,!0);this.setDOMEvents()},normalize:function(a,b){var c,d,a=a||window.event,a=Hb(a);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=Gb(this.chart.container);d.pageX===v?(c=w(a.x,a.clientX-b.left),d=a.y):(c=d.pageX-b.left,d=d.pageY-b.top);return q(a,{chartX:x(c),chartY:x(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,j,k,l,m=[],n,u;if(!e&&!g)for(h=0;hh+j&&(d=h+j),ei+k&&(e=i+k),this.hasDragged=Math.sqrt(Math.pow(n-d,2)+Math.pow(u-e,2)),this.hasDragged>10){l=b.isInsidePlot(n-h,u-i);if(b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&l&&!t&&!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:Q(d),x:(d>0?0:d)+n}));m&&g&&(d=e-u,m.attr({height:Q(d),y:(d>0?0:d)+u}));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={xAxis:[],yAxis:[],originalEvent:a.originalEvent||a},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&&r(c.min)&&(d||b[{xAxis:"zoomX", +yAxis:"zoomY"}[c.coll]])){var f=c.horiz,n=a.type==="touchend"?c.minPixelPadding:0,u=c.toValue((f?g:h)+n),f=c.toValue((f?g+i:h+j)-n);e[c.coll].push({axis:c,min:E(u,f),max:w(u,f)});k=!0}}),k&&F(c,"selection",e,function(a){c.zoom(q(a,d?{animation:!1}:null))});this.selectionMarker=this.selectionMarker.destroy();d&&this.scaleGroups()}if(c)G(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){W[oa]&&W[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(){var a=W[oa];if(a)a.pointer.reset(),a.pointer.chartPosition=null},onContainerMouseMove:function(a){var b=this.chart;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=J(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&&!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);a.originalEvent=a;b.cancelClick||(c&&this.inClass(a.target,"highcharts-tracker")?(F(c.series,"click",q(a,{point:c})),b.hoverPoint&&c.firePointEvent("click",a)):(q(a,this.getCoordinates(a)),b.isInsidePlot(a.chartX-d,a.chartY-e)&&F(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)};M(b,"mouseleave",a.onContainerMouseLeave);Va===1&&M(A,"mouseup",a.onDocumentMouseUp);if(Ua)b.ontouchstart=function(b){a.onContainerTouchStart(b)},b.ontouchmove=function(b){a.onContainerTouchMove(b)},Va===1&&M(A,"touchend",a.onDocumentTouchEnd)},destroy:function(){var a;X(this.chart.container,"mouseleave",this.onContainerMouseLeave);Va||(X(A,"mouseup",this.onDocumentMouseUp), +X(A,"touchend",this.onDocumentTouchEnd));clearInterval(this.tooltipTimeout);for(a in this)this[a]=null}};q(s.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")],u,t,o=h||1,p=i.inverted,D=i.bounds[a? +"h":"v"],q=b.length===1,w=b[0][l],r=c[0][l],v=!q&&b[1][l],s=!q&&c[1][l],x,c=function(){!q&&Q(w-v)>20&&(o=h||Q(r-s)/Q(w-v));t=(n-r)/o+w;u=i["plot"+(a?"Width":"Height")]/o};c();b=t;bD.max&&(b=D.max-u,x=!0);x?(r-=0.8*(r-g[j][0]),q||(s-=0.8*(s-g[j][1])),c()):g[j]=[r,s];p||(f[j]=t-n,f[m]=u);f=p?1/o:o;e[m]=u;e[j]=b;d[p?a?"scaleY":"scaleX":"scale"+k]=o;d["translate"+k]=f*n+(r-f*w)},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();Ha(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(p(a.options.min, +a.dataMin)),f=a.toPixels(p(a.options.max,a.dataMax)),g=E(e,f),e=w(e,f);b.min=E(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=q({destroy:T,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;oa=c.index;a.touches.length===1?(a=this.normalize(a),c.isInsidePlot(a.chartX-c.plotLeft, +a.chartY-c.plotTop)&&!c.openMenu?(b&&this.runPointActions(a),this.pinch(a)):b&&this.reset()):a.touches.length===2&&this.pinch(a)},onContainerTouchStart:function(a){this.touch(a,!0)},onContainerTouchMove:function(a){this.touch(a)},onDocumentTouchEnd:function(a){W[oa]&&W[oa].pointer.drop(a)}});if(L.PointerEvent||L.MSPointerEvent){var ta={},rb=!!L.PointerEvent,Pb=function(){var a,b=[];b.item=function(a){return this[a]};for(a in ta)ta.hasOwnProperty(a)&&b.push({pageX:ta[a].pageX,pageY:ta[a].pageY,target:ta[a].target}); +return b},sb=function(a,b,c,d){a=a.originalEvent||a;if((a.pointerType==="touch"||a.pointerType===a.MSPOINTER_TYPE_TOUCH)&&W[oa])d(a),d=W[oa].pointer,d[b]({type:c,target:a.currentTarget,preventDefault:T,touches:Pb()})};q(xa.prototype,{onContainerPointerDown:function(a){sb(a,"onContainerTouchStart","touchstart",function(a){ta[a.pointerId]={pageX:a.pageX,pageY:a.pageY,target:a.currentTarget}})},onContainerPointerMove:function(a){sb(a,"onContainerTouchMove","touchmove",function(a){ta[a.pointerId]={pageX:a.pageX, +pageY:a.pageY};if(!ta[a.pointerId].target)ta[a.pointerId].target=a.currentTarget})},onDocumentPointerUp:function(a){sb(a,"onDocumentTouchEnd","touchend",function(a){delete ta[a.pointerId]})},batchMSEvents:function(a){a(this.chart.container,rb?"pointerdown":"MSPointerDown",this.onContainerPointerDown);a(this.chart.container,rb?"pointermove":"MSPointerMove",this.onContainerPointerMove);a(A,rb?"pointerup":"MSPointerUp",this.onDocumentPointerUp)}});ca(xa.prototype,"init",function(a,b,c){a.call(this,b, +c);this.hasZoom&&G(b.container,{"-ms-touch-action":"none","touch-action":"none"})});ca(xa.prototype,"setDOMEvents",function(a){a.apply(this);(this.hasZoom||this.followTouchMove)&&this.batchMSEvents(M)});ca(xa.prototype,"destroy",function(a){this.batchMSEvents(X);a.call(this)})}var $a=s.Legend=function(a,b){this.init(a,b)};$a.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=p(b.padding,8),c.initialItemX=d,c.initialItemY=d-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.symbolWidth=p(b.symbolWidth,16),c.pages=[],c.render(),M(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!==v&&(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&&Ma(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;if(b)c=b.translateY,o(this.allItems,function(e){var f=e.checkbox,g;f&&(g=c+f.y+(a||0)+3,G(f,{left:b.translateX+e.checkboxOffset+f.x-20+"px",top:g+"px",display:g>c-6&&g(m||b.chartWidth-2*j-t-d.x))this.itemX=t,this.itemY+=u+this.lastLineHeight+n,this.lastLineHeight=0;this.maxItemWidth=w(this.maxItemWidth, +f);this.lastItemY=u+this.itemY+n;this.lastLineHeight=w(g,this.lastLineHeight);a._legendItemPos=[this.itemX,this.itemY];e?this.itemX+=f:(this.itemY+=u+g+n,this.lastLineHeight=g);this.offsetWidth=m||w((e?this.itemX-t-k:f)+j,this.offsetWidth)},getAllItems:function(){var a=[];o(this.chart.series,function(b){var c=b.options;if(p(c.showInLegend,!r(c.linkedTo)?v:!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)&&!r(a[g])&&(c[ib[g]]=w(c[ib[g]],c.legend[(g+1)%2?"legendHeight":"legendWidth"]+[1,-1,-1,1][g]*d[g%2?"x":"y"]+p(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();cb(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(q({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=p(j.animation,!0),l=j.arrowSize||12,m=this.nav,n=this.pages,u=this.padding,t,q=this.allItems,y=function(a){i.attr({height:a});if(b.contentGroup.div)b.contentGroup.div.style.clip="rect("+u+"px,9999px,"+(u+a)+"px,0)"};e.layout==="horizontal"&&(f/=2);g&&(f=E(f,g));n.length=0;if(a>f){this.clipHeight=h=w(f-20-this.titleHeight-u,0);this.currentPage=p(this.currentPage,1);this.fullHeight=a;o(q,function(a,b){var c= +a._legendItemPos[1],d=x(a.legendItem.getBBox().height),e=n.length;if(!e||c-n[e-1]>h&&(t||c)!==n[e-1])n.push(t||c),e++;b===q.length-1&&c+d-n[e-1]>h&&n.push(c);c!==t&&(t=c)});if(!i)i=b.clipRect=d.clipRect(0,u,9999,0),b.contentGroup.clip(i);y(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)y(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!==v&&Ta(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)}};var ab=s.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;d=a.symbolWidth;var e=this.chart.renderer,f=this.legendGroup,a=a.baseline-x(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=d=e.symbol(this.symbol,d/2-b,a-b,2*b,2*b).add(f),d.isMarker=!0}};(/Trident\/7\.0/.test(wa)||Ga)&&ca($a.prototype,"positionItem",function(a,b){var c=this,d=function(){b._legendItemPos&& +a.call(c,b)};d();setTimeout(d)});var la=s.Chart=function(){this.init.apply(this,arguments)};la.prototype={callbacks:[],init:function(a,b){var c,d=a.series;a.series=null;c=z(N,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=W.length;W.push(f); +Va++;d.reflow!==!1&&M(f,"load",function(){f.initReflow()});if(e)for(g in e)M(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=ja?!1:p(d.animation,!0);f.pointCount=f.colorCounter=f.symbolCounter=0;f.firstRender()},initSeries:function(a){var b=this.options.chart;(b=B[a.type||b.type||b.defaultSeriesType])||O(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(),u=[];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=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)});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,u.push(function(){F(a,"afterSetExtremes",q(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();F(this,"redraw");n&&this.cloneRenderTo(!0);o(u,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),Ma(b),delete this.renderToClone):(c&&c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=b=this.renderTo.cloneNode(0),G(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,f;this.renderTo= +a=c.renderTo;f="highcharts-"+pb++;if(za(a))this.renderTo=a=A.getElementById(a);a||O(13,!0);d=C(J(a,"data-highcharts-chart"));!isNaN(d)&&W[d]&&W[d].hasRendered&&W[d].destroy();J(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(Ea,{className:"highcharts-container"+(c.className?" "+c.className:""),id:f},q({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 (s[c.renderer]||Oa)(a,d,e,c.style,c.forExport,b.exporting&&b.exporting.allowHTML);ja&&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=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(ib,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||Wa(d,"width"),f=c.height||Wa(d,"height"),c=a?a.target:L;if(!b.hasUserSize&& +!b.isPrinting&&e&&f&&(c===L||c===A)){if(e!==b.containerWidth||f!==b.containerHeight)clearTimeout(b.reflowTimeout),b.reflowTimeout=Ja(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)};M(L,"resize",b);M(a,"destroy",function(){X(L,"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=w(0,x(a)),d.hasUserSize=!!e;if(r(b))d.chartHeight=f=w(0,x(b));a=g.globalAnimation;(a?Xa:G)(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;F(d,"resize");a=g.globalAnimation;Ja(function(){d&&F(d,"endResize",null,function(){d.isResizing-=1})},a=== +!1?0:a&&a.duration||500)},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=x(this.plotLeft);this.plotTop=j=x(this.plotTop);this.plotWidth=k=w(0,x(d-i-this.marginRight));this.plotHeight=l=w(0,x(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*ga(this.plotBorderWidth/2);b=sa(w(d,h[3])/2);c=sa(w(d,h[0])/2);this.clipBox={x:b,y:c,width:ga(this.plotSizeX-w(d,h[1])/2-b),height:w(0,ga(this.plotSizeY-w(d,h[2])/2-c))};a||o(this.axes,function(a){a.setAxisSize();a.setAxisTranslation()})},resetMargins:function(){var a=this;o(ib,function(b,c){a[b]=p(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,o=this.plotLeft,p=this.plotTop,q=this.plotWidth,y=this.plotHeight,D=this.plotBox,r=this.clipRect,w=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(D):this.plotBackground=b.rect(o,p,q,y,0).attr({fill:k}).add().shadow(a.plotShadow);if(l)h?h.animate(D):this.plotBGImage=b.image(l,o,p,q,y).add();r?r.animate({width:w.width,height:w.height}):this.clipRect=b.clipRect(w);if(m)g?g.animate(g.crisp({x:o,y:p,width:q,height:y,strokeWidth:-m})):this.plotBorder=b.rect(o,p,q,y,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=B[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&&e--;)(c=B[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(za(d)&&(d=d===":previous"?a.series[b.index-1]:a.get(d)))d.linkedSeries.push(b), +b.linkedParent=d,b.visible=p(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=q(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 $a(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)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;F(a,"destroy");W[a.index]=v;Va--;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();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="",X(d),f&&Ma(d);for(e in a)delete a[e]},isReadyToRender:function(){var a=this;return!aa&&L==L.top&&A.readyState!=="complete"||ja&&!L.canvg?(ja?Ib.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,c=a.callback;if(a.isReadyToRender()){a.getContainer();F(a,"init");a.resetMargins();a.setChartSize();a.propFromSeries();a.getAxes();o(b.series||[],function(b){a.initSeries(b)});a.linkSeries();F(a,"beforeRender");if(s.Pointer)a.pointer=new xa(a,b);a.render();a.renderer.draw();c&&c.apply(a,[a]);o(a.callbacks, +function(b){a.index!==v&&b.apply(a,[a])});F(a,"load");a.cloneRenderTo(!0)}},splashArray:function(a,b){var c=b[a],c=fa(c)?c:[c,c,c,c];return[p(b[a+"Top"],c[0]),p(b[a+"Right"],c[1]),p(b[a+"Bottom"],c[2]),p(b[a+"Left"],c[3])]}};var Z=function(){};Z.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=Z.prototype.optionsToObject.call(this,a);q(this,a);this.options=this.options?q(this.options,a):a;if(d)this.y=this[d];if(this.x===v&&c)this.x=b===v?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(typeof a==="number"||a===null)b[e[0]]=a;else if(Ba(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(;ha+1&&b.push(d.slice(a+1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments= +b},setOptions:function(a){var b=this.chart,c=b.options.plotOptions,b=b.userOptions||{},d=b.plotOptions||{},e=c[this.type];this.userOptions=a;c=z(e,c.series,a);this.tooltipOptions=z(N.tooltip,N.plotOptions[this.type].tooltip,b.tooltip,d.series&&d.series.tooltip,d[this.type]&&d[this.type].tooltip,a.tooltip);e.marker===null&&delete c.marker;this.zoneAxis=c.zoneAxis;a=this.zones=(c.zones||[]).slice();if((c.negativeColor||c.negativeFillColor)&&!c.zones)a.push({value:c[this.zoneAxis+"Threshold"]||c.threshold|| +0,color:c.negativeColor,fillColor:c.negativeFillColor});a.length&&r(a[a.length-1].value)&&a.push({color:this.color,fillColor:this.fillColor});return c},getCyclic:function(a,b,c){var d=this.userOptions,e="_"+a+"Index",f=a+"Counter";b||(r(d[e])?b=d[e]:(d[e]=b=this.chart[f]%c.length,this.chart[f]+=1),b=c[b]);this[a]=b},getColor:function(){this.options.colorByPoint?this.options.color=null:this.getCyclic("color",this.options.color||U[this.type].color,this.chart.options.colors)},getSymbol:function(){var a= +this.options.marker;this.getCyclic("symbol",a.symbol,this.chart.options.symbols);if(/^url/.test(this.symbol))a.radius=0},drawLegendSymbol:ab.drawLineMarker,setData:function(a,b,c,d){var e=this,f=e.points,g=f&&f.length||0,h,i=e.options,j=e.chart,k=null,l=e.xAxis,m=l&&!!l.categories,n=i.turboThreshold,u=this.xData,t=this.yData,q=(h=e.pointArrayMap)&&h.length,a=a||[];h=a.length;b=p(b,!0);if(d!==!1&&h&&g===h&&!e.cropped&&!e.hasGroupedData&&e.visible)o(a,function(a,b){f[b].update&&a!==i.data[b]&&f[b].update(a, +!1,null,!1)});else{e.xIncrement=null;e.colorCounter=0;o(this.parallelArrays,function(a){e[a+"Data"].length=0});if(n&&h>n){for(c=0;k===null&&ci||this.forceCrop))if(b[d-1]m)b=[],c=[];else if(b[0]m)e=this.cropData(this.xData,this.yData,l,m),b=e.xData,c=e.yData,e=e.start,f=!0;for(i=b.length-1;i>=0;i--)d=b[i]-b[i-1],d>0&&(g===v||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=Sa(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"||pa(i),k=a.threshold,l=a.startFromThreshold?k:0,m,n,o,t,q=Number.MAX_VALUE,a=0;a=0&&n<=e.len&&m>=0&&m<=c.len;y.clientX=j?c.translate(D,0,0,0,1):m;y.negative=y.y<(k||0);y.category=d&&d[y.x]!==v?d[y.x]:y.x;a&&(q=E(q,Q(m-o)));o=m}this.closestPointRangePx=q;this.getSegments()},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&&!fa(c))c=U[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();F(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,o,t,s=this.markerGroup,y=p(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= +ga(g.plotX),e=g.plotY,k=g.graphic,n=g.marker||{},o=!!g.marker,a=y&&n.enabled===v||n.enabled,t=g.isInside,a&&e!==v&&!isNaN(e)&&g.y!==null)if(a=g.pointAttr[g.selected?"select":""]||m,h=a.r,i=p(n.symbol,this.symbol),j=i.indexOf("url")===0,k)k[t?"show":"hide"](!0).animate(q({x:d-h,y:e-h},k.symbolName?{width:2*h,height:2*h}:{}));else{if(t&&(h>0||j))g.graphic=c.renderer.symbol(i,d-h,e-h,2*h,2*h,o?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]=p(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=a.options,c=U[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color,h=a.options.negativeColor;f={stroke:g,fill:g};var i=a.points||[],j,k,l=[],m=a.pointAttrToOptions;j=a.hasPointSpecificOptions;var n=c.lineColor,u=c.fillColor;k=b.turboThreshold;var t=a.zones,s=a.zoneAxis||"y",y;b.marker?(e.radius=e.radius||c.radius+e.radiusPlus,e.lineWidth=e.lineWidth||c.lineWidth+ +e.lineWidthPlus):(e.color=e.color||K(e.color||g).brighten(e.brightness).get(),e.negativeColor=e.negativeColor||K(e.negativeColor||h).brighten(e.brightness).get());l[""]=a.convertAttribs(c,f);o(["hover","select"],function(b){l[b]=a.convertAttribs(d[b],l[""])});a.pointAttr=l;g=i.length;if(!k||g=f.value;)f=t[++j];k.color=k.fillColor=p(f.color,a.color)}j=b.colorByPoint||k.color; +if(k.options)for(y in m)r(c[m[y]])&&(j=!0);if(j){c=c||{};j=[];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"]||K(k.color).brighten(f.brightness||e.brightness).get();f={color:k.color};if(!u)f.fillColor=k.color;if(!n)f.lineColor=k.color;c.hasOwnProperty("color")&&!c.color&&delete c.color;j[""]=a.convertAttribs(q(f,c),l[""]);j.hover=a.convertAttribs(d.hover,l.hover, +j[""]);j.select=a.convertAttribs(d.select,l.select,j[""])}else j=l;k.pointAttr=j}},destroy:function(){var a=this,b=a.chart,c=/AppleWebKit\/533/.test(wa),d,e=a.data||[],f,g,h;F(a,"destroy");X(a);o(a.axisTypes||[],function(b){if(h=a[b])ua(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 P&&!a[g].survive&&(d=c&&g==="group"?"hide":"destroy", +a[g][d]());if(b.hoverSeries===a)b.hoverSeries=null;ua(b.series,a);for(g in a)delete a[g]},getSegmentPath:function(a){var b=this,c=[],d=b.options.step;o(a,function(e,f){var g=e.plotX,h=e.plotY,i;b.getPointSpline?c.push.apply(c,b.getPointSpline(a,e,f)):(c.push(f?"L":"M"),d&&f&&(i=a[f-1],d==="right"?c.push(i.plotX,h,"L"):d==="center"?c.push((i.plotX+g)/2,i.plotY,"L",(i.plotX+g)/2,h,"L"):c.push(g,i.plotY,"L")),c.push(e.plotX,e.plotY))});return c},getGraphPath:function(){var a=this,b=[],c,d=[];o(a.segments, +function(e){c=a.getSegmentPath(e);e.length>1?b=b.concat(c):d.push(e[0])});a.singlePoints=d;return a.graphPath=b},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.getGraphPath(),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,u=b.inverted,t=l.horiz,q,y,s,r=!1;if(d.length&&(i||j)&&l.min!==v)i&&i.hide(),j&&j.hide(),m=l.getExtremes(),o(d,function(d,o){e=n? +t?b.plotWidth:0:t?0:l.toPixels(m.min);e=E(w(p(f,e),0),k);f=E(w(x(l.toPixels(p(d.value,m.max),!0)),0),k);r&&(e=f=l.toPixels(m.max));q=Math.abs(e-f);y=E(e,f);s=w(e,f);if(l.isXAxis){if(h={x:u?s:y,y:0,width:q,height:k},!t)h.x=b.plotHeight-h.x}else if(h={x:0,y:u?s:y,width:k,height:q},t)h.y=b.plotWidth-h.y;b.inverted&&c.isVML&&(h=l.isXAxis?{x:0,y:n?y:s,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]));r=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)M(c,"resize",a),M(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({visibility:c,zIndex:d||0.1}).add(e),f.addClass("highcharts-series-"+ +this.index));f[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=(c=d.animation)&&!!a.animate&&b.renderer.isSVG&&p(c.duration,500)||0,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=Ja(function(){a.afterAnimate()},e);a.isDirty=a.isDirtyData=!1;a.hasRendered= +!0},redraw:function(){var a=this.chart,b=this.isDirtyData,c=this.isDirty,d=this.group,e=this.xAxis,f=this.yAxis;d&&(a.inverted&&d.attr({width:a.plotWidth,height:a.plotHeight}),d.animate({translateX:p(e&&e.left,a.plotLeft),translateY:p(f&&f.top,a.plotTop)}));this.translate();this.render();b&&F(this,"updatedData");(c||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;Ja(function(){var d=jb(b.points||[],function(a){return a.y!==null});b.kdTree=a(d,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,o,p=l;o=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=(o||0)+(n||0);l.dist=r(n)?Math.sqrt(n):Number.MAX_VALUE;l.distX=r(o)?Math.sqrt(o):Number.MAX_VALUE;m=a[m]-l[m];n=m<0?"left":"right";o=m<0?"right":"left";b[n]&&(n=c(a,b[n],j+1,k),p=n[g]m;)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(){e.length===g.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=p(b,!0);f?f.firePointEvent("remove",null,i):i()},remove:function(a,b){var c=this,d=c.chart,a=p(a,!0);if(!c.isRemoving)c.isRemoving=!0,F(c,"remove",null,function(){c.destroy();d.isDirtyLegend=d.isDirtyBox=!0;d.linkSeries();a&&d.redraw(b)});c.isRemoving=!1},update:function(a,b){var c=this,d=this.chart,e=this.userOptions,f=this.type,g=B[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]=v;q(this,B[a.type||f].prototype);o(h,function(a){c[a]=h[a]});this.init(d,a);d.linkSeries();p(b,!0)&&d.redraw(!1)}});q(Y.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=v;this.init(c,q(a,{events:v}));c.isDirtyBox=!0;p(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);ua(b.axes,this);ua(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;p(a,!0)&&b.redraw()},setTitle:function(a,b){this.update({title:a},b)},setCategories:function(a,b){this.update({categories:a},b)}});V=$(S);B.line=V;U.column=z(Ya,{borderColor:"#FFFFFF",borderRadius:0,groupPadding:0.2,marker:null,pointPadding:0.1,minPointLength:0,cropThreshold:50,pointRange:null, +states:{hover:{brightness:0.1,shadow:!1,halo:!1},select:{color:"#C0C0C0",borderColor:"#000000",shadow:!1}},dataLabels:{align:null,verticalAlign:null,y:null},softThreshold:!1,startFromThreshold:!0,stickyTracking:!1,tooltip:{distance:6},threshold:0});V=$(S,{type:"column",pointAttrToOptions:{stroke:"borderColor",fill:"color",r:"borderRadius"},cropShoulder:0,directTouch:!0,trackerGroups:["group","dataLabelsGroup"],negStacks:!0,init:function(){S.prototype.init.apply(this,arguments);var a=this,b=a.chart; +b.hasRendered&&o(b.series,function(b){if(b.type===a.type)b.isDirty=!0})},getColumnMetrics:function(){var a=this,b=a.options,c=a.xAxis,d=a.yAxis,e=c.reversed,f,g={},h,i=0;b.grouping===!1?i=1:o(a.chart.series,function(b){var c=b.options,e=b.yAxis;if(b.type===a.type&&b.visible&&d.len===e.len&&d.pos===e.pos)c.stacking?(f=b.stackKey,g[f]===v&&(g[f]=i++),h=g[f]):c.grouping!==!1&&(h=i++),b.columnIndex=h});var j=E(Q(c.transA)*(c.ordinalSlope||b.pointRange||c.closestPointRange||c.tickInterval||1),c.len),k= +j*b.groupPadding,l=(j-2*k)/i,b=E(b.maxPointWidth||c.len,p(b.pointWidth,l*(1-2*b.pointPadding)));a.columnMetrics={width:b,offset:(l-b)/2+(k+((a.columnIndex||0)+(e?1:0))*l-j/2)*(e?-1:1)};return a.columnMetrics},crispCol:function(a,b,c,d){var e=this.chart,f=this.borderWidth,g=-(f%2?0.5:0),f=f%2?0.5:1;e.inverted&&e.renderer.isVML&&(f+=1);c=Math.round(a+c)+g;a=Math.round(a)+g;c-=a;d=Math.round(b+d)+f;g=Q(b)<=0.5&&d>0.5;b=Math.round(b)+f;d-=b;g&&(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=p(c.borderWidth,a.closestPointRange*a.xAxis.transA<2?0:1),e=a.yAxis,f=a.translatedThreshold=e.getThreshold(c.threshold),g=p(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=sa(j));S.prototype.translate.apply(a);o(a.points,function(c){var d=E(p(c.yBottom,f),9E4),h=999+Q(d),h=E(w(-h,c.plotY),e.len+h),o=c.plotX+k,t=j,q=E(h,d),s,r=w(h,d)-q;Q(r)g?d-g:f-(s?g:0));c.barX=o;c.pointWidth=i;c.tooltipPos=b.inverted?[e.len+e.pos-b.plotLeft-h,a.xAxis.len-o-t/2,r]:[o+t/2,h+e.pos-b.plotTop,r];c.shapeType="rect";c.shapeArgs=a.crispCol(o,q,t,r)})},getSymbol:T,drawLegendSymbol:ab.drawRectangle,drawGraph:T,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.plotY,j=h.graphic;if(i!==v&&!isNaN(i)&&h.y!==null)f=h.shapeArgs,i=r(a.borderWidth)? +{"stroke-width":a.borderWidth}:{},g=h.pointAttr[h.selected?"select":""]||a.pointAttr[""],j?(Qa(j),j.attr(i).attr(g)[b.pointCount\u25cf {series.name}
',pointFormat:"x: {point.x}
y: {point.y}
"}}); +Ya=$(S,{type:"scatter",sorted:!1,requireSorting:!1,noSharedTooltip:!0,trackerGroups:["group","markerGroup","dataLabelsGroup"],takeOrdinalPosition:!1,kdDimensions:2,drawGraph:function(){this.options.lineWidth&&S.prototype.drawGraph.call(this)}});B.scatter=Ya;S.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=a.chart.renderer;if(d.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(d),j=a.plotGroup("dataLabelsGroup", +"data-labels",d.defer?"hidden":"visible",d.zIndex||6),p(d.defer,!0)&&(j.attr({opacity:+h}),h||M(a,"afterAnimate",function(){a.visible&&j.show();j[b.animation?"animate":"attr"]({opacity:1},{duration:200})})),g=d,o(e,function(e){var h,n=e.dataLabel,o,t,s=e.connector,y=!0,w,x={};f=e.dlOptions||e.options&&e.options.dataLabels;h=p(f&&f.enabled,g.enabled)&&e.y!==null;if(n&&!h)e.dataLabel=n.destroy();else if(h){d=z(g,f);w=d.style;h=d.rotation;o=e.getLabelConfig();i=d.format?Ka(d.format,o):d.formatter.call(o, +d);w.color=p(d.color,w.color,a.color,"black");if(n)if(r(i))n.attr({text:i}),y=!1;else{if(e.dataLabel=n=n.destroy(),s)e.connector=s.destroy()}else if(r(i)){n={fill:d.backgroundColor,stroke:d.borderColor,"stroke-width":d.borderWidth,r:d.borderRadius||0,rotation:h,padding:d.padding,zIndex:1};if(w.color==="contrast")x.color=d.inside||d.distance<0||b.stacking?k.getContrast(e.color||a.color):"#000000";if(c)x.cursor=c;for(t in n)n[t]===v&&delete n[t];n=e.dataLabel=k[h?"text":"label"](i,0,-9999,d.shape,null, +null,d.useHTML).attr(n).css(q(w,x)).add(j).shadow(d.shadow)}n&&a.alignDataLabel(e,n,d,null,y)}})};S.prototype.alignDataLabel=function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=p(a.plotX,-9999),i=p(a.plotY,-9999),j=b.getBBox(),k=f.renderer.fontMetrics(c.style.fontSize).b,l=this.visible&&(a.series.forceDL||f.isInsidePlot(h,x(i),g)||d&&f.isInsidePlot(h,g?d.x+1:d.y+d.height-1,g));if(l)d=q({x:g?f.plotWidth-i:h,y:x(g?f.plotHeight-h:i),width:0,height:0},d),q(c,{width:j.width,height:j.height}),c.rotation? +(a=f.renderer.rotCorr(k,c.rotation),b[e?"attr":"animate"]({x:d.x+c.x+d.width/2+a.x,y:d.y+c.y+d.height/2}).attr({align:c.align})):(b.align(c,null,d),g=b.alignAttr,p(c.overflow,"justify")==="justify"?this.justifyDataLabel(b,c,g,j,d,e):p(c.crop,!0)&&(l=f.isInsidePlot(g.x,g.y)&&f.isInsidePlot(g.x+j.width,g.y+j.height)),c.shape&&b.attr({anchorX:a.plotX,anchorY:a.plotY}));if(!l)Qa(b),b.attr({y:-9999}),b.placed=!1};S.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(B.pie)B.pie.prototype.drawDataLabels=function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=p(e.connectorPadding,10),g=p(e.connectorWidth, +1),h=d.plotWidth,i=d.plotHeight,j,k,l=p(e.softConnector,!0),m=e.distance,n=a.center,u=n[2]/2,t=n[1],q=m>0,s,r,v,z=[[],[]],B,A,C,G,H,F=[0,0,0,0],M=function(a,b){return b.y-a.y};if(a.visible&&(e.enabled||a._hasPointLabels)){S.prototype.drawDataLabels.apply(a);o(b,function(a){a.dataLabel&&a.visible&&z[a.half].push(a)});for(G=2;G--;){var I=[],N=[],J=z[G],L=J.length,K;if(L){a.sortByAngle(J,G-0.5);for(H=b=0;!b&&J[H];)b=J[H]&&J[H].dataLabel&&(J[H].dataLabel.getBBox().height||21),H++;if(m>0){r=E(t+u+m,d.plotHeight); +for(H=w(0,t-u-m);H<=r;H+=b)I.push(H);r=I.length;if(L>r){c=[].concat(J);c.sort(M);for(H=L;H--;)c[H].rank=H;for(H=L;H--;)J[H].rank>=r&&J.splice(H,1);L=J.length}for(H=0;H0){if(r=N.pop(),K=r.i,A= +r.y,c>A&&I[K+1]!==null||ch-f&&(F[1]=w(x(B+r-h+f),F[1])),A-b/2<0?F[0]=w(x(-A+b/2),F[0]):A+b/2>i&&(F[2]=w(x(A+b/2-i),F[2]))}}}if(Da(F)===0||this.verifyDataLabelOverflow(F))this.placeDataLabels(),q&&g&&o(this.points, +function(b){j=b.connector;v=b.labelPos;if((s=b.dataLabel)&&s._pos&&b.visible)C=s._attr.visibility,B=s.connX,A=s.connY,k=l?["M",B+(v[6]==="left"?5:-5),A,"C",B,A,2*v[2]-v[4],2*v[3]-v[5],v[2],v[3],"L",v[4],v[5]]:["M",B+(v[6]==="left"?5:-5),A,"L",v[2],v[3],"L",v[4],v[5]],j?(j.animate({d:k}),j.attr("visibility",C)):b.connector=j=a.chart.renderer.path(k).attr({"stroke-width":g,stroke:e.connectorColor||b.color||"#606060",visibility:C}).add(a.dataLabelsGroup);else if(j)b.connector=j.destroy()})}},B.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})})},B.pie.prototype.alignDataLabel=T,B.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(E(f,b[2]-w(a[0],a[2])),e):(f=w(E(f,b[2]-a[0]-a[2]),e),b[1]+=(a[0]-a[2])/2);fp(this.translatedThreshold,g.yAxis.len)),j=p(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=p(c.align,!f||j?"center":i?"right":"left");c.verticalAlign=p(c.verticalAlign,f||j?"middle":i?"top":"bottom");S.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;for(e=0;el.x+(j.width-n)||m.x+(k.width-n)l.y+(j.height-n)||m.y+(k.height-n)a.minPixelPadding||a.min===a.dataMin&&a.max===a.dataMax)c=0;a.minPixelPadding-=c}});ca(Y.prototype,"render", +function(a){a.call(this);this.fixTo=null});var bb=s.ColorAxis=function(){this.isColorAxis=!0;this.init.apply(this,arguments)};q(bb.prototype,Y.prototype);q(bb.prototype,{defaultColorAxisOptions:{lineWidth:0,minPadding:0,maxPadding:0,gridLineWidth:1,tickPixelInterval:72,startOnTick:!0,endOnTick:!0,offset:0,marker:{animation:{duration:50},color:"gray",width:0.01},labels:{overflow:"justify"},minColor:"#EFEFFF",maxColor:"#003875",tickLength:5},init:function(a,b){var c=a.options.legend.layout!=="vertical", +d;d=z(this.defaultColorAxisOptions,{side:c?2:1,reversed:!c},b,{opposite:!c,showEmpty:!1,title:null,isColor:!0});Y.prototype.init.call(this,a,d);b.dataClasses&&this.initDataClasses(b);this.initStops(b);this.horiz=c;this.zoomEnabled=!1},tweenColors:function(a,b,c){var d;!b.rgba.length||!a.rgba.length?a=b.input||"none":(a=a.rgba,b=b.rgba,d=b[3]!==1||a[3]!==1,a=(d?"rgba(":"rgb(")+Math.round(b[0]+(a[0]-b[0])*(1-c))+","+Math.round(b[1]+(a[1]-b[1])*(1-c))+","+Math.round(b[2]+(a[2]-b[2])*(1-c))+(d?","+(b[3]+ +(a[3]-b[3])*(1-c)):"")+")");return a},initDataClasses:function(a){var b=this,c=this.chart,d,e=0,f=this.options,g=a.dataClasses.length;this.dataClasses=d=[];this.legendItems=[];o(a.dataClasses,function(a,i){var j,a=z(a);d.push(a);if(!a.color)f.dataClassColor==="category"?(j=c.options.colors,a.color=j[e++],e===j.length&&(e=0)):a.color=b.tweenColors(K(f.minColor),K(f.maxColor),g<2?0.5:i/(g-1))})},initStops:function(a){this.stops=a.stops||[[0,this.options.minColor],[1,this.options.maxColor]];o(this.stops, +function(a){a.color=K(a[1])})},setOptions:function(a){Y.prototype.setOptions.call(this,a);this.options.crosshair=this.options.marker;this.coll="colorAxis"},setAxisSize:function(){var a=this.legendSymbol,b=this.chart,c,d,e;if(a)this.left=c=a.attr("x"),this.top=d=a.attr("y"),this.width=e=a.attr("width"),this.height=a=a.attr("height"),this.right=b.chartWidth-c-e,this.bottom=b.chartHeight-d-a,this.len=this.horiz?e:a,this.pos=this.horiz?c:d},toColor:function(a,b){var c,d=this.stops,e,f=this.dataClasses, +g,h;if(f)for(h=f.length;h--;){if(g=f[h],e=g.from,d=g.to,(e===v||a>=e)&&(d===v||a<=d)){c=g.color;if(b)b.dataClass=h;break}}else{this.isLog&&(a=this.val2lin(a));c=1-(this.max-a)/(this.max-this.min||1);for(h=d.length;h--;)if(c>d[h][0])break;e=d[h]||d[h+1];d=d[h+1]||e;c=1-(d[0]-c)/(d[0]-e[0]||1);c=this.tweenColors(e.color,d.color,c)}return c},getOffset:function(){var a=this.legendGroup,b=this.chart.axisOffset[this.side];if(a){this.axisParent=a;Y.prototype.getOffset.call(this);if(!this.added)this.added= +!0,this.labelLeft=0,this.labelRight=this.width;this.chart.axisOffset[this.side]=b}},setLegendColor:function(){var a,b=this.options,c=this.reversed;a=c?1:0;c=c?0:1;a=this.horiz?[a,0,c,0]:[0,c,0,a];this.legendColor={linearGradient:{x1:a[0],y1:a[1],x2:a[2],y2:a[3]},stops:b.stops||[[0,b.minColor],[1,b.maxColor]]}},drawLegendSymbol:function(a,b){var c=a.padding,d=a.options,e=this.horiz,f=p(d.symbolWidth,e?200:12),g=p(d.symbolHeight,e?12:200),h=p(d.labelPadding,e?16:30),d=p(d.itemDistance,10);this.setLegendColor(); +b.legendSymbol=this.chart.renderer.rect(0,a.baseline-11,f,g).attr({zIndex:1}).add(b.legendGroup);this.legendItemWidth=f+c+(e?d:h);this.legendItemHeight=g+c+(e?h:0)},setState:T,visible:!0,setVisible:T,getSeriesExtremes:function(){var a;if(this.series.length)a=this.series[0],this.dataMin=a.valueMin,this.dataMax=a.valueMax},drawCrosshair:function(a,b){var c=b&&b.plotX,d=b&&b.plotY,e,f=this.pos,g=this.len;if(b)e=this.toPixels(b[b.series.colorKey]),ef+g&&(e=f+g+2),b.plotX=e,b.plotY=this.len- +e,Y.prototype.drawCrosshair.call(this,a,b),b.plotX=c,b.plotY=d,this.cross&&this.cross.attr({fill:this.crosshair.color}).add(this.legendGroup)},getPlotLinePath:function(a,b,c,d,e){return typeof e==="number"?this.horiz?["M",e-4,this.top-6,"L",e+4,this.top-6,e,this.top,"Z"]:["M",this.left,e,"L",this.left-6,e+6,this.left-6,e-6,"Z"]:Y.prototype.getPlotLinePath.call(this,a,b,c,d)},update:function(a,b){var c=this.chart,d=c.legend;o(this.series,function(a){a.isDirtyData=!0});if(a.dataClasses&&d.allItems)o(d.allItems, +function(a){a.isDataClass&&a.legendGroup.destroy()}),c.isDirtyLegend=!0;c.options[this.coll]=z(this.userOptions,a);Y.prototype.update.call(this,a,b);this.legendItem&&(this.setLegendColor(),d.colorizeItem(this,!0))},getDataClassLegendSymbols:function(){var a=this,b=this.chart,c=this.legendItems,d=b.options.legend,e=d.valueDecimals,f=d.valueSuffix||"",g;c.length||o(this.dataClasses,function(d,i){var j=!0,k=d.from,l=d.to;g="";k===v?g="< ":l===v&&(g="> ");k!==v&&(g+=s.numberFormat(k,e)+f);k!==v&&l!== +v&&(g+=" - ");l!==v&&(g+=s.numberFormat(l,e)+f);c.push(q({chart:b,name:g,options:{},drawLegendSymbol:ab.drawRectangle,visible:!0,setState:T,isDataClass:!0,setVisible:function(){j=this.visible=!j;o(a.series,function(a){o(a.points,function(a){a.dataClass===i&&a.setVisible(j)})});b.legend.colorizeItem(this,j)}},d))});return c},name:""});o(["fill","stroke"],function(a){s.addAnimSetter(a,function(b){b.elem.attr(a,bb.prototype.tweenColors(K(b.start),K(b.end),b.pos))})});ca(la.prototype,"getAxes",function(a){var b= +this.options.colorAxis;a.call(this);this.colorAxis=[];b&&new bb(this,b)});ca($a.prototype,"getAllItems",function(a){var b=[],c=this.chart.colorAxis[0];c&&(c.options.dataClasses?b=b.concat(c.getDataClassLegendSymbols()):b.push(c),o(c.series,function(a){a.options.showInLegend=!1}));return b.concat(a.call(this))});var Ia={setVisible:function(a){var b=this,c=a?"show":"hide";o(["graphic","dataLabel"],function(a){if(b[a])b[a][c]()})}},Kb={pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth", +fill:"color",dashstyle:"dashStyle"},pointArrayMap:["value"],axisTypes:["xAxis","yAxis","colorAxis"],optionalAxis:"colorAxis",trackerGroups:["group","markerGroup","dataLabelsGroup"],getSymbol:T,parallelArrays:["x","y","value"],colorKey:"value",translateColors:function(){var a=this,b=this.options.nullColor,c=this.colorAxis,d=this.colorKey;o(this.data,function(e){var f=e[d];if(f=e.options.color||(f===null?b:c&&f!==void 0?c.toColor(f,e):e.color||a.color))e.color=f})}},kb=document.documentElement.style.vectorEffect!== +void 0;U.map=z(U.scatter,{allAreas:!0,animation:!1,nullColor:"#F8F8F8",borderColor:"silver",borderWidth:1,marker:null,stickyTracking:!1,dataLabels:{formatter:function(){return this.point.value},inside:!0,verticalAlign:"middle",crop:!1,overflow:!1,padding:0},turboThreshold:0,tooltip:{followPointer:!0,pointFormat:"{point.name}: {point.value}
"},states:{normal:{animation:!0},hover:{brightness:0.2,halo:null}}});var Lb=$(Z,q({applyOptions:function(a,b){var c=Z.prototype.applyOptions.call(this,a,b), +d=this.series,e=d.joinBy;if(d.mapData)if(e=c[e[1]]!==void 0&&d.mapMap[c[e[1]]]){if(d.xyFromShape)c.x=e._midX,c.y=e._midY;q(c,e)}else c.value=c.value||null;return c},onMouseOver:function(a){clearTimeout(this.colorInterval);if(this.value!==null)Z.prototype.onMouseOver.call(this,a);else this.series.onMouseOut(a)},onMouseOut:function(){var a=this,b=+new Fa,c=K(a.color),d=K(a.pointAttr.hover.fill),e=a.series.options.states.normal.animation,f=e&&(e.duration||500),g;if(f&&c.rgba.length===4&&d.rgba.length=== +4&&a.state!=="select")g=a.pointAttr[""].fill,delete a.pointAttr[""].fill,clearTimeout(a.colorInterval),a.colorInterval=setInterval(function(){var e=(new Fa-b)/f,g=a.graphic;e>1&&(e=1);g&&g.attr("fill",bb.prototype.tweenColors.call(0,d,c,e));e>=1&&clearTimeout(a.colorInterval)},13);Z.prototype.onMouseOut.call(a);if(g)a.pointAttr[""].fill=g},zoomTo:function(){var a=this.series;a.xAxis.setExtremes(this._minX,this._maxX,!1);a.yAxis.setExtremes(this._minY,this._maxY,!1);a.chart.redraw()}},Ia));B.map=$(B.scatter, +z(Kb,{type:"map",pointClass:Lb,supportsDrilldown:!0,getExtremesFromAll:!0,useMapGeometry:!0,forceDL:!0,searchPoint:T,directTouch:!0,preserveAspectRatio:!0,getBox:function(a){var b=Number.MAX_VALUE,c=-b,d=b,e=-b,f=b,g=b,h=this.xAxis,i=this.yAxis,j;o(a||[],function(a){if(a.path){if(typeof a.path==="string")a.path=s.splitPath(a.path);var h=a.path||[],i=h.length,n=!1,o=-b,t=b,q=-b,r=b,v=a.properties;if(!a._foundBox){for(;i--;)typeof h[i]==="number"&&!isNaN(h[i])&&(n?(o=Math.max(o,h[i]),t=Math.min(t,h[i])): +(q=Math.max(q,h[i]),r=Math.min(r,h[i])),n=!n);a._midX=t+(o-t)*(a.middleX||v&&v["hc-middle-x"]||0.5);a._midY=r+(q-r)*(a.middleY||v&&v["hc-middle-y"]||0.5);a._maxX=o;a._minX=t;a._maxY=q;a._minY=r;a.labelrank=p(a.labelrank,(o-t)*(q-r));a._foundBox=!0}c=Math.max(c,a._maxX);d=Math.min(d,a._minX);e=Math.max(e,a._maxY);f=Math.min(f,a._minY);g=Math.min(a._maxX-a._minX,a._maxY-a._minY,g);j=!0}});if(j){this.minY=Math.min(f,p(this.minY,b));this.maxY=Math.max(e,p(this.maxY,-b));this.minX=Math.min(d,p(this.minX, +b));this.maxX=Math.max(c,p(this.maxX,-b));if(h&&h.options.minRange===void 0)h.minRange=Math.min(5*g,(this.maxX-this.minX)/5,h.minRange||b);if(i&&i.options.minRange===void 0)i.minRange=Math.min(5*g,(this.maxY-this.minY)/5,i.minRange||b)}},getExtremes:function(){S.prototype.getExtremes.call(this,this.valueData);this.chart.hasRendered&&this.isDirtyData&&this.getBox(this.options.data);this.valueMin=this.dataMin;this.valueMax=this.dataMax;this.dataMin=this.minY;this.dataMax=this.maxY},translatePath:function(a){var b= +!1,c=this.xAxis,d=this.yAxis,e=c.min,f=c.transA,c=c.minPixelPadding,g=d.min,h=d.transA,d=d.minPixelPadding,i,j=[];if(a)for(i=a.length;i--;)typeof a[i]==="number"?(j[i]=b?(a[i]-e)*f+c:(a[i]-g)*h+d,b=!b):j[i]=a[i];return j},setData:function(a,b){var c=this.options,d=c.mapData,e=c.joinBy,f=e===null,g=[],h,i,j;f&&(e="_i");e=this.joinBy=s.splat(e);e[1]||(e[1]=e[0]);a&&o(a,function(b,c){typeof b==="number"&&(a[c]={value:b});if(f)a[c]._i=c});this.getBox(a);if(d){if(d.type==="FeatureCollection"){if(d["hc-transform"])for(h in this.chart.mapTransforms= +i=d["hc-transform"],i)if(i.hasOwnProperty(h)&&h.rotation)h.cosAngle=Math.cos(h.rotation),h.sinAngle=Math.sin(h.rotation);d=s.geojson(d,this.type,this)}this.getBox(d);this.mapData=d;this.mapMap={};for(j=0;j0.99&&g<1.01&&d>0.99&&d<1.01&&(d=g=1,b=Math.round(b),c=Math.round(c)),this.transformGroup.animate({translateX:b,translateY:c,scaleX:g,scaleY:d}));kb||a.group.element.setAttribute("stroke-width",a.options.borderWidth/(g||1));this.drawMapDataLabels()},drawMapDataLabels:function(){S.prototype.drawDataLabels.call(this);this.dataLabelsGroup&&this.dataLabelsGroup.clip(this.chart.clipRect)}, +render:function(){var a=this,b=S.prototype.render;a.chart.renderer.isVML&&a.data.length>3E3?setTimeout(function(){b.call(a)}):b.call(a)},animate:function(a){var b=this.options.animation,c=this.group,d=this.xAxis,e=this.yAxis,f=d.pos,g=e.pos;if(this.chart.renderer.isSVG)b===!0&&(b={duration:1E3}),a?c.attr({translateX:f+d.len/2,translateY:g+e.len/2,scaleX:0.001,scaleY:0.001}):(c.animate({translateX:f,translateY:g,scaleX:1,scaleY:1},b),this.animate=null)},animateDrilldown:function(a){var b=this.chart.plotBox, +c=this.chart.drilldownLevels[this.chart.drilldownLevels.length-1],d=c.bBox,e=this.chart.options.drilldown.animation;if(!a)a=Math.min(d.width/b.width,d.height/b.height),c.shapeArgs={scaleX:a,scaleY:a,translateX:d.x,translateY:d.y},o(this.points,function(a){a.graphic&&a.graphic.attr(c.shapeArgs).animate({scaleX:1,scaleY:1,translateX:0,translateY:0},e)}),this.animate=null},drawLegendSymbol:ab.drawRectangle,animateDrillupFrom:function(a){B.column.prototype.animateDrillupFrom.call(this,a)},animateDrillupTo:function(a){B.column.prototype.animateDrillupTo.call(this, +a)}}));(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;for(e=0;el.x+(j.width-n)||m.x+(k.width-n)l.y+(j.height-n)||m.y+(k.height-n)b[d]+b[c]&&(a[c]>b[c]?(a[c]=b[c],a[d]=b[d]):a[d]=b[d]+b[c]- +a[c]);a[c]>b[c]&&(a[c]=b[c]);a[d]d.scaleY,this.pinchTranslateDirection(!a,b,c,d,e, +f,g,a?d.scaleX:d.scaleY))});U.mapline=z(U.map,{lineWidth:1,fillColor:"none"});B.mapline=$(B.map,{type:"mapline",pointAttrToOptions:{stroke:"color","stroke-width":"lineWidth",fill:"fillColor",dashstyle:"dashStyle"},drawLegendSymbol:B.line.prototype.drawLegendSymbol});U.mappoint=z(U.scatter,{dataLabels:{enabled:!0,formatter:function(){return this.point.name},crop:!1,defer:!1,overflow:!1,style:{color:"#000000"}}});B.mappoint=$(B.scatter,{type:"mappoint",forceDL:!0,pointClass:$(Z,{applyOptions:function(a, +b){var c=Z.prototype.applyOptions.call(this,a,b);a.lat!==void 0&&a.lon!==void 0&&(c=q(c,this.series.chart.fromLatLonToPoint(c)));return c}})});U.bubble=z(U.scatter,{dataLabels:{formatter:function(){return this.point.z},inside:!0,verticalAlign:"middle"},marker:{lineColor:null,lineWidth:1},minSize:8,maxSize:"20%",softThreshold:!1,states:{hover:{halo:{size:5}}},tooltip:{pointFormat:"({point.x}, {point.y}), Size: {point.z}"},turboThreshold:0,zThreshold:0,zoneAxis:"z"});var Qb=$(Z,{haloPath:function(){return Z.prototype.haloPath.call(this, +this.shapeArgs.r+this.series.options.states.hover.halo.size)},ttBelow:!1});B.bubble=$(B.scatter,{type:"bubble",pointClass:Qb,pointArrayMap:["y","z"],parallelArrays:["x","y","z"],trackerGroups:["group","dataLabelsGroup"],bubblePadding:!0,zoneAxis:"z",pointAttrToOptions:{stroke:"lineColor","stroke-width":"lineWidth",fill:"fillColor"},applyOpacity:function(a){var b=this.options.marker,c=p(b.fillOpacity,0.5),a=a||b.fillColor||this.color;c!==1&&(a=K(a).setOpacity(c).get("rgba"));return a},convertAttribs:function(){var a= +S.prototype.convertAttribs.apply(this,arguments);a.fill=this.applyOpacity(a.fill);return a},getRadii:function(a,b,c,d){var e,f,g,h=this.zData,i=[],j=this.options,k=j.sizeBy!=="width",l=j.zThreshold,m=b-a;for(f=0,e=h.length;f0?(g-a)/m:0.5,k&&g>=0&&(g=Math.sqrt(g)),g=I.ceil(c+g*(d-c))/2),i.push(g);this.radii=i},animate:function(a){var b=this.options.animation;if(!a)o(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,e=this.radii;B.scatter.prototype.translate.call(this);for(a=b.length;a--;)c=b[a],d=e?e[a]:0,typeof d==="number"&&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=v},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:B.column.prototype.drawPoints,alignDataLabel:B.column.prototype.alignDataLabel,buildKDTree:T,applyZones:T});Y.prototype.beforePadding=function(){var a=this,b=this.len,c=this.chart,d=0,e=b,f=this.isXAxis,g=f?"xData":"yData",h=this.min,i={},j=I.min(c.plotWidth,c.plotHeight),k=Number.MAX_VALUE,l=-Number.MAX_VALUE,m=this.max-h,n=b/m,q=[];o(this.series,function(b){var d=b.options;if(b.bubblePadding&& +(b.visible||!c.options.chart.ignoreHiddenSeries))if(a.allowZoomOutside=!0,q.push(b),f)o(["minSize","maxSize"],function(a){var b=d[a],c=/%$/.test(b),b=C(b);i[a]=c?j*b/100:b}),b.minPxSize=i.minSize,b.maxPxSize=i.maxSize,b=b.zData,b.length&&(k=p(d.zMin,I.min(k,I.max(Sa(b),d.displayNegative===!1?d.zThreshold:-Number.MAX_VALUE))),l=p(d.zMax,I.max(l,Da(b))))});o(q,function(a){var b=a[g],c=b.length,i;f&&a.getRadii(k,l,a.minPxSize,a.maxPxSize);if(m>0)for(;c--;)typeof b[c]==="number"&&(i=a.radii[c],d=Math.min((b[c]- +h)*n-i,d),e=Math.max((b[c]-h)*n+i,e))});q.length&&m>0&&!this.isLog&&(e-=b,n*=(b+d-e)/b,o([["min","userMin",d],["max","userMax",e]],function(b){p(a.options[b[0]],a[b[1]])===v&&(a[b[0]]+=b[2]/n)}))};if(B.bubble)U.mapbubble=z(U.bubble,{animationLimit:500,tooltip:{pointFormat:"{point.name}: {point.z}"}}),B.mapbubble=$(B.bubble,{pointClass:$(Z,{applyOptions:function(a,b){var c;a&&a.lat!==void 0&&a.lon!==void 0?(c=Z.prototype.applyOptions.call(this,a,b),c=q(c,this.series.chart.fromLatLonToPoint(c))):c= +Lb.prototype.applyOptions.call(this,a,b);return c},ttBelow:!1}),xyFromShape:!0,type:"mapbubble",pointArrayMap:["z"],getMapData:B.map.prototype.getMapData,getBox:B.map.prototype.getBox,setData:B.map.prototype.setData});la.prototype.transformFromLatLon=function(a,b){if(window.proj4===void 0)return O(21),{x:0,y:null};var c=window.proj4(b.crs,[a.lon,a.lat]),d=b.cosAngle||b.rotation&&Math.cos(b.rotation),e=b.sinAngle||b.rotation&&Math.sin(b.rotation),c=b.rotation?[c[0]*d+c[1]*e,-c[0]*e+c[1]*d]:c;return{x:((c[0]- +(b.xoffset||0))*(b.scale||1)+(b.xpan||0))*(b.jsonres||1)+(b.jsonmarginX||0),y:(((b.yoffset||0)-c[1])*(b.scale||1)+(b.ypan||0))*(b.jsonres||1)-(b.jsonmarginY||0)}};la.prototype.transformToLatLon=function(a,b){if(window.proj4===void 0)O(21);else{var c={x:((a.x-(b.jsonmarginX||0))/(b.jsonres||1)-(b.xpan||0))/(b.scale||1)+(b.xoffset||0),y:((-a.y-(b.jsonmarginY||0))/(b.jsonres||1)+(b.ypan||0))/(b.scale||1)+(b.yoffset||0)},d=b.cosAngle||b.rotation&&Math.cos(b.rotation),e=b.sinAngle||b.rotation&&Math.sin(b.rotation), +c=window.proj4(b.crs,"WGS84",b.rotation?{x:c.x*d+c.y*-e,y:c.x*e+c.y*d}:c);return{lat:c.y,lon:c.x}}};la.prototype.fromPointToLatLon=function(a){var b=this.mapTransforms,c;if(b){for(c in b)if(b.hasOwnProperty(c)&&b[c].hitZone&&Cb({x:a.x,y:-a.y},b[c].hitZone.coordinates[0]))return this.transformToLatLon(a,b[c]);return this.transformToLatLon(a,b["default"])}else O(22)};la.prototype.fromLatLonToPoint=function(a){var b=this.mapTransforms,c,d;if(!b)return O(22),{x:0,y:null};for(c in b)if(b.hasOwnProperty(c)&& +b[c].hitZone&&(d=this.transformFromLatLon(a,b[c]),Cb({x:d.x,y:-d.y},b[c].hitZone.coordinates[0])))return d;return this.transformFromLatLon(a,b["default"])};s.geojson=function(a,b,c){var d=[],e=[],f=function(a){var b,c=a.length;e.push("M");for(b=0;bHighcharts
\u00a9 '+a.copyrightShort+"",c.chart.mapCreditsFull=a.copyright;return d};ca(la.prototype,"showCredits",function(a,b){if(N.credits.text=== +this.options.credits.text&&this.mapCredits)b.text=this.mapCredits,b.href=null;a.call(this,b);this.credits&&this.credits.attr({title:this.mapCreditsFull})});q(N.lang,{zoomIn:"Zoom in",zoomOut:"Zoom out"});N.mapNavigation={buttonOptions:{alignTo:"plotBox",align:"left",verticalAlign:"top",x:0,width:18,height:18,style:{fontSize:"15px",fontWeight:"bold",textAlign:"center"},theme:{"stroke-width":1}},buttons:{zoomIn:{onclick:function(){this.mapZoom(0.5)},text:"+",y:0},zoomOut:{onclick:function(){this.mapZoom(2)}, +text:"-",y:28}}};s.splitPath=function(a){var b,a=a.replace(/([A-Za-z])/g," $1 "),a=a.replace(/^\s*/,"").replace(/\s*$/,""),a=a.split(/[ ,]+/);for(b=0;b"},states:{normal:{animation:!0},hover:{halo:!1,brightness:0.2}}});B.heatmap=$(B.scatter,z(Kb,{type:"heatmap",pointArrayMap:["y","value"],hasPointSpecificOptions:!0,pointClass:$(Z,Ia),supportsDrilldown:!0,getExtremesFromAll:!0,directTouch:!0,init:function(){var a;B.scatter.prototype.init.apply(this,arguments);a=this.options;a.pointRange=p(a.pointRange,a.colsize||1);this.yAxis.axisPointRange=a.rowsize|| +1},translate:function(){var a=this.options,b=this.xAxis,c=this.yAxis,d=function(a,b,c){return Math.min(Math.max(b,a),c)};this.generatePoints();o(this.points,function(e){var f=(a.colsize||1)/2,g=(a.rowsize||1)/2,h=d(Math.round(b.len-b.translate(e.x-f,0,1,0,1)),0,b.len),f=d(Math.round(b.len-b.translate(e.x+f,0,1,0,1)),0,b.len),i=d(Math.round(c.translate(e.y-g,0,1,0,1)),0,c.len),g=d(Math.round(c.translate(e.y+g,0,1,0,1)),0,c.len);e.plotX=e.clientX=(h+f)/2;e.plotY=(i+g)/2;e.shapeType="rect";e.shapeArgs= +{x:Math.min(h,f),y:Math.min(i,g),width:Math.abs(f-h),height:Math.abs(g-i)}});this.translateColors();this.chart.hasRendered&&o(this.points,function(a){a.shapeArgs.fill=a.options.color||a.color})},drawPoints:B.column.prototype.drawPoints,animate:T,getBox:T,drawLegendSymbol:ab.drawRectangle,getExtremes:function(){S.prototype.getExtremes.call(this,this.valueData);this.valueMin=this.dataMin;this.valueMax=this.dataMax;S.prototype.getExtremes.call(this)}}));Ia=s.TrackerMixin={drawTrackerPoint:function(){var a= +this,b=a.chart,c=b.pointer,d=a.options.cursor,e=d&&{cursor:d},f=function(a){for(var c=a.target,d;c&&!d;)d=c.point,c=c.parentNode;if(d!==v&&d!==b.hoverPoint)d.onMouseOver(a)};o(a.points,function(a){if(a.graphic)a.graphic.element.point=a;if(a.dataLabel)a.dataLabel.element.point=a});if(!a._hasTracking)o(a.trackerGroups,function(b){if(a[b]&&(a[b].addClass("highcharts-tracker").on("mouseover",f).on("mouseout",function(a){c.onTrackerMouseOut(a)}).css(e),Ua))a[b].on("touchstart",f)}),a._hasTracking=!0}, +drawTrackerGraph:function(){var a=this,b=a.options,c=b.trackByArea,d=[].concat(c?a.areaPath:a.graphPath),e=d.length,f=a.chart,g=f.pointer,h=f.renderer,i=f.options.tooltip.snap,j=a.tracker,k=b.cursor,l=k&&{cursor:k},k=a.singlePoints,m,n=function(){if(f.hoverSeries!==a)a.onMouseOver()},p="rgba(192,192,192,"+(aa?1.0E-4:0.002)+")";if(e&&!c)for(m=e+1;m--;)d[m]==="M"&&d.splice(m+1,0,d[m+1]-i,d[m+2],"L"),(m&&d[m]==="M"||m===e)&&d.splice(m,0,"L",d[m-2]+i,d[m-1]);for(m=0;md;if(h.series.length&& +(i||l>E(k.dataMin,k.min))&&(!i||j