UNPKG

1.34 MBJavaScriptView Raw
1/**
2 * @license Highcharts JS v6.0.3 (2017-11-14)
3 *
4 * (c) 2009-2016 Torstein Honsi
5 *
6 * License: www.highcharts.com/license
7 */
8'use strict';
9(function(root, factory) {
10 if (typeof module === 'object' && module.exports) {
11 module.exports = root.document ?
12 factory(root) :
13 factory;
14 } else {
15 root.Highcharts = factory(root);
16 }
17}(typeof window !== 'undefined' ? window : this, function(win) {
18 var Highcharts = (function() {
19 /**
20 * (c) 2010-2017 Torstein Honsi
21 *
22 * License: www.highcharts.com/license
23 */
24 /* global win, window */
25
26 // glob is a temporary fix to allow our es-modules to work.
27 var glob = typeof win === 'undefined' ? window : win,
28 doc = glob.document,
29 SVG_NS = 'http://www.w3.org/2000/svg',
30 userAgent = (glob.navigator && glob.navigator.userAgent) || '',
31 svg = doc && doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
32 isMS = /(edge|msie|trident)/i.test(userAgent) && !glob.opera,
33 isFirefox = /Firefox/.test(userAgent),
34 hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4; // issue #38;
35
36 var Highcharts = glob.Highcharts ? glob.Highcharts.error(16, true) : {
37 product: 'Highcharts',
38 version: '6.0.3',
39 deg2rad: Math.PI * 2 / 360,
40 doc: doc,
41 hasBidiBug: hasBidiBug,
42 hasTouch: doc && doc.documentElement.ontouchstart !== undefined,
43 isMS: isMS,
44 isWebKit: /AppleWebKit/.test(userAgent),
45 isFirefox: isFirefox,
46 isTouchDevice: /(Mobile|Android|Windows Phone)/.test(userAgent),
47 SVG_NS: SVG_NS,
48 chartCount: 0,
49 seriesTypes: {},
50 symbolSizes: {},
51 svg: svg,
52 win: glob,
53 marginNames: ['plotTop', 'marginRight', 'marginBottom', 'plotLeft'],
54 noop: function() {
55 return undefined;
56 },
57 /**
58 * An array containing the current chart objects in the page. A chart's
59 * position in the array is preserved throughout the page's lifetime. When
60 * a chart is destroyed, the array item becomes `undefined`.
61 * @type {Array.<Highcharts.Chart>}
62 * @memberOf Highcharts
63 */
64 charts: []
65 };
66 return Highcharts;
67 }());
68 (function(H) {
69 /**
70 * (c) 2010-2017 Torstein Honsi
71 *
72 * License: www.highcharts.com/license
73 */
74 /* eslint max-len: ["warn", 80, 4] */
75
76 /**
77 * The Highcharts object is the placeholder for all other members, and various
78 * utility functions. The most important member of the namespace would be the
79 * chart constructor.
80 *
81 * @example
82 * var chart = Highcharts.chart('container', { ... });
83 *
84 * @namespace Highcharts
85 */
86
87 H.timers = [];
88
89 var charts = H.charts,
90 doc = H.doc,
91 win = H.win;
92
93 /**
94 * Provide error messages for debugging, with links to online explanation. This
95 * function can be overridden to provide custom error handling.
96 *
97 * @function #error
98 * @memberOf Highcharts
99 * @param {Number|String} code - The error code. See [errors.xml]{@link
100 * https://github.com/highcharts/highcharts/blob/master/errors/errors.xml}
101 * for available codes. If it is a string, the error message is printed
102 * directly in the console.
103 * @param {Boolean} [stop=false] - Whether to throw an error or just log a
104 * warning in the console.
105 *
106 * @sample highcharts/chart/highcharts-error/ Custom error handler
107 */
108 H.error = function(code, stop) {
109 var msg = H.isNumber(code) ?
110 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code :
111 code;
112 if (stop) {
113 throw new Error(msg);
114 }
115 // else ...
116 if (win.console) {
117 console.log(msg); // eslint-disable-line no-console
118 }
119 };
120
121 /**
122 * An animator object used internally. One instance applies to one property
123 * (attribute or style prop) on one element. Animation is always initiated
124 * through {@link SVGElement#animate}.
125 *
126 * @constructor Fx
127 * @memberOf Highcharts
128 * @param {HTMLDOMElement|SVGElement} elem - The element to animate.
129 * @param {AnimationOptions} options - Animation options.
130 * @param {string} prop - The single attribute or CSS property to animate.
131 * @private
132 *
133 * @example
134 * var rect = renderer.rect(0, 0, 10, 10).add();
135 * rect.animate({ width: 100 });
136 */
137 H.Fx = function(elem, options, prop) {
138 this.options = options;
139 this.elem = elem;
140 this.prop = prop;
141 };
142 H.Fx.prototype = {
143
144 /**
145 * Set the current step of a path definition on SVGElement.
146 *
147 * @function #dSetter
148 * @memberOf Highcharts.Fx
149 */
150 dSetter: function() {
151 var start = this.paths[0],
152 end = this.paths[1],
153 ret = [],
154 now = this.now,
155 i = start.length,
156 startVal;
157
158 // Land on the final path without adjustment points appended in the ends
159 if (now === 1) {
160 ret = this.toD;
161
162 } else if (i === end.length && now < 1) {
163 while (i--) {
164 startVal = parseFloat(start[i]);
165 ret[i] =
166 isNaN(startVal) ? // a letter instruction like M or L
167 end[i] :
168 now * (parseFloat(end[i] - startVal)) + startVal;
169
170 }
171 // If animation is finished or length not matching, land on right value
172 } else {
173 ret = end;
174 }
175 this.elem.attr('d', ret, null, true);
176 },
177
178 /**
179 * Update the element with the current animation step.
180 *
181 * @function #update
182 * @memberOf Highcharts.Fx
183 */
184 update: function() {
185 var elem = this.elem,
186 prop = this.prop, // if destroyed, it is null
187 now = this.now,
188 step = this.options.step;
189
190 // Animation setter defined from outside
191 if (this[prop + 'Setter']) {
192 this[prop + 'Setter']();
193
194 // Other animations on SVGElement
195 } else if (elem.attr) {
196 if (elem.element) {
197 elem.attr(prop, now, null, true);
198 }
199
200 // HTML styles, raw HTML content like container size
201 } else {
202 elem.style[prop] = now + this.unit;
203 }
204
205 if (step) {
206 step.call(elem, now, this);
207 }
208
209 },
210
211 /**
212 * Run an animation.
213 *
214 * @function #run
215 * @memberOf Highcharts.Fx
216 * @param {Number} from - The current value, value to start from.
217 * @param {Number} to - The end value, value to land on.
218 * @param {String} [unit] - The property unit, for example `px`.
219 *
220 */
221 run: function(from, to, unit) {
222 var self = this,
223 options = self.options,
224 timer = function(gotoEnd) {
225 return timer.stopped ? false : self.step(gotoEnd);
226 },
227 requestAnimationFrame =
228 win.requestAnimationFrame ||
229 function(step) {
230 setTimeout(step, 13);
231 },
232 step = function() {
233 H.timers = H.grep(H.timers, function(timer) {
234 return timer();
235 });
236
237 if (H.timers.length) {
238 requestAnimationFrame(step);
239 }
240 };
241
242 if (from === to) {
243 delete options.curAnim[this.prop];
244 if (options.complete && H.keys(options.curAnim).length === 0) {
245 options.complete();
246 }
247 } else { // #7166
248 this.startTime = +new Date();
249 this.start = from;
250 this.end = to;
251 this.unit = unit;
252 this.now = this.start;
253 this.pos = 0;
254
255 timer.elem = this.elem;
256 timer.prop = this.prop;
257
258 if (timer() && H.timers.push(timer) === 1) {
259 requestAnimationFrame(step);
260 }
261 }
262 },
263
264 /**
265 * Run a single step in the animation.
266 *
267 * @function #step
268 * @memberOf Highcharts.Fx
269 * @param {Boolean} [gotoEnd] - Whether to go to the endpoint of the
270 * animation after abort.
271 * @returns {Boolean} Returns `true` if animation continues.
272 */
273 step: function(gotoEnd) {
274 var t = +new Date(),
275 ret,
276 done,
277 options = this.options,
278 elem = this.elem,
279 complete = options.complete,
280 duration = options.duration,
281 curAnim = options.curAnim;
282
283 if (elem.attr && !elem.element) { // #2616, element is destroyed
284 ret = false;
285
286 } else if (gotoEnd || t >= duration + this.startTime) {
287 this.now = this.end;
288 this.pos = 1;
289 this.update();
290
291 curAnim[this.prop] = true;
292
293 done = true;
294
295 H.objectEach(curAnim, function(val) {
296 if (val !== true) {
297 done = false;
298 }
299 });
300
301 if (done && complete) {
302 complete.call(elem);
303 }
304 ret = false;
305
306 } else {
307 this.pos = options.easing((t - this.startTime) / duration);
308 this.now = this.start + ((this.end - this.start) * this.pos);
309 this.update();
310 ret = true;
311 }
312 return ret;
313 },
314
315 /**
316 * Prepare start and end values so that the path can be animated one to one.
317 *
318 * @function #initPath
319 * @memberOf Highcharts.Fx
320 * @param {SVGElement} elem - The SVGElement item.
321 * @param {String} fromD - Starting path definition.
322 * @param {Array} toD - Ending path definition.
323 * @returns {Array} An array containing start and end paths in array form
324 * so that they can be animated in parallel.
325 */
326 initPath: function(elem, fromD, toD) {
327 fromD = fromD || '';
328 var shift,
329 startX = elem.startX,
330 endX = elem.endX,
331 bezier = fromD.indexOf('C') > -1,
332 numParams = bezier ? 7 : 3,
333 fullLength,
334 slice,
335 i,
336 start = fromD.split(' '),
337 end = toD.slice(), // copy
338 isArea = elem.isArea,
339 positionFactor = isArea ? 2 : 1,
340 reverse;
341
342 /**
343 * In splines make moveTo and lineTo points have six parameters like
344 * bezier curves, to allow animation one-to-one.
345 */
346 function sixify(arr) {
347 var isOperator,
348 nextIsOperator;
349 i = arr.length;
350 while (i--) {
351
352 // Fill in dummy coordinates only if the next operator comes
353 // three places behind (#5788)
354 isOperator = arr[i] === 'M' || arr[i] === 'L';
355 nextIsOperator = /[a-zA-Z]/.test(arr[i + 3]);
356 if (isOperator && nextIsOperator) {
357 arr.splice(
358 i + 1, 0,
359 arr[i + 1], arr[i + 2],
360 arr[i + 1], arr[i + 2]
361 );
362 }
363 }
364 }
365
366 /**
367 * Insert an array at the given position of another array
368 */
369 function insertSlice(arr, subArr, index) {
370 [].splice.apply(
371 arr, [index, 0].concat(subArr)
372 );
373 }
374
375 /**
376 * If shifting points, prepend a dummy point to the end path.
377 */
378 function prepend(arr, other) {
379 while (arr.length < fullLength) {
380
381 // Move to, line to or curve to?
382 arr[0] = other[fullLength - arr.length];
383
384 // Prepend a copy of the first point
385 insertSlice(arr, arr.slice(0, numParams), 0);
386
387 // For areas, the bottom path goes back again to the left, so we
388 // need to append a copy of the last point.
389 if (isArea) {
390 insertSlice(
391 arr,
392 arr.slice(arr.length - numParams), arr.length
393 );
394 i--;
395 }
396 }
397 arr[0] = 'M';
398 }
399
400 /**
401 * Copy and append last point until the length matches the end length
402 */
403 function append(arr, other) {
404 var i = (fullLength - arr.length) / numParams;
405 while (i > 0 && i--) {
406
407 // Pull out the slice that is going to be appended or inserted.
408 // In a line graph, the positionFactor is 1, and the last point
409 // is sliced out. In an area graph, the positionFactor is 2,
410 // causing the middle two points to be sliced out, since an area
411 // path starts at left, follows the upper path then turns and
412 // follows the bottom back.
413 slice = arr.slice().splice(
414 (arr.length / positionFactor) - numParams,
415 numParams * positionFactor
416 );
417
418 // Move to, line to or curve to?
419 slice[0] = other[fullLength - numParams - (i * numParams)];
420
421 // Disable first control point
422 if (bezier) {
423 slice[numParams - 6] = slice[numParams - 2];
424 slice[numParams - 5] = slice[numParams - 1];
425 }
426
427 // Now insert the slice, either in the middle (for areas) or at
428 // the end (for lines)
429 insertSlice(arr, slice, arr.length / positionFactor);
430
431 if (isArea) {
432 i--;
433 }
434 }
435 }
436
437 if (bezier) {
438 sixify(start);
439 sixify(end);
440 }
441
442 // For sideways animation, find out how much we need to shift to get the
443 // start path Xs to match the end path Xs.
444 if (startX && endX) {
445 for (i = 0; i < startX.length; i++) {
446 // Moving left, new points coming in on right
447 if (startX[i] === endX[0]) {
448 shift = i;
449 break;
450 // Moving right
451 } else if (startX[0] ===
452 endX[endX.length - startX.length + i]) {
453 shift = i;
454 reverse = true;
455 break;
456 }
457 }
458 if (shift === undefined) {
459 start = [];
460 }
461 }
462
463 if (start.length && H.isNumber(shift)) {
464
465 // The common target length for the start and end array, where both
466 // arrays are padded in opposite ends
467 fullLength = end.length + shift * positionFactor * numParams;
468
469 if (!reverse) {
470 prepend(end, start);
471 append(start, end);
472 } else {
473 prepend(start, end);
474 append(end, start);
475 }
476 }
477
478 return [start, end];
479 }
480 }; // End of Fx prototype
481
482 /**
483 * Handle animation of the color attributes directly.
484 */
485 H.Fx.prototype.fillSetter =
486 H.Fx.prototype.strokeSetter = function() {
487 this.elem.attr(
488 this.prop,
489 H.color(this.start).tweenTo(H.color(this.end), this.pos),
490 null,
491 true
492 );
493 };
494
495 /**
496 * Utility function to extend an object with the members of another.
497 *
498 * @function #extend
499 * @memberOf Highcharts
500 * @param {Object} a - The object to be extended.
501 * @param {Object} b - The object to add to the first one.
502 * @returns {Object} Object a, the original object.
503 */
504 H.extend = function(a, b) {
505 var n;
506 if (!a) {
507 a = {};
508 }
509 for (n in b) {
510 a[n] = b[n];
511 }
512 return a;
513 };
514
515 /**
516 * Utility function to deep merge two or more objects and return a third object.
517 * If the first argument is true, the contents of the second object is copied
518 * into the first object. The merge function can also be used with a single
519 * object argument to create a deep copy of an object.
520 *
521 * @function #merge
522 * @memberOf Highcharts
523 * @param {Boolean} [extend] - Whether to extend the left-side object (a) or
524 return a whole new object.
525 * @param {Object} a - The first object to extend. When only this is given, the
526 function returns a deep copy.
527 * @param {...Object} [n] - An object to merge into the previous one.
528 * @returns {Object} - The merged object. If the first argument is true, the
529 * return is the same as the second argument.
530 */
531 H.merge = function() {
532 var i,
533 args = arguments,
534 len,
535 ret = {},
536 doCopy = function(copy, original) {
537 // An object is replacing a primitive
538 if (typeof copy !== 'object') {
539 copy = {};
540 }
541
542 H.objectEach(original, function(value, key) {
543
544 // Copy the contents of objects, but not arrays or DOM nodes
545 if (
546 H.isObject(value, true) &&
547 !H.isClass(value) &&
548 !H.isDOMElement(value)
549 ) {
550 copy[key] = doCopy(copy[key] || {}, value);
551
552 // Primitives and arrays are copied over directly
553 } else {
554 copy[key] = original[key];
555 }
556 });
557 return copy;
558 };
559
560 // If first argument is true, copy into the existing object. Used in
561 // setOptions.
562 if (args[0] === true) {
563 ret = args[1];
564 args = Array.prototype.slice.call(args, 2);
565 }
566
567 // For each argument, extend the return
568 len = args.length;
569 for (i = 0; i < len; i++) {
570 ret = doCopy(ret, args[i]);
571 }
572
573 return ret;
574 };
575
576 /**
577 * Shortcut for parseInt
578 * @ignore
579 * @param {Object} s
580 * @param {Number} mag Magnitude
581 */
582 H.pInt = function(s, mag) {
583 return parseInt(s, mag || 10);
584 };
585
586 /**
587 * Utility function to check for string type.
588 *
589 * @function #isString
590 * @memberOf Highcharts
591 * @param {Object} s - The item to check.
592 * @returns {Boolean} - True if the argument is a string.
593 */
594 H.isString = function(s) {
595 return typeof s === 'string';
596 };
597
598 /**
599 * Utility function to check if an item is an array.
600 *
601 * @function #isArray
602 * @memberOf Highcharts
603 * @param {Object} obj - The item to check.
604 * @returns {Boolean} - True if the argument is an array.
605 */
606 H.isArray = function(obj) {
607 var str = Object.prototype.toString.call(obj);
608 return str === '[object Array]' || str === '[object Array Iterator]';
609 };
610
611 /**
612 * Utility function to check if an item is of type object.
613 *
614 * @function #isObject
615 * @memberOf Highcharts
616 * @param {Object} obj - The item to check.
617 * @param {Boolean} [strict=false] - Also checks that the object is not an
618 * array.
619 * @returns {Boolean} - True if the argument is an object.
620 */
621 H.isObject = function(obj, strict) {
622 return !!obj && typeof obj === 'object' && (!strict || !H.isArray(obj));
623 };
624
625 /**
626 * Utility function to check if an Object is a HTML Element.
627 *
628 * @function #isDOMElement
629 * @memberOf Highcharts
630 * @param {Object} obj - The item to check.
631 * @returns {Boolean} - True if the argument is a HTML Element.
632 */
633 H.isDOMElement = function(obj) {
634 return H.isObject(obj) && typeof obj.nodeType === 'number';
635 };
636
637 /**
638 * Utility function to check if an Object is an class.
639 *
640 * @function #isClass
641 * @memberOf Highcharts
642 * @param {Object} obj - The item to check.
643 * @returns {Boolean} - True if the argument is an class.
644 */
645 H.isClass = function(obj) {
646 var c = obj && obj.constructor;
647 return !!(
648 H.isObject(obj, true) &&
649 !H.isDOMElement(obj) &&
650 (c && c.name && c.name !== 'Object')
651 );
652 };
653
654 /**
655 * Utility function to check if an item is of type number.
656 *
657 * @function #isNumber
658 * @memberOf Highcharts
659 * @param {Object} n - The item to check.
660 * @returns {Boolean} - True if the item is a number and is not NaN.
661 */
662 H.isNumber = function(n) {
663 return typeof n === 'number' && !isNaN(n);
664 };
665
666 /**
667 * Remove the last occurence of an item from an array.
668 *
669 * @function #erase
670 * @memberOf Highcharts
671 * @param {Array} arr - The array.
672 * @param {*} item - The item to remove.
673 */
674 H.erase = function(arr, item) {
675 var i = arr.length;
676 while (i--) {
677 if (arr[i] === item) {
678 arr.splice(i, 1);
679 break;
680 }
681 }
682 };
683
684 /**
685 * Check if an object is null or undefined.
686 *
687 * @function #defined
688 * @memberOf Highcharts
689 * @param {Object} obj - The object to check.
690 * @returns {Boolean} - False if the object is null or undefined, otherwise
691 * true.
692 */
693 H.defined = function(obj) {
694 return obj !== undefined && obj !== null;
695 };
696
697 /**
698 * Set or get an attribute or an object of attributes. To use as a setter, pass
699 * a key and a value, or let the second argument be a collection of keys and
700 * values. To use as a getter, pass only a string as the second argument.
701 *
702 * @function #attr
703 * @memberOf Highcharts
704 * @param {Object} elem - The DOM element to receive the attribute(s).
705 * @param {String|Object} [prop] - The property or an object of key-value pairs.
706 * @param {String} [value] - The value if a single property is set.
707 * @returns {*} When used as a getter, return the value.
708 */
709 H.attr = function(elem, prop, value) {
710 var ret;
711
712 // if the prop is a string
713 if (H.isString(prop)) {
714 // set the value
715 if (H.defined(value)) {
716 elem.setAttribute(prop, value);
717
718 // get the value
719 } else if (elem && elem.getAttribute) {
720 ret = elem.getAttribute(prop);
721 }
722
723 // else if prop is defined, it is a hash of key/value pairs
724 } else if (H.defined(prop) && H.isObject(prop)) {
725 H.objectEach(prop, function(val, key) {
726 elem.setAttribute(key, val);
727 });
728 }
729 return ret;
730 };
731
732 /**
733 * Check if an element is an array, and if not, make it into an array.
734 *
735 * @function #splat
736 * @memberOf Highcharts
737 * @param obj {*} - The object to splat.
738 * @returns {Array} The produced or original array.
739 */
740 H.splat = function(obj) {
741 return H.isArray(obj) ? obj : [obj];
742 };
743
744 /**
745 * Set a timeout if the delay is given, otherwise perform the function
746 * synchronously.
747 *
748 * @function #syncTimeout
749 * @memberOf Highcharts
750 * @param {Function} fn - The function callback.
751 * @param {Number} delay - Delay in milliseconds.
752 * @param {Object} [context] - The context.
753 * @returns {Number} An identifier for the timeout that can later be cleared
754 * with clearTimeout.
755 */
756 H.syncTimeout = function(fn, delay, context) {
757 if (delay) {
758 return setTimeout(fn, delay, context);
759 }
760 fn.call(0, context);
761 };
762
763
764 /**
765 * Return the first value that is not null or undefined.
766 *
767 * @function #pick
768 * @memberOf Highcharts
769 * @param {...*} items - Variable number of arguments to inspect.
770 * @returns {*} The value of the first argument that is not null or undefined.
771 */
772 H.pick = function() {
773 var args = arguments,
774 i,
775 arg,
776 length = args.length;
777 for (i = 0; i < length; i++) {
778 arg = args[i];
779 if (arg !== undefined && arg !== null) {
780 return arg;
781 }
782 }
783 };
784
785 /**
786 * @typedef {Object} CSSObject - A style object with camel case property names.
787 * The properties can be whatever styles are supported on the given SVG or HTML
788 * element.
789 * @example
790 * {
791 * fontFamily: 'monospace',
792 * fontSize: '1.2em'
793 * }
794 */
795 /**
796 * Set CSS on a given element.
797 *
798 * @function #css
799 * @memberOf Highcharts
800 * @param {HTMLDOMElement} el - A HTML DOM element.
801 * @param {CSSObject} styles - Style object with camel case property names.
802 *
803 */
804 H.css = function(el, styles) {
805 if (H.isMS && !H.svg) { // #2686
806 if (styles && styles.opacity !== undefined) {
807 styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';
808 }
809 }
810 H.extend(el.style, styles);
811 };
812
813 /**
814 * A HTML DOM element.
815 * @typedef {Object} HTMLDOMElement
816 */
817
818 /**
819 * Utility function to create an HTML element with attributes and styles.
820 *
821 * @function #createElement
822 * @memberOf Highcharts
823 * @param {String} tag - The HTML tag.
824 * @param {Object} [attribs] - Attributes as an object of key-value pairs.
825 * @param {CSSObject} [styles] - Styles as an object of key-value pairs.
826 * @param {Object} [parent] - The parent HTML object.
827 * @param {Boolean} [nopad=false] - If true, remove all padding, border and
828 * margin.
829 * @returns {HTMLDOMElement} The created DOM element.
830 */
831 H.createElement = function(tag, attribs, styles, parent, nopad) {
832 var el = doc.createElement(tag),
833 css = H.css;
834 if (attribs) {
835 H.extend(el, attribs);
836 }
837 if (nopad) {
838 css(el, {
839 padding: 0,
840 border: 'none',
841 margin: 0
842 });
843 }
844 if (styles) {
845 css(el, styles);
846 }
847 if (parent) {
848 parent.appendChild(el);
849 }
850 return el;
851 };
852
853 /**
854 * Extend a prototyped class by new members.
855 *
856 * @function #extendClass
857 * @memberOf Highcharts
858 * @param {Object} parent - The parent prototype to inherit.
859 * @param {Object} members - A collection of prototype members to add or
860 * override compared to the parent prototype.
861 * @returns {Object} A new prototype.
862 */
863 H.extendClass = function(parent, members) {
864 var object = function() {};
865 object.prototype = new parent(); // eslint-disable-line new-cap
866 H.extend(object.prototype, members);
867 return object;
868 };
869
870 /**
871 * Left-pad a string to a given length by adding a character repetetively.
872 *
873 * @function #pad
874 * @memberOf Highcharts
875 * @param {Number} number - The input string or number.
876 * @param {Number} length - The desired string length.
877 * @param {String} [padder=0] - The character to pad with.
878 * @returns {String} The padded string.
879 */
880 H.pad = function(number, length, padder) {
881 return new Array((length || 2) + 1 -
882 String(number).length).join(padder || 0) + number;
883 };
884
885 /**
886 * @typedef {Number|String} RelativeSize - If a number is given, it defines the
887 * pixel length. If a percentage string is given, like for example `'50%'`,
888 * the setting defines a length relative to a base size, for example the size
889 * of a container.
890 */
891 /**
892 * Return a length based on either the integer value, or a percentage of a base.
893 *
894 * @function #relativeLength
895 * @memberOf Highcharts
896 * @param {RelativeSize} value
897 * A percentage string or a number.
898 * @param {number} base
899 * The full length that represents 100%.
900 * @param {number} [offset=0]
901 * A pixel offset to apply for percentage values. Used internally in
902 * axis positioning.
903 * @return {number}
904 * The computed length.
905 */
906 H.relativeLength = function(value, base, offset) {
907 return (/%$/).test(value) ?
908 (base * parseFloat(value) / 100) + (offset || 0) :
909 parseFloat(value);
910 };
911
912 /**
913 * Wrap a method with extended functionality, preserving the original function.
914 *
915 * @function #wrap
916 * @memberOf Highcharts
917 * @param {Object} obj - The context object that the method belongs to. In real
918 * cases, this is often a prototype.
919 * @param {String} method - The name of the method to extend.
920 * @param {Function} func - A wrapper function callback. This function is called
921 * with the same arguments as the original function, except that the
922 * original function is unshifted and passed as the first argument.
923 *
924 */
925 H.wrap = function(obj, method, func) {
926 var proceed = obj[method];
927 obj[method] = function() {
928 var args = Array.prototype.slice.call(arguments),
929 outerArgs = arguments,
930 ctx = this,
931 ret;
932 ctx.proceed = function() {
933 proceed.apply(ctx, arguments.length ? arguments : outerArgs);
934 };
935 args.unshift(proceed);
936 ret = func.apply(this, args);
937 ctx.proceed = null;
938 return ret;
939 };
940 };
941
942 /**
943 * Get the time zone offset based on the current timezone information as set in
944 * the global options.
945 *
946 * @function #getTZOffset
947 * @memberOf Highcharts
948 * @param {Number} timestamp - The JavaScript timestamp to inspect.
949 * @return {Number} - The timezone offset in minutes compared to UTC.
950 */
951 H.getTZOffset = function(timestamp) {
952 var d = H.Date;
953 return ((d.hcGetTimezoneOffset && d.hcGetTimezoneOffset(timestamp)) ||
954 d.hcTimezoneOffset || 0) * 60000;
955 };
956
957 /**
958 * Formats a JavaScript date timestamp (milliseconds since Jan 1st 1970) into a
959 * human readable date string. The format is a subset of the formats for PHP's
960 * [strftime]{@link
961 * http://www.php.net/manual/en/function.strftime.php} function. Additional
962 * formats can be given in the {@link Highcharts.dateFormats} hook.
963 *
964 * @function #dateFormat
965 * @memberOf Highcharts
966 * @param {String} format - The desired format where various time
967 * representations are prefixed with %.
968 * @param {Number} timestamp - The JavaScript timestamp.
969 * @param {Boolean} [capitalize=false] - Upper case first letter in the return.
970 * @returns {String} The formatted date.
971 */
972 H.dateFormat = function(format, timestamp, capitalize) {
973 if (!H.defined(timestamp) || isNaN(timestamp)) {
974 return H.defaultOptions.lang.invalidDate || '';
975 }
976 format = H.pick(format, '%Y-%m-%d %H:%M:%S');
977
978 var D = H.Date,
979 date = new D(timestamp - H.getTZOffset(timestamp)),
980 // get the basic time values
981 hours = date[D.hcGetHours](),
982 day = date[D.hcGetDay](),
983 dayOfMonth = date[D.hcGetDate](),
984 month = date[D.hcGetMonth](),
985 fullYear = date[D.hcGetFullYear](),
986 lang = H.defaultOptions.lang,
987 langWeekdays = lang.weekdays,
988 shortWeekdays = lang.shortWeekdays,
989 pad = H.pad,
990
991 // List all format keys. Custom formats can be added from the outside.
992 replacements = H.extend({
993
994 // Day
995 // Short weekday, like 'Mon'
996 'a': shortWeekdays ?
997 shortWeekdays[day] : langWeekdays[day].substr(0, 3),
998 // Long weekday, like 'Monday'
999 'A': langWeekdays[day],
1000 // Two digit day of the month, 01 to 31
1001 'd': pad(dayOfMonth),
1002 // Day of the month, 1 through 31
1003 'e': pad(dayOfMonth, 2, ' '),
1004 'w': day,
1005
1006 // Week (none implemented)
1007 // 'W': weekNumber(),
1008
1009 // Month
1010 // Short month, like 'Jan'
1011 'b': lang.shortMonths[month],
1012 // Long month, like 'January'
1013 'B': lang.months[month],
1014 // Two digit month number, 01 through 12
1015 'm': pad(month + 1),
1016
1017 // Year
1018 // Two digits year, like 09 for 2009
1019 'y': fullYear.toString().substr(2, 2),
1020 // Four digits year, like 2009
1021 'Y': fullYear,
1022
1023 // Time
1024 // Two digits hours in 24h format, 00 through 23
1025 'H': pad(hours),
1026 // Hours in 24h format, 0 through 23
1027 'k': hours,
1028 // Two digits hours in 12h format, 00 through 11
1029 'I': pad((hours % 12) || 12),
1030 // Hours in 12h format, 1 through 12
1031 'l': (hours % 12) || 12,
1032 // Two digits minutes, 00 through 59
1033 'M': pad(date[D.hcGetMinutes]()),
1034 // Upper case AM or PM
1035 'p': hours < 12 ? 'AM' : 'PM',
1036 // Lower case AM or PM
1037 'P': hours < 12 ? 'am' : 'pm',
1038 // Two digits seconds, 00 through 59
1039 'S': pad(date.getSeconds()),
1040 // Milliseconds (naming from Ruby)
1041 'L': pad(Math.round(timestamp % 1000), 3)
1042 },
1043
1044 /**
1045 * A hook for defining additional date format specifiers. New
1046 * specifiers are defined as key-value pairs by using the specifier
1047 * as key, and a function which takes the timestamp as value. This
1048 * function returns the formatted portion of the date.
1049 *
1050 * @type {Object}
1051 * @name dateFormats
1052 * @memberOf Highcharts
1053 * @sample highcharts/global/dateformats/ Adding support for week
1054 * number
1055 */
1056 H.dateFormats
1057 );
1058
1059
1060 // Do the replaces
1061 H.objectEach(replacements, function(val, key) {
1062 // Regex would do it in one line, but this is faster
1063 while (format.indexOf('%' + key) !== -1) {
1064 format = format.replace(
1065 '%' + key,
1066 typeof val === 'function' ? val(timestamp) : val
1067 );
1068 }
1069
1070 });
1071
1072 // Optionally capitalize the string and return
1073 return capitalize ?
1074 format.substr(0, 1).toUpperCase() + format.substr(1) :
1075 format;
1076 };
1077
1078 /**
1079 * Format a single variable. Similar to sprintf, without the % prefix.
1080 *
1081 * @example
1082 * formatSingle('.2f', 5); // => '5.00'.
1083 *
1084 * @function #formatSingle
1085 * @memberOf Highcharts
1086 * @param {String} format The format string.
1087 * @param {*} val The value.
1088 * @returns {String} The formatted representation of the value.
1089 */
1090 H.formatSingle = function(format, val) {
1091 var floatRegex = /f$/,
1092 decRegex = /\.([0-9])/,
1093 lang = H.defaultOptions.lang,
1094 decimals;
1095
1096 if (floatRegex.test(format)) { // float
1097 decimals = format.match(decRegex);
1098 decimals = decimals ? decimals[1] : -1;
1099 if (val !== null) {
1100 val = H.numberFormat(
1101 val,
1102 decimals,
1103 lang.decimalPoint,
1104 format.indexOf(',') > -1 ? lang.thousandsSep : ''
1105 );
1106 }
1107 } else {
1108 val = H.dateFormat(format, val);
1109 }
1110 return val;
1111 };
1112
1113 /**
1114 * Format a string according to a subset of the rules of Python's String.format
1115 * method.
1116 *
1117 * @function #format
1118 * @memberOf Highcharts
1119 * @param {String} str The string to format.
1120 * @param {Object} ctx The context, a collection of key-value pairs where each
1121 * key is replaced by its value.
1122 * @returns {String} The formatted string.
1123 *
1124 * @example
1125 * var s = Highcharts.format(
1126 * 'The {color} fox was {len:.2f} feet long',
1127 * { color: 'red', len: Math.PI }
1128 * );
1129 * // => The red fox was 3.14 feet long
1130 */
1131 H.format = function(str, ctx) {
1132 var splitter = '{',
1133 isInside = false,
1134 segment,
1135 valueAndFormat,
1136 path,
1137 i,
1138 len,
1139 ret = [],
1140 val,
1141 index;
1142
1143 while (str) {
1144 index = str.indexOf(splitter);
1145 if (index === -1) {
1146 break;
1147 }
1148
1149 segment = str.slice(0, index);
1150 if (isInside) { // we're on the closing bracket looking back
1151
1152 valueAndFormat = segment.split(':');
1153 path = valueAndFormat.shift().split('.'); // get first and leave
1154 len = path.length;
1155 val = ctx;
1156
1157 // Assign deeper paths
1158 for (i = 0; i < len; i++) {
1159 if (val) {
1160 val = val[path[i]];
1161 }
1162 }
1163
1164 // Format the replacement
1165 if (valueAndFormat.length) {
1166 val = H.formatSingle(valueAndFormat.join(':'), val);
1167 }
1168
1169 // Push the result and advance the cursor
1170 ret.push(val);
1171
1172 } else {
1173 ret.push(segment);
1174
1175 }
1176 str = str.slice(index + 1); // the rest
1177 isInside = !isInside; // toggle
1178 splitter = isInside ? '}' : '{'; // now look for next matching bracket
1179 }
1180 ret.push(str);
1181 return ret.join('');
1182 };
1183
1184 /**
1185 * Get the magnitude of a number.
1186 *
1187 * @function #getMagnitude
1188 * @memberOf Highcharts
1189 * @param {Number} number The number.
1190 * @returns {Number} The magnitude, where 1-9 are magnitude 1, 10-99 magnitude 2
1191 * etc.
1192 */
1193 H.getMagnitude = function(num) {
1194 return Math.pow(10, Math.floor(Math.log(num) / Math.LN10));
1195 };
1196
1197 /**
1198 * Take an interval and normalize it to multiples of round numbers.
1199 *
1200 * @todo Move this function to the Axis prototype. It is here only for
1201 * historical reasons.
1202 * @function #normalizeTickInterval
1203 * @memberOf Highcharts
1204 * @param {Number} interval - The raw, un-rounded interval.
1205 * @param {Array} [multiples] - Allowed multiples.
1206 * @param {Number} [magnitude] - The magnitude of the number.
1207 * @param {Boolean} [allowDecimals] - Whether to allow decimals.
1208 * @param {Boolean} [hasTickAmount] - If it has tickAmount, avoid landing
1209 * on tick intervals lower than original.
1210 * @returns {Number} The normalized interval.
1211 */
1212 H.normalizeTickInterval = function(interval, multiples, magnitude,
1213 allowDecimals, hasTickAmount) {
1214 var normalized,
1215 i,
1216 retInterval = interval;
1217
1218 // round to a tenfold of 1, 2, 2.5 or 5
1219 magnitude = H.pick(magnitude, 1);
1220 normalized = interval / magnitude;
1221
1222 // multiples for a linear scale
1223 if (!multiples) {
1224 multiples = hasTickAmount ?
1225 // Finer grained ticks when the tick amount is hard set, including
1226 // when alignTicks is true on multiple axes (#4580).
1227 [1, 1.2, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10] :
1228
1229 // Else, let ticks fall on rounder numbers
1230 [1, 2, 2.5, 5, 10];
1231
1232
1233 // the allowDecimals option
1234 if (allowDecimals === false) {
1235 if (magnitude === 1) {
1236 multiples = H.grep(multiples, function(num) {
1237 return num % 1 === 0;
1238 });
1239 } else if (magnitude <= 0.1) {
1240 multiples = [1 / magnitude];
1241 }
1242 }
1243 }
1244
1245 // normalize the interval to the nearest multiple
1246 for (i = 0; i < multiples.length; i++) {
1247 retInterval = multiples[i];
1248 // only allow tick amounts smaller than natural
1249 if ((hasTickAmount && retInterval * magnitude >= interval) ||
1250 (!hasTickAmount && (normalized <= (multiples[i] +
1251 (multiples[i + 1] || multiples[i])) / 2))) {
1252 break;
1253 }
1254 }
1255
1256 // Multiply back to the correct magnitude. Correct floats to appropriate
1257 // precision (#6085).
1258 retInterval = H.correctFloat(
1259 retInterval * magnitude, -Math.round(Math.log(0.001) / Math.LN10)
1260 );
1261
1262 return retInterval;
1263 };
1264
1265
1266 /**
1267 * Sort an object array and keep the order of equal items. The ECMAScript
1268 * standard does not specify the behaviour when items are equal.
1269 *
1270 * @function #stableSort
1271 * @memberOf Highcharts
1272 * @param {Array} arr - The array to sort.
1273 * @param {Function} sortFunction - The function to sort it with, like with
1274 * regular Array.prototype.sort.
1275 *
1276 */
1277 H.stableSort = function(arr, sortFunction) {
1278 var length = arr.length,
1279 sortValue,
1280 i;
1281
1282 // Add index to each item
1283 for (i = 0; i < length; i++) {
1284 arr[i].safeI = i; // stable sort index
1285 }
1286
1287 arr.sort(function(a, b) {
1288 sortValue = sortFunction(a, b);
1289 return sortValue === 0 ? a.safeI - b.safeI : sortValue;
1290 });
1291
1292 // Remove index from items
1293 for (i = 0; i < length; i++) {
1294 delete arr[i].safeI; // stable sort index
1295 }
1296 };
1297
1298 /**
1299 * Non-recursive method to find the lowest member of an array. `Math.min` raises
1300 * a maximum call stack size exceeded error in Chrome when trying to apply more
1301 * than 150.000 points. This method is slightly slower, but safe.
1302 *
1303 * @function #arrayMin
1304 * @memberOf Highcharts
1305 * @param {Array} data An array of numbers.
1306 * @returns {Number} The lowest number.
1307 */
1308 H.arrayMin = function(data) {
1309 var i = data.length,
1310 min = data[0];
1311
1312 while (i--) {
1313 if (data[i] < min) {
1314 min = data[i];
1315 }
1316 }
1317 return min;
1318 };
1319
1320 /**
1321 * Non-recursive method to find the lowest member of an array. `Math.max` raises
1322 * a maximum call stack size exceeded error in Chrome when trying to apply more
1323 * than 150.000 points. This method is slightly slower, but safe.
1324 *
1325 * @function #arrayMax
1326 * @memberOf Highcharts
1327 * @param {Array} data - An array of numbers.
1328 * @returns {Number} The highest number.
1329 */
1330 H.arrayMax = function(data) {
1331 var i = data.length,
1332 max = data[0];
1333
1334 while (i--) {
1335 if (data[i] > max) {
1336 max = data[i];
1337 }
1338 }
1339 return max;
1340 };
1341
1342 /**
1343 * Utility method that destroys any SVGElement instances that are properties on
1344 * the given object. It loops all properties and invokes destroy if there is a
1345 * destroy method. The property is then delete.
1346 *
1347 * @function #destroyObjectProperties
1348 * @memberOf Highcharts
1349 * @param {Object} obj - The object to destroy properties on.
1350 * @param {Object} [except] - Exception, do not destroy this property, only
1351 * delete it.
1352 *
1353 */
1354 H.destroyObjectProperties = function(obj, except) {
1355 H.objectEach(obj, function(val, n) {
1356 // If the object is non-null and destroy is defined
1357 if (val && val !== except && val.destroy) {
1358 // Invoke the destroy
1359 val.destroy();
1360 }
1361
1362 // Delete the property from the object.
1363 delete obj[n];
1364 });
1365 };
1366
1367
1368 /**
1369 * Discard a HTML element by moving it to the bin and delete.
1370 *
1371 * @function #discardElement
1372 * @memberOf Highcharts
1373 * @param {HTMLDOMElement} element - The HTML node to discard.
1374 *
1375 */
1376 H.discardElement = function(element) {
1377 var garbageBin = H.garbageBin;
1378 // create a garbage bin element, not part of the DOM
1379 if (!garbageBin) {
1380 garbageBin = H.createElement('div');
1381 }
1382
1383 // move the node and empty bin
1384 if (element) {
1385 garbageBin.appendChild(element);
1386 }
1387 garbageBin.innerHTML = '';
1388 };
1389
1390 /**
1391 * Fix JS round off float errors.
1392 *
1393 * @function #correctFloat
1394 * @memberOf Highcharts
1395 * @param {Number} num - A float number to fix.
1396 * @param {Number} [prec=14] - The precision.
1397 * @returns {Number} The corrected float number.
1398 */
1399 H.correctFloat = function(num, prec) {
1400 return parseFloat(
1401 num.toPrecision(prec || 14)
1402 );
1403 };
1404
1405 /**
1406 * Set the global animation to either a given value, or fall back to the given
1407 * chart's animation option.
1408 *
1409 * @function #setAnimation
1410 * @memberOf Highcharts
1411 * @param {Boolean|Animation} animation - The animation object.
1412 * @param {Object} chart - The chart instance.
1413 *
1414 * @todo This function always relates to a chart, and sets a property on the
1415 * renderer, so it should be moved to the SVGRenderer.
1416 */
1417 H.setAnimation = function(animation, chart) {
1418 chart.renderer.globalAnimation = H.pick(
1419 animation,
1420 chart.options.chart.animation,
1421 true
1422 );
1423 };
1424
1425 /**
1426 * Get the animation in object form, where a disabled animation is always
1427 * returned as `{ duration: 0 }`.
1428 *
1429 * @function #animObject
1430 * @memberOf Highcharts
1431 * @param {Boolean|AnimationOptions} animation - An animation setting. Can be an
1432 * object with duration, complete and easing properties, or a boolean to
1433 * enable or disable.
1434 * @returns {AnimationOptions} An object with at least a duration property.
1435 */
1436 H.animObject = function(animation) {
1437 return H.isObject(animation) ?
1438 H.merge(animation) : {
1439 duration: animation ? 500 : 0
1440 };
1441 };
1442
1443 /**
1444 * The time unit lookup
1445 */
1446 H.timeUnits = {
1447 millisecond: 1,
1448 second: 1000,
1449 minute: 60000,
1450 hour: 3600000,
1451 day: 24 * 3600000,
1452 week: 7 * 24 * 3600000,
1453 month: 28 * 24 * 3600000,
1454 year: 364 * 24 * 3600000
1455 };
1456
1457 /**
1458 * Format a number and return a string based on input settings.
1459 *
1460 * @function #numberFormat
1461 * @memberOf Highcharts
1462 * @param {Number} number - The input number to format.
1463 * @param {Number} decimals - The amount of decimals. A value of -1 preserves
1464 * the amount in the input number.
1465 * @param {String} [decimalPoint] - The decimal point, defaults to the one given
1466 * in the lang options, or a dot.
1467 * @param {String} [thousandsSep] - The thousands separator, defaults to the one
1468 * given in the lang options, or a space character.
1469 * @returns {String} The formatted number.
1470 *
1471 * @sample highcharts/members/highcharts-numberformat/ Custom number format
1472 */
1473 H.numberFormat = function(number, decimals, decimalPoint, thousandsSep) {
1474 number = +number || 0;
1475 decimals = +decimals;
1476
1477 var lang = H.defaultOptions.lang,
1478 origDec = (number.toString().split('.')[1] || '').split('e')[0].length,
1479 strinteger,
1480 thousands,
1481 ret,
1482 roundedNumber,
1483 exponent = number.toString().split('e');
1484
1485 if (decimals === -1) {
1486 // Preserve decimals. Not huge numbers (#3793).
1487 decimals = Math.min(origDec, 20);
1488 } else if (!H.isNumber(decimals)) {
1489 decimals = 2;
1490 }
1491
1492 // Add another decimal to avoid rounding errors of float numbers. (#4573)
1493 // Then use toFixed to handle rounding.
1494 roundedNumber = (
1495 Math.abs(exponent[1] ? exponent[0] : number) +
1496 Math.pow(10, -Math.max(decimals, origDec) - 1)
1497 ).toFixed(decimals);
1498
1499 // A string containing the positive integer component of the number
1500 strinteger = String(H.pInt(roundedNumber));
1501
1502 // Leftover after grouping into thousands. Can be 0, 1 or 3.
1503 thousands = strinteger.length > 3 ? strinteger.length % 3 : 0;
1504
1505 // Language
1506 decimalPoint = H.pick(decimalPoint, lang.decimalPoint);
1507 thousandsSep = H.pick(thousandsSep, lang.thousandsSep);
1508
1509 // Start building the return
1510 ret = number < 0 ? '-' : '';
1511
1512 // Add the leftover after grouping into thousands. For example, in the
1513 // number 42 000 000, this line adds 42.
1514 ret += thousands ? strinteger.substr(0, thousands) + thousandsSep : '';
1515
1516 // Add the remaining thousands groups, joined by the thousands separator
1517 ret += strinteger
1518 .substr(thousands)
1519 .replace(/(\d{3})(?=\d)/g, '$1' + thousandsSep);
1520
1521 // Add the decimal point and the decimal component
1522 if (decimals) {
1523 // Get the decimal component
1524 ret += decimalPoint + roundedNumber.slice(-decimals);
1525 }
1526
1527 if (exponent[1]) {
1528 ret += 'e' + exponent[1];
1529 }
1530
1531 return ret;
1532 };
1533
1534 /**
1535 * Easing definition
1536 * @ignore
1537 * @param {Number} pos Current position, ranging from 0 to 1.
1538 */
1539 Math.easeInOutSine = function(pos) {
1540 return -0.5 * (Math.cos(Math.PI * pos) - 1);
1541 };
1542
1543 /**
1544 * Get the computed CSS value for given element and property, only for numerical
1545 * properties. For width and height, the dimension of the inner box (excluding
1546 * padding) is returned. Used for fitting the chart within the container.
1547 *
1548 * @function #getStyle
1549 * @memberOf Highcharts
1550 * @param {HTMLDOMElement} el - A HTML element.
1551 * @param {String} prop - The property name.
1552 * @param {Boolean} [toInt=true] - Parse to integer.
1553 * @returns {Number} - The numeric value.
1554 */
1555 H.getStyle = function(el, prop, toInt) {
1556
1557 var style;
1558
1559 // For width and height, return the actual inner pixel size (#4913)
1560 if (prop === 'width') {
1561 return Math.min(el.offsetWidth, el.scrollWidth) -
1562 H.getStyle(el, 'padding-left') -
1563 H.getStyle(el, 'padding-right');
1564 } else if (prop === 'height') {
1565 return Math.min(el.offsetHeight, el.scrollHeight) -
1566 H.getStyle(el, 'padding-top') -
1567 H.getStyle(el, 'padding-bottom');
1568 }
1569
1570 if (!win.getComputedStyle) {
1571 // SVG not supported, forgot to load oldie.js?
1572 H.error(27, true);
1573 }
1574
1575 // Otherwise, get the computed style
1576 style = win.getComputedStyle(el, undefined);
1577 if (style) {
1578 style = style.getPropertyValue(prop);
1579 if (H.pick(toInt, prop !== 'opacity')) {
1580 style = H.pInt(style);
1581 }
1582 }
1583 return style;
1584 };
1585
1586 /**
1587 * Search for an item in an array.
1588 *
1589 * @function #inArray
1590 * @memberOf Highcharts
1591 * @param {*} item - The item to search for.
1592 * @param {arr} arr - The array or node collection to search in.
1593 * @returns {Number} - The index within the array, or -1 if not found.
1594 */
1595 H.inArray = function(item, arr) {
1596 return (H.indexOfPolyfill || Array.prototype.indexOf).call(arr, item);
1597 };
1598
1599 /**
1600 * Filter an array by a callback.
1601 *
1602 * @function #grep
1603 * @memberOf Highcharts
1604 * @param {Array} arr - The array to filter.
1605 * @param {Function} callback - The callback function. The function receives the
1606 * item as the first argument. Return `true` if the item is to be
1607 * preserved.
1608 * @returns {Array} - A new, filtered array.
1609 */
1610 H.grep = function(arr, callback) {
1611 return (H.filterPolyfill || Array.prototype.filter).call(arr, callback);
1612 };
1613
1614 /**
1615 * Return the value of the first element in the array that satisfies the
1616 * provided testing function.
1617 *
1618 * @function #find
1619 * @memberOf Highcharts
1620 * @param {Array} arr - The array to test.
1621 * @param {Function} callback - The callback function. The function receives the
1622 * item as the first argument. Return `true` if this item satisfies the
1623 * condition.
1624 * @returns {Mixed} - The value of the element.
1625 */
1626 H.find = Array.prototype.find ?
1627 function(arr, callback) {
1628 return arr.find(callback);
1629 } :
1630 // Legacy implementation. PhantomJS, IE <= 11 etc. #7223.
1631 function(arr, fn) {
1632 var i,
1633 length = arr.length;
1634
1635 for (i = 0; i < length; i++) {
1636 if (fn(arr[i], i)) {
1637 return arr[i];
1638 }
1639 }
1640 };
1641
1642 /**
1643 * Map an array by a callback.
1644 *
1645 * @function #map
1646 * @memberOf Highcharts
1647 * @param {Array} arr - The array to map.
1648 * @param {Function} fn - The callback function. Return the new value for the
1649 * new array.
1650 * @returns {Array} - A new array item with modified items.
1651 */
1652 H.map = function(arr, fn) {
1653 var results = [],
1654 i = 0,
1655 len = arr.length;
1656
1657 for (; i < len; i++) {
1658 results[i] = fn.call(arr[i], arr[i], i, arr);
1659 }
1660
1661 return results;
1662 };
1663
1664 /**
1665 * Returns an array of a given object's own properties.
1666 *
1667 * @function #keys
1668 * @memberOf highcharts
1669 * @param {Object} obj - The object of which the properties are to be returned.
1670 * @returns {Array} - An array of strings that represents all the properties.
1671 */
1672 H.keys = function(obj) {
1673 return (H.keysPolyfill || Object.keys).call(undefined, obj);
1674 };
1675
1676 /**
1677 * Reduce an array to a single value.
1678 *
1679 * @function #reduce
1680 * @memberOf Highcharts
1681 * @param {Array} arr - The array to reduce.
1682 * @param {Function} fn - The callback function. Return the reduced value.
1683 * Receives 4 arguments: Accumulated/reduced value, current value, current
1684 * array index, and the array.
1685 * @param {Mixed} initialValue - The initial value of the accumulator.
1686 * @returns {Mixed} - The reduced value.
1687 */
1688 H.reduce = function(arr, func, initialValue) {
1689 return (H.reducePolyfill || Array.prototype.reduce).call(
1690 arr,
1691 func,
1692 initialValue
1693 );
1694 };
1695
1696 /**
1697 * Get the element's offset position, corrected for `overflow: auto`.
1698 *
1699 * @function #offset
1700 * @memberOf Highcharts
1701 * @param {HTMLDOMElement} el - The HTML element.
1702 * @returns {Object} An object containing `left` and `top` properties for the
1703 * position in the page.
1704 */
1705 H.offset = function(el) {
1706 var docElem = doc.documentElement,
1707 box = el.parentElement ? // IE11 throws Unspecified error in test suite
1708 el.getBoundingClientRect() : {
1709 top: 0,
1710 left: 0
1711 };
1712
1713 return {
1714 top: box.top + (win.pageYOffset || docElem.scrollTop) -
1715 (docElem.clientTop || 0),
1716 left: box.left + (win.pageXOffset || docElem.scrollLeft) -
1717 (docElem.clientLeft || 0)
1718 };
1719 };
1720
1721 /**
1722 * Stop running animation.
1723 *
1724 * @todo A possible extension to this would be to stop a single property, when
1725 * we want to continue animating others. Then assign the prop to the timer
1726 * in the Fx.run method, and check for the prop here. This would be an
1727 * improvement in all cases where we stop the animation from .attr. Instead of
1728 * stopping everything, we can just stop the actual attributes we're setting.
1729 *
1730 * @function #stop
1731 * @memberOf Highcharts
1732 * @param {SVGElement} el - The SVGElement to stop animation on.
1733 * @param {string} [prop] - The property to stop animating. If given, the stop
1734 * method will stop a single property from animating, while others continue.
1735 *
1736 */
1737 H.stop = function(el, prop) {
1738
1739 var i = H.timers.length;
1740
1741 // Remove timers related to this element (#4519)
1742 while (i--) {
1743 if (H.timers[i].elem === el && (!prop || prop === H.timers[i].prop)) {
1744 H.timers[i].stopped = true; // #4667
1745 }
1746 }
1747 };
1748
1749 /**
1750 * Iterate over an array.
1751 *
1752 * @function #each
1753 * @memberOf Highcharts
1754 * @param {Array} arr - The array to iterate over.
1755 * @param {Function} fn - The iterator callback. It passes three arguments:
1756 * * item - The array item.
1757 * * index - The item's index in the array.
1758 * * arr - The array that each is being applied to.
1759 * @param {Object} [ctx] The context.
1760 */
1761 H.each = function(arr, fn, ctx) { // modern browsers
1762 return (H.forEachPolyfill || Array.prototype.forEach).call(arr, fn, ctx);
1763 };
1764
1765 /**
1766 * Iterate over object key pairs in an object.
1767 *
1768 * @function #objectEach
1769 * @memberOf Highcharts
1770 * @param {Object} obj - The object to iterate over.
1771 * @param {Function} fn - The iterator callback. It passes three arguments:
1772 * * value - The property value.
1773 * * key - The property key.
1774 * * obj - The object that objectEach is being applied to.
1775 * @param {Object} ctx The context
1776 */
1777 H.objectEach = function(obj, fn, ctx) {
1778 for (var key in obj) {
1779 if (obj.hasOwnProperty(key)) {
1780 fn.call(ctx, obj[key], key, obj);
1781 }
1782 }
1783 };
1784
1785 /**
1786 * Add an event listener.
1787 *
1788 * @function #addEvent
1789 * @memberOf Highcharts
1790 * @param {Object} el - The element or object to add a listener to. It can be a
1791 * {@link HTMLDOMElement}, an {@link SVGElement} or any other object.
1792 * @param {String} type - The event type.
1793 * @param {Function} fn - The function callback to execute when the event is
1794 * fired.
1795 * @returns {Function} A callback function to remove the added event.
1796 */
1797 H.addEvent = function(el, type, fn) {
1798
1799 var events,
1800 itemEvents,
1801 addEventListener = el.addEventListener || H.addEventListenerPolyfill;
1802
1803 // If events are previously set directly on the prototype, pick them up
1804 // and copy them over to the instance. Otherwise instance handlers would
1805 // be set on the prototype and apply to multiple charts in the page.
1806 if (el.hcEvents && !el.hasOwnProperty('hcEvents')) {
1807 itemEvents = {};
1808 H.objectEach(el.hcEvents, function(handlers, eventType) {
1809 itemEvents[eventType] = handlers.slice(0);
1810 });
1811 el.hcEvents = itemEvents;
1812 }
1813
1814 events = el.hcEvents = el.hcEvents || {};
1815
1816 // Handle DOM events
1817 if (addEventListener) {
1818 addEventListener.call(el, type, fn, false);
1819 }
1820
1821 if (!events[type]) {
1822 events[type] = [];
1823 }
1824
1825 events[type].push(fn);
1826
1827 // Return a function that can be called to remove this event.
1828 return function() {
1829 H.removeEvent(el, type, fn);
1830 };
1831 };
1832
1833 /**
1834 * Remove an event that was added with {@link Highcharts#addEvent}.
1835 *
1836 * @function #removeEvent
1837 * @memberOf Highcharts
1838 * @param {Object} el - The element to remove events on.
1839 * @param {String} [type] - The type of events to remove. If undefined, all
1840 * events are removed from the element.
1841 * @param {Function} [fn] - The specific callback to remove. If undefined, all
1842 * events that match the element and optionally the type are removed.
1843 *
1844 */
1845 H.removeEvent = function(el, type, fn) {
1846
1847 var events,
1848 hcEvents = el.hcEvents,
1849 index;
1850
1851 function removeOneEvent(type, fn) {
1852 var removeEventListener =
1853 el.removeEventListener || H.removeEventListenerPolyfill;
1854
1855 if (removeEventListener) {
1856 removeEventListener.call(el, type, fn, false);
1857 }
1858 }
1859
1860 function removeAllEvents() {
1861 var types,
1862 len;
1863
1864 if (!el.nodeName) {
1865 return; // break on non-DOM events
1866 }
1867
1868 if (type) {
1869 types = {};
1870 types[type] = true;
1871 } else {
1872 types = hcEvents;
1873 }
1874
1875 H.objectEach(types, function(val, n) {
1876 if (hcEvents[n]) {
1877 len = hcEvents[n].length;
1878 while (len--) {
1879 removeOneEvent(n, hcEvents[n][len]);
1880 }
1881 }
1882 });
1883 }
1884
1885 if (hcEvents) {
1886 if (type) {
1887 events = hcEvents[type] || [];
1888 if (fn) {
1889 index = H.inArray(fn, events);
1890 if (index > -1) {
1891 events.splice(index, 1);
1892 hcEvents[type] = events;
1893 }
1894 removeOneEvent(type, fn);
1895
1896 } else {
1897 removeAllEvents();
1898 hcEvents[type] = [];
1899 }
1900 } else {
1901 removeAllEvents();
1902 el.hcEvents = {};
1903 }
1904 }
1905 };
1906
1907 /**
1908 * Fire an event that was registered with {@link Highcharts#addEvent}.
1909 *
1910 * @function #fireEvent
1911 * @memberOf Highcharts
1912 * @param {Object} el - The object to fire the event on. It can be a
1913 * {@link HTMLDOMElement}, an {@link SVGElement} or any other object.
1914 * @param {String} type - The type of event.
1915 * @param {Object} [eventArguments] - Custom event arguments that are passed on
1916 * as an argument to the event handler.
1917 * @param {Function} [defaultFunction] - The default function to execute if the
1918 * other listeners haven't returned false.
1919 *
1920 */
1921 H.fireEvent = function(el, type, eventArguments, defaultFunction) {
1922 var e,
1923 hcEvents = el.hcEvents,
1924 events,
1925 len,
1926 i,
1927 fn;
1928
1929 eventArguments = eventArguments || {};
1930
1931 if (doc.createEvent && (el.dispatchEvent || el.fireEvent)) {
1932 e = doc.createEvent('Events');
1933 e.initEvent(type, true, true);
1934
1935 H.extend(e, eventArguments);
1936
1937 if (el.dispatchEvent) {
1938 el.dispatchEvent(e);
1939 } else {
1940 el.fireEvent(type, e);
1941 }
1942
1943 } else if (hcEvents) {
1944
1945 events = hcEvents[type] || [];
1946 len = events.length;
1947
1948 if (!eventArguments.target) { // We're running a custom event
1949
1950 H.extend(eventArguments, {
1951 // Attach a simple preventDefault function to skip default
1952 // handler if called. The built-in defaultPrevented property is
1953 // not overwritable (#5112)
1954 preventDefault: function() {
1955 eventArguments.defaultPrevented = true;
1956 },
1957 // Setting target to native events fails with clicking the
1958 // zoom-out button in Chrome.
1959 target: el,
1960 // If the type is not set, we're running a custom event (#2297).
1961 // If it is set, we're running a browser event, and setting it
1962 // will cause en error in IE8 (#2465).
1963 type: type
1964 });
1965 }
1966
1967
1968 for (i = 0; i < len; i++) {
1969 fn = events[i];
1970
1971 // If the event handler return false, prevent the default handler
1972 // from executing
1973 if (fn && fn.call(el, eventArguments) === false) {
1974 eventArguments.preventDefault();
1975 }
1976 }
1977 }
1978
1979 // Run the default if not prevented
1980 if (defaultFunction && !eventArguments.defaultPrevented) {
1981 defaultFunction(eventArguments);
1982 }
1983 };
1984
1985 /**
1986 * An animation configuration. Animation configurations can also be defined as
1987 * booleans, where `false` turns off animation and `true` defaults to a duration
1988 * of 500ms.
1989 * @typedef {Object} AnimationOptions
1990 * @property {Number} duration - The animation duration in milliseconds.
1991 * @property {String} [easing] - The name of an easing function as defined on
1992 * the `Math` object.
1993 * @property {Function} [complete] - A callback function to exectute when the
1994 * animation finishes.
1995 * @property {Function} [step] - A callback function to execute on each step of
1996 * each attribute or CSS property that's being animated. The first argument
1997 * contains information about the animation and progress.
1998 */
1999
2000
2001 /**
2002 * The global animate method, which uses Fx to create individual animators.
2003 *
2004 * @function #animate
2005 * @memberOf Highcharts
2006 * @param {HTMLDOMElement|SVGElement} el - The element to animate.
2007 * @param {Object} params - An object containing key-value pairs of the
2008 * properties to animate. Supports numeric as pixel-based CSS properties
2009 * for HTML objects and attributes for SVGElements.
2010 * @param {AnimationOptions} [opt] - Animation options.
2011 */
2012 H.animate = function(el, params, opt) {
2013 var start,
2014 unit = '',
2015 end,
2016 fx,
2017 args;
2018
2019 if (!H.isObject(opt)) { // Number or undefined/null
2020 args = arguments;
2021 opt = {
2022 duration: args[2],
2023 easing: args[3],
2024 complete: args[4]
2025 };
2026 }
2027 if (!H.isNumber(opt.duration)) {
2028 opt.duration = 400;
2029 }
2030 opt.easing = typeof opt.easing === 'function' ?
2031 opt.easing :
2032 (Math[opt.easing] || Math.easeInOutSine);
2033 opt.curAnim = H.merge(params);
2034
2035 H.objectEach(params, function(val, prop) {
2036 // Stop current running animation of this property
2037 H.stop(el, prop);
2038
2039 fx = new H.Fx(el, opt, prop);
2040 end = null;
2041
2042 if (prop === 'd') {
2043 fx.paths = fx.initPath(
2044 el,
2045 el.d,
2046 params.d
2047 );
2048 fx.toD = params.d;
2049 start = 0;
2050 end = 1;
2051 } else if (el.attr) {
2052 start = el.attr(prop);
2053 } else {
2054 start = parseFloat(H.getStyle(el, prop)) || 0;
2055 if (prop !== 'opacity') {
2056 unit = 'px';
2057 }
2058 }
2059
2060 if (!end) {
2061 end = val;
2062 }
2063 if (end && end.match && end.match('px')) {
2064 end = end.replace(/px/g, ''); // #4351
2065 }
2066 fx.run(start, end, unit);
2067 });
2068 };
2069
2070 /**
2071 * Factory to create new series prototypes.
2072 *
2073 * @function #seriesType
2074 * @memberOf Highcharts
2075 *
2076 * @param {String} type - The series type name.
2077 * @param {String} parent - The parent series type name. Use `line` to inherit
2078 * from the basic {@link Series} object.
2079 * @param {Object} options - The additional default options that is merged with
2080 * the parent's options.
2081 * @param {Object} props - The properties (functions and primitives) to set on
2082 * the new prototype.
2083 * @param {Object} [pointProps] - Members for a series-specific extension of the
2084 * {@link Point} prototype if needed.
2085 * @returns {*} - The newly created prototype as extended from {@link Series}
2086 * or its derivatives.
2087 */
2088 // docs: add to API + extending Highcharts
2089 H.seriesType = function(type, parent, options, props, pointProps) {
2090 var defaultOptions = H.getOptions(),
2091 seriesTypes = H.seriesTypes;
2092
2093 // Merge the options
2094 defaultOptions.plotOptions[type] = H.merge(
2095 defaultOptions.plotOptions[parent],
2096 options
2097 );
2098
2099 // Create the class
2100 seriesTypes[type] = H.extendClass(seriesTypes[parent] ||
2101 function() {}, props);
2102 seriesTypes[type].prototype.type = type;
2103
2104 // Create the point class if needed
2105 if (pointProps) {
2106 seriesTypes[type].prototype.pointClass =
2107 H.extendClass(H.Point, pointProps);
2108 }
2109
2110 return seriesTypes[type];
2111 };
2112
2113 /**
2114 * Get a unique key for using in internal element id's and pointers. The key
2115 * is composed of a random hash specific to this Highcharts instance, and a
2116 * counter.
2117 * @function #uniqueKey
2118 * @memberOf Highcharts
2119 * @return {string} The key.
2120 * @example
2121 * var id = H.uniqueKey(); // => 'highcharts-x45f6hp-0'
2122 */
2123 H.uniqueKey = (function() {
2124
2125 var uniqueKeyHash = Math.random().toString(36).substring(2, 9),
2126 idCounter = 0;
2127
2128 return function() {
2129 return 'highcharts-' + uniqueKeyHash + '-' + idCounter++;
2130 };
2131 }());
2132
2133 /**
2134 * Register Highcharts as a plugin in jQuery
2135 */
2136 if (win.jQuery) {
2137 win.jQuery.fn.highcharts = function() {
2138 var args = [].slice.call(arguments);
2139
2140 if (this[0]) { // this[0] is the renderTo div
2141
2142 // Create the chart
2143 if (args[0]) {
2144 new H[ // eslint-disable-line no-new
2145 // Constructor defaults to Chart
2146 H.isString(args[0]) ? args.shift() : 'Chart'
2147 ](this[0], args[0], args[1]);
2148 return this;
2149 }
2150
2151 // When called without parameters or with the return argument,
2152 // return an existing chart
2153 return charts[H.attr(this[0], 'data-highcharts-chart')];
2154 }
2155 };
2156 }
2157
2158 }(Highcharts));
2159 (function(H) {
2160 /**
2161 * (c) 2010-2017 Torstein Honsi
2162 *
2163 * License: www.highcharts.com/license
2164 */
2165 var each = H.each,
2166 isNumber = H.isNumber,
2167 map = H.map,
2168 merge = H.merge,
2169 pInt = H.pInt;
2170
2171 /**
2172 * @typedef {string} ColorString
2173 * A valid color to be parsed and handled by Highcharts. Highcharts internally
2174 * supports hex colors like `#ffffff`, rgb colors like `rgb(255,255,255)` and
2175 * rgba colors like `rgba(255,255,255,1)`. Other colors may be supported by the
2176 * browsers and displayed correctly, but Highcharts is not able to process them
2177 * and apply concepts like opacity and brightening.
2178 */
2179 /**
2180 * Handle color operations. The object methods are chainable.
2181 * @param {String} input The input color in either rbga or hex format
2182 */
2183 H.Color = function(input) {
2184 // Backwards compatibility, allow instanciation without new
2185 if (!(this instanceof H.Color)) {
2186 return new H.Color(input);
2187 }
2188 // Initialize
2189 this.init(input);
2190 };
2191 H.Color.prototype = {
2192
2193 // Collection of parsers. This can be extended from the outside by pushing parsers
2194 // to Highcharts.Color.prototype.parsers.
2195 parsers: [{
2196 // RGBA color
2197 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*\)/,
2198 parse: function(result) {
2199 return [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
2200 }
2201 }, {
2202 // RGB color
2203 regex: /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,
2204 parse: function(result) {
2205 return [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1];
2206 }
2207 }],
2208
2209 // Collection of named colors. Can be extended from the outside by adding
2210 // colors to Highcharts.Color.prototype.names.
2211 names: {
2212 none: 'rgba(255,255,255,0)',
2213 white: '#ffffff',
2214 black: '#000000'
2215 },
2216
2217 /**
2218 * Parse the input color to rgba array
2219 * @param {String} input
2220 */
2221 init: function(input) {
2222 var result,
2223 rgba,
2224 i,
2225 parser,
2226 len;
2227
2228 this.input = input = this.names[
2229 input && input.toLowerCase ?
2230 input.toLowerCase() :
2231 ''
2232 ] || input;
2233
2234 // Gradients
2235 if (input && input.stops) {
2236 this.stops = map(input.stops, function(stop) {
2237 return new H.Color(stop[1]);
2238 });
2239
2240 // Solid colors
2241 } else {
2242
2243 // Bitmasking as input[0] is not working for legacy IE.
2244 if (input && input.charAt && input.charAt() === '#') {
2245
2246 len = input.length;
2247 input = parseInt(input.substr(1), 16);
2248
2249 // Handle long-form, e.g. #AABBCC
2250 if (len === 7) {
2251
2252 rgba = [
2253 (input & 0xFF0000) >> 16,
2254 (input & 0xFF00) >> 8,
2255 (input & 0xFF),
2256 1
2257 ];
2258
2259 // Handle short-form, e.g. #ABC
2260 // In short form, the value is assumed to be the same
2261 // for both nibbles for each component. e.g. #ABC = #AABBCC
2262 } else if (len === 4) {
2263
2264 rgba = [
2265 ((input & 0xF00) >> 4) | (input & 0xF00) >> 8,
2266 ((input & 0xF0) >> 4) | (input & 0xF0),
2267 ((input & 0xF) << 4) | (input & 0xF),
2268 1
2269 ];
2270 }
2271 }
2272
2273 // Otherwise, check regex parsers
2274 if (!rgba) {
2275 i = this.parsers.length;
2276 while (i-- && !rgba) {
2277 parser = this.parsers[i];
2278 result = parser.regex.exec(input);
2279 if (result) {
2280 rgba = parser.parse(result);
2281 }
2282 }
2283 }
2284 }
2285 this.rgba = rgba || [];
2286 },
2287
2288 /**
2289 * Return the color a specified format
2290 * @param {String} format
2291 */
2292 get: function(format) {
2293 var input = this.input,
2294 rgba = this.rgba,
2295 ret;
2296
2297 if (this.stops) {
2298 ret = merge(input);
2299 ret.stops = [].concat(ret.stops);
2300 each(this.stops, function(stop, i) {
2301 ret.stops[i] = [ret.stops[i][0], stop.get(format)];
2302 });
2303
2304 // it's NaN if gradient colors on a column chart
2305 } else if (rgba && isNumber(rgba[0])) {
2306 if (format === 'rgb' || (!format && rgba[3] === 1)) {
2307 ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
2308 } else if (format === 'a') {
2309 ret = rgba[3];
2310 } else {
2311 ret = 'rgba(' + rgba.join(',') + ')';
2312 }
2313 } else {
2314 ret = input;
2315 }
2316 return ret;
2317 },
2318
2319 /**
2320 * Brighten the color
2321 * @param {Number} alpha
2322 */
2323 brighten: function(alpha) {
2324 var i,
2325 rgba = this.rgba;
2326
2327 if (this.stops) {
2328 each(this.stops, function(stop) {
2329 stop.brighten(alpha);
2330 });
2331
2332 } else if (isNumber(alpha) && alpha !== 0) {
2333 for (i = 0; i < 3; i++) {
2334 rgba[i] += pInt(alpha * 255);
2335
2336 if (rgba[i] < 0) {
2337 rgba[i] = 0;
2338 }
2339 if (rgba[i] > 255) {
2340 rgba[i] = 255;
2341 }
2342 }
2343 }
2344 return this;
2345 },
2346
2347 /**
2348 * Set the color's opacity to a given alpha value
2349 * @param {Number} alpha
2350 */
2351 setOpacity: function(alpha) {
2352 this.rgba[3] = alpha;
2353 return this;
2354 },
2355
2356 /*
2357 * Return an intermediate color between two colors.
2358 *
2359 * @param {Highcharts.Color} to
2360 * The color object to tween to.
2361 * @param {Number} pos
2362 * The intermediate position, where 0 is the from color (current
2363 * color item), and 1 is the `to` color.
2364 *
2365 * @return {String}
2366 * The intermediate color in rgba notation.
2367 */
2368 tweenTo: function(to, pos) {
2369 // Check for has alpha, because rgba colors perform worse due to lack of
2370 // support in WebKit.
2371 var fromRgba = this.rgba,
2372 toRgba = to.rgba,
2373 hasAlpha,
2374 ret;
2375
2376 // Unsupported color, return to-color (#3920, #7034)
2377 if (!toRgba.length || !fromRgba || !fromRgba.length) {
2378 ret = to.input || 'none';
2379
2380 // Interpolate
2381 } else {
2382 hasAlpha = (toRgba[3] !== 1 || fromRgba[3] !== 1);
2383 ret = (hasAlpha ? 'rgba(' : 'rgb(') +
2384 Math.round(toRgba[0] + (fromRgba[0] - toRgba[0]) * (1 - pos)) +
2385 ',' +
2386 Math.round(toRgba[1] + (fromRgba[1] - toRgba[1]) * (1 - pos)) +
2387 ',' +
2388 Math.round(toRgba[2] + (fromRgba[2] - toRgba[2]) * (1 - pos)) +
2389 (
2390 hasAlpha ?
2391 (
2392 ',' +
2393 (toRgba[3] + (fromRgba[3] - toRgba[3]) * (1 - pos))
2394 ) :
2395 ''
2396 ) +
2397 ')';
2398 }
2399 return ret;
2400 }
2401 };
2402 H.color = function(input) {
2403 return new H.Color(input);
2404 };
2405
2406 }(Highcharts));
2407 (function(H) {
2408 /**
2409 * (c) 2010-2017 Torstein Honsi
2410 *
2411 * License: www.highcharts.com/license
2412 */
2413 var SVGElement,
2414 SVGRenderer,
2415
2416 addEvent = H.addEvent,
2417 animate = H.animate,
2418 attr = H.attr,
2419 charts = H.charts,
2420 color = H.color,
2421 css = H.css,
2422 createElement = H.createElement,
2423 defined = H.defined,
2424 deg2rad = H.deg2rad,
2425 destroyObjectProperties = H.destroyObjectProperties,
2426 doc = H.doc,
2427 each = H.each,
2428 extend = H.extend,
2429 erase = H.erase,
2430 grep = H.grep,
2431 hasTouch = H.hasTouch,
2432 inArray = H.inArray,
2433 isArray = H.isArray,
2434 isFirefox = H.isFirefox,
2435 isMS = H.isMS,
2436 isObject = H.isObject,
2437 isString = H.isString,
2438 isWebKit = H.isWebKit,
2439 merge = H.merge,
2440 noop = H.noop,
2441 objectEach = H.objectEach,
2442 pick = H.pick,
2443 pInt = H.pInt,
2444 removeEvent = H.removeEvent,
2445 splat = H.splat,
2446 stop = H.stop,
2447 svg = H.svg,
2448 SVG_NS = H.SVG_NS,
2449 symbolSizes = H.symbolSizes,
2450 win = H.win;
2451
2452 /**
2453 * @typedef {Object} SVGDOMElement - An SVG DOM element.
2454 */
2455 /**
2456 * The SVGElement prototype is a JavaScript wrapper for SVG elements used in the
2457 * rendering layer of Highcharts. Combined with the {@link
2458 * Highcharts.SVGRenderer} object, these prototypes allow freeform annotation
2459 * in the charts or even in HTML pages without instanciating a chart. The
2460 * SVGElement can also wrap HTML labels, when `text` or `label` elements are
2461 * created with the `useHTML` parameter.
2462 *
2463 * The SVGElement instances are created through factory functions on the
2464 * {@link Highcharts.SVGRenderer} object, like
2465 * [rect]{@link Highcharts.SVGRenderer#rect}, [path]{@link
2466 * Highcharts.SVGRenderer#path}, [text]{@link Highcharts.SVGRenderer#text},
2467 * [label]{@link Highcharts.SVGRenderer#label}, [g]{@link
2468 * Highcharts.SVGRenderer#g} and more.
2469 *
2470 * @class Highcharts.SVGElement
2471 */
2472 SVGElement = H.SVGElement = function() {
2473 return this;
2474 };
2475 extend(SVGElement.prototype, /** @lends Highcharts.SVGElement.prototype */ {
2476
2477 // Default base for animation
2478 opacity: 1,
2479 SVG_NS: SVG_NS,
2480
2481 /**
2482 * For labels, these CSS properties are applied to the `text` node directly.
2483 *
2484 * @private
2485 * @type {Array.<string>}
2486 */
2487 textProps: ['direction', 'fontSize', 'fontWeight', 'fontFamily',
2488 'fontStyle', 'color', 'lineHeight', 'width', 'textAlign',
2489 'textDecoration', 'textOverflow', 'textOutline'
2490 ],
2491
2492 /**
2493 * Initialize the SVG renderer. This function only exists to make the
2494 * initiation process overridable. It should not be called directly.
2495 *
2496 * @param {SVGRenderer} renderer
2497 * The SVGRenderer instance to initialize to.
2498 * @param {String} nodeName
2499 * The SVG node name.
2500 *
2501 */
2502 init: function(renderer, nodeName) {
2503
2504 /**
2505 * The primary DOM node. Each `SVGElement` instance wraps a main DOM
2506 * node, but may also represent more nodes.
2507 *
2508 * @name element
2509 * @memberOf SVGElement
2510 * @type {SVGDOMNode|HTMLDOMNode}
2511 */
2512 this.element = nodeName === 'span' ?
2513 createElement(nodeName) :
2514 doc.createElementNS(this.SVG_NS, nodeName);
2515
2516 /**
2517 * The renderer that the SVGElement belongs to.
2518 *
2519 * @name renderer
2520 * @memberOf SVGElement
2521 * @type {SVGRenderer}
2522 */
2523 this.renderer = renderer;
2524 },
2525
2526 /**
2527 * Animate to given attributes or CSS properties.
2528 *
2529 * @param {SVGAttributes} params SVG attributes or CSS to animate.
2530 * @param {AnimationOptions} [options] Animation options.
2531 * @param {Function} [complete] Function to perform at the end of animation.
2532 *
2533 * @sample highcharts/members/element-on/
2534 * Setting some attributes by animation
2535 *
2536 * @returns {SVGElement} Returns the SVGElement for chaining.
2537 */
2538 animate: function(params, options, complete) {
2539 var animOptions = H.animObject(
2540 pick(options, this.renderer.globalAnimation, true)
2541 );
2542 if (animOptions.duration !== 0) {
2543 // allows using a callback with the global animation without
2544 // overwriting it
2545 if (complete) {
2546 animOptions.complete = complete;
2547 }
2548 animate(this, params, animOptions);
2549 } else {
2550 this.attr(params, null, complete);
2551 if (animOptions.step) {
2552 animOptions.step.call(this);
2553 }
2554 }
2555 return this;
2556 },
2557
2558 /**
2559 * @typedef {Object} GradientOptions
2560 * @property {Object} linearGradient Holds an object that defines the start
2561 * position and the end position relative to the shape.
2562 * @property {Number} linearGradient.x1 Start horizontal position of the
2563 * gradient. Ranges 0-1.
2564 * @property {Number} linearGradient.x2 End horizontal position of the
2565 * gradient. Ranges 0-1.
2566 * @property {Number} linearGradient.y1 Start vertical position of the
2567 * gradient. Ranges 0-1.
2568 * @property {Number} linearGradient.y2 End vertical position of the
2569 * gradient. Ranges 0-1.
2570 * @property {Object} radialGradient Holds an object that defines the center
2571 * position and the radius.
2572 * @property {Number} radialGradient.cx Center horizontal position relative
2573 * to the shape. Ranges 0-1.
2574 * @property {Number} radialGradient.cy Center vertical position relative
2575 * to the shape. Ranges 0-1.
2576 * @property {Number} radialGradient.r Radius relative to the shape. Ranges
2577 * 0-1.
2578 * @property {Array.<Array>} stops The first item in each tuple is the
2579 * position in the gradient, where 0 is the start of the gradient and 1
2580 * is the end of the gradient. Multiple stops can be applied. The second
2581 * item is the color for each stop. This color can also be given in the
2582 * rgba format.
2583 *
2584 * @example
2585 * // Linear gradient used as a color option
2586 * color: {
2587 * linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 },
2588 * stops: [
2589 * [0, '#003399'], // start
2590 * [0.5, '#ffffff'], // middle
2591 * [1, '#3366AA'] // end
2592 * ]
2593 * }
2594 * }
2595 */
2596 /**
2597 * Build and apply an SVG gradient out of a common JavaScript configuration
2598 * object. This function is called from the attribute setters.
2599 *
2600 * @private
2601 * @param {GradientOptions} color The gradient options structure.
2602 * @param {string} prop The property to apply, can either be `fill` or
2603 * `stroke`.
2604 * @param {SVGDOMElement} elem SVG DOM element to apply the gradient on.
2605 */
2606 colorGradient: function(color, prop, elem) {
2607 var renderer = this.renderer,
2608 colorObject,
2609 gradName,
2610 gradAttr,
2611 radAttr,
2612 gradients,
2613 gradientObject,
2614 stops,
2615 stopColor,
2616 stopOpacity,
2617 radialReference,
2618 id,
2619 key = [],
2620 value;
2621
2622 // Apply linear or radial gradients
2623 if (color.radialGradient) {
2624 gradName = 'radialGradient';
2625 } else if (color.linearGradient) {
2626 gradName = 'linearGradient';
2627 }
2628
2629 if (gradName) {
2630 gradAttr = color[gradName];
2631 gradients = renderer.gradients;
2632 stops = color.stops;
2633 radialReference = elem.radialReference;
2634
2635 // Keep < 2.2 kompatibility
2636 if (isArray(gradAttr)) {
2637 color[gradName] = gradAttr = {
2638 x1: gradAttr[0],
2639 y1: gradAttr[1],
2640 x2: gradAttr[2],
2641 y2: gradAttr[3],
2642 gradientUnits: 'userSpaceOnUse'
2643 };
2644 }
2645
2646 // Correct the radial gradient for the radial reference system
2647 if (
2648 gradName === 'radialGradient' &&
2649 radialReference &&
2650 !defined(gradAttr.gradientUnits)
2651 ) {
2652 radAttr = gradAttr; // Save the radial attributes for updating
2653 gradAttr = merge(
2654 gradAttr,
2655 renderer.getRadialAttr(radialReference, radAttr), {
2656 gradientUnits: 'userSpaceOnUse'
2657 }
2658 );
2659 }
2660
2661 // Build the unique key to detect whether we need to create a new
2662 // element (#1282)
2663 objectEach(gradAttr, function(val, n) {
2664 if (n !== 'id') {
2665 key.push(n, val);
2666 }
2667 });
2668 objectEach(stops, function(val) {
2669 key.push(val);
2670 });
2671 key = key.join(',');
2672
2673 // Check if a gradient object with the same config object is created
2674 // within this renderer
2675 if (gradients[key]) {
2676 id = gradients[key].attr('id');
2677
2678 } else {
2679
2680 // Set the id and create the element
2681 gradAttr.id = id = H.uniqueKey();
2682 gradients[key] = gradientObject =
2683 renderer.createElement(gradName)
2684 .attr(gradAttr)
2685 .add(renderer.defs);
2686
2687 gradientObject.radAttr = radAttr;
2688
2689 // The gradient needs to keep a list of stops to be able to
2690 // destroy them
2691 gradientObject.stops = [];
2692 each(stops, function(stop) {
2693 var stopObject;
2694 if (stop[1].indexOf('rgba') === 0) {
2695 colorObject = H.color(stop[1]);
2696 stopColor = colorObject.get('rgb');
2697 stopOpacity = colorObject.get('a');
2698 } else {
2699 stopColor = stop[1];
2700 stopOpacity = 1;
2701 }
2702 stopObject = renderer.createElement('stop').attr({
2703 offset: stop[0],
2704 'stop-color': stopColor,
2705 'stop-opacity': stopOpacity
2706 }).add(gradientObject);
2707
2708 // Add the stop element to the gradient
2709 gradientObject.stops.push(stopObject);
2710 });
2711 }
2712
2713 // Set the reference to the gradient object
2714 value = 'url(' + renderer.url + '#' + id + ')';
2715 elem.setAttribute(prop, value);
2716 elem.gradient = key;
2717
2718 // Allow the color to be concatenated into tooltips formatters etc.
2719 // (#2995)
2720 color.toString = function() {
2721 return value;
2722 };
2723 }
2724 },
2725
2726 /**
2727 * Apply a text outline through a custom CSS property, by copying the text
2728 * element and apply stroke to the copy. Used internally. Contrast checks
2729 * at http://jsfiddle.net/highcharts/43soe9m1/2/ .
2730 *
2731 * @private
2732 * @param {String} textOutline A custom CSS `text-outline` setting, defined
2733 * by `width color`.
2734 * @example
2735 * // Specific color
2736 * text.css({
2737 * textOutline: '1px black'
2738 * });
2739 * // Automatic contrast
2740 * text.css({
2741 * color: '#000000', // black text
2742 * textOutline: '1px contrast' // => white outline
2743 * });
2744 */
2745 applyTextOutline: function(textOutline) {
2746 var elem = this.element,
2747 tspans,
2748 tspan,
2749 hasContrast = textOutline.indexOf('contrast') !== -1,
2750 styles = {},
2751 color,
2752 strokeWidth,
2753 firstRealChild,
2754 i;
2755
2756 // When the text shadow is set to contrast, use dark stroke for light
2757 // text and vice versa.
2758 if (hasContrast) {
2759 styles.textOutline = textOutline = textOutline.replace(
2760 /contrast/g,
2761 this.renderer.getContrast(elem.style.fill)
2762 );
2763 }
2764
2765 // Extract the stroke width and color
2766 textOutline = textOutline.split(' ');
2767 color = textOutline[textOutline.length - 1];
2768 strokeWidth = textOutline[0];
2769
2770 if (strokeWidth && strokeWidth !== 'none' && H.svg) {
2771
2772 this.fakeTS = true; // Fake text shadow
2773
2774 tspans = [].slice.call(elem.getElementsByTagName('tspan'));
2775
2776 // In order to get the right y position of the clone,
2777 // copy over the y setter
2778 this.ySetter = this.xSetter;
2779
2780 // Since the stroke is applied on center of the actual outline, we
2781 // need to double it to get the correct stroke-width outside the
2782 // glyphs.
2783 strokeWidth = strokeWidth.replace(
2784 /(^[\d\.]+)(.*?)$/g,
2785 function(match, digit, unit) {
2786 return (2 * digit) + unit;
2787 }
2788 );
2789
2790 // Remove shadows from previous runs. Iterate from the end to
2791 // support removing items inside the cycle (#6472).
2792 i = tspans.length;
2793 while (i--) {
2794 tspan = tspans[i];
2795 if (tspan.getAttribute('class') === 'highcharts-text-outline') {
2796 // Remove then erase
2797 erase(tspans, elem.removeChild(tspan));
2798 }
2799 }
2800
2801 // For each of the tspans, create a stroked copy behind it.
2802 firstRealChild = elem.firstChild;
2803 each(tspans, function(tspan, y) {
2804 var clone;
2805
2806 // Let the first line start at the correct X position
2807 if (y === 0) {
2808 tspan.setAttribute('x', elem.getAttribute('x'));
2809 y = elem.getAttribute('y');
2810 tspan.setAttribute('y', y || 0);
2811 if (y === null) {
2812 elem.setAttribute('y', 0);
2813 }
2814 }
2815
2816 // Create the clone and apply outline properties
2817 clone = tspan.cloneNode(1);
2818 attr(clone, {
2819 'class': 'highcharts-text-outline',
2820 'fill': color,
2821 'stroke': color,
2822 'stroke-width': strokeWidth,
2823 'stroke-linejoin': 'round'
2824 });
2825 elem.insertBefore(clone, firstRealChild);
2826 });
2827 }
2828 },
2829
2830 /**
2831 *
2832 * @typedef {Object} SVGAttributes An object of key-value pairs for SVG
2833 * attributes. Attributes in Highcharts elements for the most parts
2834 * correspond to SVG, but some are specific to Highcharts, like `zIndex`,
2835 * `rotation`, `rotationOriginX`, `rotationOriginY`, `translateX`,
2836 * `translateY`, `scaleX` and `scaleY`. SVG attributes containing a hyphen
2837 * are _not_ camel-cased, they should be quoted to preserve the hyphen.
2838 *
2839 * @example
2840 * {
2841 * 'stroke': '#ff0000', // basic
2842 * 'stroke-width': 2, // hyphenated
2843 * 'rotation': 45 // custom
2844 * 'd': ['M', 10, 10, 'L', 30, 30, 'z'] // path definition, note format
2845 * }
2846 */
2847 /**
2848 * Apply native and custom attributes to the SVG elements.
2849 *
2850 * In order to set the rotation center for rotation, set x and y to 0 and
2851 * use `translateX` and `translateY` attributes to position the element
2852 * instead.
2853 *
2854 * Attributes frequently used in Highcharts are `fill`, `stroke`,
2855 * `stroke-width`.
2856 *
2857 * @param {SVGAttributes|String} hash - The native and custom SVG
2858 * attributes.
2859 * @param {string} [val] - If the type of the first argument is `string`,
2860 * the second can be a value, which will serve as a single attribute
2861 * setter. If the first argument is a string and the second is undefined,
2862 * the function serves as a getter and the current value of the property
2863 * is returned.
2864 * @param {Function} [complete] - A callback function to execute after
2865 * setting the attributes. This makes the function compliant and
2866 * interchangeable with the {@link SVGElement#animate} function.
2867 * @param {boolean} [continueAnimation=true] Used internally when `.attr` is
2868 * called as part of an animation step. Otherwise, calling `.attr` for an
2869 * attribute will stop animation for that attribute.
2870 *
2871 * @returns {SVGElement|string|number} If used as a setter, it returns the
2872 * current {@link SVGElement} so the calls can be chained. If used as a
2873 * getter, the current value of the attribute is returned.
2874 *
2875 * @sample highcharts/members/renderer-rect/
2876 * Setting some attributes
2877 *
2878 * @example
2879 * // Set multiple attributes
2880 * element.attr({
2881 * stroke: 'red',
2882 * fill: 'blue',
2883 * x: 10,
2884 * y: 10
2885 * });
2886 *
2887 * // Set a single attribute
2888 * element.attr('stroke', 'red');
2889 *
2890 * // Get an attribute
2891 * element.attr('stroke'); // => 'red'
2892 *
2893 */
2894 attr: function(hash, val, complete, continueAnimation) {
2895 var key,
2896 element = this.element,
2897 hasSetSymbolSize,
2898 ret = this,
2899 skipAttr,
2900 setter;
2901
2902 // single key-value pair
2903 if (typeof hash === 'string' && val !== undefined) {
2904 key = hash;
2905 hash = {};
2906 hash[key] = val;
2907 }
2908
2909 // used as a getter: first argument is a string, second is undefined
2910 if (typeof hash === 'string') {
2911 ret = (this[hash + 'Getter'] || this._defaultGetter).call(
2912 this,
2913 hash,
2914 element
2915 );
2916
2917 // setter
2918 } else {
2919
2920 objectEach(hash, function eachAttribute(val, key) {
2921 skipAttr = false;
2922
2923 // Unless .attr is from the animator update, stop current
2924 // running animation of this property
2925 if (!continueAnimation) {
2926 stop(this, key);
2927 }
2928
2929 // Special handling of symbol attributes
2930 if (
2931 this.symbolName &&
2932 /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)$/
2933 .test(key)
2934 ) {
2935 if (!hasSetSymbolSize) {
2936 this.symbolAttr(hash);
2937 hasSetSymbolSize = true;
2938 }
2939 skipAttr = true;
2940 }
2941
2942 if (this.rotation && (key === 'x' || key === 'y')) {
2943 this.doTransform = true;
2944 }
2945
2946 if (!skipAttr) {
2947 setter = this[key + 'Setter'] || this._defaultSetter;
2948 setter.call(this, val, key, element);
2949
2950
2951 }
2952 }, this);
2953
2954 this.afterSetters();
2955 }
2956
2957 // In accordance with animate, run a complete callback
2958 if (complete) {
2959 complete();
2960 }
2961
2962 return ret;
2963 },
2964
2965 /**
2966 * This method is executed in the end of `attr()`, after setting all
2967 * attributes in the hash. In can be used to efficiently consolidate
2968 * multiple attributes in one SVG property -- e.g., translate, rotate and
2969 * scale are merged in one "transform" attribute in the SVG node.
2970 *
2971 * @private
2972 */
2973 afterSetters: function() {
2974 // Update transform. Do this outside the loop to prevent redundant
2975 // updating for batch setting of attributes.
2976 if (this.doTransform) {
2977 this.updateTransform();
2978 this.doTransform = false;
2979 }
2980 },
2981
2982
2983
2984 /**
2985 * Add a class name to an element.
2986 *
2987 * @param {string} className - The new class name to add.
2988 * @param {boolean} [replace=false] - When true, the existing class name(s)
2989 * will be overwritten with the new one. When false, the new one is
2990 * added.
2991 * @returns {SVGElement} Return the SVG element for chainability.
2992 */
2993 addClass: function(className, replace) {
2994 var currentClassName = this.attr('class') || '';
2995 if (currentClassName.indexOf(className) === -1) {
2996 if (!replace) {
2997 className =
2998 (currentClassName + (currentClassName ? ' ' : '') +
2999 className).replace(' ', ' ');
3000 }
3001 this.attr('class', className);
3002 }
3003
3004 return this;
3005 },
3006
3007 /**
3008 * Check if an element has the given class name.
3009 * @param {string} className
3010 * The class name to check for.
3011 * @return {Boolean}
3012 * Whether the class name is found.
3013 */
3014 hasClass: function(className) {
3015 return inArray(
3016 className,
3017 (this.attr('class') || '').split(' ')
3018 ) !== -1;
3019 },
3020
3021 /**
3022 * Remove a class name from the element.
3023 * @param {String|RegExp} className The class name to remove.
3024 * @return {SVGElement} Returns the SVG element for chainability.
3025 */
3026 removeClass: function(className) {
3027 return this.attr(
3028 'class',
3029 (this.attr('class') || '').replace(className, '')
3030 );
3031 },
3032
3033 /**
3034 * If one of the symbol size affecting parameters are changed,
3035 * check all the others only once for each call to an element's
3036 * .attr() method
3037 * @param {Object} hash - The attributes to set.
3038 * @private
3039 */
3040 symbolAttr: function(hash) {
3041 var wrapper = this;
3042
3043 each([
3044 'x',
3045 'y',
3046 'r',
3047 'start',
3048 'end',
3049 'width',
3050 'height',
3051 'innerR',
3052 'anchorX',
3053 'anchorY'
3054 ], function(key) {
3055 wrapper[key] = pick(hash[key], wrapper[key]);
3056 });
3057
3058 wrapper.attr({
3059 d: wrapper.renderer.symbols[wrapper.symbolName](
3060 wrapper.x,
3061 wrapper.y,
3062 wrapper.width,
3063 wrapper.height,
3064 wrapper
3065 )
3066 });
3067 },
3068
3069 /**
3070 * Apply a clipping rectangle to this element.
3071 *
3072 * @param {ClipRect} [clipRect] - The clipping rectangle. If skipped, the
3073 * current clip is removed.
3074 * @returns {SVGElement} Returns the SVG element to allow chaining.
3075 */
3076 clip: function(clipRect) {
3077 return this.attr(
3078 'clip-path',
3079 clipRect ?
3080 'url(' + this.renderer.url + '#' + clipRect.id + ')' :
3081 'none'
3082 );
3083 },
3084
3085 /**
3086 * Calculate the coordinates needed for drawing a rectangle crisply and
3087 * return the calculated attributes.
3088 *
3089 * @param {Object} rect - A rectangle.
3090 * @param {number} rect.x - The x position.
3091 * @param {number} rect.y - The y position.
3092 * @param {number} rect.width - The width.
3093 * @param {number} rect.height - The height.
3094 * @param {number} [strokeWidth] - The stroke width to consider when
3095 * computing crisp positioning. It can also be set directly on the rect
3096 * parameter.
3097 *
3098 * @returns {{x: Number, y: Number, width: Number, height: Number}} The
3099 * modified rectangle arguments.
3100 */
3101 crisp: function(rect, strokeWidth) {
3102
3103 var wrapper = this,
3104 attribs = {},
3105 normalizer;
3106
3107 strokeWidth = strokeWidth || rect.strokeWidth || 0;
3108 // Math.round because strokeWidth can sometimes have roundoff errors
3109 normalizer = Math.round(strokeWidth) % 2 / 2;
3110
3111 // normalize for crisp edges
3112 rect.x = Math.floor(rect.x || wrapper.x || 0) + normalizer;
3113 rect.y = Math.floor(rect.y || wrapper.y || 0) + normalizer;
3114 rect.width = Math.floor(
3115 (rect.width || wrapper.width || 0) - 2 * normalizer
3116 );
3117 rect.height = Math.floor(
3118 (rect.height || wrapper.height || 0) - 2 * normalizer
3119 );
3120 if (defined(rect.strokeWidth)) {
3121 rect.strokeWidth = strokeWidth;
3122 }
3123
3124 objectEach(rect, function(val, key) {
3125 if (wrapper[key] !== val) { // only set attribute if changed
3126 wrapper[key] = attribs[key] = val;
3127 }
3128 });
3129
3130 return attribs;
3131 },
3132
3133 /**
3134 * Set styles for the element. In addition to CSS styles supported by
3135 * native SVG and HTML elements, there are also some custom made for
3136 * Highcharts, like `width`, `ellipsis` and `textOverflow` for SVG text
3137 * elements.
3138 * @param {CSSObject} styles The new CSS styles.
3139 * @returns {SVGElement} Return the SVG element for chaining.
3140 *
3141 * @sample highcharts/members/renderer-text-on-chart/
3142 * Styled text
3143 */
3144 css: function(styles) {
3145 var oldStyles = this.styles,
3146 newStyles = {},
3147 elem = this.element,
3148 textWidth,
3149 serializedCss = '',
3150 hyphenate,
3151 hasNew = !oldStyles,
3152 // These CSS properties are interpreted internally by the SVG
3153 // renderer, but are not supported by SVG and should not be added to
3154 // the DOM. In styled mode, no CSS should find its way to the DOM
3155 // whatsoever (#6173, #6474).
3156 svgPseudoProps = ['textOutline', 'textOverflow', 'width'];
3157
3158 // convert legacy
3159 if (styles && styles.color) {
3160 styles.fill = styles.color;
3161 }
3162
3163 // Filter out existing styles to increase performance (#2640)
3164 if (oldStyles) {
3165 objectEach(styles, function(style, n) {
3166 if (style !== oldStyles[n]) {
3167 newStyles[n] = style;
3168 hasNew = true;
3169 }
3170 });
3171 }
3172 if (hasNew) {
3173
3174 // Merge the new styles with the old ones
3175 if (oldStyles) {
3176 styles = extend(
3177 oldStyles,
3178 newStyles
3179 );
3180 }
3181
3182 // Get the text width from style
3183 textWidth = this.textWidth = (
3184 styles &&
3185 styles.width &&
3186 styles.width !== 'auto' &&
3187 elem.nodeName.toLowerCase() === 'text' &&
3188 pInt(styles.width)
3189 );
3190
3191 // store object
3192 this.styles = styles;
3193
3194 if (textWidth && (!svg && this.renderer.forExport)) {
3195 delete styles.width;
3196 }
3197
3198 // serialize and set style attribute
3199 if (isMS && !svg) {
3200 css(this.element, styles);
3201 } else {
3202 hyphenate = function(a, b) {
3203 return '-' + b.toLowerCase();
3204 };
3205 objectEach(styles, function(style, n) {
3206 if (inArray(n, svgPseudoProps) === -1) {
3207 serializedCss +=
3208 n.replace(/([A-Z])/g, hyphenate) + ':' +
3209 style + ';';
3210 }
3211 });
3212 if (serializedCss) {
3213 attr(elem, 'style', serializedCss); // #1881
3214 }
3215 }
3216
3217
3218 if (this.added) {
3219
3220 // Rebuild text after added. Cache mechanisms in the buildText
3221 // will prevent building if there are no significant changes.
3222 if (this.element.nodeName === 'text') {
3223 this.renderer.buildText(this);
3224 }
3225
3226 // Apply text outline after added
3227 if (styles && styles.textOutline) {
3228 this.applyTextOutline(styles.textOutline);
3229 }
3230 }
3231 }
3232
3233 return this;
3234 },
3235
3236
3237 /**
3238 * Get the computed style. Only in styled mode.
3239 * @param {string} prop - The property name to check for.
3240 * @returns {string} The current computed value.
3241 * @example
3242 * chart.series[0].points[0].graphic.getStyle('stroke-width'); // => '1px'
3243 */
3244 getStyle: function(prop) {
3245 return win.getComputedStyle(this.element || this, '')
3246 .getPropertyValue(prop);
3247 },
3248
3249 /**
3250 * Get the computed stroke width in pixel values. This is used extensively
3251 * when drawing shapes to ensure the shapes are rendered crisp and
3252 * positioned correctly relative to each other. Using `shape-rendering:
3253 * crispEdges` leaves us less control over positioning, for example when we
3254 * want to stack columns next to each other, or position things
3255 * pixel-perfectly within the plot box.
3256 *
3257 * The common pattern when placing a shape is:
3258 * * Create the SVGElement and add it to the DOM. In styled mode, it will
3259 * now receive a stroke width from the style sheet. In classic mode we
3260 * will add the `stroke-width` attribute.
3261 * * Read the computed `elem.strokeWidth()`.
3262 * * Place it based on the stroke width.
3263 *
3264 * @returns {Number} The stroke width in pixels. Even if the given stroke
3265 * widtch (in CSS or by attributes) is based on `em` or other units, the
3266 * pixel size is returned.
3267 */
3268 strokeWidth: function() {
3269 var val = this.getStyle('stroke-width'),
3270 ret,
3271 dummy;
3272
3273 // Read pixel values directly
3274 if (val.indexOf('px') === val.length - 2) {
3275 ret = pInt(val);
3276
3277 // Other values like em, pt etc need to be measured
3278 } else {
3279 dummy = doc.createElementNS(SVG_NS, 'rect');
3280 attr(dummy, {
3281 'width': val,
3282 'stroke-width': 0
3283 });
3284 this.element.parentNode.appendChild(dummy);
3285 ret = dummy.getBBox().width;
3286 dummy.parentNode.removeChild(dummy);
3287 }
3288 return ret;
3289 },
3290
3291 /**
3292 * Add an event listener. This is a simple setter that replaces all other
3293 * events of the same type, opposed to the {@link Highcharts#addEvent}
3294 * function.
3295 * @param {string} eventType - The event type. If the type is `click`,
3296 * Highcharts will internally translate it to a `touchstart` event on
3297 * touch devices, to prevent the browser from waiting for a click event
3298 * from firing.
3299 * @param {Function} handler - The handler callback.
3300 * @returns {SVGElement} The SVGElement for chaining.
3301 *
3302 * @sample highcharts/members/element-on/
3303 * A clickable rectangle
3304 */
3305 on: function(eventType, handler) {
3306 var svgElement = this,
3307 element = svgElement.element;
3308
3309 // touch
3310 if (hasTouch && eventType === 'click') {
3311 element.ontouchstart = function(e) {
3312 svgElement.touchEventFired = Date.now(); // #2269
3313 e.preventDefault();
3314 handler.call(element, e);
3315 };
3316 element.onclick = function(e) {
3317 if (win.navigator.userAgent.indexOf('Android') === -1 ||
3318 Date.now() - (svgElement.touchEventFired || 0) > 1100) {
3319 handler.call(element, e);
3320 }
3321 };
3322 } else {
3323 // simplest possible event model for internal use
3324 element['on' + eventType] = handler;
3325 }
3326 return this;
3327 },
3328
3329 /**
3330 * Set the coordinates needed to draw a consistent radial gradient across
3331 * a shape regardless of positioning inside the chart. Used on pie slices
3332 * to make all the slices have the same radial reference point.
3333 *
3334 * @param {Array} coordinates The center reference. The format is
3335 * `[centerX, centerY, diameter]` in pixels.
3336 * @returns {SVGElement} Returns the SVGElement for chaining.
3337 */
3338 setRadialReference: function(coordinates) {
3339 var existingGradient = this.renderer.gradients[this.element.gradient];
3340
3341 this.element.radialReference = coordinates;
3342
3343 // On redrawing objects with an existing gradient, the gradient needs
3344 // to be repositioned (#3801)
3345 if (existingGradient && existingGradient.radAttr) {
3346 existingGradient.animate(
3347 this.renderer.getRadialAttr(
3348 coordinates,
3349 existingGradient.radAttr
3350 )
3351 );
3352 }
3353
3354 return this;
3355 },
3356
3357 /**
3358 * Move an object and its children by x and y values.
3359 *
3360 * @param {number} x - The x value.
3361 * @param {number} y - The y value.
3362 */
3363 translate: function(x, y) {
3364 return this.attr({
3365 translateX: x,
3366 translateY: y
3367 });
3368 },
3369
3370 /**
3371 * Invert a group, rotate and flip. This is used internally on inverted
3372 * charts, where the points and graphs are drawn as if not inverted, then
3373 * the series group elements are inverted.
3374 *
3375 * @param {boolean} inverted
3376 * Whether to invert or not. An inverted shape can be un-inverted by
3377 * setting it to false.
3378 * @return {SVGElement}
3379 * Return the SVGElement for chaining.
3380 */
3381 invert: function(inverted) {
3382 var wrapper = this;
3383 wrapper.inverted = inverted;
3384 wrapper.updateTransform();
3385 return wrapper;
3386 },
3387
3388 /**
3389 * Update the transform attribute based on internal properties. Deals with
3390 * the custom `translateX`, `translateY`, `rotation`, `scaleX` and `scaleY`
3391 * attributes and updates the SVG `transform` attribute.
3392 * @private
3393 *
3394 */
3395 updateTransform: function() {
3396 var wrapper = this,
3397 translateX = wrapper.translateX || 0,
3398 translateY = wrapper.translateY || 0,
3399 scaleX = wrapper.scaleX,
3400 scaleY = wrapper.scaleY,
3401 inverted = wrapper.inverted,
3402 rotation = wrapper.rotation,
3403 matrix = wrapper.matrix,
3404 element = wrapper.element,
3405 transform;
3406
3407 // Flipping affects translate as adjustment for flipping around the
3408 // group's axis
3409 if (inverted) {
3410 translateX += wrapper.width;
3411 translateY += wrapper.height;
3412 }
3413
3414 // Apply translate. Nearly all transformed elements have translation,
3415 // so instead of checking for translate = 0, do it always (#1767,
3416 // #1846).
3417 transform = ['translate(' + translateX + ',' + translateY + ')'];
3418
3419 // apply matrix
3420 if (defined(matrix)) {
3421 transform.push(
3422 'matrix(' + matrix.join(',') + ')'
3423 );
3424 }
3425
3426 // apply rotation
3427 if (inverted) {
3428 transform.push('rotate(90) scale(-1,1)');
3429 } else if (rotation) { // text rotation
3430 transform.push(
3431 'rotate(' + rotation + ' ' +
3432 pick(this.rotationOriginX, element.getAttribute('x'), 0) +
3433 ' ' +
3434 pick(this.rotationOriginY, element.getAttribute('y') || 0) + ')'
3435 );
3436 }
3437
3438 // apply scale
3439 if (defined(scaleX) || defined(scaleY)) {
3440 transform.push(
3441 'scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')'
3442 );
3443 }
3444
3445 if (transform.length) {
3446 element.setAttribute('transform', transform.join(' '));
3447 }
3448 },
3449
3450 /**
3451 * Bring the element to the front. Alternatively, a new zIndex can be set.
3452 *
3453 * @returns {SVGElement} Returns the SVGElement for chaining.
3454 *
3455 * @sample highcharts/members/element-tofront/
3456 * Click an element to bring it to front
3457 */
3458 toFront: function() {
3459 var element = this.element;
3460 element.parentNode.appendChild(element);
3461 return this;
3462 },
3463
3464
3465 /**
3466 * Align the element relative to the chart or another box.
3467 *
3468 * @param {Object} [alignOptions] The alignment options. The function can be
3469 * called without this parameter in order to re-align an element after the
3470 * box has been updated.
3471 * @param {string} [alignOptions.align=left] Horizontal alignment. Can be
3472 * one of `left`, `center` and `right`.
3473 * @param {string} [alignOptions.verticalAlign=top] Vertical alignment. Can
3474 * be one of `top`, `middle` and `bottom`.
3475 * @param {number} [alignOptions.x=0] Horizontal pixel offset from
3476 * alignment.
3477 * @param {number} [alignOptions.y=0] Vertical pixel offset from alignment.
3478 * @param {Boolean} [alignByTranslate=false] Use the `transform` attribute
3479 * with translateX and translateY custom attributes to align this elements
3480 * rather than `x` and `y` attributes.
3481 * @param {String|Object} box The box to align to, needs a width and height.
3482 * When the box is a string, it refers to an object in the Renderer. For
3483 * example, when box is `spacingBox`, it refers to `Renderer.spacingBox`
3484 * which holds `width`, `height`, `x` and `y` properties.
3485 * @returns {SVGElement} Returns the SVGElement for chaining.
3486 */
3487 align: function(alignOptions, alignByTranslate, box) {
3488 var align,
3489 vAlign,
3490 x,
3491 y,
3492 attribs = {},
3493 alignTo,
3494 renderer = this.renderer,
3495 alignedObjects = renderer.alignedObjects,
3496 alignFactor,
3497 vAlignFactor;
3498
3499 // First call on instanciate
3500 if (alignOptions) {
3501 this.alignOptions = alignOptions;
3502 this.alignByTranslate = alignByTranslate;
3503 if (!box || isString(box)) {
3504 this.alignTo = alignTo = box || 'renderer';
3505 // prevent duplicates, like legendGroup after resize
3506 erase(alignedObjects, this);
3507 alignedObjects.push(this);
3508 box = null; // reassign it below
3509 }
3510
3511 // When called on resize, no arguments are supplied
3512 } else {
3513 alignOptions = this.alignOptions;
3514 alignByTranslate = this.alignByTranslate;
3515 alignTo = this.alignTo;
3516 }
3517
3518 box = pick(box, renderer[alignTo], renderer);
3519
3520 // Assign variables
3521 align = alignOptions.align;
3522 vAlign = alignOptions.verticalAlign;
3523 x = (box.x || 0) + (alignOptions.x || 0); // default: left align
3524 y = (box.y || 0) + (alignOptions.y || 0); // default: top align
3525
3526 // Align
3527 if (align === 'right') {
3528 alignFactor = 1;
3529 } else if (align === 'center') {
3530 alignFactor = 2;
3531 }
3532 if (alignFactor) {
3533 x += (box.width - (alignOptions.width || 0)) / alignFactor;
3534 }
3535 attribs[alignByTranslate ? 'translateX' : 'x'] = Math.round(x);
3536
3537
3538 // Vertical align
3539 if (vAlign === 'bottom') {
3540 vAlignFactor = 1;
3541 } else if (vAlign === 'middle') {
3542 vAlignFactor = 2;
3543 }
3544 if (vAlignFactor) {
3545 y += (box.height - (alignOptions.height || 0)) / vAlignFactor;
3546 }
3547 attribs[alignByTranslate ? 'translateY' : 'y'] = Math.round(y);
3548
3549 // Animate only if already placed
3550 this[this.placed ? 'animate' : 'attr'](attribs);
3551 this.placed = true;
3552 this.alignAttr = attribs;
3553
3554 return this;
3555 },
3556
3557 /**
3558 * Get the bounding box (width, height, x and y) for the element. Generally
3559 * used to get rendered text size. Since this is called a lot in charts,
3560 * the results are cached based on text properties, in order to save DOM
3561 * traffic. The returned bounding box includes the rotation, so for example
3562 * a single text line of rotation 90 will report a greater height, and a
3563 * width corresponding to the line-height.
3564 *
3565 * @param {boolean} [reload] Skip the cache and get the updated DOM bouding
3566 * box.
3567 * @param {number} [rot] Override the element's rotation. This is internally
3568 * used on axis labels with a value of 0 to find out what the bounding box
3569 * would be have been if it were not rotated.
3570 * @returns {Object} The bounding box with `x`, `y`, `width` and `height`
3571 * properties.
3572 *
3573 * @sample highcharts/members/renderer-on-chart/
3574 * Draw a rectangle based on a text's bounding box
3575 */
3576 getBBox: function(reload, rot) {
3577 var wrapper = this,
3578 bBox, // = wrapper.bBox,
3579 renderer = wrapper.renderer,
3580 width,
3581 height,
3582 rotation,
3583 rad,
3584 element = wrapper.element,
3585 styles = wrapper.styles,
3586 fontSize,
3587 textStr = wrapper.textStr,
3588 toggleTextShadowShim,
3589 cache = renderer.cache,
3590 cacheKeys = renderer.cacheKeys,
3591 cacheKey;
3592
3593 rotation = pick(rot, wrapper.rotation);
3594 rad = rotation * deg2rad;
3595
3596
3597 fontSize = element &&
3598 SVGElement.prototype.getStyle.call(element, 'font-size');
3599
3600
3601 // Avoid undefined and null (#7316)
3602 if (defined(textStr)) {
3603
3604 cacheKey = textStr.toString();
3605
3606 // Since numbers are monospaced, and numerical labels appear a lot
3607 // in a chart, we assume that a label of n characters has the same
3608 // bounding box as others of the same length. Unless there is inner
3609 // HTML in the label. In that case, leave the numbers as is (#5899).
3610 if (cacheKey.indexOf('<') === -1) {
3611 cacheKey = cacheKey.replace(/[0-9]/g, '0');
3612 }
3613
3614 // Properties that affect bounding box
3615 cacheKey += [
3616 '',
3617 rotation || 0,
3618 fontSize,
3619 styles && styles.width,
3620 styles && styles.textOverflow // #5968
3621 ]
3622 .join(',');
3623
3624 }
3625
3626 if (cacheKey && !reload) {
3627 bBox = cache[cacheKey];
3628 }
3629
3630 // No cache found
3631 if (!bBox) {
3632
3633 // SVG elements
3634 if (element.namespaceURI === wrapper.SVG_NS || renderer.forExport) {
3635 try { // Fails in Firefox if the container has display: none.
3636
3637 // When the text shadow shim is used, we need to hide the
3638 // fake shadows to get the correct bounding box (#3872)
3639 toggleTextShadowShim = this.fakeTS && function(display) {
3640 each(
3641 element.querySelectorAll(
3642 '.highcharts-text-outline'
3643 ),
3644 function(tspan) {
3645 tspan.style.display = display;
3646 }
3647 );
3648 };
3649
3650 // Workaround for #3842, Firefox reporting wrong bounding
3651 // box for shadows
3652 if (toggleTextShadowShim) {
3653 toggleTextShadowShim('none');
3654 }
3655
3656 bBox = element.getBBox ?
3657 // SVG: use extend because IE9 is not allowed to change
3658 // width and height in case of rotation (below)
3659 extend({}, element.getBBox()) : {
3660
3661 // Legacy IE in export mode
3662 width: element.offsetWidth,
3663 height: element.offsetHeight
3664 };
3665
3666 // #3842
3667 if (toggleTextShadowShim) {
3668 toggleTextShadowShim('');
3669 }
3670 } catch (e) {}
3671
3672 // If the bBox is not set, the try-catch block above failed. The
3673 // other condition is for Opera that returns a width of
3674 // -Infinity on hidden elements.
3675 if (!bBox || bBox.width < 0) {
3676 bBox = {
3677 width: 0,
3678 height: 0
3679 };
3680 }
3681
3682
3683 // VML Renderer or useHTML within SVG
3684 } else {
3685
3686 bBox = wrapper.htmlGetBBox();
3687
3688 }
3689
3690 // True SVG elements as well as HTML elements in modern browsers
3691 // using the .useHTML option need to compensated for rotation
3692 if (renderer.isSVG) {
3693 width = bBox.width;
3694 height = bBox.height;
3695
3696 // Workaround for wrong bounding box in IE, Edge and Chrome on
3697 // Windows. With Highcharts' default font, IE and Edge report
3698 // a box height of 16.899 and Chrome rounds it to 17. If this
3699 // stands uncorrected, it results in more padding added below
3700 // the text than above when adding a label border or background.
3701 // Also vertical positioning is affected.
3702 // http://jsfiddle.net/highcharts/em37nvuj/
3703 // (#1101, #1505, #1669, #2568, #6213).
3704 if (
3705 styles &&
3706 styles.fontSize === '11px' &&
3707 Math.round(height) === 17
3708 ) {
3709 bBox.height = height = 14;
3710 }
3711
3712 // Adjust for rotated text
3713 if (rotation) {
3714 bBox.width = Math.abs(height * Math.sin(rad)) +
3715 Math.abs(width * Math.cos(rad));
3716 bBox.height = Math.abs(height * Math.cos(rad)) +
3717 Math.abs(width * Math.sin(rad));
3718 }
3719 }
3720
3721 // Cache it. When loading a chart in a hidden iframe in Firefox and
3722 // IE/Edge, the bounding box height is 0, so don't cache it (#5620).
3723 if (cacheKey && bBox.height > 0) {
3724
3725 // Rotate (#4681)
3726 while (cacheKeys.length > 250) {
3727 delete cache[cacheKeys.shift()];
3728 }
3729
3730 if (!cache[cacheKey]) {
3731 cacheKeys.push(cacheKey);
3732 }
3733 cache[cacheKey] = bBox;
3734 }
3735 }
3736 return bBox;
3737 },
3738
3739 /**
3740 * Show the element after it has been hidden.
3741 *
3742 * @param {boolean} [inherit=false] Set the visibility attribute to
3743 * `inherit` rather than `visible`. The difference is that an element with
3744 * `visibility="visible"` will be visible even if the parent is hidden.
3745 *
3746 * @returns {SVGElement} Returns the SVGElement for chaining.
3747 */
3748 show: function(inherit) {
3749 return this.attr({
3750 visibility: inherit ? 'inherit' : 'visible'
3751 });
3752 },
3753
3754 /**
3755 * Hide the element, equivalent to setting the `visibility` attribute to
3756 * `hidden`.
3757 *
3758 * @returns {SVGElement} Returns the SVGElement for chaining.
3759 */
3760 hide: function() {
3761 return this.attr({
3762 visibility: 'hidden'
3763 });
3764 },
3765
3766 /**
3767 * Fade out an element by animating its opacity down to 0, and hide it on
3768 * complete. Used internally for the tooltip.
3769 *
3770 * @param {number} [duration=150] The fade duration in milliseconds.
3771 */
3772 fadeOut: function(duration) {
3773 var elemWrapper = this;
3774 elemWrapper.animate({
3775 opacity: 0
3776 }, {
3777 duration: duration || 150,
3778 complete: function() {
3779 // #3088, assuming we're only using this for tooltips
3780 elemWrapper.attr({
3781 y: -9999
3782 });
3783 }
3784 });
3785 },
3786
3787 /**
3788 * Add the element to the DOM. All elements must be added this way.
3789 *
3790 * @param {SVGElement|SVGDOMElement} [parent] The parent item to add it to.
3791 * If undefined, the element is added to the {@link
3792 * Highcharts.SVGRenderer.box}.
3793 *
3794 * @returns {SVGElement} Returns the SVGElement for chaining.
3795 *
3796 * @sample highcharts/members/renderer-g - Elements added to a group
3797 */
3798 add: function(parent) {
3799
3800 var renderer = this.renderer,
3801 element = this.element,
3802 inserted;
3803
3804 if (parent) {
3805 this.parentGroup = parent;
3806 }
3807
3808 // mark as inverted
3809 this.parentInverted = parent && parent.inverted;
3810
3811 // build formatted text
3812 if (this.textStr !== undefined) {
3813 renderer.buildText(this);
3814 }
3815
3816 // Mark as added
3817 this.added = true;
3818
3819 // If we're adding to renderer root, or other elements in the group
3820 // have a z index, we need to handle it
3821 if (!parent || parent.handleZ || this.zIndex) {
3822 inserted = this.zIndexSetter();
3823 }
3824
3825 // If zIndex is not handled, append at the end
3826 if (!inserted) {
3827 (parent ? parent.element : renderer.box).appendChild(element);
3828 }
3829
3830 // fire an event for internal hooks
3831 if (this.onAdd) {
3832 this.onAdd();
3833 }
3834
3835 return this;
3836 },
3837
3838 /**
3839 * Removes an element from the DOM.
3840 *
3841 * @private
3842 * @param {SVGDOMElement|HTMLDOMElement} element The DOM node to remove.
3843 */
3844 safeRemoveChild: function(element) {
3845 var parentNode = element.parentNode;
3846 if (parentNode) {
3847 parentNode.removeChild(element);
3848 }
3849 },
3850
3851 /**
3852 * Destroy the element and element wrapper and clear up the DOM and event
3853 * hooks.
3854 *
3855 *
3856 */
3857 destroy: function() {
3858 var wrapper = this,
3859 element = wrapper.element || {},
3860 parentToClean =
3861 wrapper.renderer.isSVG &&
3862 element.nodeName === 'SPAN' &&
3863 wrapper.parentGroup,
3864 grandParent,
3865 ownerSVGElement = element.ownerSVGElement,
3866 i;
3867
3868 // remove events
3869 element.onclick = element.onmouseout = element.onmouseover =
3870 element.onmousemove = element.point = null;
3871 stop(wrapper); // stop running animations
3872
3873 if (wrapper.clipPath && ownerSVGElement) {
3874 // Look for existing references to this clipPath and remove them
3875 // before destroying the element (#6196).
3876 each(
3877 // The upper case version is for Edge
3878 ownerSVGElement.querySelectorAll('[clip-path],[CLIP-PATH]'),
3879 function(el) {
3880 // Include the closing paranthesis in the test to rule out
3881 // id's from 10 and above (#6550)
3882 if (el
3883 .getAttribute('clip-path')
3884 .match(RegExp(
3885 // Edge puts quotes inside the url, others not
3886 '[\("]#' + wrapper.clipPath.element.id + '[\)"]'
3887 ))
3888 ) {
3889 el.removeAttribute('clip-path');
3890 }
3891 }
3892 );
3893 wrapper.clipPath = wrapper.clipPath.destroy();
3894 }
3895
3896 // Destroy stops in case this is a gradient object
3897 if (wrapper.stops) {
3898 for (i = 0; i < wrapper.stops.length; i++) {
3899 wrapper.stops[i] = wrapper.stops[i].destroy();
3900 }
3901 wrapper.stops = null;
3902 }
3903
3904 // remove element
3905 wrapper.safeRemoveChild(element);
3906
3907
3908
3909 // In case of useHTML, clean up empty containers emulating SVG groups
3910 // (#1960, #2393, #2697).
3911 while (
3912 parentToClean &&
3913 parentToClean.div &&
3914 parentToClean.div.childNodes.length === 0
3915 ) {
3916 grandParent = parentToClean.parentGroup;
3917 wrapper.safeRemoveChild(parentToClean.div);
3918 delete parentToClean.div;
3919 parentToClean = grandParent;
3920 }
3921
3922 // remove from alignObjects
3923 if (wrapper.alignTo) {
3924 erase(wrapper.renderer.alignedObjects, wrapper);
3925 }
3926
3927 objectEach(wrapper, function(val, key) {
3928 delete wrapper[key];
3929 });
3930
3931 return null;
3932 },
3933
3934
3935
3936 xGetter: function(key) {
3937 if (this.element.nodeName === 'circle') {
3938 if (key === 'x') {
3939 key = 'cx';
3940 } else if (key === 'y') {
3941 key = 'cy';
3942 }
3943 }
3944 return this._defaultGetter(key);
3945 },
3946
3947 /**
3948 * Get the current value of an attribute or pseudo attribute, used mainly
3949 * for animation. Called internally from the {@link
3950 * Highcharts.SVGRenderer#attr}
3951 * function.
3952 *
3953 * @private
3954 */
3955 _defaultGetter: function(key) {
3956 var ret = pick(
3957 this[key],
3958 this.element ? this.element.getAttribute(key) : null,
3959 0
3960 );
3961
3962 if (/^[\-0-9\.]+$/.test(ret)) { // is numerical
3963 ret = parseFloat(ret);
3964 }
3965 return ret;
3966 },
3967
3968
3969 dSetter: function(value, key, element) {
3970 if (value && value.join) { // join path
3971 value = value.join(' ');
3972 }
3973 if (/(NaN| {2}|^$)/.test(value)) {
3974 value = 'M 0 0';
3975 }
3976
3977 // Check for cache before resetting. Resetting causes disturbance in the
3978 // DOM, causing flickering in some cases in Edge/IE (#6747). Also
3979 // possible performance gain.
3980 if (this[key] !== value) {
3981 element.setAttribute(key, value);
3982 this[key] = value;
3983 }
3984
3985 },
3986
3987 alignSetter: function(value) {
3988 var convert = {
3989 left: 'start',
3990 center: 'middle',
3991 right: 'end'
3992 };
3993 this.element.setAttribute('text-anchor', convert[value]);
3994 },
3995 opacitySetter: function(value, key, element) {
3996 this[key] = value;
3997 element.setAttribute(key, value);
3998 },
3999 titleSetter: function(value) {
4000 var titleNode = this.element.getElementsByTagName('title')[0];
4001 if (!titleNode) {
4002 titleNode = doc.createElementNS(this.SVG_NS, 'title');
4003 this.element.appendChild(titleNode);
4004 }
4005
4006 // Remove text content if it exists
4007 if (titleNode.firstChild) {
4008 titleNode.removeChild(titleNode.firstChild);
4009 }
4010
4011 titleNode.appendChild(
4012 doc.createTextNode(
4013 // #3276, #3895
4014 (String(pick(value), '')).replace(/<[^>]*>/g, '')
4015 )
4016 );
4017 },
4018 textSetter: function(value) {
4019 if (value !== this.textStr) {
4020 // Delete bBox memo when the text changes
4021 delete this.bBox;
4022
4023 this.textStr = value;
4024 if (this.added) {
4025 this.renderer.buildText(this);
4026 }
4027 }
4028 },
4029 fillSetter: function(value, key, element) {
4030 if (typeof value === 'string') {
4031 element.setAttribute(key, value);
4032 } else if (value) {
4033 this.colorGradient(value, key, element);
4034 }
4035 },
4036 visibilitySetter: function(value, key, element) {
4037 // IE9-11 doesn't handle visibilty:inherit well, so we remove the
4038 // attribute instead (#2881, #3909)
4039 if (value === 'inherit') {
4040 element.removeAttribute(key);
4041 } else if (this[key] !== value) { // #6747
4042 element.setAttribute(key, value);
4043 }
4044 this[key] = value;
4045 },
4046 zIndexSetter: function(value, key) {
4047 var renderer = this.renderer,
4048 parentGroup = this.parentGroup,
4049 parentWrapper = parentGroup || renderer,
4050 parentNode = parentWrapper.element || renderer.box,
4051 childNodes,
4052 otherElement,
4053 otherZIndex,
4054 element = this.element,
4055 inserted,
4056 undefinedOtherZIndex,
4057 svgParent = parentNode === renderer.box,
4058 run = this.added,
4059 i;
4060
4061 if (defined(value)) {
4062 // So we can read it for other elements in the group
4063 element.zIndex = value;
4064
4065 value = +value;
4066 if (this[key] === value) { // Only update when needed (#3865)
4067 run = false;
4068 }
4069 this[key] = value;
4070 }
4071
4072 // Insert according to this and other elements' zIndex. Before .add() is
4073 // called, nothing is done. Then on add, or by later calls to
4074 // zIndexSetter, the node is placed on the right place in the DOM.
4075 if (run) {
4076 value = this.zIndex;
4077
4078 if (value && parentGroup) {
4079 parentGroup.handleZ = true;
4080 }
4081
4082 childNodes = parentNode.childNodes;
4083 for (i = childNodes.length - 1; i >= 0 && !inserted; i--) {
4084 otherElement = childNodes[i];
4085 otherZIndex = otherElement.zIndex;
4086 undefinedOtherZIndex = !defined(otherZIndex);
4087
4088 if (otherElement !== element) {
4089 if (
4090 // Negative zIndex versus no zIndex:
4091 // On all levels except the highest. If the parent is <svg>,
4092 // then we don't want to put items before <desc> or <defs>
4093 (value < 0 && undefinedOtherZIndex && !svgParent && !i)
4094 ) {
4095 parentNode.insertBefore(element, childNodes[i]);
4096 inserted = true;
4097 } else if (
4098 // Insert after the first element with a lower zIndex
4099 pInt(otherZIndex) <= value ||
4100 // If negative zIndex, add this before first undefined zIndex element
4101 (undefinedOtherZIndex && (!defined(value) || value >= 0))
4102 ) {
4103 parentNode.insertBefore(
4104 element,
4105 childNodes[i + 1] || null // null for oldIE export
4106 );
4107 inserted = true;
4108 }
4109 }
4110 }
4111
4112 if (!inserted) {
4113 parentNode.insertBefore(
4114 element,
4115 childNodes[svgParent ? 3 : 0] || null // null for oldIE
4116 );
4117 inserted = true;
4118 }
4119 }
4120 return inserted;
4121 },
4122 _defaultSetter: function(value, key, element) {
4123 element.setAttribute(key, value);
4124 }
4125 });
4126
4127 // Some shared setters and getters
4128 SVGElement.prototype.yGetter =
4129 SVGElement.prototype.xGetter;
4130 SVGElement.prototype.translateXSetter =
4131 SVGElement.prototype.translateYSetter =
4132 SVGElement.prototype.rotationSetter =
4133 SVGElement.prototype.verticalAlignSetter =
4134 SVGElement.prototype.rotationOriginXSetter =
4135 SVGElement.prototype.rotationOriginYSetter =
4136 SVGElement.prototype.scaleXSetter =
4137 SVGElement.prototype.scaleYSetter =
4138 SVGElement.prototype.matrixSetter = function(value, key) {
4139 this[key] = value;
4140 this.doTransform = true;
4141 };
4142
4143
4144
4145 /**
4146 * Allows direct access to the Highcharts rendering layer in order to draw
4147 * primitive shapes like circles, rectangles, paths or text directly on a chart,
4148 * or independent from any chart. The SVGRenderer represents a wrapper object
4149 * for SVG in modern browsers. Through the VMLRenderer, part of the `oldie.js`
4150 * module, it also brings vector graphics to IE <= 8.
4151 *
4152 * An existing chart's renderer can be accessed through {@link Chart.renderer}.
4153 * The renderer can also be used completely decoupled from a chart.
4154 *
4155 * @param {HTMLDOMElement} container - Where to put the SVG in the web page.
4156 * @param {number} width - The width of the SVG.
4157 * @param {number} height - The height of the SVG.
4158 * @param {boolean} [forExport=false] - Whether the rendered content is intended
4159 * for export.
4160 * @param {boolean} [allowHTML=true] - Whether the renderer is allowed to
4161 * include HTML text, which will be projected on top of the SVG.
4162 *
4163 * @example
4164 * // Use directly without a chart object.
4165 * var renderer = new Highcharts.Renderer(parentNode, 600, 400);
4166 *
4167 * @sample highcharts/members/renderer-on-chart
4168 * Annotating a chart programmatically.
4169 * @sample highcharts/members/renderer-basic
4170 * Independent SVG drawing.
4171 *
4172 * @class Highcharts.SVGRenderer
4173 */
4174 SVGRenderer = H.SVGRenderer = function() {
4175 this.init.apply(this, arguments);
4176 };
4177 extend(SVGRenderer.prototype, /** @lends Highcharts.SVGRenderer.prototype */ {
4178 /**
4179 * A pointer to the renderer's associated Element class. The VMLRenderer
4180 * will have a pointer to VMLElement here.
4181 * @type {SVGElement}
4182 */
4183 Element: SVGElement,
4184 SVG_NS: SVG_NS,
4185 /**
4186 * Initialize the SVGRenderer. Overridable initiator function that takes
4187 * the same parameters as the constructor.
4188 */
4189 init: function(container, width, height, style, forExport, allowHTML) {
4190 var renderer = this,
4191 boxWrapper,
4192 element,
4193 desc;
4194
4195 boxWrapper = renderer.createElement('svg')
4196 .attr({
4197 'version': '1.1',
4198 'class': 'highcharts-root'
4199 });
4200 element = boxWrapper.element;
4201 container.appendChild(element);
4202
4203 // Always use ltr on the container, otherwise text-anchor will be
4204 // flipped and text appear outside labels, buttons, tooltip etc (#3482)
4205 attr(container, 'dir', 'ltr');
4206
4207 // For browsers other than IE, add the namespace attribute (#1978)
4208 if (container.innerHTML.indexOf('xmlns') === -1) {
4209 attr(element, 'xmlns', this.SVG_NS);
4210 }
4211
4212 // object properties
4213 renderer.isSVG = true;
4214
4215 /**
4216 * The root `svg` node of the renderer.
4217 * @name box
4218 * @memberOf SVGRenderer
4219 * @type {SVGDOMElement}
4220 */
4221 this.box = element;
4222 /**
4223 * The wrapper for the root `svg` node of the renderer.
4224 *
4225 * @name boxWrapper
4226 * @memberOf SVGRenderer
4227 * @type {SVGElement}
4228 */
4229 this.boxWrapper = boxWrapper;
4230 renderer.alignedObjects = [];
4231
4232 /**
4233 * Page url used for internal references.
4234 * @type {string}
4235 */
4236 // #24, #672, #1070
4237 this.url = (
4238 (isFirefox || isWebKit) &&
4239 doc.getElementsByTagName('base').length
4240 ) ?
4241 win.location.href
4242 .replace(/#.*?$/, '') // remove the hash
4243 .replace(/<[^>]*>/g, '') // wing cut HTML
4244 // escape parantheses and quotes
4245 .replace(/([\('\)])/g, '\\$1')
4246 // replace spaces (needed for Safari only)
4247 .replace(/ /g, '%20') :
4248 '';
4249
4250 // Add description
4251 desc = this.createElement('desc').add();
4252 desc.element.appendChild(
4253 doc.createTextNode('Created with Highcharts 6.0.3')
4254 );
4255
4256 /**
4257 * A pointer to the `defs` node of the root SVG.
4258 * @type {SVGElement}
4259 * @name defs
4260 * @memberOf SVGRenderer
4261 */
4262 renderer.defs = this.createElement('defs').add();
4263 renderer.allowHTML = allowHTML;
4264 renderer.forExport = forExport;
4265 renderer.gradients = {}; // Object where gradient SvgElements are stored
4266 renderer.cache = {}; // Cache for numerical bounding boxes
4267 renderer.cacheKeys = [];
4268 renderer.imgCount = 0;
4269
4270 renderer.setSize(width, height, false);
4271
4272
4273
4274 // Issue 110 workaround:
4275 // In Firefox, if a div is positioned by percentage, its pixel position
4276 // may land between pixels. The container itself doesn't display this,
4277 // but an SVG element inside this container will be drawn at subpixel
4278 // precision. In order to draw sharp lines, this must be compensated
4279 // for. This doesn't seem to work inside iframes though (like in
4280 // jsFiddle).
4281 var subPixelFix, rect;
4282 if (isFirefox && container.getBoundingClientRect) {
4283 subPixelFix = function() {
4284 css(container, {
4285 left: 0,
4286 top: 0
4287 });
4288 rect = container.getBoundingClientRect();
4289 css(container, {
4290 left: (Math.ceil(rect.left) - rect.left) + 'px',
4291 top: (Math.ceil(rect.top) - rect.top) + 'px'
4292 });
4293 };
4294
4295 // run the fix now
4296 subPixelFix();
4297
4298 // run it on resize
4299 renderer.unSubPixelFix = addEvent(win, 'resize', subPixelFix);
4300 }
4301 },
4302
4303 /**
4304 * General method for adding a definition to the SVG `defs` tag. Can be used
4305 * for gradients, fills, filters etc. Styled mode only. A hook for adding
4306 * general definitions to the SVG's defs tag. Definitions can be
4307 * referenced from the CSS by its `id`. Read more in
4308 * [gradients, shadows and patterns]{@link http://www.highcharts.com/docs/
4309 * chart-design-and-style/gradients-shadows-and-patterns}.
4310 * Styled mode only.
4311 *
4312 * @param {Object} def - A serialized form of an SVG definition, including
4313 * children
4314 *
4315 * @return {SVGElement} The inserted node.
4316 */
4317 definition: function(def) {
4318 var ren = this;
4319
4320 function recurse(config, parent) {
4321 var ret;
4322 each(splat(config), function(item) {
4323 var node = ren.createElement(item.tagName),
4324 attr = {};
4325
4326 // Set attributes
4327 objectEach(item, function(val, key) {
4328 if (
4329 key !== 'tagName' &&
4330 key !== 'children' &&
4331 key !== 'textContent'
4332 ) {
4333 attr[key] = val;
4334 }
4335 });
4336 node.attr(attr);
4337
4338 // Add to the tree
4339 node.add(parent || ren.defs);
4340
4341 // Add text content
4342 if (item.textContent) {
4343 node.element.appendChild(
4344 doc.createTextNode(item.textContent)
4345 );
4346 }
4347
4348 // Recurse
4349 recurse(item.children || [], node);
4350
4351 ret = node;
4352 });
4353
4354 // Return last node added (on top level it's the only one)
4355 return ret;
4356 }
4357 return recurse(def);
4358 },
4359
4360
4361
4362
4363 /**
4364 * Detect whether the renderer is hidden. This happens when one of the
4365 * parent elements has `display: none`. Used internally to detect when we
4366 * needto render preliminarily in another div to get the text bounding boxes
4367 * right.
4368 *
4369 * @returns {boolean} True if it is hidden.
4370 */
4371 isHidden: function() { // #608
4372 return !this.boxWrapper.getBBox().width;
4373 },
4374
4375 /**
4376 * Destroys the renderer and its allocated members.
4377 */
4378 destroy: function() {
4379 var renderer = this,
4380 rendererDefs = renderer.defs;
4381 renderer.box = null;
4382 renderer.boxWrapper = renderer.boxWrapper.destroy();
4383
4384 // Call destroy on all gradient elements
4385 destroyObjectProperties(renderer.gradients || {});
4386 renderer.gradients = null;
4387
4388 // Defs are null in VMLRenderer
4389 // Otherwise, destroy them here.
4390 if (rendererDefs) {
4391 renderer.defs = rendererDefs.destroy();
4392 }
4393
4394 // Remove sub pixel fix handler (#982)
4395 if (renderer.unSubPixelFix) {
4396 renderer.unSubPixelFix();
4397 }
4398
4399 renderer.alignedObjects = null;
4400
4401 return null;
4402 },
4403
4404 /**
4405 * Create a wrapper for an SVG element. Serves as a factory for
4406 * {@link SVGElement}, but this function is itself mostly called from
4407 * primitive factories like {@link SVGRenderer#path}, {@link
4408 * SVGRenderer#rect} or {@link SVGRenderer#text}.
4409 *
4410 * @param {string} nodeName - The node name, for example `rect`, `g` etc.
4411 * @returns {SVGElement} The generated SVGElement.
4412 */
4413 createElement: function(nodeName) {
4414 var wrapper = new this.Element();
4415 wrapper.init(this, nodeName);
4416 return wrapper;
4417 },
4418
4419 /**
4420 * Dummy function for plugins, called every time the renderer is updated.
4421 * Prior to Highcharts 5, this was used for the canvg renderer.
4422 * @function
4423 */
4424 draw: noop,
4425
4426 /**
4427 * Get converted radial gradient attributes according to the radial
4428 * reference. Used internally from the {@link SVGElement#colorGradient}
4429 * function.
4430 *
4431 * @private
4432 */
4433 getRadialAttr: function(radialReference, gradAttr) {
4434 return {
4435 cx: (radialReference[0] - radialReference[2] / 2) +
4436 gradAttr.cx * radialReference[2],
4437 cy: (radialReference[1] - radialReference[2] / 2) +
4438 gradAttr.cy * radialReference[2],
4439 r: gradAttr.r * radialReference[2]
4440 };
4441 },
4442
4443 getSpanWidth: function(wrapper, tspan) {
4444 var renderer = this,
4445 bBox = wrapper.getBBox(true),
4446 actualWidth = bBox.width;
4447
4448 // Old IE cannot measure the actualWidth for SVG elements (#2314)
4449 if (!svg && renderer.forExport) {
4450 actualWidth = renderer.measureSpanWidth(
4451 tspan.firstChild.data,
4452 wrapper.styles
4453 );
4454 }
4455 return actualWidth;
4456 },
4457
4458 applyEllipsis: function(wrapper, tspan, text, width) {
4459 var renderer = this,
4460 rotation = wrapper.rotation,
4461 str = text,
4462 currentIndex,
4463 minIndex = 0,
4464 maxIndex = text.length,
4465 updateTSpan = function(s) {
4466 tspan.removeChild(tspan.firstChild);
4467 if (s) {
4468 tspan.appendChild(doc.createTextNode(s));
4469 }
4470 },
4471 actualWidth,
4472 wasTooLong;
4473 wrapper.rotation = 0; // discard rotation when computing box
4474 actualWidth = renderer.getSpanWidth(wrapper, tspan);
4475 wasTooLong = actualWidth > width;
4476 if (wasTooLong) {
4477 while (minIndex <= maxIndex) {
4478 currentIndex = Math.ceil((minIndex + maxIndex) / 2);
4479 str = text.substring(0, currentIndex) + '\u2026';
4480 updateTSpan(str);
4481 actualWidth = renderer.getSpanWidth(wrapper, tspan);
4482 if (minIndex === maxIndex) {
4483 // Complete
4484 minIndex = maxIndex + 1;
4485 } else if (actualWidth > width) {
4486 // Too large. Set max index to current.
4487 maxIndex = currentIndex - 1;
4488 } else {
4489 // Within width. Set min index to current.
4490 minIndex = currentIndex;
4491 }
4492 }
4493 // If max index was 0 it means just ellipsis was also to large.
4494 if (maxIndex === 0) {
4495 // Remove ellipses.
4496 updateTSpan('');
4497 }
4498 }
4499 wrapper.rotation = rotation; // Apply rotation again.
4500 return wasTooLong;
4501 },
4502
4503 /**
4504 * A collection of characters mapped to HTML entities. When `useHTML` on an
4505 * element is true, these entities will be rendered correctly by HTML. In
4506 * the SVG pseudo-HTML, they need to be unescaped back to simple characters,
4507 * so for example `&lt;` will render as `<`.
4508 *
4509 * @example
4510 * // Add support for unescaping quotes
4511 * Highcharts.SVGRenderer.prototype.escapes['"'] = '&quot;';
4512 *
4513 * @type {Object}
4514 */
4515 escapes: {
4516 '&': '&amp;',
4517 '<': '&lt;',
4518 '>': '&gt;',
4519 "'": '&#39;', // eslint-disable-line quotes
4520 '"': '&quot'
4521 },
4522
4523 /**
4524 * Parse a simple HTML string into SVG tspans. Called internally when text
4525 * is set on an SVGElement. The function supports a subset of HTML tags,
4526 * CSS text features like `width`, `text-overflow`, `white-space`, and
4527 * also attributes like `href` and `style`.
4528 * @private
4529 * @param {SVGElement} wrapper The parent SVGElement.
4530 */
4531 buildText: function(wrapper) {
4532 var textNode = wrapper.element,
4533 renderer = this,
4534 forExport = renderer.forExport,
4535 textStr = pick(wrapper.textStr, '').toString(),
4536 hasMarkup = textStr.indexOf('<') !== -1,
4537 lines,
4538 childNodes = textNode.childNodes,
4539 clsRegex,
4540 styleRegex,
4541 hrefRegex,
4542 wasTooLong,
4543 parentX = attr(textNode, 'x'),
4544 textStyles = wrapper.styles,
4545 width = wrapper.textWidth,
4546 textLineHeight = textStyles && textStyles.lineHeight,
4547 textOutline = textStyles && textStyles.textOutline,
4548 ellipsis = textStyles && textStyles.textOverflow === 'ellipsis',
4549 noWrap = textStyles && textStyles.whiteSpace === 'nowrap',
4550 fontSize = textStyles && textStyles.fontSize,
4551 textCache,
4552 isSubsequentLine,
4553 i = childNodes.length,
4554 tempParent = width && !wrapper.added && this.box,
4555 getLineHeight = function(tspan) {
4556 var fontSizeStyle;
4557
4558
4559 return textLineHeight ?
4560 pInt(textLineHeight) :
4561 renderer.fontMetrics(
4562 fontSizeStyle,
4563 // Get the computed size from parent if not explicit
4564 tspan.getAttribute('style') ? tspan : textNode
4565 ).h;
4566 },
4567 unescapeEntities = function(inputStr) {
4568 objectEach(renderer.escapes, function(value, key) {
4569 inputStr = inputStr.replace(
4570 new RegExp(value, 'g'),
4571 key
4572 );
4573 });
4574 return inputStr;
4575 };
4576
4577 // The buildText code is quite heavy, so if we're not changing something
4578 // that affects the text, skip it (#6113).
4579 textCache = [
4580 textStr,
4581 ellipsis,
4582 noWrap,
4583 textLineHeight,
4584 textOutline,
4585 fontSize,
4586 width
4587 ].join(',');
4588 if (textCache === wrapper.textCache) {
4589 return;
4590 }
4591 wrapper.textCache = textCache;
4592
4593 // Remove old text
4594 while (i--) {
4595 textNode.removeChild(childNodes[i]);
4596 }
4597
4598 // Skip tspans, add text directly to text node. The forceTSpan is a hook
4599 // used in text outline hack.
4600 if (!hasMarkup &&
4601 !textOutline &&
4602 !ellipsis &&
4603 !width &&
4604 textStr.indexOf(' ') === -1
4605 ) {
4606 textNode.appendChild(doc.createTextNode(unescapeEntities(textStr)));
4607
4608 // Complex strings, add more logic
4609 } else {
4610
4611 clsRegex = /<.*class="([^"]+)".*>/;
4612 styleRegex = /<.*style="([^"]+)".*>/;
4613 hrefRegex = /<.*href="([^"]+)".*>/;
4614
4615 if (tempParent) {
4616 // attach it to the DOM to read offset width
4617 tempParent.appendChild(textNode);
4618 }
4619
4620 if (hasMarkup) {
4621 lines = textStr
4622
4623 .replace(
4624 /<(b|strong)>/g,
4625 '<span class="highcharts-strong">'
4626 )
4627 .replace(
4628 /<(i|em)>/g,
4629 '<span class="highcharts-emphasized">'
4630 )
4631
4632 .replace(/<a/g, '<span')
4633 .replace(/<\/(b|strong|i|em|a)>/g, '</span>')
4634 .split(/<br.*?>/g);
4635
4636 } else {
4637 lines = [textStr];
4638 }
4639
4640
4641 // Trim empty lines (#5261)
4642 lines = grep(lines, function(line) {
4643 return line !== '';
4644 });
4645
4646
4647 // build the lines
4648 each(lines, function buildTextLines(line, lineNo) {
4649 var spans,
4650 spanNo = 0;
4651 line = line
4652 // Trim to prevent useless/costly process on the spaces
4653 // (#5258)
4654 .replace(/^\s+|\s+$/g, '')
4655 .replace(/<span/g, '|||<span')
4656 .replace(/<\/span>/g, '</span>|||');
4657 spans = line.split('|||');
4658
4659 each(spans, function buildTextSpans(span) {
4660 if (span !== '' || spans.length === 1) {
4661 var attributes = {},
4662 tspan = doc.createElementNS(
4663 renderer.SVG_NS,
4664 'tspan'
4665 ),
4666 spanCls,
4667 spanStyle; // #390
4668 if (clsRegex.test(span)) {
4669 spanCls = span.match(clsRegex)[1];
4670 attr(tspan, 'class', spanCls);
4671 }
4672 if (styleRegex.test(span)) {
4673 spanStyle = span.match(styleRegex)[1].replace(
4674 /(;| |^)color([ :])/,
4675 '$1fill$2'
4676 );
4677 attr(tspan, 'style', spanStyle);
4678 }
4679
4680 // Not for export - #1529
4681 if (hrefRegex.test(span) && !forExport) {
4682 attr(
4683 tspan,
4684 'onclick',
4685 'location.href=\"' +
4686 span.match(hrefRegex)[1] + '\"'
4687 );
4688 attr(tspan, 'class', 'highcharts-anchor');
4689
4690 }
4691
4692 // Strip away unsupported HTML tags (#7126)
4693 span = unescapeEntities(
4694 span.replace(/<[a-zA-Z\/](.|\n)*?>/g, '') || ' '
4695 );
4696
4697 // Nested tags aren't supported, and cause crash in
4698 // Safari (#1596)
4699 if (span !== ' ') {
4700
4701 // add the text node
4702 tspan.appendChild(doc.createTextNode(span));
4703
4704 // First span in a line, align it to the left
4705 if (!spanNo) {
4706 if (lineNo && parentX !== null) {
4707 attributes.x = parentX;
4708 }
4709 } else {
4710 attributes.dx = 0; // #16
4711 }
4712
4713 // add attributes
4714 attr(tspan, attributes);
4715
4716 // Append it
4717 textNode.appendChild(tspan);
4718
4719 // first span on subsequent line, add the line
4720 // height
4721 if (!spanNo && isSubsequentLine) {
4722
4723 // allow getting the right offset height in
4724 // exporting in IE
4725 if (!svg && forExport) {
4726 css(tspan, {
4727 display: 'block'
4728 });
4729 }
4730
4731 // Set the line height based on the font size of
4732 // either the text element or the tspan element
4733 attr(
4734 tspan,
4735 'dy',
4736 getLineHeight(tspan)
4737 );
4738 }
4739
4740 /*
4741 if (width) {
4742 renderer.breakText(wrapper, width);
4743 }
4744 */
4745
4746 // Check width and apply soft breaks or ellipsis
4747 if (width) {
4748 var words = span.replace(
4749 /([^\^])-/g,
4750 '$1- '
4751 ).split(' '), // #1273
4752 hasWhiteSpace = (
4753 spans.length > 1 ||
4754 lineNo ||
4755 (words.length > 1 && !noWrap)
4756 ),
4757 tooLong,
4758 rest = [],
4759 actualWidth,
4760 dy = getLineHeight(tspan),
4761 rotation = wrapper.rotation;
4762
4763 if (ellipsis) {
4764 wasTooLong = renderer.applyEllipsis(
4765 wrapper,
4766 tspan,
4767 span,
4768 width
4769 );
4770 }
4771
4772 while (!ellipsis &&
4773 hasWhiteSpace &&
4774 (words.length || rest.length)
4775 ) {
4776 // discard rotation when computing box
4777 wrapper.rotation = 0;
4778 actualWidth = renderer.getSpanWidth(
4779 wrapper,
4780 tspan
4781 );
4782 tooLong = actualWidth > width;
4783
4784 // For ellipsis, do a binary search for the
4785 // correct string length
4786 if (wasTooLong === undefined) {
4787 wasTooLong = tooLong; // First time
4788 }
4789
4790 // Looping down, this is the first word
4791 // sequence that is not too long, so we can
4792 // move on to build the next line.
4793 if (!tooLong || words.length === 1) {
4794 words = rest;
4795 rest = [];
4796
4797 if (words.length && !noWrap) {
4798 tspan = doc.createElementNS(
4799 SVG_NS,
4800 'tspan'
4801 );
4802 attr(tspan, {
4803 dy: dy,
4804 x: parentX
4805 });
4806 if (spanStyle) { // #390
4807 attr(tspan, 'style', spanStyle);
4808 }
4809 textNode.appendChild(tspan);
4810 }
4811
4812 // a single word is pressing it out
4813 if (actualWidth > width) {
4814 width = actualWidth;
4815 }
4816 } else { // append to existing line tspan
4817 tspan.removeChild(tspan.firstChild);
4818 rest.unshift(words.pop());
4819 }
4820 if (words.length) {
4821 tspan.appendChild(
4822 doc.createTextNode(
4823 words.join(' ')
4824 .replace(/- /g, '-')
4825 )
4826 );
4827 }
4828 }
4829 wrapper.rotation = rotation;
4830 }
4831
4832 spanNo++;
4833 }
4834 }
4835 });
4836 // To avoid beginning lines that doesn't add to the textNode
4837 // (#6144)
4838 isSubsequentLine = (
4839 isSubsequentLine ||
4840 textNode.childNodes.length
4841 );
4842 });
4843
4844 if (wasTooLong) {
4845 wrapper.attr('title', wrapper.textStr);
4846 }
4847 if (tempParent) {
4848 tempParent.removeChild(textNode);
4849 }
4850
4851 // Apply the text outline
4852 if (textOutline && wrapper.applyTextOutline) {
4853 wrapper.applyTextOutline(textOutline);
4854 }
4855 }
4856 },
4857
4858
4859
4860 /*
4861 breakText: function (wrapper, width) {
4862 var bBox = wrapper.getBBox(),
4863 node = wrapper.element,
4864 textLength = node.textContent.length,
4865 // try this position first, based on average character width
4866 pos = Math.round(width * textLength / bBox.width),
4867 increment = 0,
4868 finalPos;
4869
4870 if (bBox.width > width) {
4871 while (finalPos === undefined) {
4872 textLength = node.getSubStringLength(0, pos);
4873
4874 if (textLength <= width) {
4875 if (increment === -1) {
4876 finalPos = pos;
4877 } else {
4878 increment = 1;
4879 }
4880 } else {
4881 if (increment === 1) {
4882 finalPos = pos - 1;
4883 } else {
4884 increment = -1;
4885 }
4886 }
4887 pos += increment;
4888 }
4889 }
4890 console.log(
4891 'width',
4892 width,
4893 'stringWidth',
4894 node.getSubStringLength(0, finalPos)
4895 )
4896 },
4897 */
4898
4899 /**
4900 * Returns white for dark colors and black for bright colors.
4901 *
4902 * @param {ColorString} rgba - The color to get the contrast for.
4903 * @returns {string} The contrast color, either `#000000` or `#FFFFFF`.
4904 */
4905 getContrast: function(rgba) {
4906 rgba = color(rgba).rgba;
4907
4908 // The threshold may be discussed. Here's a proposal for adding
4909 // different weight to the color channels (#6216)
4910 /*
4911 rgba[0] *= 1; // red
4912 rgba[1] *= 1.2; // green
4913 rgba[2] *= 0.7; // blue
4914 */
4915
4916 return rgba[0] + rgba[1] + rgba[2] > 2 * 255 ? '#000000' : '#FFFFFF';
4917 },
4918
4919 /**
4920 * Create a button with preset states.
4921 * @param {string} text - The text or HTML to draw.
4922 * @param {number} x - The x position of the button's left side.
4923 * @param {number} y - The y position of the button's top side.
4924 * @param {Function} callback - The function to execute on button click or
4925 * touch.
4926 * @param {SVGAttributes} [normalState] - SVG attributes for the normal
4927 * state.
4928 * @param {SVGAttributes} [hoverState] - SVG attributes for the hover state.
4929 * @param {SVGAttributes} [pressedState] - SVG attributes for the pressed
4930 * state.
4931 * @param {SVGAttributes} [disabledState] - SVG attributes for the disabled
4932 * state.
4933 * @param {Symbol} [shape=rect] - The shape type.
4934 * @returns {SVGRenderer} The button element.
4935 */
4936 button: function(
4937 text,
4938 x,
4939 y,
4940 callback,
4941 normalState,
4942 hoverState,
4943 pressedState,
4944 disabledState,
4945 shape
4946 ) {
4947 var label = this.label(
4948 text,
4949 x,
4950 y,
4951 shape,
4952 null,
4953 null,
4954 null,
4955 null,
4956 'button'
4957 ),
4958 curState = 0;
4959
4960 // Default, non-stylable attributes
4961 label.attr(merge({
4962 'padding': 8,
4963 'r': 2
4964 }, normalState));
4965
4966
4967
4968 // Add the events. IE9 and IE10 need mouseover and mouseout to funciton
4969 // (#667).
4970 addEvent(label.element, isMS ? 'mouseover' : 'mouseenter', function() {
4971 if (curState !== 3) {
4972 label.setState(1);
4973 }
4974 });
4975 addEvent(label.element, isMS ? 'mouseout' : 'mouseleave', function() {
4976 if (curState !== 3) {
4977 label.setState(curState);
4978 }
4979 });
4980
4981 label.setState = function(state) {
4982 // Hover state is temporary, don't record it
4983 if (state !== 1) {
4984 label.state = curState = state;
4985 }
4986 // Update visuals
4987 label.removeClass(
4988 /highcharts-button-(normal|hover|pressed|disabled)/
4989 )
4990 .addClass(
4991 'highcharts-button-' + ['normal', 'hover', 'pressed', 'disabled'][state || 0]
4992 );
4993
4994
4995 };
4996
4997
4998
4999
5000 return label
5001 .on('click', function(e) {
5002 if (curState !== 3) {
5003 callback.call(label, e);
5004 }
5005 });
5006 },
5007
5008 /**
5009 * Make a straight line crisper by not spilling out to neighbour pixels.
5010 *
5011 * @param {Array} points - The original points on the format `['M', 0, 0,
5012 * 'L', 100, 0]`.
5013 * @param {number} width - The width of the line.
5014 * @returns {Array} The original points array, but modified to render
5015 * crisply.
5016 */
5017 crispLine: function(points, width) {
5018 // normalize to a crisp line
5019 if (points[1] === points[4]) {
5020 // Substract due to #1129. Now bottom and left axis gridlines behave
5021 // the same.
5022 points[1] = points[4] = Math.round(points[1]) - (width % 2 / 2);
5023 }
5024 if (points[2] === points[5]) {
5025 points[2] = points[5] = Math.round(points[2]) + (width % 2 / 2);
5026 }
5027 return points;
5028 },
5029
5030
5031 /**
5032 * Draw a path, wraps the SVG `path` element.
5033 *
5034 * @param {Array} [path] An SVG path definition in array form.
5035 *
5036 * @example
5037 * var path = renderer.path(['M', 10, 10, 'L', 30, 30, 'z'])
5038 * .attr({ stroke: '#ff00ff' })
5039 * .add();
5040 * @returns {SVGElement} The generated wrapper element.
5041 *
5042 * @sample highcharts/members/renderer-path-on-chart/
5043 * Draw a path in a chart
5044 * @sample highcharts/members/renderer-path/
5045 * Draw a path independent from a chart
5046 *
5047 */
5048 /**
5049 * Draw a path, wraps the SVG `path` element.
5050 *
5051 * @param {SVGAttributes} [attribs] The initial attributes.
5052 * @returns {SVGElement} The generated wrapper element.
5053 */
5054 path: function(path) {
5055 var attribs = {
5056
5057 };
5058 if (isArray(path)) {
5059 attribs.d = path;
5060 } else if (isObject(path)) { // attributes
5061 extend(attribs, path);
5062 }
5063 return this.createElement('path').attr(attribs);
5064 },
5065
5066 /**
5067 * Draw a circle, wraps the SVG `circle` element.
5068 *
5069 * @param {number} [x] The center x position.
5070 * @param {number} [y] The center y position.
5071 * @param {number} [r] The radius.
5072 * @returns {SVGElement} The generated wrapper element.
5073 *
5074 * @sample highcharts/members/renderer-circle/ Drawing a circle
5075 */
5076 /**
5077 * Draw a circle, wraps the SVG `circle` element.
5078 *
5079 * @param {SVGAttributes} [attribs] The initial attributes.
5080 * @returns {SVGElement} The generated wrapper element.
5081 */
5082 circle: function(x, y, r) {
5083 var attribs = isObject(x) ? x : {
5084 x: x,
5085 y: y,
5086 r: r
5087 },
5088 wrapper = this.createElement('circle');
5089
5090 // Setting x or y translates to cx and cy
5091 wrapper.xSetter = wrapper.ySetter = function(value, key, element) {
5092 element.setAttribute('c' + key, value);
5093 };
5094
5095 return wrapper.attr(attribs);
5096 },
5097
5098 /**
5099 * Draw and return an arc.
5100 * @param {number} [x=0] Center X position.
5101 * @param {number} [y=0] Center Y position.
5102 * @param {number} [r=0] The outer radius of the arc.
5103 * @param {number} [innerR=0] Inner radius like used in donut charts.
5104 * @param {number} [start=0] The starting angle of the arc in radians, where
5105 * 0 is to the right and `-Math.PI/2` is up.
5106 * @param {number} [end=0] The ending angle of the arc in radians, where 0
5107 * is to the right and `-Math.PI/2` is up.
5108 * @returns {SVGElement} The generated wrapper element.
5109 *
5110 * @sample highcharts/members/renderer-arc/
5111 * Drawing an arc
5112 */
5113 /**
5114 * Draw and return an arc. Overloaded function that takes arguments object.
5115 * @param {SVGAttributes} attribs Initial SVG attributes.
5116 * @returns {SVGElement} The generated wrapper element.
5117 */
5118 arc: function(x, y, r, innerR, start, end) {
5119 var arc,
5120 options;
5121
5122 if (isObject(x)) {
5123 options = x;
5124 y = options.y;
5125 r = options.r;
5126 innerR = options.innerR;
5127 start = options.start;
5128 end = options.end;
5129 x = options.x;
5130 } else {
5131 options = {
5132 innerR: innerR,
5133 start: start,
5134 end: end
5135 };
5136 }
5137
5138 // Arcs are defined as symbols for the ability to set
5139 // attributes in attr and animate
5140 arc = this.symbol('arc', x, y, r, r, options);
5141 arc.r = r; // #959
5142 return arc;
5143 },
5144
5145 /**
5146 * Draw and return a rectangle.
5147 * @param {number} [x] Left position.
5148 * @param {number} [y] Top position.
5149 * @param {number} [width] Width of the rectangle.
5150 * @param {number} [height] Height of the rectangle.
5151 * @param {number} [r] Border corner radius.
5152 * @param {number} [strokeWidth] A stroke width can be supplied to allow
5153 * crisp drawing.
5154 * @returns {SVGElement} The generated wrapper element.
5155 */
5156 /**
5157 * Draw and return a rectangle.
5158 * @param {SVGAttributes} [attributes]
5159 * General SVG attributes for the rectangle.
5160 * @return {SVGElement}
5161 * The generated wrapper element.
5162 *
5163 * @sample highcharts/members/renderer-rect-on-chart/
5164 * Draw a rectangle in a chart
5165 * @sample highcharts/members/renderer-rect/
5166 * Draw a rectangle independent from a chart
5167 */
5168 rect: function(x, y, width, height, r, strokeWidth) {
5169
5170 r = isObject(x) ? x.r : r;
5171
5172 var wrapper = this.createElement('rect'),
5173 attribs = isObject(x) ? x : x === undefined ? {} : {
5174 x: x,
5175 y: y,
5176 width: Math.max(width, 0),
5177 height: Math.max(height, 0)
5178 };
5179
5180
5181
5182 if (r) {
5183 attribs.r = r;
5184 }
5185
5186 wrapper.rSetter = function(value, key, element) {
5187 attr(element, {
5188 rx: value,
5189 ry: value
5190 });
5191 };
5192
5193 return wrapper.attr(attribs);
5194 },
5195
5196 /**
5197 * Resize the {@link SVGRenderer#box} and re-align all aligned child
5198 * elements.
5199 * @param {number} width
5200 * The new pixel width.
5201 * @param {number} height
5202 * The new pixel height.
5203 * @param {Boolean|AnimationOptions} [animate=true]
5204 * Whether and how to animate.
5205 */
5206 setSize: function(width, height, animate) {
5207 var renderer = this,
5208 alignedObjects = renderer.alignedObjects,
5209 i = alignedObjects.length;
5210
5211 renderer.width = width;
5212 renderer.height = height;
5213
5214 renderer.boxWrapper.animate({
5215 width: width,
5216 height: height
5217 }, {
5218 step: function() {
5219 this.attr({
5220 viewBox: '0 0 ' + this.attr('width') + ' ' +
5221 this.attr('height')
5222 });
5223 },
5224 duration: pick(animate, true) ? undefined : 0
5225 });
5226
5227 while (i--) {
5228 alignedObjects[i].align();
5229 }
5230 },
5231
5232 /**
5233 * Create and return an svg group element. Child
5234 * {@link Highcharts.SVGElement} objects are added to the group by using the
5235 * group as the first parameter
5236 * in {@link Highcharts.SVGElement#add|add()}.
5237 *
5238 * @param {string} [name] The group will be given a class name of
5239 * `highcharts-{name}`. This can be used for styling and scripting.
5240 * @returns {SVGElement} The generated wrapper element.
5241 *
5242 * @sample highcharts/members/renderer-g/
5243 * Show and hide grouped objects
5244 */
5245 g: function(name) {
5246 var elem = this.createElement('g');
5247 return name ? elem.attr({
5248 'class': 'highcharts-' + name
5249 }) : elem;
5250 },
5251
5252 /**
5253 * Display an image.
5254 * @param {string} src The image source.
5255 * @param {number} [x] The X position.
5256 * @param {number} [y] The Y position.
5257 * @param {number} [width] The image width. If omitted, it defaults to the
5258 * image file width.
5259 * @param {number} [height] The image height. If omitted it defaults to the
5260 * image file height.
5261 * @returns {SVGElement} The generated wrapper element.
5262 *
5263 * @sample highcharts/members/renderer-image-on-chart/
5264 * Add an image in a chart
5265 * @sample highcharts/members/renderer-image/
5266 * Add an image independent of a chart
5267 */
5268 image: function(src, x, y, width, height) {
5269 var attribs = {
5270 preserveAspectRatio: 'none'
5271 },
5272 elemWrapper;
5273
5274 // optional properties
5275 if (arguments.length > 1) {
5276 extend(attribs, {
5277 x: x,
5278 y: y,
5279 width: width,
5280 height: height
5281 });
5282 }
5283
5284 elemWrapper = this.createElement('image').attr(attribs);
5285
5286 // set the href in the xlink namespace
5287 if (elemWrapper.element.setAttributeNS) {
5288 elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
5289 'href', src);
5290 } else {
5291 // could be exporting in IE
5292 // using href throws "not supported" in ie7 and under, requries
5293 // regex shim to fix later
5294 elemWrapper.element.setAttribute('hc-svg-href', src);
5295 }
5296 return elemWrapper;
5297 },
5298
5299 /**
5300 * Draw a symbol out of pre-defined shape paths from
5301 * {@link SVGRenderer#symbols}.
5302 * It is used in Highcharts for point makers, which cake a `symbol` option,
5303 * and label and button backgrounds like in the tooltip and stock flags.
5304 *
5305 * @param {Symbol} symbol - The symbol name.
5306 * @param {number} x - The X coordinate for the top left position.
5307 * @param {number} y - The Y coordinate for the top left position.
5308 * @param {number} width - The pixel width.
5309 * @param {number} height - The pixel height.
5310 * @param {Object} [options] - Additional options, depending on the actual
5311 * symbol drawn.
5312 * @param {number} [options.anchorX] - The anchor X position for the
5313 * `callout` symbol. This is where the chevron points to.
5314 * @param {number} [options.anchorY] - The anchor Y position for the
5315 * `callout` symbol. This is where the chevron points to.
5316 * @param {number} [options.end] - The end angle of an `arc` symbol.
5317 * @param {boolean} [options.open] - Whether to draw `arc` symbol open or
5318 * closed.
5319 * @param {number} [options.r] - The radius of an `arc` symbol, or the
5320 * border radius for the `callout` symbol.
5321 * @param {number} [options.start] - The start angle of an `arc` symbol.
5322 */
5323 symbol: function(symbol, x, y, width, height, options) {
5324
5325 var ren = this,
5326 obj,
5327 imageRegex = /^url\((.*?)\)$/,
5328 isImage = imageRegex.test(symbol),
5329 sym = !isImage && (this.symbols[symbol] ? symbol : 'circle'),
5330
5331
5332 // get the symbol definition function
5333 symbolFn = sym && this.symbols[sym],
5334
5335 // check if there's a path defined for this symbol
5336 path = defined(x) && symbolFn && symbolFn.call(
5337 this.symbols,
5338 Math.round(x),
5339 Math.round(y),
5340 width,
5341 height,
5342 options
5343 ),
5344 imageSrc,
5345 centerImage;
5346
5347 if (symbolFn) {
5348 obj = this.path(path);
5349
5350
5351
5352 // expando properties for use in animate and attr
5353 extend(obj, {
5354 symbolName: sym,
5355 x: x,
5356 y: y,
5357 width: width,
5358 height: height
5359 });
5360 if (options) {
5361 extend(obj, options);
5362 }
5363
5364
5365 // Image symbols
5366 } else if (isImage) {
5367
5368
5369 imageSrc = symbol.match(imageRegex)[1];
5370
5371 // Create the image synchronously, add attribs async
5372 obj = this.image(imageSrc);
5373
5374 // The image width is not always the same as the symbol width. The
5375 // image may be centered within the symbol, as is the case when
5376 // image shapes are used as label backgrounds, for example in flags.
5377 obj.imgwidth = pick(
5378 symbolSizes[imageSrc] && symbolSizes[imageSrc].width,
5379 options && options.width
5380 );
5381 obj.imgheight = pick(
5382 symbolSizes[imageSrc] && symbolSizes[imageSrc].height,
5383 options && options.height
5384 );
5385 /**
5386 * Set the size and position
5387 */
5388 centerImage = function() {
5389 obj.attr({
5390 width: obj.width,
5391 height: obj.height
5392 });
5393 };
5394
5395 /**
5396 * Width and height setters that take both the image's physical size
5397 * and the label size into consideration, and translates the image
5398 * to center within the label.
5399 */
5400 each(['width', 'height'], function(key) {
5401 obj[key + 'Setter'] = function(value, key) {
5402 var attribs = {},
5403 imgSize = this['img' + key],
5404 trans = key === 'width' ? 'translateX' : 'translateY';
5405 this[key] = value;
5406 if (defined(imgSize)) {
5407 if (this.element) {
5408 this.element.setAttribute(key, imgSize);
5409 }
5410 if (!this.alignByTranslate) {
5411 attribs[trans] = ((this[key] || 0) - imgSize) / 2;
5412 this.attr(attribs);
5413 }
5414 }
5415 };
5416 });
5417
5418
5419 if (defined(x)) {
5420 obj.attr({
5421 x: x,
5422 y: y
5423 });
5424 }
5425 obj.isImg = true;
5426
5427 if (defined(obj.imgwidth) && defined(obj.imgheight)) {
5428 centerImage();
5429 } else {
5430 // Initialize image to be 0 size so export will still function
5431 // if there's no cached sizes.
5432 obj.attr({
5433 width: 0,
5434 height: 0
5435 });
5436
5437 // Create a dummy JavaScript image to get the width and height.
5438 createElement('img', {
5439 onload: function() {
5440
5441 var chart = charts[ren.chartIndex];
5442
5443 // Special case for SVGs on IE11, the width is not
5444 // accessible until the image is part of the DOM
5445 // (#2854).
5446 if (this.width === 0) {
5447 css(this, {
5448 position: 'absolute',
5449 top: '-999em'
5450 });
5451 doc.body.appendChild(this);
5452 }
5453
5454 // Center the image
5455 symbolSizes[imageSrc] = { // Cache for next
5456 width: this.width,
5457 height: this.height
5458 };
5459 obj.imgwidth = this.width;
5460 obj.imgheight = this.height;
5461
5462 if (obj.element) {
5463 centerImage();
5464 }
5465
5466 // Clean up after #2854 workaround.
5467 if (this.parentNode) {
5468 this.parentNode.removeChild(this);
5469 }
5470
5471 // Fire the load event when all external images are
5472 // loaded
5473 ren.imgCount--;
5474 if (!ren.imgCount && chart && chart.onload) {
5475 chart.onload();
5476 }
5477 },
5478 src: imageSrc
5479 });
5480 this.imgCount++;
5481 }
5482 }
5483
5484 return obj;
5485 },
5486
5487 /**
5488 * @typedef {string} Symbol
5489 *
5490 * Can be one of `arc`, `callout`, `circle`, `diamond`, `square`,
5491 * `triangle`, `triangle-down`. Symbols are used internally for point
5492 * markers, button and label borders and backgrounds, or custom shapes.
5493 * Extendable by adding to {@link SVGRenderer#symbols}.
5494 */
5495 /**
5496 * An extendable collection of functions for defining symbol paths.
5497 */
5498 symbols: {
5499 'circle': function(x, y, w, h) {
5500 // Return a full arc
5501 return this.arc(x + w / 2, y + h / 2, w / 2, h / 2, {
5502 start: 0,
5503 end: Math.PI * 2,
5504 open: false
5505 });
5506 },
5507
5508 'square': function(x, y, w, h) {
5509 return [
5510 'M', x, y,
5511 'L', x + w, y,
5512 x + w, y + h,
5513 x, y + h,
5514 'Z'
5515 ];
5516 },
5517
5518 'triangle': function(x, y, w, h) {
5519 return [
5520 'M', x + w / 2, y,
5521 'L', x + w, y + h,
5522 x, y + h,
5523 'Z'
5524 ];
5525 },
5526
5527 'triangle-down': function(x, y, w, h) {
5528 return [
5529 'M', x, y,
5530 'L', x + w, y,
5531 x + w / 2, y + h,
5532 'Z'
5533 ];
5534 },
5535 'diamond': function(x, y, w, h) {
5536 return [
5537 'M', x + w / 2, y,
5538 'L', x + w, y + h / 2,
5539 x + w / 2, y + h,
5540 x, y + h / 2,
5541 'Z'
5542 ];
5543 },
5544 'arc': function(x, y, w, h, options) {
5545 var start = options.start,
5546 rx = options.r || w,
5547 ry = options.r || h || w,
5548 proximity = 0.001,
5549 fullCircle =
5550 Math.abs(options.end - options.start - 2 * Math.PI) <
5551 proximity,
5552 // Substract a small number to prevent cos and sin of start and
5553 // end from becoming equal on 360 arcs (related: #1561)
5554 end = options.end - proximity,
5555 innerRadius = options.innerR,
5556 open = pick(options.open, fullCircle),
5557 cosStart = Math.cos(start),
5558 sinStart = Math.sin(start),
5559 cosEnd = Math.cos(end),
5560 sinEnd = Math.sin(end),
5561 // Proximity takes care of rounding errors around PI (#6971)
5562 longArc = options.end - start - Math.PI < proximity ? 0 : 1,
5563 arc;
5564
5565 arc = [
5566 'M',
5567 x + rx * cosStart,
5568 y + ry * sinStart,
5569 'A', // arcTo
5570 rx, // x radius
5571 ry, // y radius
5572 0, // slanting
5573 longArc, // long or short arc
5574 1, // clockwise
5575 x + rx * cosEnd,
5576 y + ry * sinEnd
5577 ];
5578
5579 if (defined(innerRadius)) {
5580 arc.push(
5581 open ? 'M' : 'L',
5582 x + innerRadius * cosEnd,
5583 y + innerRadius * sinEnd,
5584 'A', // arcTo
5585 innerRadius, // x radius
5586 innerRadius, // y radius
5587 0, // slanting
5588 longArc, // long or short arc
5589 0, // clockwise
5590 x + innerRadius * cosStart,
5591 y + innerRadius * sinStart
5592 );
5593 }
5594
5595 arc.push(open ? '' : 'Z'); // close
5596 return arc;
5597 },
5598
5599 /**
5600 * Callout shape used for default tooltips, also used for rounded
5601 * rectangles in VML
5602 */
5603 callout: function(x, y, w, h, options) {
5604 var arrowLength = 6,
5605 halfDistance = 6,
5606 r = Math.min((options && options.r) || 0, w, h),
5607 safeDistance = r + halfDistance,
5608 anchorX = options && options.anchorX,
5609 anchorY = options && options.anchorY,
5610 path;
5611
5612 path = [
5613 'M', x + r, y,
5614 'L', x + w - r, y, // top side
5615 'C', x + w, y, x + w, y, x + w, y + r, // top-right corner
5616 'L', x + w, y + h - r, // right side
5617 'C', x + w, y + h, x + w, y + h, x + w - r, y + h, // bottom-rgt
5618 'L', x + r, y + h, // bottom side
5619 'C', x, y + h, x, y + h, x, y + h - r, // bottom-left corner
5620 'L', x, y + r, // left side
5621 'C', x, y, x, y, x + r, y // top-left corner
5622 ];
5623
5624 // Anchor on right side
5625 if (anchorX && anchorX > w) {
5626
5627 // Chevron
5628 if (
5629 anchorY > y + safeDistance &&
5630 anchorY < y + h - safeDistance
5631 ) {
5632 path.splice(13, 3,
5633 'L', x + w, anchorY - halfDistance,
5634 x + w + arrowLength, anchorY,
5635 x + w, anchorY + halfDistance,
5636 x + w, y + h - r
5637 );
5638
5639 // Simple connector
5640 } else {
5641 path.splice(13, 3,
5642 'L', x + w, h / 2,
5643 anchorX, anchorY,
5644 x + w, h / 2,
5645 x + w, y + h - r
5646 );
5647 }
5648
5649 // Anchor on left side
5650 } else if (anchorX && anchorX < 0) {
5651
5652 // Chevron
5653 if (
5654 anchorY > y + safeDistance &&
5655 anchorY < y + h - safeDistance
5656 ) {
5657 path.splice(33, 3,
5658 'L', x, anchorY + halfDistance,
5659 x - arrowLength, anchorY,
5660 x, anchorY - halfDistance,
5661 x, y + r
5662 );
5663
5664 // Simple connector
5665 } else {
5666 path.splice(33, 3,
5667 'L', x, h / 2,
5668 anchorX, anchorY,
5669 x, h / 2,
5670 x, y + r
5671 );
5672 }
5673
5674 } else if ( // replace bottom
5675 anchorY &&
5676 anchorY > h &&
5677 anchorX > x + safeDistance &&
5678 anchorX < x + w - safeDistance
5679 ) {
5680 path.splice(23, 3,
5681 'L', anchorX + halfDistance, y + h,
5682 anchorX, y + h + arrowLength,
5683 anchorX - halfDistance, y + h,
5684 x + r, y + h
5685 );
5686
5687 } else if ( // replace top
5688 anchorY &&
5689 anchorY < 0 &&
5690 anchorX > x + safeDistance &&
5691 anchorX < x + w - safeDistance
5692 ) {
5693 path.splice(3, 3,
5694 'L', anchorX - halfDistance, y,
5695 anchorX, y - arrowLength,
5696 anchorX + halfDistance, y,
5697 w - r, y
5698 );
5699 }
5700
5701 return path;
5702 }
5703 },
5704
5705 /**
5706 * @typedef {SVGElement} ClipRect - A clipping rectangle that can be applied
5707 * to one or more {@link SVGElement} instances. It is instanciated with the
5708 * {@link SVGRenderer#clipRect} function and applied with the {@link
5709 * SVGElement#clip} function.
5710 *
5711 * @example
5712 * var circle = renderer.circle(100, 100, 100)
5713 * .attr({ fill: 'red' })
5714 * .add();
5715 * var clipRect = renderer.clipRect(100, 100, 100, 100);
5716 *
5717 * // Leave only the lower right quarter visible
5718 * circle.clip(clipRect);
5719 */
5720 /**
5721 * Define a clipping rectangle. The clipping rectangle is later applied
5722 * to {@link SVGElement} objects through the {@link SVGElement#clip}
5723 * function.
5724 *
5725 * @param {String} id
5726 * @param {number} x
5727 * @param {number} y
5728 * @param {number} width
5729 * @param {number} height
5730 * @returns {ClipRect} A clipping rectangle.
5731 *
5732 * @example
5733 * var circle = renderer.circle(100, 100, 100)
5734 * .attr({ fill: 'red' })
5735 * .add();
5736 * var clipRect = renderer.clipRect(100, 100, 100, 100);
5737 *
5738 * // Leave only the lower right quarter visible
5739 * circle.clip(clipRect);
5740 */
5741 clipRect: function(x, y, width, height) {
5742 var wrapper,
5743 id = H.uniqueKey(),
5744
5745 clipPath = this.createElement('clipPath').attr({
5746 id: id
5747 }).add(this.defs);
5748
5749 wrapper = this.rect(x, y, width, height, 0).add(clipPath);
5750 wrapper.id = id;
5751 wrapper.clipPath = clipPath;
5752 wrapper.count = 0;
5753
5754 return wrapper;
5755 },
5756
5757
5758
5759
5760
5761 /**
5762 * Draw text. The text can contain a subset of HTML, like spans and anchors
5763 * and some basic text styling of these. For more advanced features like
5764 * border and background, use {@link Highcharts.SVGRenderer#label} instead.
5765 * To update the text after render, run `text.attr({ text: 'New text' })`.
5766 * @param {String} str
5767 * The text of (subset) HTML to draw.
5768 * @param {number} x
5769 * The x position of the text's lower left corner.
5770 * @param {number} y
5771 * The y position of the text's lower left corner.
5772 * @param {Boolean} [useHTML=false]
5773 * Use HTML to render the text.
5774 *
5775 * @return {SVGElement} The text object.
5776 *
5777 * @sample highcharts/members/renderer-text-on-chart/
5778 * Annotate the chart freely
5779 * @sample highcharts/members/renderer-on-chart/
5780 * Annotate with a border and in response to the data
5781 * @sample highcharts/members/renderer-text/
5782 * Formatted text
5783 */
5784 text: function(str, x, y, useHTML) {
5785
5786 // declare variables
5787 var renderer = this,
5788 wrapper,
5789 attribs = {};
5790
5791 if (useHTML && (renderer.allowHTML || !renderer.forExport)) {
5792 return renderer.html(str, x, y);
5793 }
5794
5795 attribs.x = Math.round(x || 0); // X always needed for line-wrap logic
5796 if (y) {
5797 attribs.y = Math.round(y);
5798 }
5799 if (str || str === 0) {
5800 attribs.text = str;
5801 }
5802
5803 wrapper = renderer.createElement('text')
5804 .attr(attribs);
5805
5806 if (!useHTML) {
5807 wrapper.xSetter = function(value, key, element) {
5808 var tspans = element.getElementsByTagName('tspan'),
5809 tspan,
5810 parentVal = element.getAttribute(key),
5811 i;
5812 for (i = 0; i < tspans.length; i++) {
5813 tspan = tspans[i];
5814 // If the x values are equal, the tspan represents a
5815 // linebreak
5816 if (tspan.getAttribute(key) === parentVal) {
5817 tspan.setAttribute(key, value);
5818 }
5819 }
5820 element.setAttribute(key, value);
5821 };
5822 }
5823
5824 return wrapper;
5825 },
5826
5827 /**
5828 * Utility to return the baseline offset and total line height from the font
5829 * size.
5830 *
5831 * @param {?string} fontSize The current font size to inspect. If not given,
5832 * the font size will be found from the DOM element.
5833 * @param {SVGElement|SVGDOMElement} [elem] The element to inspect for a
5834 * current font size.
5835 * @returns {Object} An object containing `h`: the line height, `b`: the
5836 * baseline relative to the top of the box, and `f`: the font size.
5837 */
5838 fontMetrics: function(fontSize, elem) {
5839 var lineHeight,
5840 baseline;
5841
5842
5843 fontSize = elem && SVGElement.prototype.getStyle.call(
5844 elem,
5845 'font-size'
5846 );
5847
5848
5849 // Handle different units
5850 if (/px/.test(fontSize)) {
5851 fontSize = pInt(fontSize);
5852 } else if (/em/.test(fontSize)) {
5853 // The em unit depends on parent items
5854 fontSize = parseFloat(fontSize) *
5855 (elem ? this.fontMetrics(null, elem.parentNode).f : 16);
5856 } else {
5857 fontSize = 12;
5858 }
5859
5860 // Empirical values found by comparing font size and bounding box
5861 // height. Applies to the default font family.
5862 // http://jsfiddle.net/highcharts/7xvn7/
5863 lineHeight = fontSize < 24 ? fontSize + 3 : Math.round(fontSize * 1.2);
5864 baseline = Math.round(lineHeight * 0.8);
5865
5866 return {
5867 h: lineHeight,
5868 b: baseline,
5869 f: fontSize
5870 };
5871 },
5872
5873 /**
5874 * Correct X and Y positioning of a label for rotation (#1764).
5875 *
5876 * @private
5877 */
5878 rotCorr: function(baseline, rotation, alterY) {
5879 var y = baseline;
5880 if (rotation && alterY) {
5881 y = Math.max(y * Math.cos(rotation * deg2rad), 4);
5882 }
5883 return {
5884 x: (-baseline / 3) * Math.sin(rotation * deg2rad),
5885 y: y
5886 };
5887 },
5888
5889 /**
5890 * Draw a label, which is an extended text element with support for border
5891 * and background. Highcharts creates a `g` element with a text and a `path`
5892 * or `rect` inside, to make it behave somewhat like a HTML div. Border and
5893 * background are set through `stroke`, `stroke-width` and `fill` attributes
5894 * using the {@link Highcharts.SVGElement#attr|attr} method. To update the
5895 * text after render, run `label.attr({ text: 'New text' })`.
5896 *
5897 * @param {string} str
5898 * The initial text string or (subset) HTML to render.
5899 * @param {number} x
5900 * The x position of the label's left side.
5901 * @param {number} y
5902 * The y position of the label's top side or baseline, depending on
5903 * the `baseline` parameter.
5904 * @param {String} shape
5905 * The shape of the label's border/background, if any. Defaults to
5906 * `rect`. Other possible values are `callout` or other shapes
5907 * defined in {@link Highcharts.SVGRenderer#symbols}.
5908 * @param {number} anchorX
5909 * In case the `shape` has a pointer, like a flag, this is the
5910 * coordinates it should be pinned to.
5911 * @param {number} anchorY
5912 * In case the `shape` has a pointer, like a flag, this is the
5913 * coordinates it should be pinned to.
5914 * @param {Boolean} baseline
5915 * Whether to position the label relative to the text baseline,
5916 * like {@link Highcharts.SVGRenderer#text|renderer.text}, or to the
5917 * upper border of the rectangle.
5918 * @param {String} className
5919 * Class name for the group.
5920 *
5921 * @return {SVGElement}
5922 * The generated label.
5923 *
5924 * @sample highcharts/members/renderer-label-on-chart/
5925 * A label on the chart
5926 */
5927 label: function(
5928 str,
5929 x,
5930 y,
5931 shape,
5932 anchorX,
5933 anchorY,
5934 useHTML,
5935 baseline,
5936 className
5937 ) {
5938
5939 var renderer = this,
5940 wrapper = renderer.g(className !== 'button' && 'label'),
5941 text = wrapper.text = renderer.text('', 0, 0, useHTML)
5942 .attr({
5943 zIndex: 1
5944 }),
5945 box,
5946 bBox,
5947 alignFactor = 0,
5948 padding = 3,
5949 paddingLeft = 0,
5950 width,
5951 height,
5952 wrapperX,
5953 wrapperY,
5954 textAlign,
5955 deferredAttr = {},
5956 strokeWidth,
5957 baselineOffset,
5958 hasBGImage = /^url\((.*?)\)$/.test(shape),
5959 needsBox = hasBGImage,
5960 getCrispAdjust,
5961 updateBoxSize,
5962 updateTextPadding,
5963 boxAttr;
5964
5965 if (className) {
5966 wrapper.addClass('highcharts-' + className);
5967 }
5968
5969
5970 needsBox = true; // for styling
5971 getCrispAdjust = function() {
5972 return box.strokeWidth() % 2 / 2;
5973 };
5974
5975
5976 /**
5977 * This function runs after the label is added to the DOM (when the
5978 * bounding box is available), and after the text of the label is
5979 * updated to detect the new bounding box and reflect it in the border
5980 * box.
5981 */
5982 updateBoxSize = function() {
5983 var style = text.element.style,
5984 crispAdjust,
5985 attribs = {};
5986
5987 bBox = (
5988 (width === undefined || height === undefined || textAlign) &&
5989 defined(text.textStr) &&
5990 text.getBBox()
5991 ); // #3295 && 3514 box failure when string equals 0
5992 wrapper.width = (
5993 (width || bBox.width || 0) +
5994 2 * padding +
5995 paddingLeft
5996 );
5997 wrapper.height = (height || bBox.height || 0) + 2 * padding;
5998
5999 // Update the label-scoped y offset
6000 baselineOffset = padding +
6001 renderer.fontMetrics(style && style.fontSize, text).b;
6002
6003
6004 if (needsBox) {
6005
6006 // Create the border box if it is not already present
6007 if (!box) {
6008 // Symbol definition exists (#5324)
6009 wrapper.box = box = renderer.symbols[shape] || hasBGImage ?
6010 renderer.symbol(shape) :
6011 renderer.rect();
6012
6013 box.addClass( // Don't use label className for buttons
6014 (className === 'button' ? '' : 'highcharts-label-box') +
6015 (className ? ' highcharts-' + className + '-box' : '')
6016 );
6017
6018 box.add(wrapper);
6019
6020 crispAdjust = getCrispAdjust();
6021 attribs.x = crispAdjust;
6022 attribs.y = (baseline ? -baselineOffset : 0) + crispAdjust;
6023 }
6024
6025 // Apply the box attributes
6026 attribs.width = Math.round(wrapper.width);
6027 attribs.height = Math.round(wrapper.height);
6028
6029 box.attr(extend(attribs, deferredAttr));
6030 deferredAttr = {};
6031 }
6032 };
6033
6034 /**
6035 * This function runs after setting text or padding, but only if padding
6036 * is changed
6037 */
6038 updateTextPadding = function() {
6039 var textX = paddingLeft + padding,
6040 textY;
6041
6042 // determin y based on the baseline
6043 textY = baseline ? 0 : baselineOffset;
6044
6045 // compensate for alignment
6046 if (
6047 defined(width) &&
6048 bBox &&
6049 (textAlign === 'center' || textAlign === 'right')
6050 ) {
6051 textX += {
6052 center: 0.5,
6053 right: 1
6054 }[textAlign] *
6055 (width - bBox.width);
6056 }
6057
6058 // update if anything changed
6059 if (textX !== text.x || textY !== text.y) {
6060 text.attr('x', textX);
6061 if (textY !== undefined) {
6062 text.attr('y', textY);
6063 }
6064 }
6065
6066 // record current values
6067 text.x = textX;
6068 text.y = textY;
6069 };
6070
6071 /**
6072 * Set a box attribute, or defer it if the box is not yet created
6073 * @param {Object} key
6074 * @param {Object} value
6075 */
6076 boxAttr = function(key, value) {
6077 if (box) {
6078 box.attr(key, value);
6079 } else {
6080 deferredAttr[key] = value;
6081 }
6082 };
6083
6084 /**
6085 * After the text element is added, get the desired size of the border
6086 * box and add it before the text in the DOM.
6087 */
6088 wrapper.onAdd = function() {
6089 text.add(wrapper);
6090 wrapper.attr({
6091 // Alignment is available now (#3295, 0 not rendered if given
6092 // as a value)
6093 text: (str || str === 0) ? str : '',
6094 x: x,
6095 y: y
6096 });
6097
6098 if (box && defined(anchorX)) {
6099 wrapper.attr({
6100 anchorX: anchorX,
6101 anchorY: anchorY
6102 });
6103 }
6104 };
6105
6106 /*
6107 * Add specific attribute setters.
6108 */
6109
6110 // only change local variables
6111 wrapper.widthSetter = function(value) {
6112 width = H.isNumber(value) ? value : null; // width:auto => null
6113 };
6114 wrapper.heightSetter = function(value) {
6115 height = value;
6116 };
6117 wrapper['text-alignSetter'] = function(value) {
6118 textAlign = value;
6119 };
6120 wrapper.paddingSetter = function(value) {
6121 if (defined(value) && value !== padding) {
6122 padding = wrapper.padding = value;
6123 updateTextPadding();
6124 }
6125 };
6126 wrapper.paddingLeftSetter = function(value) {
6127 if (defined(value) && value !== paddingLeft) {
6128 paddingLeft = value;
6129 updateTextPadding();
6130 }
6131 };
6132
6133
6134 // change local variable and prevent setting attribute on the group
6135 wrapper.alignSetter = function(value) {
6136 value = {
6137 left: 0,
6138 center: 0.5,
6139 right: 1
6140 }[value];
6141 if (value !== alignFactor) {
6142 alignFactor = value;
6143 // Bounding box exists, means we're dynamically changing
6144 if (bBox) {
6145 wrapper.attr({
6146 x: wrapperX
6147 }); // #5134
6148 }
6149 }
6150 };
6151
6152 // apply these to the box and the text alike
6153 wrapper.textSetter = function(value) {
6154 if (value !== undefined) {
6155 text.textSetter(value);
6156 }
6157 updateBoxSize();
6158 updateTextPadding();
6159 };
6160
6161 // apply these to the box but not to the text
6162 wrapper['stroke-widthSetter'] = function(value, key) {
6163 if (value) {
6164 needsBox = true;
6165 }
6166 strokeWidth = this['stroke-width'] = value;
6167 boxAttr(key, value);
6168 };
6169
6170 wrapper.rSetter = function(value, key) {
6171 boxAttr(key, value);
6172 };
6173
6174 wrapper.anchorXSetter = function(value, key) {
6175 anchorX = wrapper.anchorX = value;
6176 boxAttr(key, Math.round(value) - getCrispAdjust() - wrapperX);
6177 };
6178 wrapper.anchorYSetter = function(value, key) {
6179 anchorY = wrapper.anchorY = value;
6180 boxAttr(key, value - wrapperY);
6181 };
6182
6183 // rename attributes
6184 wrapper.xSetter = function(value) {
6185 wrapper.x = value; // for animation getter
6186 if (alignFactor) {
6187 value -= alignFactor * ((width || bBox.width) + 2 * padding);
6188 }
6189 wrapperX = Math.round(value);
6190 wrapper.attr('translateX', wrapperX);
6191 };
6192 wrapper.ySetter = function(value) {
6193 wrapperY = wrapper.y = Math.round(value);
6194 wrapper.attr('translateY', wrapperY);
6195 };
6196
6197 // Redirect certain methods to either the box or the text
6198 var baseCss = wrapper.css;
6199 return extend(wrapper, {
6200 /**
6201 * Pick up some properties and apply them to the text instead of the
6202 * wrapper.
6203 * @ignore
6204 */
6205 css: function(styles) {
6206 if (styles) {
6207 var textStyles = {};
6208 // Create a copy to avoid altering the original object
6209 // (#537)
6210 styles = merge(styles);
6211 each(wrapper.textProps, function(prop) {
6212 if (styles[prop] !== undefined) {
6213 textStyles[prop] = styles[prop];
6214 delete styles[prop];
6215 }
6216 });
6217 text.css(textStyles);
6218 }
6219 return baseCss.call(wrapper, styles);
6220 },
6221 /**
6222 * Return the bounding box of the box, not the group.
6223 * @ignore
6224 */
6225 getBBox: function() {
6226 return {
6227 width: bBox.width + 2 * padding,
6228 height: bBox.height + 2 * padding,
6229 x: bBox.x - padding,
6230 y: bBox.y - padding
6231 };
6232 },
6233
6234 /**
6235 * Destroy and release memory.
6236 * @ignore
6237 */
6238 destroy: function() {
6239
6240 // Added by button implementation
6241 removeEvent(wrapper.element, 'mouseenter');
6242 removeEvent(wrapper.element, 'mouseleave');
6243
6244 if (text) {
6245 text = text.destroy();
6246 }
6247 if (box) {
6248 box = box.destroy();
6249 }
6250 // Call base implementation to destroy the rest
6251 SVGElement.prototype.destroy.call(wrapper);
6252
6253 // Release local pointers (#1298)
6254 wrapper =
6255 renderer =
6256 updateBoxSize =
6257 updateTextPadding =
6258 boxAttr = null;
6259 }
6260 });
6261 }
6262 }); // end SVGRenderer
6263
6264
6265 // general renderer
6266 H.Renderer = SVGRenderer;
6267
6268 }(Highcharts));
6269 (function(H) {
6270 /**
6271 * (c) 2010-2017 Torstein Honsi
6272 *
6273 * License: www.highcharts.com/license
6274 */
6275 /* eslint max-len: ["warn", 80, 4] */
6276 var attr = H.attr,
6277 createElement = H.createElement,
6278 css = H.css,
6279 defined = H.defined,
6280 each = H.each,
6281 extend = H.extend,
6282 isFirefox = H.isFirefox,
6283 isMS = H.isMS,
6284 isWebKit = H.isWebKit,
6285 pick = H.pick,
6286 pInt = H.pInt,
6287 SVGElement = H.SVGElement,
6288 SVGRenderer = H.SVGRenderer,
6289 win = H.win,
6290 wrap = H.wrap;
6291
6292 // Extend SvgElement for useHTML option
6293 extend(SVGElement.prototype, /** @lends SVGElement.prototype */ {
6294 /**
6295 * Apply CSS to HTML elements. This is used in text within SVG rendering and
6296 * by the VML renderer
6297 */
6298 htmlCss: function(styles) {
6299 var wrapper = this,
6300 element = wrapper.element,
6301 textWidth = styles && element.tagName === 'SPAN' && styles.width;
6302
6303 if (textWidth) {
6304 delete styles.width;
6305 wrapper.textWidth = textWidth;
6306 wrapper.updateTransform();
6307 }
6308 if (styles && styles.textOverflow === 'ellipsis') {
6309 styles.whiteSpace = 'nowrap';
6310 styles.overflow = 'hidden';
6311 }
6312 wrapper.styles = extend(wrapper.styles, styles);
6313 css(wrapper.element, styles);
6314
6315 return wrapper;
6316 },
6317
6318 /**
6319 * VML and useHTML method for calculating the bounding box based on offsets
6320 * @param {Boolean} refresh Whether to force a fresh value from the DOM or
6321 * to use the cached value.
6322 *
6323 * @return {Object} A hash containing values for x, y, width and height
6324 */
6325
6326 htmlGetBBox: function() {
6327 var wrapper = this,
6328 element = wrapper.element;
6329
6330 return {
6331 x: element.offsetLeft,
6332 y: element.offsetTop,
6333 width: element.offsetWidth,
6334 height: element.offsetHeight
6335 };
6336 },
6337
6338 /**
6339 * VML override private method to update elements based on internal
6340 * properties based on SVG transform
6341 */
6342 htmlUpdateTransform: function() {
6343 // aligning non added elements is expensive
6344 if (!this.added) {
6345 this.alignOnAdd = true;
6346 return;
6347 }
6348
6349 var wrapper = this,
6350 renderer = wrapper.renderer,
6351 elem = wrapper.element,
6352 translateX = wrapper.translateX || 0,
6353 translateY = wrapper.translateY || 0,
6354 x = wrapper.x || 0,
6355 y = wrapper.y || 0,
6356 align = wrapper.textAlign || 'left',
6357 alignCorrection = {
6358 left: 0,
6359 center: 0.5,
6360 right: 1
6361 }[align],
6362 styles = wrapper.styles;
6363
6364 // apply translate
6365 css(elem, {
6366 marginLeft: translateX,
6367 marginTop: translateY
6368 });
6369
6370
6371
6372 // apply inversion
6373 if (wrapper.inverted) { // wrapper is a group
6374 each(elem.childNodes, function(child) {
6375 renderer.invertChild(child, elem);
6376 });
6377 }
6378
6379 if (elem.tagName === 'SPAN') {
6380
6381 var rotation = wrapper.rotation,
6382 baseline,
6383 textWidth = pInt(wrapper.textWidth),
6384 whiteSpace = styles && styles.whiteSpace,
6385 currentTextTransform = [
6386 rotation,
6387 align,
6388 elem.innerHTML,
6389 wrapper.textWidth,
6390 wrapper.textAlign
6391 ].join(',');
6392
6393 // Do the calculations and DOM access only if properties changed
6394 if (currentTextTransform !== wrapper.cTT) {
6395
6396
6397 baseline = renderer.fontMetrics(elem.style.fontSize).b;
6398
6399 // Renderer specific handling of span rotation
6400 if (defined(rotation)) {
6401 wrapper.setSpanRotation(
6402 rotation,
6403 alignCorrection,
6404 baseline
6405 );
6406 }
6407
6408 // Reset multiline/ellipsis in order to read width (#4928,
6409 // #5417)
6410 css(elem, {
6411 width: '',
6412 whiteSpace: whiteSpace || 'nowrap'
6413 });
6414
6415 // Update textWidth
6416 if (
6417 elem.offsetWidth > textWidth &&
6418 /[ \-]/.test(elem.textContent || elem.innerText)
6419 ) { // #983, #1254
6420 css(elem, {
6421 width: textWidth + 'px',
6422 display: 'block',
6423 whiteSpace: whiteSpace || 'normal' // #3331
6424 });
6425 }
6426
6427
6428 wrapper.getSpanCorrection(
6429 elem.offsetWidth,
6430 baseline,
6431 alignCorrection,
6432 rotation,
6433 align
6434 );
6435 }
6436
6437 // apply position with correction
6438 css(elem, {
6439 left: (x + (wrapper.xCorr || 0)) + 'px',
6440 top: (y + (wrapper.yCorr || 0)) + 'px'
6441 });
6442
6443 // Force reflow in webkit to apply the left and top on useHTML
6444 // element (#1249)
6445 if (isWebKit) {
6446 // Assigned to baseline for lint purpose
6447 baseline = elem.offsetHeight;
6448 }
6449
6450 // record current text transform
6451 wrapper.cTT = currentTextTransform;
6452 }
6453 },
6454
6455 /**
6456 * Set the rotation of an individual HTML span
6457 */
6458 setSpanRotation: function(rotation, alignCorrection, baseline) {
6459 var rotationStyle = {},
6460 cssTransformKey = this.renderer.getTransformKey();
6461
6462 rotationStyle[cssTransformKey] = rotationStyle.transform =
6463 'rotate(' + rotation + 'deg)';
6464 rotationStyle[cssTransformKey + (isFirefox ? 'Origin' : '-origin')] =
6465 rotationStyle.transformOrigin =
6466 (alignCorrection * 100) + '% ' + baseline + 'px';
6467 css(this.element, rotationStyle);
6468 },
6469
6470 /**
6471 * Get the correction in X and Y positioning as the element is rotated.
6472 */
6473 getSpanCorrection: function(width, baseline, alignCorrection) {
6474 this.xCorr = -width * alignCorrection;
6475 this.yCorr = -baseline;
6476 }
6477 });
6478
6479 // Extend SvgRenderer for useHTML option.
6480 extend(SVGRenderer.prototype, /** @lends SVGRenderer.prototype */ {
6481
6482 getTransformKey: function() {
6483 return isMS && !/Edge/.test(win.navigator.userAgent) ?
6484 '-ms-transform' :
6485 isWebKit ?
6486 '-webkit-transform' :
6487 isFirefox ?
6488 'MozTransform' :
6489 win.opera ?
6490 '-o-transform' :
6491 '';
6492 },
6493
6494 /**
6495 * Create HTML text node. This is used by the VML renderer as well as the
6496 * SVG renderer through the useHTML option.
6497 *
6498 * @param {String} str
6499 * @param {Number} x
6500 * @param {Number} y
6501 */
6502 html: function(str, x, y) {
6503 var wrapper = this.createElement('span'),
6504 element = wrapper.element,
6505 renderer = wrapper.renderer,
6506 isSVG = renderer.isSVG,
6507 addSetters = function(element, style) {
6508 // These properties are set as attributes on the SVG group, and
6509 // as identical CSS properties on the div. (#3542)
6510 each(['opacity', 'visibility'], function(prop) {
6511 wrap(element, prop + 'Setter', function(
6512 proceed,
6513 value,
6514 key,
6515 elem
6516 ) {
6517 proceed.call(this, value, key, elem);
6518 style[key] = value;
6519 });
6520 });
6521 };
6522
6523 // Text setter
6524 wrapper.textSetter = function(value) {
6525 if (value !== element.innerHTML) {
6526 delete this.bBox;
6527 }
6528 this.textStr = value;
6529 element.innerHTML = pick(value, '');
6530 wrapper.htmlUpdateTransform();
6531 };
6532
6533 // Add setters for the element itself (#4938)
6534 if (isSVG) { // #4938, only for HTML within SVG
6535 addSetters(wrapper, wrapper.element.style);
6536 }
6537
6538 // Various setters which rely on update transform
6539 wrapper.xSetter =
6540 wrapper.ySetter =
6541 wrapper.alignSetter =
6542 wrapper.rotationSetter =
6543 function(value, key) {
6544 if (key === 'align') {
6545 // Do not overwrite the SVGElement.align method. Same as VML.
6546 key = 'textAlign';
6547 }
6548 wrapper[key] = value;
6549 wrapper.htmlUpdateTransform();
6550 };
6551
6552 // Set the default attributes
6553 wrapper
6554 .attr({
6555 text: str,
6556 x: Math.round(x),
6557 y: Math.round(y)
6558 })
6559 .css({
6560
6561 position: 'absolute'
6562 });
6563
6564 // Keep the whiteSpace style outside the wrapper.styles collection
6565 element.style.whiteSpace = 'nowrap';
6566
6567 // Use the HTML specific .css method
6568 wrapper.css = wrapper.htmlCss;
6569
6570 // This is specific for HTML within SVG
6571 if (isSVG) {
6572 wrapper.add = function(svgGroupWrapper) {
6573
6574 var htmlGroup,
6575 container = renderer.box.parentNode,
6576 parentGroup,
6577 parents = [];
6578
6579 this.parentGroup = svgGroupWrapper;
6580
6581 // Create a mock group to hold the HTML elements
6582 if (svgGroupWrapper) {
6583 htmlGroup = svgGroupWrapper.div;
6584 if (!htmlGroup) {
6585
6586 // Read the parent chain into an array and read from top
6587 // down
6588 parentGroup = svgGroupWrapper;
6589 while (parentGroup) {
6590
6591 parents.push(parentGroup);
6592
6593 // Move up to the next parent group
6594 parentGroup = parentGroup.parentGroup;
6595 }
6596
6597 // Ensure dynamically updating position when any parent
6598 // is translated
6599 each(parents.reverse(), function(parentGroup) {
6600 var htmlGroupStyle,
6601 cls = attr(parentGroup.element, 'class');
6602
6603 // Common translate setter for X and Y on the HTML
6604 // group. Using CSS transform instead of left and
6605 // right prevents flickering in IE and Edge when
6606 // moving tooltip (#6957).
6607 function translateSetter(value, key) {
6608 parentGroup[key] = value;
6609
6610 // In IE and Edge, use translate because items
6611 // would flicker below a HTML tooltip (#6957)
6612 if (isMS) {
6613 htmlGroupStyle[renderer.getTransformKey()] =
6614 'translate(' + (
6615 parentGroup.x ||
6616 parentGroup.translateX
6617 ) + 'px,' + (
6618 parentGroup.y ||
6619 parentGroup.translateY
6620 ) + 'px)';
6621
6622 // Otherwise, use left and top. Using translate
6623 // doesn't work well with offline export (#7254,
6624 // #7280)
6625 } else {
6626 if (key === 'translateX') {
6627 htmlGroupStyle.left = value + 'px';
6628 } else {
6629 htmlGroupStyle.top = value + 'px';
6630 }
6631 }
6632
6633 parentGroup.doTransform = true;
6634 }
6635
6636 if (cls) {
6637 cls = {
6638 className: cls
6639 };
6640 } // else null
6641
6642 // Create a HTML div and append it to the parent div
6643 // to emulate the SVG group structure
6644 htmlGroup =
6645 parentGroup.div =
6646 parentGroup.div || createElement('div', cls, {
6647 position: 'absolute',
6648 left: (parentGroup.translateX || 0) + 'px',
6649 top: (parentGroup.translateY || 0) + 'px',
6650 display: parentGroup.display,
6651 opacity: parentGroup.opacity, // #5075
6652 pointerEvents: (
6653 parentGroup.styles &&
6654 parentGroup.styles.pointerEvents
6655 ) // #5595
6656
6657 // the top group is appended to container
6658 }, htmlGroup || container);
6659
6660 // Shortcut
6661 htmlGroupStyle = htmlGroup.style;
6662
6663 // Set listeners to update the HTML div's position
6664 // whenever the SVG group position is changed.
6665 extend(parentGroup, {
6666 classSetter: function(value) {
6667 this.element.setAttribute('class', value);
6668 htmlGroup.className = value;
6669 },
6670 on: function() {
6671 if (parents[0].div) { // #6418
6672 wrapper.on.apply({
6673 element: parents[0].div
6674 },
6675 arguments
6676 );
6677 }
6678 return parentGroup;
6679 },
6680 translateXSetter: translateSetter,
6681 translateYSetter: translateSetter
6682 });
6683 addSetters(parentGroup, htmlGroupStyle);
6684 });
6685
6686 }
6687 } else {
6688 htmlGroup = container;
6689 }
6690
6691 htmlGroup.appendChild(element);
6692
6693 // Shared with VML:
6694 wrapper.added = true;
6695 if (wrapper.alignOnAdd) {
6696 wrapper.htmlUpdateTransform();
6697 }
6698
6699 return wrapper;
6700 };
6701 }
6702 return wrapper;
6703 }
6704 });
6705
6706 }(Highcharts));
6707 (function(H) {
6708 /**
6709 * (c) 2010-2017 Torstein Honsi
6710 *
6711 * License: www.highcharts.com/license
6712 */
6713 var color = H.color,
6714 getTZOffset = H.getTZOffset,
6715 isTouchDevice = H.isTouchDevice,
6716 merge = H.merge,
6717 pick = H.pick,
6718 svg = H.svg,
6719 win = H.win;
6720
6721 /* ****************************************************************************
6722 * Handle the options *
6723 *****************************************************************************/
6724 /**
6725 * @optionparent
6726 */
6727 H.defaultOptions = {
6728
6729
6730
6731 /**
6732 * Styled mode only. Configuration object for adding SVG definitions for
6733 * reusable elements. See [gradients, shadows and patterns](http://www.
6734 * highcharts.com/docs/chart-design-and-style/gradients-shadows-and-
6735 * patterns) for more information and code examples.
6736 *
6737 * @type {Object}
6738 * @since 5.0.0
6739 * @apioption defs
6740 */
6741
6742 /**
6743 * @ignore
6744 */
6745 symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
6746 lang: {
6747
6748 /**
6749 * The loading text that appears when the chart is set into the loading
6750 * state following a call to `chart.showLoading`.
6751 *
6752 * @type {String}
6753 * @default Loading...
6754 */
6755 loading: 'Loading...',
6756
6757 /**
6758 * An array containing the months names. Corresponds to the `%B` format
6759 * in `Highcharts.dateFormat()`.
6760 *
6761 * @type {Array<String>}
6762 * @default [ "January" , "February" , "March" , "April" , "May" ,
6763 * "June" , "July" , "August" , "September" , "October" ,
6764 * "November" , "December"]
6765 */
6766 months: [
6767 'January', 'February', 'March', 'April', 'May', 'June', 'July',
6768 'August', 'September', 'October', 'November', 'December'
6769 ],
6770
6771 /**
6772 * An array containing the months names in abbreviated form. Corresponds
6773 * to the `%b` format in `Highcharts.dateFormat()`.
6774 *
6775 * @type {Array<String>}
6776 * @default [ "Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" ,
6777 * "Jul" , "Aug" , "Sep" , "Oct" , "Nov" , "Dec"]
6778 */
6779 shortMonths: [
6780 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
6781 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
6782 ],
6783
6784 /**
6785 * An array containing the weekday names.
6786 *
6787 * @type {Array<String>}
6788 * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
6789 * "Friday", "Saturday"]
6790 */
6791 weekdays: [
6792 'Sunday', 'Monday', 'Tuesday', 'Wednesday',
6793 'Thursday', 'Friday', 'Saturday'
6794 ],
6795
6796 /**
6797 * Short week days, starting Sunday. If not specified, Highcharts uses
6798 * the first three letters of the `lang.weekdays` option.
6799 *
6800 * @type {Array<String>}
6801 * @sample highcharts/lang/shortweekdays/
6802 * Finnish two-letter abbreviations
6803 * @since 4.2.4
6804 * @apioption lang.shortWeekdays
6805 */
6806
6807 /**
6808 * What to show in a date field for invalid dates. Defaults to an empty
6809 * string.
6810 *
6811 * @type {String}
6812 * @since 4.1.8
6813 * @product highcharts highstock
6814 * @apioption lang.invalidDate
6815 */
6816
6817 /**
6818 * The default decimal point used in the `Highcharts.numberFormat`
6819 * method unless otherwise specified in the function arguments.
6820 *
6821 * @type {String}
6822 * @default .
6823 * @since 1.2.2
6824 */
6825 decimalPoint: '.',
6826
6827 /**
6828 * [Metric prefixes](http://en.wikipedia.org/wiki/Metric_prefix) used
6829 * to shorten high numbers in axis labels. Replacing any of the positions
6830 * with `null` causes the full number to be written. Setting `numericSymbols`
6831 * to `null` disables shortening altogether.
6832 *
6833 * @type {Array<String>}
6834 * @sample {highcharts} highcharts/lang/numericsymbols/
6835 * Replacing the symbols with text
6836 * @sample {highstock} highcharts/lang/numericsymbols/
6837 * Replacing the symbols with text
6838 * @default [ "k" , "M" , "G" , "T" , "P" , "E"]
6839 * @since 2.3.0
6840 */
6841 numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'],
6842
6843 /**
6844 * The magnitude of [numericSymbols](#lang.numericSymbol) replacements.
6845 * Use 10000 for Japanese, Korean and various Chinese locales, which
6846 * use symbols for 10^4, 10^8 and 10^12.
6847 *
6848 * @type {Number}
6849 * @sample highcharts/lang/numericsymbolmagnitude/
6850 * 10000 magnitude for Japanese
6851 * @default 1000
6852 * @since 5.0.3
6853 * @apioption lang.numericSymbolMagnitude
6854 */
6855
6856 /**
6857 * The text for the label appearing when a chart is zoomed.
6858 *
6859 * @type {String}
6860 * @default Reset zoom
6861 * @since 1.2.4
6862 */
6863 resetZoom: 'Reset zoom',
6864
6865 /**
6866 * The tooltip title for the label appearing when a chart is zoomed.
6867 *
6868 * @type {String}
6869 * @default Reset zoom level 1:1
6870 * @since 1.2.4
6871 */
6872 resetZoomTitle: 'Reset zoom level 1:1',
6873
6874 /**
6875 * The default thousands separator used in the `Highcharts.numberFormat`
6876 * method unless otherwise specified in the function arguments. Since
6877 * Highcharts 4.1 it defaults to a single space character, which is
6878 * compatible with ISO and works across Anglo-American and continental
6879 * European languages.
6880 *
6881 * The default is a single space.
6882 *
6883 * @type {String}
6884 * @default
6885 * @since 1.2.2
6886 */
6887 thousandsSep: ' '
6888 },
6889
6890 /**
6891 * Global options that don't apply to each chart. These options, like
6892 * the `lang` options, must be set using the `Highcharts.setOptions`
6893 * method.
6894 *
6895 * <pre>Highcharts.setOptions({
6896 * global: {
6897 * useUTC: false
6898 * }
6899 * });</pre>
6900 *
6901 */
6902 global: {
6903
6904 /**
6905 * Whether to use UTC time for axis scaling, tickmark placement and
6906 * time display in `Highcharts.dateFormat`. Advantages of using UTC
6907 * is that the time displays equally regardless of the user agent's
6908 * time zone settings. Local time can be used when the data is loaded
6909 * in real time or when correct Daylight Saving Time transitions are
6910 * required.
6911 *
6912 * @type {Boolean}
6913 * @sample {highcharts} highcharts/global/useutc-true/ True by default
6914 * @sample {highcharts} highcharts/global/useutc-false/ False
6915 * @default true
6916 */
6917 useUTC: true
6918
6919 /**
6920 * A custom `Date` class for advanced date handling. For example,
6921 * [JDate](https://githubcom/tahajahangir/jdate) can be hooked in to
6922 * handle Jalali dates.
6923 *
6924 * @type {Object}
6925 * @since 4.0.4
6926 * @product highcharts highstock
6927 * @apioption global.Date
6928 */
6929
6930 /**
6931 * _Canvg rendering for Android 2.x is removed as of Highcharts 5.0\.
6932 * Use the [libURL](#exporting.libURL) option to configure exporting._
6933 *
6934 * The URL to the additional file to lazy load for Android 2.x devices.
6935 * These devices don't support SVG, so we download a helper file that
6936 * contains [canvg](http://code.google.com/p/canvg/), its dependency
6937 * rbcolor, and our own CanVG Renderer class. To avoid hotlinking to
6938 * our site, you can install canvas-tools.js on your own server and
6939 * change this option accordingly.
6940 *
6941 * @type {String}
6942 * @deprecated
6943 * @default http://code.highcharts.com/{version}/modules/canvas-tools.js
6944 * @product highcharts highmaps
6945 * @apioption global.canvasToolsURL
6946 */
6947
6948 /**
6949 * A callback to return the time zone offset for a given datetime. It
6950 * takes the timestamp in terms of milliseconds since January 1 1970,
6951 * and returns the timezone offset in minutes. This provides a hook
6952 * for drawing time based charts in specific time zones using their
6953 * local DST crossover dates, with the help of external libraries.
6954 *
6955 * @type {Function}
6956 * @see [global.timezoneOffset](#global.timezoneOffset)
6957 * @sample {highcharts} highcharts/global/gettimezoneoffset/
6958 * Use moment.js to draw Oslo time regardless of browser locale
6959 * @sample {highstock} highcharts/global/gettimezoneoffset/
6960 * Use moment.js to draw Oslo time regardless of browser locale
6961 * @since 4.1.0
6962 * @product highcharts highstock
6963 * @apioption global.getTimezoneOffset
6964 */
6965
6966 /**
6967 * Requires [moment.js](http://momentjs.com/). If the timezone option
6968 * is specified, it creates a default
6969 * [getTimezoneOffset](#global.getTimezoneOffset) function that looks
6970 * up the specified timezone in moment.js. If moment.js is not included,
6971 * this throws a Highcharts error in the console, but does not crash the
6972 * chart.
6973 *
6974 * @type {String}
6975 * @see [getTimezoneOffset](#global.getTimezoneOffset)
6976 * @sample {highcharts} highcharts/global/timezone/ Europe/Oslo
6977 * @sample {highstock} highcharts/global/timezone/ Europe/Oslo
6978 * @default undefined
6979 * @since 5.0.7
6980 * @product highcharts highstock
6981 * @apioption global.timezone
6982 */
6983
6984 /**
6985 * The timezone offset in minutes. Positive values are west, negative
6986 * values are east of UTC, as in the ECMAScript [getTimezoneOffset](https://developer.
6987 * mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset)
6988 * method. Use this to display UTC based data in a predefined time zone.
6989 *
6990 * @type {Number}
6991 * @see [global.getTimezoneOffset](#global.getTimezoneOffset)
6992 * @sample {highcharts} highcharts/global/timezoneoffset/
6993 * Timezone offset
6994 * @sample {highstock} highcharts/global/timezoneoffset/
6995 * Timezone offset
6996 * @default 0
6997 * @since 3.0.8
6998 * @product highcharts highstock
6999 * @apioption global.timezoneOffset
7000 */
7001 },
7002 chart: {
7003
7004 /**
7005 * When using multiple axis, the ticks of two or more opposite axes
7006 * will automatically be aligned by adding ticks to the axis or axes
7007 * with the least ticks, as if `tickAmount` were specified.
7008 *
7009 * This can be prevented by setting `alignTicks` to false. If the grid
7010 * lines look messy, it's a good idea to hide them for the secondary
7011 * axis by setting `gridLineWidth` to 0.
7012 *
7013 * @type {Boolean}
7014 * @sample {highcharts} highcharts/chart/alignticks-true/ True by default
7015 * @sample {highcharts} highcharts/chart/alignticks-false/ False
7016 * @sample {highstock} stock/chart/alignticks-true/
7017 * True by default
7018 * @sample {highstock} stock/chart/alignticks-false/
7019 * False
7020 * @default true
7021 * @product highcharts highstock
7022 * @apioption chart.alignTicks
7023 */
7024
7025
7026 /**
7027 * Set the overall animation for all chart updating. Animation can be
7028 * disabled throughout the chart by setting it to false here. It can
7029 * be overridden for each individual API method as a function parameter.
7030 * The only animation not affected by this option is the initial series
7031 * animation, see [plotOptions.series.animation](#plotOptions.series.
7032 * animation).
7033 *
7034 * The animation can either be set as a boolean or a configuration
7035 * object. If `true`, it will use the 'swing' jQuery easing and a
7036 * duration of 500 ms. If used as a configuration object, the following
7037 * properties are supported:
7038 *
7039 * <dl>
7040 *
7041 * <dt>duration</dt>
7042 *
7043 * <dd>The duration of the animation in milliseconds.</dd>
7044 *
7045 * <dt>easing</dt>
7046 *
7047 * <dd>A string reference to an easing function set on the `Math` object.
7048 * See [the easing demo](http://jsfiddle.net/gh/get/library/pure/
7049 * highcharts/highcharts/tree/master/samples/highcharts/plotoptions/
7050 * series-animation-easing/).</dd>
7051 *
7052 * </dl>
7053 *
7054 * @type {Boolean|Object}
7055 * @sample {highcharts} highcharts/chart/animation-none/
7056 * Updating with no animation
7057 * @sample {highcharts} highcharts/chart/animation-duration/
7058 * With a longer duration
7059 * @sample {highcharts} highcharts/chart/animation-easing/
7060 * With a jQuery UI easing
7061 * @sample {highmaps} maps/chart/animation-none/
7062 * Updating with no animation
7063 * @sample {highmaps} maps/chart/animation-duration/
7064 * With a longer duration
7065 * @default true
7066 * @apioption chart.animation
7067 */
7068
7069 /**
7070 * A CSS class name to apply to the charts container `div`, allowing
7071 * unique CSS styling for each chart.
7072 *
7073 * @type {String}
7074 * @apioption chart.className
7075 */
7076
7077 /**
7078 * Event listeners for the chart.
7079 *
7080 * @apioption chart.events
7081 */
7082
7083 /**
7084 * Fires when a series is added to the chart after load time, using
7085 * the `addSeries` method. One parameter, `event`, is passed to the
7086 * function, containing common event information.
7087 * Through `event.options` you can access the series options that was
7088 * passed to the `addSeries` method. Returning false prevents the series
7089 * from being added.
7090 *
7091 * @type {Function}
7092 * @context Chart
7093 * @sample {highcharts} highcharts/chart/events-addseries/ Alert on add series
7094 * @sample {highstock} stock/chart/events-addseries/ Alert on add series
7095 * @since 1.2.0
7096 * @apioption chart.events.addSeries
7097 */
7098
7099 /**
7100 * Fires when clicking on the plot background. One parameter, `event`,
7101 * is passed to the function, containing common event information.
7102 *
7103 * Information on the clicked spot can be found through `event.xAxis`
7104 * and `event.yAxis`, which are arrays containing the axes of each dimension
7105 * and each axis' value at the clicked spot. The primary axes are `event.
7106 * xAxis[0]` and `event.yAxis[0]`. Remember the unit of a datetime axis
7107 * is milliseconds since 1970-01-01 00:00:00.
7108 *
7109 * <pre>click: function(e) {
7110 * console.log(
7111 * Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', e.xAxis[0].value),
7112 * e.yAxis[0].value
7113 * )
7114 * }</pre>
7115 *
7116 * @type {Function}
7117 * @context Chart
7118 * @sample {highcharts} highcharts/chart/events-click/
7119 * Alert coordinates on click
7120 * @sample {highcharts} highcharts/chart/events-container/
7121 * Alternatively, attach event to container
7122 * @sample {highstock} stock/chart/events-click/
7123 * Alert coordinates on click
7124 * @sample {highstock} highcharts/chart/events-container/
7125 * Alternatively, attach event to container
7126 * @sample {highmaps} maps/chart/events-click/
7127 * Record coordinates on click
7128 * @sample {highmaps} highcharts/chart/events-container/
7129 * Alternatively, attach event to container
7130 * @since 1.2.0
7131 * @apioption chart.events.click
7132 */
7133
7134
7135 /**
7136 * Fires when the chart is finished loading. Since v4.2.2, it also waits
7137 * for images to be loaded, for example from point markers. One parameter,
7138 * `event`, is passed to the function, containing common event information.
7139 *
7140 * There is also a second parameter to the chart constructor where a
7141 * callback function can be passed to be executed on chart.load.
7142 *
7143 * @type {Function}
7144 * @context Chart
7145 * @sample {highcharts} highcharts/chart/events-load/
7146 * Alert on chart load
7147 * @sample {highstock} stock/chart/events-load/
7148 * Alert on chart load
7149 * @sample {highmaps} maps/chart/events-load/
7150 * Add series on chart load
7151 * @apioption chart.events.load
7152 */
7153
7154 /**
7155 * Fires when the chart is redrawn, either after a call to chart.redraw()
7156 * or after an axis, series or point is modified with the `redraw` option
7157 * set to true. One parameter, `event`, is passed to the function, containing common event information.
7158 *
7159 * @type {Function}
7160 * @context Chart
7161 * @sample {highcharts} highcharts/chart/events-redraw/
7162 * Alert on chart redraw
7163 * @sample {highstock} stock/chart/events-redraw/
7164 * Alert on chart redraw when adding a series or moving the
7165 * zoomed range
7166 * @sample {highmaps} maps/chart/events-redraw/
7167 * Set subtitle on chart redraw
7168 * @since 1.2.0
7169 * @apioption chart.events.redraw
7170 */
7171
7172 /**
7173 * Fires after initial load of the chart (directly after the `load`
7174 * event), and after each redraw (directly after the `redraw` event).
7175 *
7176 * @type {Function}
7177 * @context Chart
7178 * @since 5.0.7
7179 * @apioption chart.events.render
7180 */
7181
7182 /**
7183 * Fires when an area of the chart has been selected. Selection is enabled
7184 * by setting the chart's zoomType. One parameter, `event`, is passed
7185 * to the function, containing common event information. The default action for the selection event is to
7186 * zoom the chart to the selected area. It can be prevented by calling
7187 * `event.preventDefault()`.
7188 *
7189 * Information on the selected area can be found through `event.xAxis`
7190 * and `event.yAxis`, which are arrays containing the axes of each dimension
7191 * and each axis' min and max values. The primary axes are `event.xAxis[0]`
7192 * and `event.yAxis[0]`. Remember the unit of a datetime axis is milliseconds
7193 * since 1970-01-01 00:00:00.
7194 *
7195 * <pre>selection: function(event) {
7196 * // log the min and max of the primary, datetime x-axis
7197 * console.log(
7198 * Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', event.xAxis[0].min),
7199 * Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', event.xAxis[0].max)
7200 * );
7201 * // log the min and max of the y axis
7202 * console.log(event.yAxis[0].min, event.yAxis[0].max);
7203 * }</pre>
7204 *
7205 * @type {Function}
7206 * @sample {highcharts} highcharts/chart/events-selection/
7207 * Report on selection and reset
7208 * @sample {highcharts} highcharts/chart/events-selection-points/
7209 * Select a range of points through a drag selection
7210 * @sample {highstock} stock/chart/events-selection/
7211 * Report on selection and reset
7212 * @sample {highstock} highcharts/chart/events-selection-points/
7213 * Select a range of points through a drag selection (Highcharts)
7214 * @apioption chart.events.selection
7215 */
7216
7217 /**
7218 * The margin between the outer edge of the chart and the plot area.
7219 * The numbers in the array designate top, right, bottom and left
7220 * respectively. Use the options `marginTop`, `marginRight`,
7221 * `marginBottom` and `marginLeft` for shorthand setting of one option.
7222 *
7223 * By default there is no margin. The actual space is dynamically calculated
7224 * from the offset of axis labels, axis title, title, subtitle and legend
7225 * in addition to the `spacingTop`, `spacingRight`, `spacingBottom`
7226 * and `spacingLeft` options.
7227 *
7228 * @type {Array}
7229 * @sample {highcharts} highcharts/chart/margins-zero/
7230 * Zero margins
7231 * @sample {highstock} stock/chart/margin-zero/
7232 * Zero margins
7233 *
7234 * @defaults {all} null
7235 * @apioption chart.margin
7236 */
7237
7238 /**
7239 * The margin between the bottom outer edge of the chart and the plot
7240 * area. Use this to set a fixed pixel value for the margin as opposed
7241 * to the default dynamic margin. See also `spacingBottom`.
7242 *
7243 * @type {Number}
7244 * @sample {highcharts} highcharts/chart/marginbottom/
7245 * 100px bottom margin
7246 * @sample {highstock} stock/chart/marginbottom/
7247 * 100px bottom margin
7248 * @sample {highmaps} maps/chart/margin/
7249 * 100px margins
7250 * @since 2.0
7251 * @apioption chart.marginBottom
7252 */
7253
7254 /**
7255 * The margin between the left outer edge of the chart and the plot
7256 * area. Use this to set a fixed pixel value for the margin as opposed
7257 * to the default dynamic margin. See also `spacingLeft`.
7258 *
7259 * @type {Number}
7260 * @sample {highcharts} highcharts/chart/marginleft/
7261 * 150px left margin
7262 * @sample {highstock} stock/chart/marginleft/
7263 * 150px left margin
7264 * @sample {highmaps} maps/chart/margin/
7265 * 100px margins
7266 * @default null
7267 * @since 2.0
7268 * @apioption chart.marginLeft
7269 */
7270
7271 /**
7272 * The margin between the right outer edge of the chart and the plot
7273 * area. Use this to set a fixed pixel value for the margin as opposed
7274 * to the default dynamic margin. See also `spacingRight`.
7275 *
7276 * @type {Number}
7277 * @sample {highcharts} highcharts/chart/marginright/
7278 * 100px right margin
7279 * @sample {highstock} stock/chart/marginright/
7280 * 100px right margin
7281 * @sample {highmaps} maps/chart/margin/
7282 * 100px margins
7283 * @default null
7284 * @since 2.0
7285 * @apioption chart.marginRight
7286 */
7287
7288 /**
7289 * The margin between the top outer edge of the chart and the plot area.
7290 * Use this to set a fixed pixel value for the margin as opposed to
7291 * the default dynamic margin. See also `spacingTop`.
7292 *
7293 * @type {Number}
7294 * @sample {highcharts} highcharts/chart/margintop/ 100px top margin
7295 * @sample {highstock} stock/chart/margintop/
7296 * 100px top margin
7297 * @sample {highmaps} maps/chart/margin/
7298 * 100px margins
7299 * @default null
7300 * @since 2.0
7301 * @apioption chart.marginTop
7302 */
7303
7304 /**
7305 * Allows setting a key to switch between zooming and panning. Can be
7306 * one of `alt`, `ctrl`, `meta` (the command key on Mac and Windows
7307 * key on Windows) or `shift`. The keys are mapped directly to the key
7308 * properties of the click event argument (`event.altKey`, `event.ctrlKey`,
7309 * `event.metaKey` and `event.shiftKey`).
7310 *
7311 * @validvalue [null, "alt", "ctrl", "meta", "shift"]
7312 * @type {String}
7313 * @since 4.0.3
7314 * @product highcharts
7315 * @apioption chart.panKey
7316 */
7317
7318 /**
7319 * Allow panning in a chart. Best used with [panKey](#chart.panKey)
7320 * to combine zooming and panning.
7321 *
7322 * On touch devices, when the [tooltip.followTouchMove](#tooltip.followTouchMove)
7323 * option is `true` (default), panning requires two fingers. To allow
7324 * panning with one finger, set `followTouchMove` to `false`.
7325 *
7326 * @type {Boolean}
7327 * @sample {highcharts} highcharts/chart/pankey/ Zooming and panning
7328 * @default {highcharts} false
7329 * @default {highstock} true
7330 * @since 4.0.3
7331 * @product highcharts highstock
7332 * @apioption chart.panning
7333 */
7334
7335
7336 /**
7337 * Equivalent to [zoomType](#chart.zoomType), but for multitouch gestures
7338 * only. By default, the `pinchType` is the same as the `zoomType` setting.
7339 * However, pinching can be enabled separately in some cases, for example
7340 * in stock charts where a mouse drag pans the chart, while pinching
7341 * is enabled. When [tooltip.followTouchMove](#tooltip.followTouchMove)
7342 * is true, pinchType only applies to two-finger touches.
7343 *
7344 * @validvalue ["x", "y", "xy"]
7345 * @type {String}
7346 * @default {highcharts} null
7347 * @default {highstock} x
7348 * @since 3.0
7349 * @product highcharts highstock
7350 * @apioption chart.pinchType
7351 */
7352
7353 /**
7354 * The corner radius of the outer chart border.
7355 *
7356 * @type {Number}
7357 * @sample {highcharts} highcharts/chart/borderradius/ 20px radius
7358 * @sample {highstock} stock/chart/border/ 10px radius
7359 * @sample {highmaps} maps/chart/border/ Border options
7360 * @default 0
7361 */
7362 borderRadius: 0,
7363
7364
7365 /**
7366 * In styled mode, this sets how many colors the class names
7367 * should rotate between. With ten colors, series (or points) are
7368 * given class names like `highcharts-color-0`, `highcharts-color-
7369 * 0` [...] `highcharts-color-9`. The equivalent in non-styled mode
7370 * is to set colors using the [colors](#colors) setting.
7371 *
7372 * @type {Number}
7373 * @default 10
7374 * @since 5.0.0
7375 */
7376 colorCount: 10,
7377
7378
7379 /**
7380 * Alias of `type`.
7381 *
7382 * @validvalue ["line", "spline", "column", "area", "areaspline", "pie"]
7383 * @type {String}
7384 * @deprecated
7385 * @sample {highcharts} highcharts/chart/defaultseriestype/ Bar
7386 * @default line
7387 * @product highcharts
7388 */
7389 defaultSeriesType: 'line',
7390
7391 /**
7392 * If true, the axes will scale to the remaining visible series once
7393 * one series is hidden. If false, hiding and showing a series will
7394 * not affect the axes or the other series. For stacks, once one series
7395 * within the stack is hidden, the rest of the stack will close in
7396 * around it even if the axis is not affected.
7397 *
7398 * @type {Boolean}
7399 * @sample {highcharts} highcharts/chart/ignorehiddenseries-true/
7400 * True by default
7401 * @sample {highcharts} highcharts/chart/ignorehiddenseries-false/
7402 * False
7403 * @sample {highcharts} highcharts/chart/ignorehiddenseries-true-stacked/
7404 * True with stack
7405 * @sample {highstock} stock/chart/ignorehiddenseries-true/
7406 * True by default
7407 * @sample {highstock} stock/chart/ignorehiddenseries-false/
7408 * False
7409 * @default true
7410 * @since 1.2.0
7411 * @product highcharts highstock
7412 */
7413 ignoreHiddenSeries: true,
7414
7415
7416 /**
7417 * Whether to invert the axes so that the x axis is vertical and y axis
7418 * is horizontal. When `true`, the x axis is [reversed](#xAxis.reversed)
7419 * by default.
7420 *
7421 * @productdesc {highcharts}
7422 * If a bar series is present in the chart, it will be inverted
7423 * automatically. Inverting the chart doesn't have an effect if there
7424 * are no cartesian series in the chart, or if the chart is
7425 * [polar](#chart.polar).
7426 *
7427 * @type {Boolean}
7428 * @sample {highcharts} highcharts/chart/inverted/
7429 * Inverted line
7430 * @sample {highstock} stock/navigator/inverted/
7431 * Inverted stock chart
7432 * @default false
7433 * @product highcharts highstock
7434 * @apioption chart.inverted
7435 */
7436
7437 /**
7438 * The distance between the outer edge of the chart and the content,
7439 * like title or legend, or axis title and labels if present. The
7440 * numbers in the array designate top, right, bottom and left respectively.
7441 * Use the options spacingTop, spacingRight, spacingBottom and spacingLeft
7442 * options for shorthand setting of one option.
7443 *
7444 * @type {Array<Number>}
7445 * @see [chart.margin](#chart.margin)
7446 * @default [10, 10, 15, 10]
7447 * @since 3.0.6
7448 */
7449 spacing: [10, 10, 15, 10],
7450
7451 /**
7452 * The button that appears after a selection zoom, allowing the user
7453 * to reset zoom.
7454 *
7455 */
7456 resetZoomButton: {
7457
7458 /**
7459 * A collection of attributes for the button. The object takes SVG
7460 * attributes like `fill`, `stroke`, `stroke-width` or `r`, the border
7461 * radius. The theme also supports `style`, a collection of CSS properties
7462 * for the text. Equivalent attributes for the hover state are given
7463 * in `theme.states.hover`.
7464 *
7465 * @type {Object}
7466 * @sample {highcharts} highcharts/chart/resetzoombutton-theme/
7467 * Theming the button
7468 * @sample {highstock} highcharts/chart/resetzoombutton-theme/
7469 * Theming the button
7470 * @since 2.2
7471 */
7472 theme: {
7473
7474 /**
7475 * The Z index for the reset zoom button.
7476 */
7477 zIndex: 20
7478 },
7479
7480 /**
7481 * The position of the button.
7482 *
7483 * @type {Object}
7484 * @sample {highcharts} highcharts/chart/resetzoombutton-position/
7485 * Above the plot area
7486 * @sample {highstock} highcharts/chart/resetzoombutton-position/
7487 * Above the plot area
7488 * @sample {highmaps} highcharts/chart/resetzoombutton-position/
7489 * Above the plot area
7490 * @since 2.2
7491 */
7492 position: {
7493
7494 /**
7495 * The horizontal alignment of the button.
7496 *
7497 * @type {String}
7498 */
7499 align: 'right',
7500
7501 /**
7502 * The horizontal offset of the button.
7503 *
7504 * @type {Number}
7505 */
7506 x: -10,
7507
7508 /**
7509 * The vertical alignment of the button.
7510 *
7511 * @validvalue ["top", "middle", "bottom"]
7512 * @type {String}
7513 * @default top
7514 * @apioption chart.resetZoomButton.position.verticalAlign
7515 */
7516
7517 /**
7518 * The vertical offset of the button.
7519 *
7520 * @type {Number}
7521 */
7522 y: 10
7523 }
7524
7525 /**
7526 * What frame the button should be placed related to. Can be either
7527 * `plot` or `chart`
7528 *
7529 * @validvalue ["plot", "chart"]
7530 * @type {String}
7531 * @sample {highcharts} highcharts/chart/resetzoombutton-relativeto/
7532 * Relative to the chart
7533 * @sample {highstock} highcharts/chart/resetzoombutton-relativeto/
7534 * Relative to the chart
7535 * @default plot
7536 * @since 2.2
7537 * @apioption chart.resetZoomButton.relativeTo
7538 */
7539 },
7540
7541 /**
7542 * An explicit width for the chart. By default (when `null`) the width
7543 * is calculated from the offset width of the containing element.
7544 *
7545 * @type {Number}
7546 * @sample {highcharts} highcharts/chart/width/ 800px wide
7547 * @sample {highstock} stock/chart/width/ 800px wide
7548 * @sample {highmaps} maps/chart/size/ Chart with explicit size
7549 * @default null
7550 */
7551 width: null,
7552
7553 /**
7554 * An explicit height for the chart. If a _number_, the height is
7555 * given in pixels. If given a _percentage string_ (for example `'56%'`),
7556 * the height is given as the percentage of the actual chart width.
7557 * This allows for preserving the aspect ratio across responsive
7558 * sizes.
7559 *
7560 * By default (when `null`) the height is calculated from the offset
7561 * height of the containing element, or 400 pixels if the containing
7562 * element's height is 0.
7563 *
7564 * @type {Number|String}
7565 * @sample {highcharts} highcharts/chart/height/
7566 * 500px height
7567 * @sample {highstock} stock/chart/height/
7568 * 300px height
7569 * @sample {highmaps} maps/chart/size/
7570 * Chart with explicit size
7571 * @sample highcharts/chart/height-percent/
7572 * Highcharts with percentage height
7573 * @default null
7574 */
7575 height: null,
7576
7577
7578
7579
7580
7581 /**
7582 * The HTML element where the chart will be rendered. If it is a string,
7583 * the element by that id is used. The HTML element can also be passed
7584 * by direct reference, or as the first argument of the chart constructor,
7585 * in which case the option is not needed.
7586 *
7587 * @type {String|Object}
7588 * @sample {highcharts} highcharts/chart/reflow-true/
7589 * String
7590 * @sample {highcharts} highcharts/chart/renderto-object/
7591 * Object reference
7592 * @sample {highcharts} highcharts/chart/renderto-jquery/
7593 * Object reference through jQuery
7594 * @sample {highstock} stock/chart/renderto-string/
7595 * String
7596 * @sample {highstock} stock/chart/renderto-object/
7597 * Object reference
7598 * @sample {highstock} stock/chart/renderto-jquery/
7599 * Object reference through jQuery
7600 * @apioption chart.renderTo
7601 */
7602
7603 /**
7604 * The background color of the marker square when selecting (zooming
7605 * in on) an area of the chart.
7606 *
7607 * @type {Color}
7608 * @see In styled mode, the selection marker fill is set with the
7609 * `.highcharts-selection-marker` class.
7610 * @default rgba(51,92,173,0.25)
7611 * @since 2.1.7
7612 * @apioption chart.selectionMarkerFill
7613 */
7614
7615 /**
7616 * Whether to apply a drop shadow to the outer chart area. Requires
7617 * that backgroundColor be set. The shadow can be an object configuration
7618 * containing `color`, `offsetX`, `offsetY`, `opacity` and `width`.
7619 *
7620 * @type {Boolean|Object}
7621 * @sample {highcharts} highcharts/chart/shadow/ Shadow
7622 * @sample {highstock} stock/chart/shadow/
7623 * Shadow
7624 * @sample {highmaps} maps/chart/border/
7625 * Chart border and shadow
7626 * @default false
7627 * @apioption chart.shadow
7628 */
7629
7630 /**
7631 * Whether to show the axes initially. This only applies to empty charts
7632 * where series are added dynamically, as axes are automatically added
7633 * to cartesian series.
7634 *
7635 * @type {Boolean}
7636 * @sample {highcharts} highcharts/chart/showaxes-false/ False by default
7637 * @sample {highcharts} highcharts/chart/showaxes-true/ True
7638 * @since 1.2.5
7639 * @product highcharts
7640 * @apioption chart.showAxes
7641 */
7642
7643 /**
7644 * The space between the bottom edge of the chart and the content (plot
7645 * area, axis title and labels, title, subtitle or legend in top position).
7646 *
7647 * @type {Number}
7648 * @sample {highcharts} highcharts/chart/spacingbottom/
7649 * Spacing bottom set to 100
7650 * @sample {highstock} stock/chart/spacingbottom/
7651 * Spacing bottom set to 100
7652 * @sample {highmaps} maps/chart/spacing/
7653 * Spacing 100 all around
7654 * @default 15
7655 * @since 2.1
7656 * @apioption chart.spacingBottom
7657 */
7658
7659 /**
7660 * The space between the left edge of the chart and the content (plot
7661 * area, axis title and labels, title, subtitle or legend in top position).
7662 *
7663 * @type {Number}
7664 * @sample {highcharts} highcharts/chart/spacingleft/
7665 * Spacing left set to 100
7666 * @sample {highstock} stock/chart/spacingleft/
7667 * Spacing left set to 100
7668 * @sample {highmaps} maps/chart/spacing/
7669 * Spacing 100 all around
7670 * @default 10
7671 * @since 2.1
7672 * @apioption chart.spacingLeft
7673 */
7674
7675 /**
7676 * The space between the right edge of the chart and the content (plot
7677 * area, axis title and labels, title, subtitle or legend in top
7678 * position).
7679 *
7680 * @type {Number}
7681 * @sample {highcharts} highcharts/chart/spacingright-100/
7682 * Spacing set to 100
7683 * @sample {highcharts} highcharts/chart/spacingright-legend/
7684 * Legend in right position with default spacing
7685 * @sample {highstock} stock/chart/spacingright/
7686 * Spacing set to 100
7687 * @sample {highmaps} maps/chart/spacing/
7688 * Spacing 100 all around
7689 * @default 10
7690 * @since 2.1
7691 * @apioption chart.spacingRight
7692 */
7693
7694 /**
7695 * The space between the top edge of the chart and the content (plot
7696 * area, axis title and labels, title, subtitle or legend in top
7697 * position).
7698 *
7699 * @type {Number}
7700 * @sample {highcharts} highcharts/chart/spacingtop-100/
7701 * A top spacing of 100
7702 * @sample {highcharts} highcharts/chart/spacingtop-10/
7703 * Floating chart title makes the plot area align to the default
7704 * spacingTop of 10.
7705 * @sample {highstock} stock/chart/spacingtop/
7706 * A top spacing of 100
7707 * @sample {highmaps} maps/chart/spacing/
7708 * Spacing 100 all around
7709 * @default 10
7710 * @since 2.1
7711 * @apioption chart.spacingTop
7712 */
7713
7714 /**
7715 * Additional CSS styles to apply inline to the container `div`. Note
7716 * that since the default font styles are applied in the renderer, it
7717 * is ignorant of the individual chart options and must be set globally.
7718 *
7719 * @type {CSSObject}
7720 * @see In styled mode, general chart styles can be set with the `.highcharts-root` class.
7721 * @sample {highcharts} highcharts/chart/style-serif-font/
7722 * Using a serif type font
7723 * @sample {highcharts} highcharts/css/em/
7724 * Styled mode with relative font sizes
7725 * @sample {highstock} stock/chart/style/
7726 * Using a serif type font
7727 * @sample {highmaps} maps/chart/style-serif-font/
7728 * Using a serif type font
7729 * @default {"fontFamily":"\"Lucida Grande\", \"Lucida Sans Unicode\", Verdana, Arial, Helvetica, sans-serif","fontSize":"12px"}
7730 * @apioption chart.style
7731 */
7732
7733 /**
7734 * The default series type for the chart. Can be any of the chart types
7735 * listed under [plotOptions](#plotOptions).
7736 *
7737 * @validvalue ["line", "spline", "column", "bar", "area", "areaspline", "pie", "arearange", "areasplinerange", "boxplot", "bubble", "columnrange", "errorbar", "funnel", "gauge", "heatmap", "polygon", "pyramid", "scatter", "solidgauge", "treemap", "waterfall"]
7738 * @type {String}
7739 * @sample {highcharts} highcharts/chart/type-bar/ Bar
7740 * @sample {highstock} stock/chart/type/
7741 * Areaspline
7742 * @sample {highmaps} maps/chart/type-mapline/
7743 * Mapline
7744 * @default {highcharts} line
7745 * @default {highstock} line
7746 * @default {highmaps} map
7747 * @since 2.1.0
7748 * @apioption chart.type
7749 */
7750
7751 /**
7752 * Decides in what dimensions the user can zoom by dragging the mouse.
7753 * Can be one of `x`, `y` or `xy`.
7754 *
7755 * @validvalue [null, "x", "y", "xy"]
7756 * @type {String}
7757 * @see [panKey](#chart.panKey)
7758 * @sample {highcharts} highcharts/chart/zoomtype-none/ None by default
7759 * @sample {highcharts} highcharts/chart/zoomtype-x/ X
7760 * @sample {highcharts} highcharts/chart/zoomtype-y/ Y
7761 * @sample {highcharts} highcharts/chart/zoomtype-xy/ Xy
7762 * @sample {highstock} stock/demo/basic-line/ None by default
7763 * @sample {highstock} stock/chart/zoomtype-x/ X
7764 * @sample {highstock} stock/chart/zoomtype-y/ Y
7765 * @sample {highstock} stock/chart/zoomtype-xy/ Xy
7766 * @product highcharts highstock
7767 * @apioption chart.zoomType
7768 */
7769 },
7770
7771 /**
7772 * The chart's main title.
7773 *
7774 * @sample {highmaps} maps/title/title/ Title options demonstrated
7775 */
7776 title: {
7777
7778 /**
7779 * The title of the chart. To disable the title, set the `text` to
7780 * `null`.
7781 *
7782 * @type {String}
7783 * @sample {highcharts} highcharts/title/text/ Custom title
7784 * @sample {highstock} stock/chart/title-text/ Custom title
7785 * @default {highcharts|highmaps} Chart title
7786 * @default {highstock} null
7787 */
7788 text: 'Chart title',
7789
7790 /**
7791 * The horizontal alignment of the title. Can be one of "left", "center"
7792 * and "right".
7793 *
7794 * @validvalue ["left", "center", "right"]
7795 * @type {String}
7796 * @sample {highcharts} highcharts/title/align/ Aligned to the plot area (x = 70px = margin left - spacing left)
7797 * @sample {highstock} stock/chart/title-align/ Aligned to the plot area (x = 50px = margin left - spacing left)
7798 * @default center
7799 * @since 2.0
7800 */
7801 align: 'center',
7802
7803 /**
7804 * The margin between the title and the plot area, or if a subtitle
7805 * is present, the margin between the subtitle and the plot area.
7806 *
7807 * @type {Number}
7808 * @sample {highcharts} highcharts/title/margin-50/ A chart title margin of 50
7809 * @sample {highcharts} highcharts/title/margin-subtitle/ The same margin applied with a subtitle
7810 * @sample {highstock} stock/chart/title-margin/ A chart title margin of 50
7811 * @default 15
7812 * @since 2.1
7813 */
7814 margin: 15,
7815
7816 /**
7817 * Adjustment made to the title width, normally to reserve space for
7818 * the exporting burger menu.
7819 *
7820 * @type {Number}
7821 * @sample {highcharts} highcharts/title/widthadjust/ Wider menu, greater padding
7822 * @sample {highstock} highcharts/title/widthadjust/ Wider menu, greater padding
7823 * @sample {highmaps} highcharts/title/widthadjust/ Wider menu, greater padding
7824 * @default -44
7825 * @since 4.2.5
7826 */
7827 widthAdjust: -44
7828
7829 /**
7830 * When the title is floating, the plot area will not move to make space
7831 * for it.
7832 *
7833 * @type {Boolean}
7834 * @sample {highcharts} highcharts/chart/zoomtype-none/ False by default
7835 * @sample {highcharts} highcharts/title/floating/
7836 * True - title on top of the plot area
7837 * @sample {highstock} stock/chart/title-floating/
7838 * True - title on top of the plot area
7839 * @default false
7840 * @since 2.1
7841 * @apioption title.floating
7842 */
7843
7844 /**
7845 * CSS styles for the title. Use this for font styling, but use `align`,
7846 * `x` and `y` for text alignment.
7847 *
7848 * In styled mode, the title style is given in the `.highcharts-title` class.
7849 *
7850 * @type {CSSObject}
7851 * @sample {highcharts} highcharts/title/style/ Custom color and weight
7852 * @sample {highstock} stock/chart/title-style/ Custom color and weight
7853 * @sample highcharts/css/titles/ Styled mode
7854 * @default {highcharts|highmaps} { "color": "#333333", "fontSize": "18px" }
7855 * @default {highstock} { "color": "#333333", "fontSize": "16px" }
7856 * @apioption title.style
7857 */
7858
7859 /**
7860 * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
7861 * and-string-formatting#html) to render the text.
7862 *
7863 * @type {Boolean}
7864 * @default false
7865 * @apioption title.useHTML
7866 */
7867
7868 /**
7869 * The vertical alignment of the title. Can be one of `"top"`, `"middle"`
7870 * and `"bottom"`. When a value is given, the title behaves as if [floating](#title.
7871 * floating) were `true`.
7872 *
7873 * @validvalue ["top", "middle", "bottom"]
7874 * @type {String}
7875 * @sample {highcharts} highcharts/title/verticalalign/
7876 * Chart title in bottom right corner
7877 * @sample {highstock} stock/chart/title-verticalalign/
7878 * Chart title in bottom right corner
7879 * @since 2.1
7880 * @apioption title.verticalAlign
7881 */
7882
7883 /**
7884 * The x position of the title relative to the alignment within chart.
7885 * spacingLeft and chart.spacingRight.
7886 *
7887 * @type {Number}
7888 * @sample {highcharts} highcharts/title/align/
7889 * Aligned to the plot area (x = 70px = margin left - spacing left)
7890 * @sample {highstock} stock/chart/title-align/
7891 * Aligned to the plot area (x = 50px = margin left - spacing left)
7892 * @default 0
7893 * @since 2.0
7894 * @apioption title.x
7895 */
7896
7897 /**
7898 * The y position of the title relative to the alignment within [chart.
7899 * spacingTop](#chart.spacingTop) and [chart.spacingBottom](#chart.spacingBottom).
7900 * By default it depends on the font size.
7901 *
7902 * @type {Number}
7903 * @sample {highcharts} highcharts/title/y/
7904 * Title inside the plot area
7905 * @sample {highstock} stock/chart/title-verticalalign/
7906 * Chart title in bottom right corner
7907 * @since 2.0
7908 * @apioption title.y
7909 */
7910
7911 },
7912
7913 /**
7914 * The chart's subtitle. This can be used both to display a subtitle below
7915 * the main title, and to display random text anywhere in the chart. The
7916 * subtitle can be updated after chart initialization through the
7917 * `Chart.setTitle` method.
7918 *
7919 * @sample {highmaps} maps/title/subtitle/ Subtitle options demonstrated
7920 */
7921 subtitle: {
7922
7923 /**
7924 * The subtitle of the chart.
7925 *
7926 * @type {String}
7927 * @sample {highcharts} highcharts/subtitle/text/ Custom subtitle
7928 * @sample {highcharts} highcharts/subtitle/text-formatted/ Formatted and linked text.
7929 * @sample {highstock} stock/chart/subtitle-text Custom subtitle
7930 * @sample {highstock} stock/chart/subtitle-text-formatted Formatted and linked text.
7931 */
7932 text: '',
7933
7934 /**
7935 * The horizontal alignment of the subtitle. Can be one of "left",
7936 * "center" and "right".
7937 *
7938 * @validvalue ["left", "center", "right"]
7939 * @type {String}
7940 * @sample {highcharts} highcharts/subtitle/align/ Footnote at right of plot area
7941 * @sample {highstock} stock/chart/subtitle-footnote Footnote at bottom right of plot area
7942 * @default center
7943 * @since 2.0
7944 */
7945 align: 'center',
7946
7947 /**
7948 * Adjustment made to the subtitle width, normally to reserve space
7949 * for the exporting burger menu.
7950 *
7951 * @type {Number}
7952 * @see [title.widthAdjust](#title.widthAdjust)
7953 * @sample {highcharts} highcharts/title/widthadjust/ Wider menu, greater padding
7954 * @sample {highstock} highcharts/title/widthadjust/ Wider menu, greater padding
7955 * @sample {highmaps} highcharts/title/widthadjust/ Wider menu, greater padding
7956 * @default -44
7957 * @since 4.2.5
7958 */
7959 widthAdjust: -44
7960
7961 /**
7962 * When the subtitle is floating, the plot area will not move to make
7963 * space for it.
7964 *
7965 * @type {Boolean}
7966 * @sample {highcharts} highcharts/subtitle/floating/
7967 * Floating title and subtitle
7968 * @sample {highstock} stock/chart/subtitle-footnote
7969 * Footnote floating at bottom right of plot area
7970 * @default false
7971 * @since 2.1
7972 * @apioption subtitle.floating
7973 */
7974
7975 /**
7976 * CSS styles for the title.
7977 *
7978 * In styled mode, the subtitle style is given in the `.highcharts-subtitle` class.
7979 *
7980 * @type {CSSObject}
7981 * @sample {highcharts} highcharts/subtitle/style/
7982 * Custom color and weight
7983 * @sample {highcharts} highcharts/css/titles/
7984 * Styled mode
7985 * @sample {highstock} stock/chart/subtitle-style
7986 * Custom color and weight
7987 * @sample {highstock} highcharts/css/titles/
7988 * Styled mode
7989 * @sample {highmaps} highcharts/css/titles/
7990 * Styled mode
7991 * @default { "color": "#666666" }
7992 * @apioption subtitle.style
7993 */
7994
7995 /**
7996 * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
7997 * and-string-formatting#html) to render the text.
7998 *
7999 * @type {Boolean}
8000 * @default false
8001 * @apioption subtitle.useHTML
8002 */
8003
8004 /**
8005 * The vertical alignment of the title. Can be one of "top", "middle"
8006 * and "bottom". When a value is given, the title behaves as floating.
8007 *
8008 * @validvalue ["top", "middle", "bottom"]
8009 * @type {String}
8010 * @sample {highcharts} highcharts/subtitle/verticalalign/
8011 * Footnote at the bottom right of plot area
8012 * @sample {highstock} stock/chart/subtitle-footnote
8013 * Footnote at the bottom right of plot area
8014 * @default
8015 * @since 2.1
8016 * @apioption subtitle.verticalAlign
8017 */
8018
8019 /**
8020 * The x position of the subtitle relative to the alignment within chart.
8021 * spacingLeft and chart.spacingRight.
8022 *
8023 * @type {Number}
8024 * @sample {highcharts} highcharts/subtitle/align/
8025 * Footnote at right of plot area
8026 * @sample {highstock} stock/chart/subtitle-footnote
8027 * Footnote at the bottom right of plot area
8028 * @default 0
8029 * @since 2.0
8030 * @apioption subtitle.x
8031 */
8032
8033 /**
8034 * The y position of the subtitle relative to the alignment within chart.
8035 * spacingTop and chart.spacingBottom. By default the subtitle is laid
8036 * out below the title unless the title is floating.
8037 *
8038 * @type {Number}
8039 * @sample {highcharts} highcharts/subtitle/verticalalign/
8040 * Footnote at the bottom right of plot area
8041 * @sample {highstock} stock/chart/subtitle-footnote
8042 * Footnote at the bottom right of plot area
8043 * @default {highcharts} null
8044 * @default {highstock} null
8045 * @default {highmaps}
8046 * @since 2.0
8047 * @apioption subtitle.y
8048 */
8049 },
8050
8051 /**
8052 * The plotOptions is a wrapper object for config objects for each series
8053 * type. The config objects for each series can also be overridden for
8054 * each series item as given in the series array.
8055 *
8056 * Configuration options for the series are given in three levels. Options
8057 * for all series in a chart are given in the [plotOptions.series](#plotOptions.
8058 * series) object. Then options for all series of a specific type are
8059 * given in the plotOptions of that type, for example plotOptions.line.
8060 * Next, options for one single series are given in [the series array](#series).
8061 *
8062 */
8063 plotOptions: {},
8064
8065 /**
8066 * HTML labels that can be positioned anywhere in the chart area.
8067 *
8068 */
8069 labels: {
8070
8071 /**
8072 * A HTML label that can be positioned anywhere in the chart area.
8073 *
8074 * @type {Array<Object>}
8075 * @apioption labels.items
8076 */
8077
8078 /**
8079 * Inner HTML or text for the label.
8080 *
8081 * @type {String}
8082 * @apioption labels.items.html
8083 */
8084
8085 /**
8086 * CSS styles for each label. To position the label, use left and top
8087 * like this:
8088 *
8089 * <pre>style: {
8090 * left: '100px',
8091 * top: '100px'
8092 * }</pre>
8093 *
8094 * @type {CSSObject}
8095 * @apioption labels.items.style
8096 */
8097
8098 /**
8099 * Shared CSS styles for all labels.
8100 *
8101 * @type {CSSObject}
8102 * @default { "color": "#333333" }
8103 */
8104 style: {
8105 position: 'absolute',
8106 color: '#333333'
8107 }
8108 },
8109
8110 /**
8111 * The legend is a box containing a symbol and name for each series
8112 * item or point item in the chart. Each series (or points in case
8113 * of pie charts) is represented by a symbol and its name in the legend.
8114 *
8115 * It is possible to override the symbol creator function and
8116 * create [custom legend symbols](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/studies/legend-
8117 * custom-symbol/).
8118 *
8119 * @productdesc {highmaps}
8120 * A Highmaps legend by default contains one legend item per series, but if
8121 * a `colorAxis` is defined, the axis will be displayed in the legend.
8122 * Either as a gradient, or as multiple legend items for `dataClasses`.
8123 */
8124 legend: {
8125
8126 /**
8127 * The background color of the legend.
8128 *
8129 * @type {Color}
8130 * @see In styled mode, the legend background fill can be applied with
8131 * the `.highcharts-legend-box` class.
8132 * @sample {highcharts} highcharts/legend/backgroundcolor/ Yellowish background
8133 * @sample {highstock} stock/legend/align/ Various legend options
8134 * @sample {highmaps} maps/legend/border-background/ Border and background options
8135 * @apioption legend.backgroundColor
8136 */
8137
8138 /**
8139 * The width of the drawn border around the legend.
8140 *
8141 * @type {Number}
8142 * @see In styled mode, the legend border stroke width can be applied
8143 * with the `.highcharts-legend-box` class.
8144 * @sample {highcharts} highcharts/legend/borderwidth/ 2px border width
8145 * @sample {highstock} stock/legend/align/ Various legend options
8146 * @sample {highmaps} maps/legend/border-background/ Border and background options
8147 * @default 0
8148 * @apioption legend.borderWidth
8149 */
8150
8151 /**
8152 * Enable or disable the legend.
8153 *
8154 * @type {Boolean}
8155 * @sample {highcharts} highcharts/legend/enabled-false/ Legend disabled
8156 * @sample {highstock} stock/legend/align/ Various legend options
8157 * @sample {highmaps} maps/legend/enabled-false/ Legend disabled
8158 * @default {highstock} false
8159 * @default {highmaps} true
8160 */
8161 enabled: true,
8162
8163 /**
8164 * The horizontal alignment of the legend box within the chart area.
8165 * Valid values are `left`, `center` and `right`.
8166 *
8167 * In the case that the legend is aligned in a corner position, the
8168 * `layout` option will determine whether to place it above/below
8169 * or on the side of the plot area.
8170 *
8171 * @validvalue ["left", "center", "right"]
8172 * @type {String}
8173 * @sample {highcharts} highcharts/legend/align/
8174 * Legend at the right of the chart
8175 * @sample {highstock} stock/legend/align/
8176 * Various legend options
8177 * @sample {highmaps} maps/legend/alignment/
8178 * Legend alignment
8179 * @since 2.0
8180 */
8181 align: 'center',
8182
8183 /**
8184 * When the legend is floating, the plot area ignores it and is allowed
8185 * to be placed below it.
8186 *
8187 * @type {Boolean}
8188 * @sample {highcharts} highcharts/legend/floating-false/ False by default
8189 * @sample {highcharts} highcharts/legend/floating-true/ True
8190 * @sample {highmaps} maps/legend/alignment/ Floating legend
8191 * @default false
8192 * @since 2.1
8193 * @apioption legend.floating
8194 */
8195
8196 /**
8197 * The layout of the legend items. Can be one of "horizontal" or "vertical".
8198 *
8199 * @validvalue ["horizontal", "vertical"]
8200 * @type {String}
8201 * @sample {highcharts} highcharts/legend/layout-horizontal/ Horizontal by default
8202 * @sample {highcharts} highcharts/legend/layout-vertical/ Vertical
8203 * @sample {highstock} stock/legend/layout-horizontal/ Horizontal by default
8204 * @sample {highmaps} maps/legend/padding-itemmargin/ Vertical with data classes
8205 * @sample {highmaps} maps/legend/layout-vertical/ Vertical with color axis gradient
8206 * @default horizontal
8207 */
8208 layout: 'horizontal',
8209
8210 /**
8211 * In a legend with horizontal layout, the itemDistance defines the
8212 * pixel distance between each item.
8213 *
8214 * @type {Number}
8215 * @sample {highcharts} highcharts/legend/layout-horizontal/ 50px item distance
8216 * @sample {highstock} highcharts/legend/layout-horizontal/ 50px item distance
8217 * @default {highcharts} 20
8218 * @default {highstock} 20
8219 * @default {highmaps} 8
8220 * @since 3.0.3
8221 * @apioption legend.itemDistance
8222 */
8223
8224 /**
8225 * The pixel bottom margin for each legend item.
8226 *
8227 * @type {Number}
8228 * @sample {highcharts} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
8229 * @sample {highstock} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
8230 * @sample {highmaps} maps/legend/padding-itemmargin/ Padding and item margins demonstrated
8231 * @default 0
8232 * @since 2.2.0
8233 * @apioption legend.itemMarginBottom
8234 */
8235
8236 /**
8237 * The pixel top margin for each legend item.
8238 *
8239 * @type {Number}
8240 * @sample {highcharts} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
8241 * @sample {highstock} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
8242 * @sample {highmaps} maps/legend/padding-itemmargin/ Padding and item margins demonstrated
8243 * @default 0
8244 * @since 2.2.0
8245 * @apioption legend.itemMarginTop
8246 */
8247
8248 /**
8249 * The width for each legend item. This is useful in a horizontal layout
8250 * with many items when you want the items to align vertically. .
8251 *
8252 * @type {Number}
8253 * @sample {highcharts} highcharts/legend/itemwidth-default/ Null by default
8254 * @sample {highcharts} highcharts/legend/itemwidth-80/ 80 for aligned legend items
8255 * @default null
8256 * @since 2.0
8257 * @apioption legend.itemWidth
8258 */
8259
8260 /**
8261 * A [format string](http://www.highcharts.com/docs/chart-concepts/labels-
8262 * and-string-formatting) for each legend label. Available variables
8263 * relates to properties on the series, or the point in case of pies.
8264 *
8265 * @type {String}
8266 * @default {name}
8267 * @since 1.3
8268 * @apioption legend.labelFormat
8269 */
8270
8271 /**
8272 * Callback function to format each of the series' labels. The `this`
8273 * keyword refers to the series object, or the point object in case
8274 * of pie charts. By default the series or point name is printed.
8275 *
8276 * @productdesc {highmaps}
8277 * In Highmaps the context can also be a data class in case
8278 * of a `colorAxis`.
8279 *
8280 * @type {Function}
8281 * @sample {highcharts} highcharts/legend/labelformatter/ Add text
8282 * @sample {highmaps} maps/legend/labelformatter/ Data classes with label formatter
8283 * @context {Series|Point}
8284 */
8285 labelFormatter: function() {
8286 return this.name;
8287 },
8288
8289 /**
8290 * Line height for the legend items. Deprecated as of 2.1\. Instead,
8291 * the line height for each item can be set using itemStyle.lineHeight,
8292 * and the padding between items using itemMarginTop and itemMarginBottom.
8293 *
8294 * @type {Number}
8295 * @sample {highcharts} highcharts/legend/lineheight/ Setting padding
8296 * @default 16
8297 * @since 2.0
8298 * @product highcharts
8299 * @apioption legend.lineHeight
8300 */
8301
8302 /**
8303 * If the plot area sized is calculated automatically and the legend
8304 * is not floating, the legend margin is the space between the legend
8305 * and the axis labels or plot area.
8306 *
8307 * @type {Number}
8308 * @sample {highcharts} highcharts/legend/margin-default/ 12 pixels by default
8309 * @sample {highcharts} highcharts/legend/margin-30/ 30 pixels
8310 * @default 12
8311 * @since 2.1
8312 * @apioption legend.margin
8313 */
8314
8315 /**
8316 * Maximum pixel height for the legend. When the maximum height is extended,
8317 * navigation will show.
8318 *
8319 * @type {Number}
8320 * @default undefined
8321 * @since 2.3.0
8322 * @apioption legend.maxHeight
8323 */
8324
8325 /**
8326 * The color of the drawn border around the legend.
8327 *
8328 * @type {Color}
8329 * @see In styled mode, the legend border stroke can be applied with
8330 * the `.highcharts-legend-box` class.
8331 * @sample {highcharts} highcharts/legend/bordercolor/ Brown border
8332 * @sample {highstock} stock/legend/align/ Various legend options
8333 * @sample {highmaps} maps/legend/border-background/ Border and background options
8334 * @default #999999
8335 */
8336 borderColor: '#999999',
8337
8338 /**
8339 * The border corner radius of the legend.
8340 *
8341 * @type {Number}
8342 * @sample {highcharts} highcharts/legend/borderradius-default/ Square by default
8343 * @sample {highcharts} highcharts/legend/borderradius-round/ 5px rounded
8344 * @sample {highmaps} maps/legend/border-background/ Border and background options
8345 * @default 0
8346 */
8347 borderRadius: 0,
8348
8349 /**
8350 * Options for the paging or navigation appearing when the legend
8351 * is overflown. Navigation works well on screen, but not in static
8352 * exported images. One way of working around that is to [increase
8353 * the chart height in export](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/legend/navigation-
8354 * enabled-false/).
8355 *
8356 */
8357 navigation: {
8358
8359
8360 /**
8361 * How to animate the pages when navigating up or down. A value of `true`
8362 * applies the default navigation given in the chart.animation option.
8363 * Additional options can be given as an object containing values for
8364 * easing and duration.
8365 *
8366 * @type {Boolean|Object}
8367 * @sample {highcharts} highcharts/legend/navigation/
8368 * Legend page navigation demonstrated
8369 * @sample {highstock} highcharts/legend/navigation/
8370 * Legend page navigation demonstrated
8371 * @default true
8372 * @since 2.2.4
8373 * @apioption legend.navigation.animation
8374 */
8375
8376 /**
8377 * The pixel size of the up and down arrows in the legend paging
8378 * navigation.
8379 *
8380 * @type {Number}
8381 * @sample {highcharts} highcharts/legend/navigation/
8382 * Legend page navigation demonstrated
8383 * @sample {highstock} highcharts/legend/navigation/
8384 * Legend page navigation demonstrated
8385 * @default 12
8386 * @since 2.2.4
8387 * @apioption legend.navigation.arrowSize
8388 */
8389
8390 /**
8391 * Whether to enable the legend navigation. In most cases, disabling
8392 * the navigation results in an unwanted overflow.
8393 *
8394 * See also the [adapt chart to legend](http://www.highcharts.com/plugin-
8395 * registry/single/8/Adapt-Chart-To-Legend) plugin for a solution to
8396 * extend the chart height to make room for the legend, optionally in
8397 * exported charts only.
8398 *
8399 * @type {Boolean}
8400 * @default true
8401 * @since 4.2.4
8402 * @apioption legend.navigation.enabled
8403 */
8404
8405 /**
8406 * Text styles for the legend page navigation.
8407 *
8408 * @type {CSSObject}
8409 * @see In styled mode, the navigation items are styled with the
8410 * `.highcharts-legend-navigation` class.
8411 * @sample {highcharts} highcharts/legend/navigation/
8412 * Legend page navigation demonstrated
8413 * @sample {highstock} highcharts/legend/navigation/
8414 * Legend page navigation demonstrated
8415 * @since 2.2.4
8416 * @apioption legend.navigation.style
8417 */
8418 },
8419
8420 /**
8421 * The inner padding of the legend box.
8422 *
8423 * @type {Number}
8424 * @sample {highcharts} highcharts/legend/padding-itemmargin/
8425 * Padding and item margins demonstrated
8426 * @sample {highstock} highcharts/legend/padding-itemmargin/
8427 * Padding and item margins demonstrated
8428 * @sample {highmaps} maps/legend/padding-itemmargin/
8429 * Padding and item margins demonstrated
8430 * @default 8
8431 * @since 2.2.0
8432 * @apioption legend.padding
8433 */
8434
8435 /**
8436 * Whether to reverse the order of the legend items compared to the
8437 * order of the series or points as defined in the configuration object.
8438 *
8439 * @type {Boolean}
8440 * @see [yAxis.reversedStacks](#yAxis.reversedStacks),
8441 * [series.legendIndex](#series.legendIndex)
8442 * @sample {highcharts} highcharts/legend/reversed/
8443 * Stacked bar with reversed legend
8444 * @default false
8445 * @since 1.2.5
8446 * @apioption legend.reversed
8447 */
8448
8449 /**
8450 * Whether to show the symbol on the right side of the text rather than
8451 * the left side. This is common in Arabic and Hebraic.
8452 *
8453 * @type {Boolean}
8454 * @sample {highcharts} highcharts/legend/rtl/ Symbol to the right
8455 * @default false
8456 * @since 2.2
8457 * @apioption legend.rtl
8458 */
8459
8460 /**
8461 * CSS styles for the legend area. In the 1.x versions the position
8462 * of the legend area was determined by CSS. In 2.x, the position is
8463 * determined by properties like `align`, `verticalAlign`, `x` and `y`,
8464 * but the styles are still parsed for backwards compatibility.
8465 *
8466 * @type {CSSObject}
8467 * @deprecated
8468 * @product highcharts highstock
8469 * @apioption legend.style
8470 */
8471
8472
8473
8474 /**
8475 * Default styling for the checkbox next to a legend item when
8476 * `showCheckbox` is true.
8477 */
8478 itemCheckboxStyle: {
8479 position: 'absolute',
8480 width: '13px', // for IE precision
8481 height: '13px'
8482 },
8483 // itemWidth: undefined,
8484
8485 /**
8486 * When this is true, the legend symbol width will be the same as
8487 * the symbol height, which in turn defaults to the font size of the
8488 * legend items.
8489 *
8490 * @type {Boolean}
8491 * @default true
8492 * @since 5.0.0
8493 */
8494 squareSymbol: true,
8495
8496 /**
8497 * The pixel height of the symbol for series types that use a rectangle
8498 * in the legend. Defaults to the font size of legend items.
8499 *
8500 * @productdesc {highmaps}
8501 * In Highmaps, when the symbol is the gradient of a vertical color
8502 * axis, the height defaults to 200.
8503 *
8504 * @type {Number}
8505 * @sample {highmaps} maps/legend/layout-vertical-sized/
8506 * Sized vertical gradient
8507 * @sample {highmaps} maps/legend/padding-itemmargin/
8508 * No distance between data classes
8509 * @since 3.0.8
8510 * @apioption legend.symbolHeight
8511 */
8512
8513 /**
8514 * The border radius of the symbol for series types that use a rectangle
8515 * in the legend. Defaults to half the `symbolHeight`.
8516 *
8517 * @type {Number}
8518 * @sample {highcharts} highcharts/legend/symbolradius/ Round symbols
8519 * @sample {highstock} highcharts/legend/symbolradius/ Round symbols
8520 * @sample {highmaps} highcharts/legend/symbolradius/ Round symbols
8521 * @since 3.0.8
8522 * @apioption legend.symbolRadius
8523 */
8524
8525 /**
8526 * The pixel width of the legend item symbol. When the `squareSymbol`
8527 * option is set, this defaults to the `symbolHeight`, otherwise 16.
8528 *
8529 * @productdesc {highmaps}
8530 * In Highmaps, when the symbol is the gradient of a horizontal color
8531 * axis, the width defaults to 200.
8532 *
8533 * @type {Number}
8534 * @sample {highcharts} highcharts/legend/symbolwidth/
8535 * Greater symbol width and padding
8536 * @sample {highmaps} maps/legend/padding-itemmargin/
8537 * Padding and item margins demonstrated
8538 * @sample {highmaps} maps/legend/layout-vertical-sized/
8539 * Sized vertical gradient
8540 * @apioption legend.symbolWidth
8541 */
8542
8543 /**
8544 * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
8545 * and-string-formatting#html) to render the legend item texts. Prior
8546 * to 4.1.7, when using HTML, [legend.navigation](#legend.navigation)
8547 * was disabled.
8548 *
8549 * @type {Boolean}
8550 * @default false
8551 * @apioption legend.useHTML
8552 */
8553
8554 /**
8555 * The width of the legend box.
8556 *
8557 * @type {Number}
8558 * @sample {highcharts} highcharts/legend/width/ Aligned to the plot area
8559 * @default null
8560 * @since 2.0
8561 * @apioption legend.width
8562 */
8563
8564 /**
8565 * The pixel padding between the legend item symbol and the legend
8566 * item text.
8567 *
8568 * @type {Number}
8569 * @sample {highcharts} highcharts/legend/symbolpadding/ Greater symbol width and padding
8570 * @default 5
8571 */
8572 symbolPadding: 5,
8573
8574 /**
8575 * The vertical alignment of the legend box. Can be one of `top`,
8576 * `middle` or `bottom`. Vertical position can be further determined
8577 * by the `y` option.
8578 *
8579 * In the case that the legend is aligned in a corner position, the
8580 * `layout` option will determine whether to place it above/below
8581 * or on the side of the plot area.
8582 *
8583 * @validvalue ["top", "middle", "bottom"]
8584 * @type {String}
8585 * @sample {highcharts} highcharts/legend/verticalalign/ Legend 100px from the top of the chart
8586 * @sample {highstock} stock/legend/align/ Various legend options
8587 * @sample {highmaps} maps/legend/alignment/ Legend alignment
8588 * @default bottom
8589 * @since 2.0
8590 */
8591 verticalAlign: 'bottom',
8592 // width: undefined,
8593
8594 /**
8595 * The x offset of the legend relative to its horizontal alignment
8596 * `align` within chart.spacingLeft and chart.spacingRight. Negative
8597 * x moves it to the left, positive x moves it to the right.
8598 *
8599 * @type {Number}
8600 * @sample {highcharts} highcharts/legend/width/ Aligned to the plot area
8601 * @default 0
8602 * @since 2.0
8603 */
8604 x: 0,
8605
8606 /**
8607 * The vertical offset of the legend relative to it's vertical alignment
8608 * `verticalAlign` within chart.spacingTop and chart.spacingBottom.
8609 * Negative y moves it up, positive y moves it down.
8610 *
8611 * @type {Number}
8612 * @sample {highcharts} highcharts/legend/verticalalign/ Legend 100px from the top of the chart
8613 * @sample {highstock} stock/legend/align/ Various legend options
8614 * @sample {highmaps} maps/legend/alignment/ Legend alignment
8615 * @default 0
8616 * @since 2.0
8617 */
8618 y: 0,
8619
8620 /**
8621 * A title to be added on top of the legend.
8622 *
8623 * @sample {highcharts} highcharts/legend/title/ Legend title
8624 * @sample {highmaps} maps/legend/alignment/ Legend with title
8625 * @since 3.0
8626 */
8627 title: {
8628 /**
8629 * A text or HTML string for the title.
8630 *
8631 * @type {String}
8632 * @default null
8633 * @since 3.0
8634 * @apioption legend.title.text
8635 */
8636
8637
8638 }
8639 },
8640
8641
8642 /**
8643 * The loading options control the appearance of the loading screen
8644 * that covers the plot area on chart operations. This screen only
8645 * appears after an explicit call to `chart.showLoading()`. It is a
8646 * utility for developers to communicate to the end user that something
8647 * is going on, for example while retrieving new data via an XHR connection.
8648 * The "Loading..." text itself is not part of this configuration
8649 * object, but part of the `lang` object.
8650 *
8651 */
8652 loading: {
8653
8654 /**
8655 * The duration in milliseconds of the fade out effect.
8656 *
8657 * @type {Number}
8658 * @sample highcharts/loading/hideduration/ Fade in and out over a second
8659 * @default 100
8660 * @since 1.2.0
8661 * @apioption loading.hideDuration
8662 */
8663
8664 /**
8665 * The duration in milliseconds of the fade in effect.
8666 *
8667 * @type {Number}
8668 * @sample highcharts/loading/hideduration/ Fade in and out over a second
8669 * @default 100
8670 * @since 1.2.0
8671 * @apioption loading.showDuration
8672 */
8673
8674 },
8675
8676
8677 /**
8678 * Options for the tooltip that appears when the user hovers over a
8679 * series or point.
8680 *
8681 */
8682 tooltip: {
8683
8684 /**
8685 * Enable or disable the tooltip.
8686 *
8687 * @type {Boolean}
8688 * @sample {highcharts} highcharts/tooltip/enabled/ Disabled
8689 * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ Disable tooltip and show values on chart instead
8690 * @default true
8691 */
8692 enabled: true,
8693
8694 /**
8695 * Enable or disable animation of the tooltip. In slow legacy IE browsers
8696 * the animation is disabled by default.
8697 *
8698 * @type {Boolean}
8699 * @default true
8700 * @since 2.3.0
8701 */
8702 animation: svg,
8703
8704 /**
8705 * The radius of the rounded border corners.
8706 *
8707 * @type {Number}
8708 * @sample {highcharts} highcharts/tooltip/bordercolor-default/ 5px by default
8709 * @sample {highcharts} highcharts/tooltip/borderradius-0/ Square borders
8710 * @sample {highmaps} maps/tooltip/background-border/ Background and border demo
8711 * @default 3
8712 */
8713 borderRadius: 3,
8714
8715 /**
8716 * For series on a datetime axes, the date format in the tooltip's
8717 * header will by default be guessed based on the closest data points.
8718 * This member gives the default string representations used for
8719 * each unit. For an overview of the replacement codes, see [dateFormat](#Highcharts.
8720 * dateFormat).
8721 *
8722 * Defaults to:
8723 *
8724 * <pre>{
8725 * millisecond:"%A, %b %e, %H:%M:%S.%L",
8726 * second:"%A, %b %e, %H:%M:%S",
8727 * minute:"%A, %b %e, %H:%M",
8728 * hour:"%A, %b %e, %H:%M",
8729 * day:"%A, %b %e, %Y",
8730 * week:"Week from %A, %b %e, %Y",
8731 * month:"%B %Y",
8732 * year:"%Y"
8733 * }</pre>
8734 *
8735 * @type {Object}
8736 * @see [xAxis.dateTimeLabelFormats](#xAxis.dateTimeLabelFormats)
8737 * @product highcharts highstock
8738 */
8739 dateTimeLabelFormats: {
8740 millisecond: '%A, %b %e, %H:%M:%S.%L',
8741 second: '%A, %b %e, %H:%M:%S',
8742 minute: '%A, %b %e, %H:%M',
8743 hour: '%A, %b %e, %H:%M',
8744 day: '%A, %b %e, %Y',
8745 week: 'Week from %A, %b %e, %Y',
8746 month: '%B %Y',
8747 year: '%Y'
8748 },
8749
8750 /**
8751 * A string to append to the tooltip format.
8752 *
8753 * @type {String}
8754 * @sample {highcharts} highcharts/tooltip/footerformat/ A table for value alignment
8755 * @sample {highmaps} maps/tooltip/format/ Format demo
8756 * @default false
8757 * @since 2.2
8758 */
8759 footerFormat: '',
8760
8761 /**
8762 * Padding inside the tooltip, in pixels.
8763 *
8764 * @type {Number}
8765 * @default 8
8766 * @since 5.0.0
8767 */
8768 padding: 8,
8769
8770 /**
8771 * Proximity snap for graphs or single points. It defaults to 10 for
8772 * mouse-powered devices and 25 for touch devices.
8773 *
8774 * Note that in most cases the whole plot area captures the mouse
8775 * movement, and in these cases `tooltip.snap` doesn't make sense.
8776 * This applies when [stickyTracking](#plotOptions.series.stickyTracking)
8777 * is `true` (default) and when the tooltip is [shared](#tooltip.shared)
8778 * or [split](#tooltip.split).
8779 *
8780 * @type {Number}
8781 * @sample {highcharts} highcharts/tooltip/bordercolor-default/ 10 px by default
8782 * @sample {highcharts} highcharts/tooltip/snap-50/ 50 px on graph
8783 * @default 10/25
8784 * @since 1.2.0
8785 * @product highcharts highstock
8786 */
8787 snap: isTouchDevice ? 25 : 10,
8788
8789 headerFormat: '<span class="highcharts-header">{point.key}</span><br/>',
8790 pointFormat: '<span class="highcharts-color-{point.colorIndex}">' +
8791 '\u25CF</span> {series.name}: <span class="highcharts-strong">' +
8792 '{point.y}</span><br/>',
8793
8794
8795
8796 /**
8797 * The color of the tooltip border. When `null`, the border takes the
8798 * color of the corresponding series or point.
8799 *
8800 * @type {Color}
8801 * @sample {highcharts} highcharts/tooltip/bordercolor-default/
8802 * Follow series by default
8803 * @sample {highcharts} highcharts/tooltip/bordercolor-black/
8804 * Black border
8805 * @sample {highstock} stock/tooltip/general/
8806 * Styled tooltip
8807 * @sample {highmaps} maps/tooltip/background-border/
8808 * Background and border demo
8809 * @default null
8810 * @apioption tooltip.borderColor
8811 */
8812
8813 /**
8814 * Since 4.1, the crosshair definitions are moved to the Axis object
8815 * in order for a better separation from the tooltip. See [xAxis.crosshair](#xAxis.
8816 * crosshair)<a>.</a>
8817 *
8818 * @type {Mixed}
8819 * @deprecated
8820 * @sample {highcharts} highcharts/tooltip/crosshairs-x/
8821 * Enable a crosshair for the x value
8822 * @default true
8823 * @apioption tooltip.crosshairs
8824 */
8825
8826 /**
8827 * Whether the tooltip should follow the mouse as it moves across columns,
8828 * pie slices and other point types with an extent. By default it behaves
8829 * this way for scatter, bubble and pie series by override in the `plotOptions`
8830 * for those series types.
8831 *
8832 * For touch moves to behave the same way, [followTouchMove](#tooltip.
8833 * followTouchMove) must be `true` also.
8834 *
8835 * @type {Boolean}
8836 * @default {highcharts} false
8837 * @default {highstock} false
8838 * @default {highmaps} true
8839 * @since 3.0
8840 * @apioption tooltip.followPointer
8841 */
8842
8843 /**
8844 * Whether the tooltip should follow the finger as it moves on a touch
8845 * device. If this is `true` and [chart.panning](#chart.panning) is
8846 * set,`followTouchMove` will take over one-finger touches, so the user
8847 * needs to use two fingers for zooming and panning.
8848 *
8849 * @type {Boolean}
8850 * @default {highcharts} true
8851 * @default {highstock} true
8852 * @default {highmaps} false
8853 * @since 3.0.1
8854 * @apioption tooltip.followTouchMove
8855 */
8856
8857 /**
8858 * Callback function to format the text of the tooltip from scratch. Return
8859 * `false` to disable tooltip for a specific point on series.
8860 *
8861 * A subset of HTML is supported. Unless `useHTML` is true, the HTML of the
8862 * tooltip is parsed and converted to SVG, therefore this isn't a complete HTML
8863 * renderer. The following tags are supported: `<b>`, `<strong>`, `<i>`, `<em>`,
8864 * `<br/>`, `<span>`. Spans can be styled with a `style` attribute,
8865 * but only text-related CSS that is shared with SVG is handled.
8866 *
8867 * Since version 2.1 the tooltip can be shared between multiple series
8868 * through the `shared` option. The available data in the formatter
8869 * differ a bit depending on whether the tooltip is shared or not. In
8870 * a shared tooltip, all properties except `x`, which is common for
8871 * all points, are kept in an array, `this.points`.
8872 *
8873 * Available data are:
8874 *
8875 * <dl>
8876 *
8877 * <dt>this.percentage (not shared) / this.points[i].percentage (shared)</dt>
8878 *
8879 * <dd>Stacked series and pies only. The point's percentage of the total.
8880 * </dd>
8881 *
8882 * <dt>this.point (not shared) / this.points[i].point (shared)</dt>
8883 *
8884 * <dd>The point object. The point name, if defined, is available through
8885 * `this.point.name`.</dd>
8886 *
8887 * <dt>this.points</dt>
8888 *
8889 * <dd>In a shared tooltip, this is an array containing all other properties
8890 * for each point.</dd>
8891 *
8892 * <dt>this.series (not shared) / this.points[i].series (shared)</dt>
8893 *
8894 * <dd>The series object. The series name is available through
8895 * `this.series.name`.</dd>
8896 *
8897 * <dt>this.total (not shared) / this.points[i].total (shared)</dt>
8898 *
8899 * <dd>Stacked series only. The total value at this point's x value.
8900 * </dd>
8901 *
8902 * <dt>this.x</dt>
8903 *
8904 * <dd>The x value. This property is the same regardless of the tooltip
8905 * being shared or not.</dd>
8906 *
8907 * <dt>this.y (not shared) / this.points[i].y (shared)</dt>
8908 *
8909 * <dd>The y value.</dd>
8910 *
8911 * </dl>
8912 *
8913 * @type {Function}
8914 * @sample {highcharts} highcharts/tooltip/formatter-simple/
8915 * Simple string formatting
8916 * @sample {highcharts} highcharts/tooltip/formatter-shared/
8917 * Formatting with shared tooltip
8918 * @sample {highstock} stock/tooltip/formatter/
8919 * Formatting with shared tooltip
8920 * @sample {highmaps} maps/tooltip/formatter/
8921 * String formatting
8922 * @apioption tooltip.formatter
8923 */
8924
8925 /**
8926 * The number of milliseconds to wait until the tooltip is hidden when
8927 * mouse out from a point or chart.
8928 *
8929 * @type {Number}
8930 * @default 500
8931 * @since 3.0
8932 * @apioption tooltip.hideDelay
8933 */
8934
8935 /**
8936 * A callback function for formatting the HTML output for a single point
8937 * in the tooltip. Like the `pointFormat` string, but with more flexibility.
8938 *
8939 * @type {Function}
8940 * @context Point
8941 * @since 4.1.0
8942 * @apioption tooltip.pointFormatter
8943 */
8944
8945 /**
8946 * A callback function to place the tooltip in a default position. The
8947 * callback receives three parameters: `labelWidth`, `labelHeight` and
8948 * `point`, where point contains values for `plotX` and `plotY` telling
8949 * where the reference point is in the plot area. Add `chart.plotLeft`
8950 * and `chart.plotTop` to get the full coordinates.
8951 *
8952 * The return should be an object containing x and y values, for example
8953 * `{ x: 100, y: 100 }`.
8954 *
8955 * @type {Function}
8956 * @sample {highcharts} highcharts/tooltip/positioner/ A fixed tooltip position
8957 * @sample {highstock} stock/tooltip/positioner/ A fixed tooltip position on top of the chart
8958 * @sample {highmaps} maps/tooltip/positioner/ A fixed tooltip position
8959 * @since 2.2.4
8960 * @apioption tooltip.positioner
8961 */
8962
8963 /**
8964 * The name of a symbol to use for the border around the tooltip.
8965 *
8966 * @type {String}
8967 * @default callout
8968 * @validvalue ["callout", "square"]
8969 * @since 4.0
8970 * @apioption tooltip.shape
8971 */
8972
8973 /**
8974 * When the tooltip is shared, the entire plot area will capture mouse
8975 * movement or touch events. Tooltip texts for series types with ordered
8976 * data (not pie, scatter, flags etc) will be shown in a single bubble.
8977 * This is recommended for single series charts and for tablet/mobile
8978 * optimized charts.
8979 *
8980 * See also [tooltip.split](#tooltip.split), that is better suited for
8981 * charts with many series, especially line-type series.
8982 *
8983 * @type {Boolean}
8984 * @sample {highcharts} highcharts/tooltip/shared-false/ False by default
8985 * @sample {highcharts} highcharts/tooltip/shared-true/ True
8986 * @sample {highcharts} highcharts/tooltip/shared-x-crosshair/ True with x axis crosshair
8987 * @sample {highcharts} highcharts/tooltip/shared-true-mixed-types/ True with mixed series types
8988 * @default false
8989 * @since 2.1
8990 * @product highcharts highstock
8991 * @apioption tooltip.shared
8992 */
8993
8994 /**
8995 * Split the tooltip into one label per series, with the header close
8996 * to the axis. This is recommended over [shared](#tooltip.shared) tooltips
8997 * for charts with multiple line series, generally making them easier
8998 * to read.
8999 *
9000 * @productdesc {highstock} In Highstock, tooltips are split by default
9001 * since v6.0.0. Stock charts typically contain multi-dimension points
9002 * and multiple panes, making split tooltips the preferred layout over
9003 * the previous `shared` tooltip.
9004 *
9005 * @type {Boolean}
9006 * @sample {highcharts} highcharts/tooltip/split/ Split tooltip
9007 * @sample {highstock} highcharts/tooltip/split/ Split tooltip
9008 * @sample {highmaps} highcharts/tooltip/split/ Split tooltip
9009 * @default {highcharts} false
9010 * @default {highstock} true
9011 * @product highcharts highstock
9012 * @since 5.0.0
9013 * @apioption tooltip.split
9014 */
9015
9016 /**
9017 * Use HTML to render the contents of the tooltip instead of SVG. Using
9018 * HTML allows advanced formatting like tables and images in the tooltip.
9019 * It is also recommended for rtl languages as it works around rtl
9020 * bugs in early Firefox.
9021 *
9022 * @type {Boolean}
9023 * @sample {highcharts} highcharts/tooltip/footerformat/ A table for value alignment
9024 * @sample {highcharts} highcharts/tooltip/fullhtml/ Full HTML tooltip
9025 * @sample {highstock} highcharts/tooltip/footerformat/ A table for value alignment
9026 * @sample {highstock} highcharts/tooltip/fullhtml/ Full HTML tooltip
9027 * @sample {highmaps} maps/tooltip/usehtml/ Pure HTML tooltip
9028 * @default false
9029 * @since 2.2
9030 * @apioption tooltip.useHTML
9031 */
9032
9033 /**
9034 * How many decimals to show in each series' y value. This is overridable
9035 * in each series' tooltip options object. The default is to preserve
9036 * all decimals.
9037 *
9038 * @type {Number}
9039 * @sample {highcharts} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9040 * @sample {highstock} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9041 * @sample {highmaps} maps/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9042 * @since 2.2
9043 * @apioption tooltip.valueDecimals
9044 */
9045
9046 /**
9047 * A string to prepend to each series' y value. Overridable in each
9048 * series' tooltip options object.
9049 *
9050 * @type {String}
9051 * @sample {highcharts} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9052 * @sample {highstock} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9053 * @sample {highmaps} maps/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9054 * @since 2.2
9055 * @apioption tooltip.valuePrefix
9056 */
9057
9058 /**
9059 * A string to append to each series' y value. Overridable in each series'
9060 * tooltip options object.
9061 *
9062 * @type {String}
9063 * @sample {highcharts} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9064 * @sample {highstock} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9065 * @sample {highmaps} maps/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9066 * @since 2.2
9067 * @apioption tooltip.valueSuffix
9068 */
9069
9070 /**
9071 * The format for the date in the tooltip header if the X axis is a
9072 * datetime axis. The default is a best guess based on the smallest
9073 * distance between points in the chart.
9074 *
9075 * @type {String}
9076 * @sample {highcharts} highcharts/tooltip/xdateformat/ A different format
9077 * @product highcharts highstock
9078 * @apioption tooltip.xDateFormat
9079 */
9080 },
9081
9082
9083 /**
9084 * Highchart by default puts a credits label in the lower right corner
9085 * of the chart. This can be changed using these options.
9086 */
9087 credits: {
9088
9089 /**
9090 * Whether to show the credits text.
9091 *
9092 * @type {Boolean}
9093 * @sample {highcharts} highcharts/credits/enabled-false/ Credits disabled
9094 * @sample {highstock} stock/credits/enabled/ Credits disabled
9095 * @sample {highmaps} maps/credits/enabled-false/ Credits disabled
9096 * @default true
9097 */
9098 enabled: true,
9099
9100 /**
9101 * The URL for the credits label.
9102 *
9103 * @type {String}
9104 * @sample {highcharts} highcharts/credits/href/ Custom URL and text
9105 * @sample {highmaps} maps/credits/customized/ Custom URL and text
9106 * @default {highcharts} http://www.highcharts.com
9107 * @default {highstock} "http://www.highcharts.com"
9108 * @default {highmaps} http://www.highcharts.com
9109 */
9110 href: 'http://www.highcharts.com',
9111
9112 /**
9113 * Position configuration for the credits label.
9114 *
9115 * @type {Object}
9116 * @sample {highcharts} highcharts/credits/position-left/ Left aligned
9117 * @sample {highcharts} highcharts/credits/position-left/ Left aligned
9118 * @sample {highmaps} maps/credits/customized/ Left aligned
9119 * @sample {highmaps} maps/credits/customized/ Left aligned
9120 * @since 2.1
9121 */
9122 position: {
9123
9124 /**
9125 * Horizontal alignment of the credits.
9126 *
9127 * @validvalue ["left", "center", "right"]
9128 * @type {String}
9129 * @default right
9130 */
9131 align: 'right',
9132
9133 /**
9134 * Horizontal pixel offset of the credits.
9135 *
9136 * @type {Number}
9137 * @default -10
9138 */
9139 x: -10,
9140
9141 /**
9142 * Vertical alignment of the credits.
9143 *
9144 * @validvalue ["top", "middle", "bottom"]
9145 * @type {String}
9146 * @default bottom
9147 */
9148 verticalAlign: 'bottom',
9149
9150 /**
9151 * Vertical pixel offset of the credits.
9152 *
9153 * @type {Number}
9154 * @default -5
9155 */
9156 y: -5
9157 },
9158
9159
9160 /**
9161 * The text for the credits label.
9162 *
9163 * @productdesc {highmaps}
9164 * If a map is loaded as GeoJSON, the text defaults to `Highcharts @
9165 * {map-credits}`. Otherwise, it defaults to `Highcharts.com`.
9166 *
9167 * @type {String}
9168 * @sample {highcharts} highcharts/credits/href/ Custom URL and text
9169 * @sample {highmaps} maps/credits/customized/ Custom URL and text
9170 * @default {highcharts|highstock} Highcharts.com
9171 */
9172 text: 'Highcharts.com'
9173 }
9174 };
9175
9176
9177
9178 /**
9179 * Sets the getTimezoneOffset function. If the timezone option is set, a default
9180 * getTimezoneOffset function with that timezone is returned. If not, the
9181 * specified getTimezoneOffset function is returned. If neither are specified,
9182 * undefined is returned.
9183 * @return {function} a getTimezoneOffset function or undefined
9184 */
9185 function getTimezoneOffsetOption() {
9186 var globalOptions = H.defaultOptions.global,
9187 moment = win.moment;
9188
9189 if (globalOptions.timezone) {
9190 if (!moment) {
9191 // getTimezoneOffset-function stays undefined because it depends on
9192 // Moment.js
9193 H.error(25);
9194
9195 } else {
9196 return function(timestamp) {
9197 return -moment.tz(
9198 timestamp,
9199 globalOptions.timezone
9200 ).utcOffset();
9201 };
9202 }
9203 }
9204
9205 // If not timezone is set, look for the getTimezoneOffset callback
9206 return globalOptions.useUTC && globalOptions.getTimezoneOffset;
9207 }
9208
9209 /**
9210 * Set the time methods globally based on the useUTC option. Time method can be
9211 * either local time or UTC (default). It is called internally on initiating
9212 * Highcharts and after running `Highcharts.setOptions`.
9213 *
9214 * @private
9215 */
9216 function setTimeMethods() {
9217 var globalOptions = H.defaultOptions.global,
9218 Date,
9219 useUTC = globalOptions.useUTC,
9220 GET = useUTC ? 'getUTC' : 'get',
9221 SET = useUTC ? 'setUTC' : 'set',
9222 setters = ['Minutes', 'Hours', 'Day', 'Date', 'Month', 'FullYear'],
9223 getters = setters.concat(['Milliseconds', 'Seconds']),
9224 n;
9225
9226 H.Date = Date = globalOptions.Date || win.Date; // Allow using a different Date class
9227 Date.hcTimezoneOffset = useUTC && globalOptions.timezoneOffset;
9228 Date.hcGetTimezoneOffset = getTimezoneOffsetOption();
9229 Date.hcMakeTime = function(year, month, date, hours, minutes, seconds) {
9230 var d;
9231 if (useUTC) {
9232 d = Date.UTC.apply(0, arguments);
9233 d += getTZOffset(d);
9234 } else {
9235 d = new Date(
9236 year,
9237 month,
9238 pick(date, 1),
9239 pick(hours, 0),
9240 pick(minutes, 0),
9241 pick(seconds, 0)
9242 ).getTime();
9243 }
9244 return d;
9245 };
9246
9247 // Dynamically set setters and getters. Use for loop, H.each is not yet
9248 // overridden in oldIE.
9249 for (n = 0; n < setters.length; n++) {
9250 Date['hcGet' + setters[n]] = GET + setters[n];
9251 }
9252 for (n = 0; n < getters.length; n++) {
9253 Date['hcSet' + getters[n]] = SET + getters[n];
9254 }
9255 }
9256
9257 /**
9258 * Merge the default options with custom options and return the new options
9259 * structure. Commonly used for defining reusable templates.
9260 *
9261 * @function #setOptions
9262 * @memberOf Highcharts
9263 * @sample highcharts/global/useutc-false Setting a global option
9264 * @sample highcharts/members/setoptions Applying a global theme
9265 * @param {Object} options The new custom chart options.
9266 * @returns {Object} Updated options.
9267 */
9268 H.setOptions = function(options) {
9269
9270 // Copy in the default options
9271 H.defaultOptions = merge(true, H.defaultOptions, options);
9272
9273 // Apply UTC
9274 setTimeMethods();
9275
9276 return H.defaultOptions;
9277 };
9278
9279 /**
9280 * Get the updated default options. Until 3.0.7, merely exposing defaultOptions for outside modules
9281 * wasn't enough because the setOptions method created a new object.
9282 */
9283 H.getOptions = function() {
9284 return H.defaultOptions;
9285 };
9286
9287
9288 // Series defaults
9289 H.defaultPlotOptions = H.defaultOptions.plotOptions;
9290
9291 // set the default time methods
9292 setTimeMethods();
9293
9294 }(Highcharts));
9295 (function(H) {
9296 /**
9297 * (c) 2010-2017 Torstein Honsi
9298 *
9299 * License: www.highcharts.com/license
9300 */
9301 var correctFloat = H.correctFloat,
9302 defined = H.defined,
9303 destroyObjectProperties = H.destroyObjectProperties,
9304 isNumber = H.isNumber,
9305 merge = H.merge,
9306 pick = H.pick,
9307 deg2rad = H.deg2rad;
9308
9309 /**
9310 * The Tick class
9311 */
9312 H.Tick = function(axis, pos, type, noLabel) {
9313 this.axis = axis;
9314 this.pos = pos;
9315 this.type = type || '';
9316 this.isNew = true;
9317 this.isNewLabel = true;
9318
9319 if (!type && !noLabel) {
9320 this.addLabel();
9321 }
9322 };
9323
9324 H.Tick.prototype = {
9325 /**
9326 * Write the tick label
9327 */
9328 addLabel: function() {
9329 var tick = this,
9330 axis = tick.axis,
9331 options = axis.options,
9332 chart = axis.chart,
9333 categories = axis.categories,
9334 names = axis.names,
9335 pos = tick.pos,
9336 labelOptions = options.labels,
9337 str,
9338 tickPositions = axis.tickPositions,
9339 isFirst = pos === tickPositions[0],
9340 isLast = pos === tickPositions[tickPositions.length - 1],
9341 value = categories ?
9342 pick(categories[pos], names[pos], pos) :
9343 pos,
9344 label = tick.label,
9345 tickPositionInfo = tickPositions.info,
9346 dateTimeLabelFormat;
9347
9348 // Set the datetime label format. If a higher rank is set for this
9349 // position, use that. If not, use the general format.
9350 if (axis.isDatetimeAxis && tickPositionInfo) {
9351 dateTimeLabelFormat =
9352 options.dateTimeLabelFormats[
9353 tickPositionInfo.higherRanks[pos] ||
9354 tickPositionInfo.unitName
9355 ];
9356 }
9357 // set properties for access in render method
9358 tick.isFirst = isFirst;
9359 tick.isLast = isLast;
9360
9361 // get the string
9362 str = axis.labelFormatter.call({
9363 axis: axis,
9364 chart: chart,
9365 isFirst: isFirst,
9366 isLast: isLast,
9367 dateTimeLabelFormat: dateTimeLabelFormat,
9368 value: axis.isLog ? correctFloat(axis.lin2log(value)) : value,
9369 pos: pos
9370 });
9371
9372 // first call
9373 if (!defined(label)) {
9374
9375 tick.label = label =
9376 defined(str) && labelOptions.enabled ?
9377 chart.renderer.text(
9378 str,
9379 0,
9380 0,
9381 labelOptions.useHTML
9382 )
9383
9384 .add(axis.labelGroup) :
9385 null;
9386
9387 // Un-rotated length
9388 tick.labelLength = label && label.getBBox().width;
9389 // Base value to detect change for new calls to getBBox
9390 tick.rotation = 0;
9391
9392 // update
9393 } else if (label) {
9394 label.attr({
9395 text: str
9396 });
9397 }
9398 },
9399
9400 /**
9401 * Get the offset height or width of the label
9402 */
9403 getLabelSize: function() {
9404 return this.label ?
9405 this.label.getBBox()[this.axis.horiz ? 'height' : 'width'] :
9406 0;
9407 },
9408
9409 /**
9410 * Handle the label overflow by adjusting the labels to the left and right
9411 * edge, or hide them if they collide into the neighbour label.
9412 */
9413 handleOverflow: function(xy) {
9414 var axis = this.axis,
9415 pxPos = xy.x,
9416 chartWidth = axis.chart.chartWidth,
9417 spacing = axis.chart.spacing,
9418 leftBound = pick(axis.labelLeft, Math.min(axis.pos, spacing[3])),
9419 rightBound = pick(
9420 axis.labelRight,
9421 Math.max(axis.pos + axis.len, chartWidth - spacing[1])
9422 ),
9423 label = this.label,
9424 rotation = this.rotation,
9425 factor = {
9426 left: 0,
9427 center: 0.5,
9428 right: 1
9429 }[axis.labelAlign],
9430 labelWidth = label.getBBox().width,
9431 slotWidth = axis.getSlotWidth(),
9432 modifiedSlotWidth = slotWidth,
9433 xCorrection = factor,
9434 goRight = 1,
9435 leftPos,
9436 rightPos,
9437 textWidth,
9438 css = {};
9439
9440 // Check if the label overshoots the chart spacing box. If it does, move
9441 // it. If it now overshoots the slotWidth, add ellipsis.
9442 if (!rotation) {
9443 leftPos = pxPos - factor * labelWidth;
9444 rightPos = pxPos + (1 - factor) * labelWidth;
9445
9446 if (leftPos < leftBound) {
9447 modifiedSlotWidth = xy.x + modifiedSlotWidth * (1 - factor) - leftBound;
9448 } else if (rightPos > rightBound) {
9449 modifiedSlotWidth =
9450 rightBound - xy.x + modifiedSlotWidth * factor;
9451 goRight = -1;
9452 }
9453
9454 modifiedSlotWidth = Math.min(slotWidth, modifiedSlotWidth); // #4177
9455 if (modifiedSlotWidth < slotWidth && axis.labelAlign === 'center') {
9456 xy.x += (
9457 goRight *
9458 (
9459 slotWidth -
9460 modifiedSlotWidth -
9461 xCorrection * (
9462 slotWidth - Math.min(labelWidth, modifiedSlotWidth)
9463 )
9464 )
9465 );
9466 }
9467 // If the label width exceeds the available space, set a text width
9468 // to be picked up below. Also, if a width has been set before, we
9469 // need to set a new one because the reported labelWidth will be
9470 // limited by the box (#3938).
9471 if (
9472 labelWidth > modifiedSlotWidth ||
9473 (axis.autoRotation && (label.styles || {}).width)
9474 ) {
9475 textWidth = modifiedSlotWidth;
9476 }
9477
9478 // Add ellipsis to prevent rotated labels to be clipped against the edge
9479 // of the chart
9480 } else if (rotation < 0 && pxPos - factor * labelWidth < leftBound) {
9481 textWidth = Math.round(
9482 pxPos / Math.cos(rotation * deg2rad) - leftBound
9483 );
9484 } else if (rotation > 0 && pxPos + factor * labelWidth > rightBound) {
9485 textWidth = Math.round(
9486 (chartWidth - pxPos) / Math.cos(rotation * deg2rad)
9487 );
9488 }
9489
9490 if (textWidth) {
9491 css.width = textWidth;
9492 if (!(axis.options.labels.style || {}).textOverflow) {
9493 css.textOverflow = 'ellipsis';
9494 }
9495 label.css(css);
9496 }
9497 },
9498
9499 /**
9500 * Get the x and y position for ticks and labels
9501 */
9502 getPosition: function(horiz, pos, tickmarkOffset, old) {
9503 var axis = this.axis,
9504 chart = axis.chart,
9505 cHeight = (old && chart.oldChartHeight) || chart.chartHeight;
9506
9507 return {
9508 x: horiz ?
9509 (
9510 axis.translate(pos + tickmarkOffset, null, null, old) +
9511 axis.transB
9512 ) :
9513 (
9514 axis.left +
9515 axis.offset +
9516 (
9517 axis.opposite ?
9518 (
9519 (
9520 (old && chart.oldChartWidth) ||
9521 chart.chartWidth
9522 ) -
9523 axis.right -
9524 axis.left
9525 ) :
9526 0
9527 )
9528 ),
9529
9530 y: horiz ?
9531 (
9532 cHeight -
9533 axis.bottom +
9534 axis.offset -
9535 (axis.opposite ? axis.height : 0)
9536 ) :
9537 (
9538 cHeight -
9539 axis.translate(pos + tickmarkOffset, null, null, old) -
9540 axis.transB
9541 )
9542 };
9543
9544 },
9545
9546 /**
9547 * Get the x, y position of the tick label
9548 */
9549 getLabelPosition: function(
9550 x,
9551 y,
9552 label,
9553 horiz,
9554 labelOptions,
9555 tickmarkOffset,
9556 index,
9557 step
9558 ) {
9559 var axis = this.axis,
9560 transA = axis.transA,
9561 reversed = axis.reversed,
9562 staggerLines = axis.staggerLines,
9563 rotCorr = axis.tickRotCorr || {
9564 x: 0,
9565 y: 0
9566 },
9567 yOffset = labelOptions.y,
9568 line;
9569
9570 if (!defined(yOffset)) {
9571 if (axis.side === 0) {
9572 yOffset = label.rotation ? -8 : -label.getBBox().height;
9573 } else if (axis.side === 2) {
9574 yOffset = rotCorr.y + 8;
9575 } else {
9576 // #3140, #3140
9577 yOffset = Math.cos(label.rotation * deg2rad) *
9578 (rotCorr.y - label.getBBox(false, 0).height / 2);
9579 }
9580 }
9581
9582 x = x + labelOptions.x + rotCorr.x - (tickmarkOffset && horiz ?
9583 tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
9584 y = y + yOffset - (tickmarkOffset && !horiz ?
9585 tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
9586
9587 // Correct for staggered labels
9588 if (staggerLines) {
9589 line = (index / (step || 1) % staggerLines);
9590 if (axis.opposite) {
9591 line = staggerLines - line - 1;
9592 }
9593 y += line * (axis.labelOffset / staggerLines);
9594 }
9595
9596 return {
9597 x: x,
9598 y: Math.round(y)
9599 };
9600 },
9601
9602 /**
9603 * Extendible method to return the path of the marker
9604 */
9605 getMarkPath: function(x, y, tickLength, tickWidth, horiz, renderer) {
9606 return renderer.crispLine([
9607 'M',
9608 x,
9609 y,
9610 'L',
9611 x + (horiz ? 0 : -tickLength),
9612 y + (horiz ? tickLength : 0)
9613 ], tickWidth);
9614 },
9615
9616 /**
9617 * Renders the gridLine.
9618 * @param {Boolean} old Whether or not the tick is old
9619 * @param {number} opacity The opacity of the grid line
9620 * @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1
9621 * @return {undefined}
9622 */
9623 renderGridLine: function(old, opacity, reverseCrisp) {
9624 var tick = this,
9625 axis = tick.axis,
9626 options = axis.options,
9627 gridLine = tick.gridLine,
9628 gridLinePath,
9629 attribs = {},
9630 pos = tick.pos,
9631 type = tick.type,
9632 tickmarkOffset = axis.tickmarkOffset,
9633 renderer = axis.chart.renderer;
9634
9635
9636
9637 if (!gridLine) {
9638
9639 if (!type) {
9640 attribs.zIndex = 1;
9641 }
9642 if (old) {
9643 attribs.opacity = 0;
9644 }
9645 tick.gridLine = gridLine = renderer.path()
9646 .attr(attribs)
9647 .addClass(
9648 'highcharts-' + (type ? type + '-' : '') + 'grid-line'
9649 )
9650 .add(axis.gridGroup);
9651 }
9652
9653 // If the parameter 'old' is set, the current call will be followed
9654 // by another call, therefore do not do any animations this time
9655 if (!old && gridLine) {
9656 gridLinePath = axis.getPlotLinePath(
9657 pos + tickmarkOffset,
9658 gridLine.strokeWidth() * reverseCrisp,
9659 old, true
9660 );
9661 if (gridLinePath) {
9662 gridLine[tick.isNew ? 'attr' : 'animate']({
9663 d: gridLinePath,
9664 opacity: opacity
9665 });
9666 }
9667 }
9668 },
9669
9670 /**
9671 * Renders the tick mark.
9672 * @param {Object} xy The position vector of the mark
9673 * @param {number} xy.x The x position of the mark
9674 * @param {number} xy.y The y position of the mark
9675 * @param {number} opacity The opacity of the mark
9676 * @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1
9677 * @return {undefined}
9678 */
9679 renderMark: function(xy, opacity, reverseCrisp) {
9680 var tick = this,
9681 axis = tick.axis,
9682 options = axis.options,
9683 renderer = axis.chart.renderer,
9684 type = tick.type,
9685 tickPrefix = type ? type + 'Tick' : 'tick',
9686 tickSize = axis.tickSize(tickPrefix),
9687 mark = tick.mark,
9688 isNewMark = !mark,
9689 x = xy.x,
9690 y = xy.y;
9691
9692
9693
9694 if (tickSize) {
9695
9696 // negate the length
9697 if (axis.opposite) {
9698 tickSize[0] = -tickSize[0];
9699 }
9700
9701 // First time, create it
9702 if (isNewMark) {
9703 tick.mark = mark = renderer.path()
9704 .addClass('highcharts-' + (type ? type + '-' : '') + 'tick')
9705 .add(axis.axisGroup);
9706
9707
9708 }
9709 mark[isNewMark ? 'attr' : 'animate']({
9710 d: tick.getMarkPath(
9711 x,
9712 y,
9713 tickSize[0],
9714 mark.strokeWidth() * reverseCrisp,
9715 axis.horiz,
9716 renderer),
9717 opacity: opacity
9718 });
9719
9720 }
9721 },
9722
9723 /**
9724 * Renders the tick label.
9725 * Note: The label should already be created in init(), so it should only
9726 * have to be moved into place.
9727 * @param {Object} xy The position vector of the label
9728 * @param {number} xy.x The x position of the label
9729 * @param {number} xy.y The y position of the label
9730 * @param {Boolean} old Whether or not the tick is old
9731 * @param {number} opacity The opacity of the label
9732 * @param {number} index The index of the tick
9733 * @return {undefined}
9734 */
9735 renderLabel: function(xy, old, opacity, index) {
9736 var tick = this,
9737 axis = tick.axis,
9738 horiz = axis.horiz,
9739 options = axis.options,
9740 label = tick.label,
9741 labelOptions = options.labels,
9742 step = labelOptions.step,
9743 tickmarkOffset = axis.tickmarkOffset,
9744 show = true,
9745 x = xy.x,
9746 y = xy.y;
9747 if (label && isNumber(x)) {
9748 label.xy = xy = tick.getLabelPosition(
9749 x,
9750 y,
9751 label,
9752 horiz,
9753 labelOptions,
9754 tickmarkOffset,
9755 index,
9756 step
9757 );
9758
9759 // Apply show first and show last. If the tick is both first and
9760 // last, it is a single centered tick, in which case we show the
9761 // label anyway (#2100).
9762 if (
9763 (
9764 tick.isFirst &&
9765 !tick.isLast &&
9766 !pick(options.showFirstLabel, 1)
9767 ) ||
9768 (
9769 tick.isLast &&
9770 !tick.isFirst &&
9771 !pick(options.showLastLabel, 1)
9772 )
9773 ) {
9774 show = false;
9775
9776 // Handle label overflow and show or hide accordingly
9777 } else if (horiz && !axis.isRadial && !labelOptions.step &&
9778 !labelOptions.rotation && !old && opacity !== 0) {
9779 tick.handleOverflow(xy);
9780 }
9781
9782 // apply step
9783 if (step && index % step) {
9784 // show those indices dividable by step
9785 show = false;
9786 }
9787
9788 // Set the new position, and show or hide
9789 if (show && isNumber(xy.y)) {
9790 xy.opacity = opacity;
9791 label[tick.isNewLabel ? 'attr' : 'animate'](xy);
9792 tick.isNewLabel = false;
9793 } else {
9794 label.attr('y', -9999); // #1338
9795 tick.isNewLabel = true;
9796 }
9797 }
9798 },
9799
9800 /**
9801 * Put everything in place
9802 *
9803 * @param index {Number}
9804 * @param old {Boolean} Use old coordinates to prepare an animation into new
9805 * position
9806 */
9807 render: function(index, old, opacity) {
9808 var tick = this,
9809 axis = tick.axis,
9810 horiz = axis.horiz,
9811 pos = tick.pos,
9812 tickmarkOffset = axis.tickmarkOffset,
9813 xy = tick.getPosition(horiz, pos, tickmarkOffset, old),
9814 x = xy.x,
9815 y = xy.y,
9816 reverseCrisp = ((horiz && x === axis.pos + axis.len) ||
9817 (!horiz && y === axis.pos)) ? -1 : 1; // #1480, #1687
9818
9819 opacity = pick(opacity, 1);
9820 this.isActive = true;
9821
9822 // Create the grid line
9823 this.renderGridLine(old, opacity, reverseCrisp);
9824
9825 // create the tick mark
9826 this.renderMark(xy, opacity, reverseCrisp);
9827
9828 // the label is created on init - now move it into place
9829 this.renderLabel(xy, old, opacity, index);
9830
9831 tick.isNew = false;
9832 },
9833
9834 /**
9835 * Destructor for the tick prototype
9836 */
9837 destroy: function() {
9838 destroyObjectProperties(this, this.axis);
9839 }
9840 };
9841
9842 }(Highcharts));
9843 var Axis = (function(H) {
9844 /**
9845 * (c) 2010-2017 Torstein Honsi
9846 *
9847 * License: www.highcharts.com/license
9848 */
9849
9850 var addEvent = H.addEvent,
9851 animObject = H.animObject,
9852 arrayMax = H.arrayMax,
9853 arrayMin = H.arrayMin,
9854 color = H.color,
9855 correctFloat = H.correctFloat,
9856 defaultOptions = H.defaultOptions,
9857 defined = H.defined,
9858 deg2rad = H.deg2rad,
9859 destroyObjectProperties = H.destroyObjectProperties,
9860 each = H.each,
9861 extend = H.extend,
9862 fireEvent = H.fireEvent,
9863 format = H.format,
9864 getMagnitude = H.getMagnitude,
9865 grep = H.grep,
9866 inArray = H.inArray,
9867 isArray = H.isArray,
9868 isNumber = H.isNumber,
9869 isString = H.isString,
9870 merge = H.merge,
9871 normalizeTickInterval = H.normalizeTickInterval,
9872 objectEach = H.objectEach,
9873 pick = H.pick,
9874 removeEvent = H.removeEvent,
9875 splat = H.splat,
9876 syncTimeout = H.syncTimeout,
9877 Tick = H.Tick;
9878
9879 /**
9880 * Create a new axis object. Called internally when instanciating a new chart or
9881 * adding axes by {@link Highcharts.Chart#addAxis}.
9882 *
9883 * A chart can have from 0 axes (pie chart) to multiples. In a normal, single
9884 * series cartesian chart, there is one X axis and one Y axis.
9885 *
9886 * The X axis or axes are referenced by {@link Highcharts.Chart.xAxis}, which is
9887 * an array of Axis objects. If there is only one axis, it can be referenced
9888 * through `chart.xAxis[0]`, and multiple axes have increasing indices. The same
9889 * pattern goes for Y axes.
9890 *
9891 * If you need to get the axes from a series object, use the `series.xAxis` and
9892 * `series.yAxis` properties. These are not arrays, as one series can only be
9893 * associated to one X and one Y axis.
9894 *
9895 * A third way to reference the axis programmatically is by `id`. Add an `id` in
9896 * the axis configuration options, and get the axis by
9897 * {@link Highcharts.Chart#get}.
9898 *
9899 * Configuration options for the axes are given in options.xAxis and
9900 * options.yAxis.
9901 *
9902 * @class Highcharts.Axis
9903 * @memberOf Highcharts
9904 * @param {Highcharts.Chart} chart - The Chart instance to apply the axis on.
9905 * @param {Object} options - Axis options
9906 */
9907 var Axis = function() {
9908 this.init.apply(this, arguments);
9909 };
9910
9911 H.extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {
9912
9913 /**
9914 * The X axis or category axis. Normally this is the horizontal axis,
9915 * though if the chart is inverted this is the vertical axis. In case of
9916 * multiple axes, the xAxis node is an array of configuration objects.
9917 *
9918 * See [the Axis object](#Axis) for programmatic access to the axis.
9919 *
9920 * @productdesc {highmaps}
9921 * In Highmaps, the axis is hidden, but it is used behind the scenes to
9922 * control features like zooming and panning. Zooming is in effect the same
9923 * as setting the extremes of one of the exes.
9924 *
9925 * @optionparent xAxis
9926 */
9927 defaultOptions: {
9928 /**
9929 * Whether to allow decimals in this axis' ticks. When counting
9930 * integers, like persons or hits on a web page, decimals should
9931 * be avoided in the labels.
9932 *
9933 * @type {Boolean}
9934 * @see [minTickInterval](#xAxis.minTickInterval)
9935 * @sample {highcharts|highstock}
9936 * highcharts/yaxis/allowdecimals-true/
9937 * True by default
9938 * @sample {highcharts|highstock}
9939 * highcharts/yaxis/allowdecimals-false/
9940 * False
9941 * @default true
9942 * @since 2.0
9943 * @apioption xAxis.allowDecimals
9944 */
9945 // allowDecimals: null,
9946
9947
9948 /**
9949 * When using an alternate grid color, a band is painted across the
9950 * plot area between every other grid line.
9951 *
9952 * @type {Color}
9953 * @sample {highcharts} highcharts/yaxis/alternategridcolor/
9954 * Alternate grid color on the Y axis
9955 * @sample {highstock} stock/xaxis/alternategridcolor/
9956 * Alternate grid color on the Y axis
9957 * @default null
9958 * @apioption xAxis.alternateGridColor
9959 */
9960 // alternateGridColor: null,
9961
9962 /**
9963 * An array defining breaks in the axis, the sections defined will be
9964 * left out and all the points shifted closer to each other.
9965 *
9966 * @productdesc {highcharts}
9967 * Requires that the broken-axis.js module is loaded.
9968 *
9969 * @type {Array}
9970 * @sample {highcharts}
9971 * highcharts/axisbreak/break-simple/
9972 * Simple break
9973 * @sample {highcharts|highstock}
9974 * highcharts/axisbreak/break-visualized/
9975 * Advanced with callback
9976 * @sample {highstock}
9977 * stock/demo/intraday-breaks/
9978 * Break on nights and weekends
9979 * @since 4.1.0
9980 * @product highcharts highstock
9981 * @apioption xAxis.breaks
9982 */
9983
9984 /**
9985 * A number indicating how much space should be left between the start
9986 * and the end of the break. The break size is given in axis units,
9987 * so for instance on a `datetime` axis, a break size of 3600000 would
9988 * indicate the equivalent of an hour.
9989 *
9990 * @type {Number}
9991 * @default 0
9992 * @since 4.1.0
9993 * @product highcharts highstock
9994 * @apioption xAxis.breaks.breakSize
9995 */
9996
9997 /**
9998 * The point where the break starts.
9999 *
10000 * @type {Number}
10001 * @since 4.1.0
10002 * @product highcharts highstock
10003 * @apioption xAxis.breaks.from
10004 */
10005
10006 /**
10007 * Defines an interval after which the break appears again. By default
10008 * the breaks do not repeat.
10009 *
10010 * @type {Number}
10011 * @default 0
10012 * @since 4.1.0
10013 * @product highcharts highstock
10014 * @apioption xAxis.breaks.repeat
10015 */
10016
10017 /**
10018 * The point where the break ends.
10019 *
10020 * @type {Number}
10021 * @since 4.1.0
10022 * @product highcharts highstock
10023 * @apioption xAxis.breaks.to
10024 */
10025
10026 /**
10027 * If categories are present for the xAxis, names are used instead of
10028 * numbers for that axis. Since Highcharts 3.0, categories can also
10029 * be extracted by giving each point a [name](#series.data) and setting
10030 * axis [type](#xAxis.type) to `category`. However, if you have multiple
10031 * series, best practice remains defining the `categories` array.
10032 *
10033 * Example:
10034 *
10035 * <pre>categories: ['Apples', 'Bananas', 'Oranges']</pre>
10036 *
10037 * @type {Array<String>}
10038 * @sample {highcharts} highcharts/chart/reflow-true/
10039 * With
10040 * @sample {highcharts} highcharts/xaxis/categories/
10041 * Without
10042 * @product highcharts
10043 * @default null
10044 * @apioption xAxis.categories
10045 */
10046 // categories: [],
10047
10048 /**
10049 * The highest allowed value for automatically computed axis extremes.
10050 *
10051 * @type {Number}
10052 * @see [floor](#xAxis.floor)
10053 * @sample {highcharts|highstock} highcharts/yaxis/floor-ceiling/
10054 * Floor and ceiling
10055 * @since 4.0
10056 * @product highcharts highstock
10057 * @apioption xAxis.ceiling
10058 */
10059
10060 /**
10061 * A class name that opens for styling the axis by CSS, especially in
10062 * Highcharts styled mode. The class name is applied to group elements
10063 * for the grid, axis elements and labels.
10064 *
10065 * @type {String}
10066 * @sample {highcharts|highstock|highmaps}
10067 * highcharts/css/axis/
10068 * Multiple axes with separate styling
10069 * @since 5.0.0
10070 * @apioption xAxis.className
10071 */
10072
10073 /**
10074 * Configure a crosshair that follows either the mouse pointer or the
10075 * hovered point.
10076 *
10077 * In styled mode, the crosshairs are styled in the
10078 * `.highcharts-crosshair`, `.highcharts-crosshair-thin` or
10079 * `.highcharts-xaxis-category` classes.
10080 *
10081 * @productdesc {highstock}
10082 * In Highstock, bu default, the crosshair is enabled on the X axis and
10083 * disabled on the Y axis.
10084 *
10085 * @type {Boolean|Object}
10086 * @sample {highcharts} highcharts/xaxis/crosshair-both/
10087 * Crosshair on both axes
10088 * @sample {highstock} stock/xaxis/crosshairs-xy/
10089 * Crosshair on both axes
10090 * @sample {highmaps} highcharts/xaxis/crosshair-both/
10091 * Crosshair on both axes
10092 * @default false
10093 * @since 4.1
10094 * @apioption xAxis.crosshair
10095 */
10096
10097 /**
10098 * A class name for the crosshair, especially as a hook for styling.
10099 *
10100 * @type {String}
10101 * @since 5.0.0
10102 * @apioption xAxis.crosshair.className
10103 */
10104
10105 /**
10106 * The color of the crosshair. Defaults to `#cccccc` for numeric and
10107 * datetime axes, and `rgba(204,214,235,0.25)` for category axes, where
10108 * the crosshair by default highlights the whole category.
10109 *
10110 * @type {Color}
10111 * @sample {highcharts|highstock|highmaps}
10112 * highcharts/xaxis/crosshair-customized/
10113 * Customized crosshairs
10114 * @default #cccccc
10115 * @since 4.1
10116 * @apioption xAxis.crosshair.color
10117 */
10118
10119 /**
10120 * The dash style for the crosshair. See
10121 * [series.dashStyle](#plotOptions.series.dashStyle)
10122 * for possible values.
10123 *
10124 * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
10125 * "ShortDashDotDot", "Dot", "Dash" ,"LongDash",
10126 * "DashDot", "LongDashDot", "LongDashDotDot"]
10127 * @type {String}
10128 * @sample {highcharts|highmaps} highcharts/xaxis/crosshair-dotted/
10129 * Dotted crosshair
10130 * @sample {highstock} stock/xaxis/crosshair-dashed/
10131 * Dashed X axis crosshair
10132 * @default Solid
10133 * @since 4.1
10134 * @apioption xAxis.crosshair.dashStyle
10135 */
10136
10137 /**
10138 * Whether the crosshair should snap to the point or follow the pointer
10139 * independent of points.
10140 *
10141 * @type {Boolean}
10142 * @sample {highcharts|highstock}
10143 * highcharts/xaxis/crosshair-snap-false/
10144 * True by default
10145 * @sample {highmaps}
10146 * maps/demo/latlon-advanced/
10147 * Snap is false
10148 * @default true
10149 * @since 4.1
10150 * @apioption xAxis.crosshair.snap
10151 */
10152
10153 /**
10154 * The pixel width of the crosshair. Defaults to 1 for numeric or
10155 * datetime axes, and for one category width for category axes.
10156 *
10157 * @type {Number}
10158 * @sample {highcharts} highcharts/xaxis/crosshair-customized/
10159 * Customized crosshairs
10160 * @sample {highstock} highcharts/xaxis/crosshair-customized/
10161 * Customized crosshairs
10162 * @sample {highmaps} highcharts/xaxis/crosshair-customized/
10163 * Customized crosshairs
10164 * @default 1
10165 * @since 4.1
10166 * @apioption xAxis.crosshair.width
10167 */
10168
10169 /**
10170 * The Z index of the crosshair. Higher Z indices allow drawing the
10171 * crosshair on top of the series or behind the grid lines.
10172 *
10173 * @type {Number}
10174 * @default 2
10175 * @since 4.1
10176 * @apioption xAxis.crosshair.zIndex
10177 */
10178
10179 /**
10180 * For a datetime axis, the scale will automatically adjust to the
10181 * appropriate unit. This member gives the default string
10182 * representations used for each unit. For intermediate values,
10183 * different units may be used, for example the `day` unit can be used
10184 * on midnight and `hour` unit be used for intermediate values on the
10185 * same axis. For an overview of the replacement codes, see
10186 * [dateFormat](#Highcharts.dateFormat). Defaults to:
10187 *
10188 * <pre>{
10189 * millisecond: '%H:%M:%S.%L',
10190 * second: '%H:%M:%S',
10191 * minute: '%H:%M',
10192 * hour: '%H:%M',
10193 * day: '%e. %b',
10194 * week: '%e. %b',
10195 * month: '%b \'%y',
10196 * year: '%Y'
10197 * }</pre>
10198 *
10199 * @type {Object}
10200 * @sample {highcharts} highcharts/xaxis/datetimelabelformats/
10201 * Different day format on X axis
10202 * @sample {highstock} stock/xaxis/datetimelabelformats/
10203 * More information in x axis labels
10204 * @product highcharts highstock
10205 */
10206 dateTimeLabelFormats: {
10207 millisecond: '%H:%M:%S.%L',
10208 second: '%H:%M:%S',
10209 minute: '%H:%M',
10210 hour: '%H:%M',
10211 day: '%e. %b',
10212 week: '%e. %b',
10213 month: '%b \'%y',
10214 year: '%Y'
10215 },
10216
10217 /**
10218 * _Requires Accessibility module_
10219 *
10220 * Description of the axis to screen reader users.
10221 *
10222 * @type {String}
10223 * @default undefined
10224 * @since 5.0.0
10225 * @apioption xAxis.description
10226 */
10227
10228 /**
10229 * Whether to force the axis to end on a tick. Use this option with
10230 * the `maxPadding` option to control the axis end.
10231 *
10232 * @productdesc {highstock}
10233 * In Highstock, `endOnTick` is always false when the navigator is
10234 * enabled, to prevent jumpy scrolling.
10235 *
10236 * @sample {highcharts} highcharts/chart/reflow-true/
10237 * True by default
10238 * @sample {highcharts} highcharts/yaxis/endontick/
10239 * False
10240 * @sample {highstock} stock/demo/basic-line/
10241 * True by default
10242 * @sample {highstock} stock/xaxis/endontick/
10243 * False
10244 * @since 1.2.0
10245 */
10246 endOnTick: false,
10247
10248 /**
10249 * Event handlers for the axis.
10250 *
10251 * @apioption xAxis.events
10252 */
10253
10254 /**
10255 * An event fired after the breaks have rendered.
10256 *
10257 * @type {Function}
10258 * @see [breaks](#xAxis.breaks)
10259 * @sample {highcharts} highcharts/axisbreak/break-event/
10260 * AfterBreak Event
10261 * @since 4.1.0
10262 * @product highcharts
10263 * @apioption xAxis.events.afterBreaks
10264 */
10265
10266 /**
10267 * As opposed to the `setExtremes` event, this event fires after the
10268 * final min and max values are computed and corrected for `minRange`.
10269 *
10270 *
10271 * Fires when the minimum and maximum is set for the axis, either by
10272 * calling the `.setExtremes()` method or by selecting an area in the
10273 * chart. One parameter, `event`, is passed to the function, containing
10274 * common event information.
10275 *
10276 * The new user set minimum and maximum values can be found by `event.
10277 * min` and `event.max`. These reflect the axis minimum and maximum
10278 * in axis values. The actual data extremes are found in `event.dataMin`
10279 * and `event.dataMax`.
10280 *
10281 * @type {Function}
10282 * @context Axis
10283 * @since 2.3
10284 * @apioption xAxis.events.afterSetExtremes
10285 */
10286
10287 /**
10288 * An event fired when a break from this axis occurs on a point.
10289 *
10290 * @type {Function}
10291 * @see [breaks](#xAxis.breaks)
10292 * @context Axis
10293 * @sample {highcharts} highcharts/axisbreak/break-visualized/
10294 * Visualization of a Break
10295 * @since 4.1.0
10296 * @product highcharts
10297 * @apioption xAxis.events.pointBreak
10298 */
10299
10300 /**
10301 * An event fired when a point falls inside a break from this axis.
10302 *
10303 * @type {Function}
10304 * @context Axis
10305 * @product highcharts highstock
10306 * @apioption xAxis.events.pointInBreak
10307 */
10308
10309 /**
10310 * Fires when the minimum and maximum is set for the axis, either by
10311 * calling the `.setExtremes()` method or by selecting an area in the
10312 * chart. One parameter, `event`, is passed to the function,
10313 * containing common event information.
10314 *
10315 * The new user set minimum and maximum values can be found by `event.
10316 * min` and `event.max`. These reflect the axis minimum and maximum
10317 * in data values. When an axis is zoomed all the way out from the
10318 * "Reset zoom" button, `event.min` and `event.max` are null, and
10319 * the new extremes are set based on `this.dataMin` and `this.dataMax`.
10320 *
10321 * @type {Function}
10322 * @context Axis
10323 * @sample {highstock} stock/xaxis/events-setextremes/
10324 * Log new extremes on x axis
10325 * @since 1.2.0
10326 * @apioption xAxis.events.setExtremes
10327 */
10328
10329 /**
10330 * The lowest allowed value for automatically computed axis extremes.
10331 *
10332 * @type {Number}
10333 * @see [ceiling](#yAxis.ceiling)
10334 * @sample {highcharts} highcharts/yaxis/floor-ceiling/
10335 * Floor and ceiling
10336 * @sample {highstock} stock/demo/lazy-loading/
10337 * Prevent negative stock price on Y axis
10338 * @default null
10339 * @since 4.0
10340 * @product highcharts highstock
10341 * @apioption xAxis.floor
10342 */
10343
10344 /**
10345 * The dash or dot style of the grid lines. For possible values, see
10346 * [this demonstration](http://jsfiddle.net/gh/get/library/pure/
10347 *highcharts/highcharts/tree/master/samples/highcharts/plotoptions/
10348 *series-dashstyle-all/).
10349 *
10350 * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
10351 * "ShortDashDotDot", "Dot", "Dash" ,"LongDash",
10352 * "DashDot", "LongDashDot", "LongDashDotDot"]
10353 * @type {String}
10354 * @sample {highcharts} highcharts/yaxis/gridlinedashstyle/
10355 * Long dashes
10356 * @sample {highstock} stock/xaxis/gridlinedashstyle/
10357 * Long dashes
10358 * @default Solid
10359 * @since 1.2
10360 * @apioption xAxis.gridLineDashStyle
10361 */
10362
10363 /**
10364 * The Z index of the grid lines.
10365 *
10366 * @type {Number}
10367 * @sample {highcharts|highstock} highcharts/xaxis/gridzindex/
10368 * A Z index of 4 renders the grid above the graph
10369 * @default 1
10370 * @product highcharts highstock
10371 * @apioption xAxis.gridZIndex
10372 */
10373
10374 /**
10375 * An id for the axis. This can be used after render time to get
10376 * a pointer to the axis object through `chart.get()`.
10377 *
10378 * @type {String}
10379 * @sample {highcharts} highcharts/xaxis/id/
10380 * Get the object
10381 * @sample {highstock} stock/xaxis/id/
10382 * Get the object
10383 * @default null
10384 * @since 1.2.0
10385 * @apioption xAxis.id
10386 */
10387
10388 /**
10389 * The axis labels show the number or category for each tick.
10390 *
10391 * @productdesc {highmaps}
10392 * X and Y axis labels are by default disabled in Highmaps, but the
10393 * functionality is inherited from Highcharts and used on `colorAxis`,
10394 * and can be enabled on X and Y axes too.
10395 */
10396 labels: {
10397 /**
10398 * What part of the string the given position is anchored to.
10399 * If `left`, the left side of the string is at the axis position.
10400 * Can be one of `"left"`, `"center"` or `"right"`. Defaults to
10401 * an intelligent guess based on which side of the chart the axis
10402 * is on and the rotation of the label.
10403 *
10404 * @validvalue ["left", "center", "right"]
10405 * @type {String}
10406 * @sample {highcharts} highcharts/xaxis/labels-align-left/
10407 * Left
10408 * @sample {highcharts} highcharts/xaxis/labels-align-right/
10409 * Right
10410 * @apioption xAxis.labels.align
10411 */
10412 // align: 'center',
10413
10414 /**
10415 * For horizontal axes, the allowed degrees of label rotation
10416 * to prevent overlapping labels. If there is enough space,
10417 * labels are not rotated. As the chart gets narrower, it
10418 * will start rotating the labels -45 degrees, then remove
10419 * every second label and try again with rotations 0 and -45 etc.
10420 * Set it to `false` to disable rotation, which will
10421 * cause the labels to word-wrap if possible.
10422 *
10423 * @type {Array<Number>}
10424 * @sample {highcharts|highstock}
10425 * highcharts/xaxis/labels-autorotation-default/
10426 * Default auto rotation of 0 or -45
10427 * @sample {highcharts|highstock}
10428 * highcharts/xaxis/labels-autorotation-0-90/
10429 * Custom graded auto rotation
10430 * @default [-45]
10431 * @since 4.1.0
10432 * @product highcharts highstock
10433 * @apioption xAxis.labels.autoRotation
10434 */
10435
10436 /**
10437 * When each category width is more than this many pixels, we don't
10438 * apply auto rotation. Instead, we lay out the axis label with word
10439 * wrap. A lower limit makes sense when the label contains multiple
10440 * short words that don't extend the available horizontal space for
10441 * each label.
10442 *
10443 * @type {Number}
10444 * @sample {highcharts}
10445 * highcharts/xaxis/labels-autorotationlimit/
10446 * Lower limit
10447 * @default 80
10448 * @since 4.1.5
10449 * @product highcharts
10450 * @apioption xAxis.labels.autoRotationLimit
10451 */
10452
10453 /**
10454 * Polar charts only. The label's pixel distance from the perimeter
10455 * of the plot area.
10456 *
10457 * @type {Number}
10458 * @default 15
10459 * @product highcharts
10460 * @apioption xAxis.labels.distance
10461 */
10462
10463 /**
10464 * Enable or disable the axis labels.
10465 *
10466 * @sample {highcharts} highcharts/xaxis/labels-enabled/
10467 * X axis labels disabled
10468 * @sample {highstock} stock/xaxis/labels-enabled/
10469 * X axis labels disabled
10470 * @default {highcharts|highstock} true
10471 * @default {highmaps} false
10472 */
10473 enabled: true,
10474
10475 /**
10476 * A [format string](http://www.highcharts.com/docs/chart-
10477 * concepts/labels-and-string-formatting) for the axis label.
10478 *
10479 * @type {String}
10480 * @sample {highcharts|highstock} highcharts/yaxis/labels-format/
10481 * Add units to Y axis label
10482 * @default {value}
10483 * @since 3.0
10484 * @apioption xAxis.labels.format
10485 */
10486
10487 /**
10488 * Callback JavaScript function to format the label. The value
10489 * is given by `this.value`. Additional properties for `this` are
10490 * `axis`, `chart`, `isFirst` and `isLast`. The value of the default
10491 * label formatter can be retrieved by calling
10492 * `this.axis.defaultLabelFormatter.call(this)` within the function.
10493 *
10494 * Defaults to:
10495 *
10496 * <pre>function() {
10497 * return this.value;
10498 * }</pre>
10499 *
10500 * @type {Function}
10501 * @sample {highcharts}
10502 * highcharts/xaxis/labels-formatter-linked/
10503 * Linked category names
10504 * @sample {highcharts}
10505 * highcharts/xaxis/labels-formatter-extended/
10506 * Modified numeric labels
10507 * @sample {highstock}
10508 * stock/xaxis/labels-formatter/
10509 * Added units on Y axis
10510 * @apioption xAxis.labels.formatter
10511 */
10512
10513 /**
10514 * How to handle overflowing labels on horizontal axis. Can be
10515 * undefined, `false` or `"justify"`. By default it aligns inside
10516 * the chart area. If "justify", labels will not render outside
10517 * the plot area. If `false`, it will not be aligned at all.
10518 * If there is room to move it, it will be aligned to the edge,
10519 * else it will be removed.
10520 *
10521 * @deprecated
10522 * @validvalue [null, "justify"]
10523 * @type {String}
10524 * @since 2.2.5
10525 * @apioption xAxis.labels.overflow
10526 */
10527
10528 /**
10529 * The pixel padding for axis labels, to ensure white space between
10530 * them.
10531 *
10532 * @type {Number}
10533 * @default 5
10534 * @product highcharts
10535 * @apioption xAxis.labels.padding
10536 */
10537
10538 /**
10539 * Whether to reserve space for the labels. This can be turned off
10540 * when for example the labels are rendered inside the plot area
10541 * instead of outside.
10542 *
10543 * @type {Boolean}
10544 * @sample {highcharts} highcharts/xaxis/labels-reservespace/
10545 * No reserved space, labels inside plot
10546 * @default true
10547 * @since 4.1.10
10548 * @product highcharts
10549 * @apioption xAxis.labels.reserveSpace
10550 */
10551
10552 /**
10553 * Rotation of the labels in degrees.
10554 *
10555 * @type {Number}
10556 * @sample {highcharts} highcharts/xaxis/labels-rotation/
10557 * X axis labels rotated 90°
10558 * @default 0
10559 * @apioption xAxis.labels.rotation
10560 */
10561 // rotation: 0,
10562
10563 /**
10564 * Horizontal axes only. The number of lines to spread the labels
10565 * over to make room or tighter labels.
10566 *
10567 * @type {Number}
10568 * @sample {highcharts} highcharts/xaxis/labels-staggerlines/
10569 * Show labels over two lines
10570 * @sample {highstock} stock/xaxis/labels-staggerlines/
10571 * Show labels over two lines
10572 * @default null
10573 * @since 2.1
10574 * @apioption xAxis.labels.staggerLines
10575 */
10576
10577 /**
10578 * To show only every _n_'th label on the axis, set the step to _n_.
10579 * Setting the step to 2 shows every other label.
10580 *
10581 * By default, the step is calculated automatically to avoid
10582 * overlap. To prevent this, set it to 1\. This usually only
10583 * happens on a category axis, and is often a sign that you have
10584 * chosen the wrong axis type.
10585 *
10586 * Read more at
10587 * [Axis docs](http://www.highcharts.com/docs/chart-concepts/axes)
10588 * => What axis should I use?
10589 *
10590 * @type {Number}
10591 * @sample {highcharts} highcharts/xaxis/labels-step/
10592 * Showing only every other axis label on a categorized
10593 * x axis
10594 * @sample {highcharts} highcharts/xaxis/labels-step-auto/
10595 * Auto steps on a category axis
10596 * @default null
10597 * @since 2.1
10598 * @apioption xAxis.labels.step
10599 */
10600 // step: null,
10601
10602
10603
10604 /**
10605 * Whether to [use HTML](http://www.highcharts.com/docs/chart-
10606 * concepts/labels-and-string-formatting#html) to render the labels.
10607 *
10608 * @type {Boolean}
10609 * @default false
10610 * @apioption xAxis.labels.useHTML
10611 */
10612
10613 /**
10614 * The x position offset of the label relative to the tick position
10615 * on the axis.
10616 *
10617 * @sample {highcharts} highcharts/xaxis/labels-x/
10618 * Y axis labels placed on grid lines
10619 */
10620 x: 0
10621
10622 /**
10623 * The y position offset of the label relative to the tick position
10624 * on the axis. The default makes it adapt to the font size on
10625 * bottom axis.
10626 *
10627 * @type {Number}
10628 * @sample {highcharts} highcharts/xaxis/labels-x/
10629 * Y axis labels placed on grid lines
10630 * @default null
10631 * @apioption xAxis.labels.y
10632 */
10633
10634 /**
10635 * The Z index for the axis labels.
10636 *
10637 * @type {Number}
10638 * @default 7
10639 * @apioption xAxis.labels.zIndex
10640 */
10641 },
10642
10643 /**
10644 * Index of another axis that this axis is linked to. When an axis is
10645 * linked to a master axis, it will take the same extremes as
10646 * the master, but as assigned by min or max or by setExtremes.
10647 * It can be used to show additional info, or to ease reading the
10648 * chart by duplicating the scales.
10649 *
10650 * @type {Number}
10651 * @sample {highcharts} highcharts/xaxis/linkedto/
10652 * Different string formats of the same date
10653 * @sample {highcharts} highcharts/yaxis/linkedto/
10654 * Y values on both sides
10655 * @default null
10656 * @since 2.0.2
10657 * @product highcharts highstock
10658 * @apioption xAxis.linkedTo
10659 */
10660
10661 /**
10662 * The maximum value of the axis. If `null`, the max value is
10663 * automatically calculated.
10664 *
10665 * If the `endOnTick` option is true, the `max` value might
10666 * be rounded up.
10667 *
10668 * If a [tickAmount](#yAxis.tickAmount) is set, the axis may be extended
10669 * beyond the set max in order to reach the given number of ticks. The
10670 * same may happen in a chart with multiple axes, determined by [chart.
10671 * alignTicks](#chart), where a `tickAmount` is applied internally.
10672 *
10673 * @type {Number}
10674 * @sample {highcharts} highcharts/yaxis/max-200/
10675 * Y axis max of 200
10676 * @sample {highcharts} highcharts/yaxis/max-logarithmic/
10677 * Y axis max on logarithmic axis
10678 * @sample {highstock} stock/xaxis/min-max/
10679 * Fixed min and max on X axis
10680 * @sample {highmaps} maps/axis/min-max/
10681 * Pre-zoomed to a specific area
10682 * @apioption xAxis.max
10683 */
10684
10685 /**
10686 * Padding of the max value relative to the length of the axis. A
10687 * padding of 0.05 will make a 100px axis 5px longer. This is useful
10688 * when you don't want the highest data value to appear on the edge
10689 * of the plot area. When the axis' `max` option is set or a max extreme
10690 * is set using `axis.setExtremes()`, the maxPadding will be ignored.
10691 *
10692 * @sample {highcharts} highcharts/yaxis/maxpadding/
10693 * Max padding of 0.25 on y axis
10694 * @sample {highstock} stock/xaxis/minpadding-maxpadding/
10695 * Greater min- and maxPadding
10696 * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
10697 * Add some padding
10698 * @default {highcharts} 0.01
10699 * @default {highstock|highmaps} 0
10700 * @since 1.2.0
10701 */
10702 maxPadding: 0.01,
10703
10704 /**
10705 * Deprecated. Use `minRange` instead.
10706 *
10707 * @deprecated
10708 * @type {Number}
10709 * @product highcharts highstock
10710 * @apioption xAxis.maxZoom
10711 */
10712
10713 /**
10714 * The minimum value of the axis. If `null` the min value is
10715 * automatically calculated.
10716 *
10717 * If the `startOnTick` option is true (default), the `min` value might
10718 * be rounded down.
10719 *
10720 * The automatically calculated minimum value is also affected by
10721 * [floor](#yAxis.floor), [softMin](#yAxis.softMin),
10722 * [minPadding](#yAxis.minPadding), [minRange](#yAxis.minRange)
10723 * as well as [series.threshold](#plotOptions.series.threshold)
10724 * and [series.softThreshold](#plotOptions.series.softThreshold).
10725 *
10726 * @type {Number}
10727 * @sample {highcharts} highcharts/yaxis/min-startontick-false/
10728 * -50 with startOnTick to false
10729 * @sample {highcharts} highcharts/yaxis/min-startontick-true/
10730 * -50 with startOnTick true by default
10731 * @sample {highstock} stock/xaxis/min-max/
10732 * Set min and max on X axis
10733 * @sample {highmaps} maps/axis/min-max/
10734 * Pre-zoomed to a specific area
10735 * @apioption xAxis.min
10736 */
10737
10738 /**
10739 * The dash or dot style of the minor grid lines. For possible values,
10740 * see [this demonstration](http://jsfiddle.net/gh/get/library/pure/
10741 * highcharts/highcharts/tree/master/samples/highcharts/plotoptions/
10742 * series-dashstyle-all/).
10743 *
10744 * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
10745 * "ShortDashDotDot", "Dot", "Dash" ,"LongDash",
10746 * "DashDot", "LongDashDot", "LongDashDotDot"]
10747 * @type {String}
10748 * @sample {highcharts} highcharts/yaxis/minorgridlinedashstyle/
10749 * Long dashes on minor grid lines
10750 * @sample {highstock} stock/xaxis/minorgridlinedashstyle/
10751 * Long dashes on minor grid lines
10752 * @default Solid
10753 * @since 1.2
10754 * @apioption xAxis.minorGridLineDashStyle
10755 */
10756
10757 /**
10758 * Specific tick interval in axis units for the minor ticks.
10759 * On a linear axis, if `"auto"`, the minor tick interval is
10760 * calculated as a fifth of the tickInterval. If `null`, minor
10761 * ticks are not shown.
10762 *
10763 * On logarithmic axes, the unit is the power of the value. For example,
10764 * setting the minorTickInterval to 1 puts one tick on each of 0.1,
10765 * 1, 10, 100 etc. Setting the minorTickInterval to 0.1 produces 9
10766 * ticks between 1 and 10, 10 and 100 etc.
10767 *
10768 * If user settings dictate minor ticks to become too dense, they don't
10769 * make sense, and will be ignored to prevent performance problems.
10770 *
10771 * @type {Number|String}
10772 * @sample {highcharts} highcharts/yaxis/minortickinterval-null/
10773 * Null by default
10774 * @sample {highcharts} highcharts/yaxis/minortickinterval-5/
10775 * 5 units
10776 * @sample {highcharts} highcharts/yaxis/minortickinterval-log-auto/
10777 * "auto"
10778 * @sample {highcharts} highcharts/yaxis/minortickinterval-log/
10779 * 0.1
10780 * @sample {highstock} stock/demo/basic-line/
10781 * Null by default
10782 * @sample {highstock} stock/xaxis/minortickinterval-auto/
10783 * "auto"
10784 * @apioption xAxis.minorTickInterval
10785 */
10786
10787 /**
10788 * The pixel length of the minor tick marks.
10789 *
10790 * @sample {highcharts} highcharts/yaxis/minorticklength/
10791 * 10px on Y axis
10792 * @sample {highstock} stock/xaxis/minorticks/
10793 * 10px on Y axis
10794 */
10795 minorTickLength: 2,
10796
10797 /**
10798 * The position of the minor tick marks relative to the axis line.
10799 * Can be one of `inside` and `outside`.
10800 *
10801 * @validvalue ["inside", "outside"]
10802 * @sample {highcharts} highcharts/yaxis/minortickposition-outside/
10803 * Outside by default
10804 * @sample {highcharts} highcharts/yaxis/minortickposition-inside/
10805 * Inside
10806 * @sample {highstock} stock/xaxis/minorticks/
10807 * Inside
10808 */
10809 minorTickPosition: 'outside',
10810
10811 /**
10812 * Enable or disable minor ticks. Unless
10813 * [minorTickInterval](#xAxis.minorTickInterval) is set, the tick
10814 * interval is calculated as a fifth of the `tickInterval`.
10815 *
10816 * On a logarithmic axis, minor ticks are laid out based on a best
10817 * guess, attempting to enter approximately 5 minor ticks between
10818 * each major tick.
10819 *
10820 * Prior to v6.0.0, ticks were unabled in auto layout by setting
10821 * `minorTickInterval` to `"auto"`.
10822 *
10823 * @productdesc {highcharts}
10824 * On axes using [categories](#xAxis.categories), minor ticks are not
10825 * supported.
10826 *
10827 * @type {Boolean}
10828 * @default false
10829 * @since 6.0.0
10830 * @sample {highcharts} highcharts/yaxis/minorticks-true/
10831 * Enabled on linear Y axis
10832 * @apioption xAxis.minorTicks
10833 */
10834
10835 /**
10836 * The pixel width of the minor tick mark.
10837 *
10838 * @type {Number}
10839 * @sample {highcharts} highcharts/yaxis/minortickwidth/
10840 * 3px width
10841 * @sample {highstock} stock/xaxis/minorticks/
10842 * 1px width
10843 * @default 0
10844 * @apioption xAxis.minorTickWidth
10845 */
10846
10847 /**
10848 * Padding of the min value relative to the length of the axis. A
10849 * padding of 0.05 will make a 100px axis 5px longer. This is useful
10850 * when you don't want the lowest data value to appear on the edge
10851 * of the plot area. When the axis' `min` option is set or a min extreme
10852 * is set using `axis.setExtremes()`, the minPadding will be ignored.
10853 *
10854 * @sample {highcharts} highcharts/yaxis/minpadding/
10855 * Min padding of 0.2
10856 * @sample {highstock} stock/xaxis/minpadding-maxpadding/
10857 * Greater min- and maxPadding
10858 * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
10859 * Add some padding
10860 * @default {highcharts} 0.01
10861 * @default {highstock|highmaps} 0
10862 * @since 1.2.0
10863 */
10864 minPadding: 0.01,
10865
10866 /**
10867 * The minimum range to display on this axis. The entire axis will not
10868 * be allowed to span over a smaller interval than this. For example,
10869 * for a datetime axis the main unit is milliseconds. If minRange is
10870 * set to 3600000, you can't zoom in more than to one hour.
10871 *
10872 * The default minRange for the x axis is five times the smallest
10873 * interval between any of the data points.
10874 *
10875 * On a logarithmic axis, the unit for the minimum range is the power.
10876 * So a minRange of 1 means that the axis can be zoomed to 10-100,
10877 * 100-1000, 1000-10000 etc.
10878 *
10879 * Note that the `minPadding`, `maxPadding`, `startOnTick` and
10880 * `endOnTick` settings also affect how the extremes of the axis
10881 * are computed.
10882 *
10883 * @type {Number}
10884 * @sample {highcharts} highcharts/xaxis/minrange/
10885 * Minimum range of 5
10886 * @sample {highstock} stock/xaxis/minrange/
10887 * Max zoom of 6 months overrides user selections
10888 * @sample {highmaps} maps/axis/minrange/
10889 * Minimum range of 1000
10890 * @apioption xAxis.minRange
10891 */
10892
10893 /**
10894 * The minimum tick interval allowed in axis values. For example on
10895 * zooming in on an axis with daily data, this can be used to prevent
10896 * the axis from showing hours. Defaults to the closest distance between
10897 * two points on the axis.
10898 *
10899 * @type {Number}
10900 * @since 2.3.0
10901 * @apioption xAxis.minTickInterval
10902 */
10903
10904 /**
10905 * The distance in pixels from the plot area to the axis line.
10906 * A positive offset moves the axis with it's line, labels and ticks
10907 * away from the plot area. This is typically used when two or more
10908 * axes are displayed on the same side of the plot. With multiple
10909 * axes the offset is dynamically adjusted to avoid collision, this
10910 * can be overridden by setting offset explicitly.
10911 *
10912 * @type {Number}
10913 * @sample {highcharts} highcharts/yaxis/offset/
10914 * Y axis offset of 70
10915 * @sample {highcharts} highcharts/yaxis/offset-centered/
10916 * Axes positioned in the center of the plot
10917 * @sample {highstock} stock/xaxis/offset/
10918 * Y axis offset by 70 px
10919 * @default 0
10920 * @apioption xAxis.offset
10921 */
10922
10923 /**
10924 * Whether to display the axis on the opposite side of the normal. The
10925 * normal is on the left side for vertical axes and bottom for
10926 * horizontal, so the opposite sides will be right and top respectively.
10927 * This is typically used with dual or multiple axes.
10928 *
10929 * @type {Boolean}
10930 * @sample {highcharts} highcharts/yaxis/opposite/
10931 * Secondary Y axis opposite
10932 * @sample {highstock} stock/xaxis/opposite/
10933 * Y axis on left side
10934 * @default false
10935 * @apioption xAxis.opposite
10936 */
10937
10938 /**
10939 * Whether to reverse the axis so that the highest number is closest
10940 * to the origin. If the chart is inverted, the x axis is reversed by
10941 * default.
10942 *
10943 * @type {Boolean}
10944 * @sample {highcharts} highcharts/yaxis/reversed/
10945 * Reversed Y axis
10946 * @sample {highstock} stock/xaxis/reversed/
10947 * Reversed Y axis
10948 * @default false
10949 * @apioption xAxis.reversed
10950 */
10951 // reversed: false,
10952
10953 /**
10954 * Whether to show the last tick label. Defaults to `true` on cartesian
10955 * charts, and `false` on polar charts.
10956 *
10957 * @type {Boolean}
10958 * @sample {highcharts} highcharts/xaxis/showlastlabel-true/
10959 * Set to true on X axis
10960 * @sample {highstock} stock/xaxis/showfirstlabel/
10961 * Labels below plot lines on Y axis
10962 * @default true
10963 * @product highcharts highstock
10964 * @apioption xAxis.showLastLabel
10965 */
10966
10967 /**
10968 * For datetime axes, this decides where to put the tick between weeks.
10969 * 0 = Sunday, 1 = Monday.
10970 *
10971 * @sample {highcharts} highcharts/xaxis/startofweek-monday/
10972 * Monday by default
10973 * @sample {highcharts} highcharts/xaxis/startofweek-sunday/
10974 * Sunday
10975 * @sample {highstock} stock/xaxis/startofweek-1
10976 * Monday by default
10977 * @sample {highstock} stock/xaxis/startofweek-0
10978 * Sunday
10979 * @product highcharts highstock
10980 */
10981 startOfWeek: 1,
10982
10983 /**
10984 * Whether to force the axis to start on a tick. Use this option with
10985 * the `minPadding` option to control the axis start.
10986 *
10987 * @productdesc {highstock}
10988 * In Highstock, `startOnTick` is always false when the navigator is
10989 * enabled, to prevent jumpy scrolling.
10990 *
10991 * @sample {highcharts} highcharts/xaxis/startontick-false/
10992 * False by default
10993 * @sample {highcharts} highcharts/xaxis/startontick-true/
10994 * True
10995 * @sample {highstock} stock/xaxis/endontick/
10996 * False for Y axis
10997 * @since 1.2.0
10998 */
10999 startOnTick: false,
11000
11001 /**
11002 * The pixel length of the main tick marks.
11003 *
11004 * @sample {highcharts} highcharts/xaxis/ticklength/
11005 * 20 px tick length on the X axis
11006 * @sample {highstock} stock/xaxis/ticks/
11007 * Formatted ticks on X axis
11008 */
11009 tickLength: 10,
11010
11011 /**
11012 * For categorized axes only. If `on` the tick mark is placed in the
11013 * center of the category, if `between` the tick mark is placed between
11014 * categories. The default is `between` if the `tickInterval` is 1,
11015 * else `on`.
11016 *
11017 * @validvalue [null, "on", "between"]
11018 * @sample {highcharts} highcharts/xaxis/tickmarkplacement-between/
11019 * "between" by default
11020 * @sample {highcharts} highcharts/xaxis/tickmarkplacement-on/
11021 * "on"
11022 * @product highcharts
11023 */
11024 tickmarkPlacement: 'between',
11025
11026 /**
11027 * If tickInterval is `null` this option sets the approximate pixel
11028 * interval of the tick marks. Not applicable to categorized axis.
11029 *
11030 * The tick interval is also influenced by the [minTickInterval](#xAxis.
11031 * minTickInterval) option, that, by default prevents ticks from being
11032 * denser than the data points.
11033 *
11034 * @see [tickInterval](#xAxis.tickInterval),
11035 * [tickPositioner](#xAxis.tickPositioner),
11036 * [tickPositions](#xAxis.tickPositions).
11037 * @sample {highcharts} highcharts/xaxis/tickpixelinterval-50/
11038 * 50 px on X axis
11039 * @sample {highstock} stock/xaxis/tickpixelinterval/
11040 * 200 px on X axis
11041 */
11042 tickPixelInterval: 100,
11043
11044 /**
11045 * The position of the major tick marks relative to the axis line.
11046 * Can be one of `inside` and `outside`.
11047 *
11048 * @validvalue ["inside", "outside"]
11049 * @sample {highcharts} highcharts/xaxis/tickposition-outside/
11050 * "outside" by default
11051 * @sample {highcharts} highcharts/xaxis/tickposition-inside/
11052 * "inside"
11053 * @sample {highstock} stock/xaxis/ticks/
11054 * Formatted ticks on X axis
11055 */
11056 tickPosition: 'outside',
11057
11058 /**
11059 * The axis title, showing next to the axis line.
11060 *
11061 * @productdesc {highmaps}
11062 * In Highmaps, the axis is hidden by default, but adding an axis title
11063 * is still possible. X axis and Y axis titles will appear at the bottom
11064 * and left by default.
11065 */
11066 title: {
11067
11068 /**
11069 * Alignment of the title relative to the axis values. Possible
11070 * values are "low", "middle" or "high".
11071 *
11072 * @validvalue ["low", "middle", "high"]
11073 * @sample {highcharts} highcharts/xaxis/title-align-low/
11074 * "low"
11075 * @sample {highcharts} highcharts/xaxis/title-align-center/
11076 * "middle" by default
11077 * @sample {highcharts} highcharts/xaxis/title-align-high/
11078 * "high"
11079 * @sample {highcharts} highcharts/yaxis/title-offset/
11080 * Place the Y axis title on top of the axis
11081 * @sample {highstock} stock/xaxis/title-align/
11082 * Aligned to "high" value
11083 */
11084 align: 'middle'
11085
11086
11087 },
11088
11089 /**
11090 * The type of axis. Can be one of `linear`, `logarithmic`, `datetime`
11091 * or `category`. In a datetime axis, the numbers are given in
11092 * milliseconds, and tick marks are placed on appropriate values like
11093 * full hours or days. In a category axis, the
11094 * [point names](#series.line.data.name) of the chart's series are used
11095 * for categories, if not a [categories](#xAxis.categories) array is
11096 * defined.
11097 *
11098 * @validvalue ["linear", "logarithmic", "datetime", "category"]
11099 * @sample {highcharts} highcharts/xaxis/type-linear/
11100 * Linear
11101 * @sample {highcharts} highcharts/yaxis/type-log/
11102 * Logarithmic
11103 * @sample {highcharts} highcharts/yaxis/type-log-minorgrid/
11104 * Logarithmic with minor grid lines
11105 * @sample {highcharts} highcharts/xaxis/type-log-both/
11106 * Logarithmic on two axes
11107 * @sample {highcharts} highcharts/yaxis/type-log-negative/
11108 * Logarithmic with extension to emulate negative values
11109 * @product highcharts
11110 */
11111 type: 'linear'
11112
11113
11114 },
11115
11116 /**
11117 * The Y axis or value axis. Normally this is the vertical axis,
11118 * though if the chart is inverted this is the horizontal axis.
11119 * In case of multiple axes, the yAxis node is an array of
11120 * configuration objects.
11121 *
11122 * See [the Axis object](#Axis) for programmatic access to the axis.
11123 *
11124 * @extends xAxis
11125 * @excluding ordinal,overscroll
11126 * @optionparent yAxis
11127 */
11128 defaultYAxisOptions: {
11129 /**
11130 * @productdesc {highstock}
11131 * In Highstock, `endOnTick` is always false when the navigator is
11132 * enabled, to prevent jumpy scrolling.
11133 */
11134 endOnTick: true,
11135
11136 /**
11137 * @productdesc {highstock}
11138 * In Highstock 1.x, the Y axis was placed on the left side by default.
11139 *
11140 * @sample {highcharts} highcharts/yaxis/opposite/
11141 * Secondary Y axis opposite
11142 * @sample {highstock} stock/xaxis/opposite/
11143 * Y axis on left side
11144 * @default {highstock} true
11145 * @default {highcharts} false
11146 * @product highstock highcharts
11147 * @apioption yAxis.opposite
11148 */
11149
11150 /**
11151 * @see [tickInterval](#xAxis.tickInterval),
11152 * [tickPositioner](#xAxis.tickPositioner),
11153 * [tickPositions](#xAxis.tickPositions).
11154 */
11155 tickPixelInterval: 72,
11156
11157 showLastLabel: true,
11158
11159 /**
11160 * @extends xAxis.labels
11161 */
11162 labels: {
11163 /**
11164 * What part of the string the given position is anchored to. Can
11165 * be one of `"left"`, `"center"` or `"right"`. The exact position
11166 * also depends on the `labels.x` setting.
11167 *
11168 * Angular gauges and solid gauges defaults to `center`.
11169 *
11170 * @validvalue ["left", "center", "right"]
11171 * @type {String}
11172 * @sample {highcharts} highcharts/yaxis/labels-align-left/
11173 * Left
11174 * @default {highcharts|highmaps} right
11175 * @default {highstock} left
11176 * @apioption yAxis.labels.align
11177 */
11178
11179 /**
11180 * The x position offset of the label relative to the tick position
11181 * on the axis. Defaults to -15 for left axis, 15 for right axis.
11182 *
11183 * @sample {highcharts} highcharts/xaxis/labels-x/
11184 * Y axis labels placed on grid lines
11185 */
11186 x: -8
11187 },
11188
11189 /**
11190 * @productdesc {highmaps}
11191 * In Highmaps, the axis line is hidden by default, because the axis is
11192 * not visible by default.
11193 *
11194 * @apioption yAxis.lineColor
11195 */
11196
11197 /**
11198 * @sample {highcharts} highcharts/yaxis/min-startontick-false/
11199 * -50 with startOnTick to false
11200 * @sample {highcharts} highcharts/yaxis/min-startontick-true/
11201 * -50 with startOnTick true by default
11202 * @sample {highstock} stock/yaxis/min-max/
11203 * Fixed min and max on Y axis
11204 * @sample {highmaps} maps/axis/min-max/
11205 * Pre-zoomed to a specific area
11206 * @apioption yAxis.min
11207 */
11208
11209 /**
11210 * @sample {highcharts} highcharts/yaxis/max-200/
11211 * Y axis max of 200
11212 * @sample {highcharts} highcharts/yaxis/max-logarithmic/
11213 * Y axis max on logarithmic axis
11214 * @sample {highstock} stock/yaxis/min-max/
11215 * Fixed min and max on Y axis
11216 * @sample {highmaps} maps/axis/min-max/
11217 * Pre-zoomed to a specific area
11218 * @apioption yAxis.max
11219 */
11220
11221 /**
11222 * Padding of the max value relative to the length of the axis. A
11223 * padding of 0.05 will make a 100px axis 5px longer. This is useful
11224 * when you don't want the highest data value to appear on the edge
11225 * of the plot area. When the axis' `max` option is set or a max extreme
11226 * is set using `axis.setExtremes()`, the maxPadding will be ignored.
11227 *
11228 * @sample {highcharts} highcharts/yaxis/maxpadding-02/
11229 * Max padding of 0.2
11230 * @sample {highstock} stock/xaxis/minpadding-maxpadding/
11231 * Greater min- and maxPadding
11232 * @since 1.2.0
11233 * @product highcharts highstock
11234 */
11235 maxPadding: 0.05,
11236
11237 /**
11238 * Padding of the min value relative to the length of the axis. A
11239 * padding of 0.05 will make a 100px axis 5px longer. This is useful
11240 * when you don't want the lowest data value to appear on the edge
11241 * of the plot area. When the axis' `min` option is set or a max extreme
11242 * is set using `axis.setExtremes()`, the maxPadding will be ignored.
11243 *
11244 * @sample {highcharts} highcharts/yaxis/minpadding/
11245 * Min padding of 0.2
11246 * @sample {highstock} stock/xaxis/minpadding-maxpadding/
11247 * Greater min- and maxPadding
11248 * @since 1.2.0
11249 * @product highcharts highstock
11250 */
11251 minPadding: 0.05,
11252
11253 /**
11254 * Whether to force the axis to start on a tick. Use this option with
11255 * the `maxPadding` option to control the axis start.
11256 *
11257 * @sample {highcharts} highcharts/xaxis/startontick-false/
11258 * False by default
11259 * @sample {highcharts} highcharts/xaxis/startontick-true/
11260 * True
11261 * @sample {highstock} stock/xaxis/endontick/
11262 * False for Y axis
11263 * @since 1.2.0
11264 * @product highcharts highstock
11265 */
11266 startOnTick: true,
11267
11268 /**
11269 * @extends xAxis.title
11270 */
11271 title: {
11272
11273 /**
11274 * The rotation of the text in degrees. 0 is horizontal, 270 is
11275 * vertical reading from bottom to top.
11276 *
11277 * @sample {highcharts} highcharts/yaxis/title-offset/
11278 * Horizontal
11279 */
11280 rotation: 270,
11281
11282 /**
11283 * The actual text of the axis title. Horizontal texts can contain
11284 * HTML, but rotated texts are painted using vector techniques and
11285 * must be clean text. The Y axis title is disabled by setting the
11286 * `text` option to `null`.
11287 *
11288 * @sample {highcharts} highcharts/xaxis/title-text/
11289 * Custom HTML
11290 * @default {highcharts} Values
11291 * @default {highstock} null
11292 * @product highcharts highstock
11293 */
11294 text: 'Values'
11295 },
11296
11297 /**
11298 * The stack labels show the total value for each bar in a stacked
11299 * column or bar chart. The label will be placed on top of positive
11300 * columns and below negative columns. In case of an inverted column
11301 * chart or a bar chart the label is placed to the right of positive
11302 * bars and to the left of negative bars.
11303 *
11304 * @product highcharts
11305 */
11306 stackLabels: {
11307
11308 /**
11309 * Allow the stack labels to overlap.
11310 *
11311 * @sample {highcharts}
11312 * highcharts/yaxis/stacklabels-allowoverlap-false/
11313 * Default false
11314 * @since 5.0.13
11315 * @product highcharts
11316 */
11317 allowOverlap: false,
11318
11319 /**
11320 * Enable or disable the stack total labels.
11321 *
11322 * @sample {highcharts} highcharts/yaxis/stacklabels-enabled/
11323 * Enabled stack total labels
11324 * @since 2.1.5
11325 * @product highcharts
11326 */
11327 enabled: false,
11328
11329 /**
11330 * Callback JavaScript function to format the label. The value is
11331 * given by `this.total`.
11332 *
11333 * @default function() { return this.total; }
11334 *
11335 * @type {Function}
11336 * @sample {highcharts} highcharts/yaxis/stacklabels-formatter/
11337 * Added units to stack total value
11338 * @since 2.1.5
11339 * @product highcharts
11340 */
11341 formatter: function() {
11342 return H.numberFormat(this.total, -1);
11343 }
11344
11345 }
11346
11347 },
11348
11349 /**
11350 * These options extend the defaultOptions for left axes.
11351 *
11352 * @private
11353 * @type {Object}
11354 */
11355 defaultLeftAxisOptions: {
11356 labels: {
11357 x: -15
11358 },
11359 title: {
11360 rotation: 270
11361 }
11362 },
11363
11364 /**
11365 * These options extend the defaultOptions for right axes.
11366 *
11367 * @private
11368 * @type {Object}
11369 */
11370 defaultRightAxisOptions: {
11371 labels: {
11372 x: 15
11373 },
11374 title: {
11375 rotation: 90
11376 }
11377 },
11378
11379 /**
11380 * These options extend the defaultOptions for bottom axes.
11381 *
11382 * @private
11383 * @type {Object}
11384 */
11385 defaultBottomAxisOptions: {
11386 labels: {
11387 autoRotation: [-45],
11388 x: 0
11389 // overflow: undefined,
11390 // staggerLines: null
11391 },
11392 title: {
11393 rotation: 0
11394 }
11395 },
11396 /**
11397 * These options extend the defaultOptions for top axes.
11398 *
11399 * @private
11400 * @type {Object}
11401 */
11402 defaultTopAxisOptions: {
11403 labels: {
11404 autoRotation: [-45],
11405 x: 0
11406 // overflow: undefined
11407 // staggerLines: null
11408 },
11409 title: {
11410 rotation: 0
11411 }
11412 },
11413
11414 /**
11415 * Overrideable function to initialize the axis.
11416 *
11417 * @see {@link Axis}
11418 */
11419 init: function(chart, userOptions) {
11420
11421
11422 var isXAxis = userOptions.isX,
11423 axis = this;
11424
11425
11426 /**
11427 * The Chart that the axis belongs to.
11428 *
11429 * @name chart
11430 * @memberOf Axis
11431 * @type {Chart}
11432 */
11433 axis.chart = chart;
11434
11435 /**
11436 * Whether the axis is horizontal.
11437 *
11438 * @name horiz
11439 * @memberOf Axis
11440 * @type {Boolean}
11441 */
11442 axis.horiz = chart.inverted && !axis.isZAxis ? !isXAxis : isXAxis;
11443
11444 // Flag, isXAxis
11445 axis.isXAxis = isXAxis;
11446
11447 /**
11448 * The collection where the axis belongs, for example `xAxis`, `yAxis`
11449 * or `colorAxis`. Corresponds to properties on Chart, for example
11450 * {@link Chart.xAxis}.
11451 *
11452 * @name coll
11453 * @memberOf Axis
11454 * @type {String}
11455 */
11456 axis.coll = axis.coll || (isXAxis ? 'xAxis' : 'yAxis');
11457
11458
11459 axis.opposite = userOptions.opposite; // needed in setOptions
11460
11461 /**
11462 * The side on which the axis is rendered. 0 is top, 1 is right, 2 is
11463 * bottom and 3 is left.
11464 *
11465 * @name side
11466 * @memberOf Axis
11467 * @type {Number}
11468 */
11469 axis.side = userOptions.side || (axis.horiz ?
11470 (axis.opposite ? 0 : 2) : // top : bottom
11471 (axis.opposite ? 1 : 3)); // right : left
11472
11473 axis.setOptions(userOptions);
11474
11475
11476 var options = this.options,
11477 type = options.type,
11478 isDatetimeAxis = type === 'datetime';
11479
11480 axis.labelFormatter = options.labels.formatter ||
11481 axis.defaultLabelFormatter; // can be overwritten by dynamic format
11482
11483
11484 // Flag, stagger lines or not
11485 axis.userOptions = userOptions;
11486
11487 axis.minPixelPadding = 0;
11488
11489
11490 /**
11491 * Whether the axis is reversed. Based on the `axis.reversed`,
11492 * option, but inverted charts have reversed xAxis by default.
11493 *
11494 * @name reversed
11495 * @memberOf Axis
11496 * @type {Boolean}
11497 */
11498 axis.reversed = options.reversed;
11499 axis.visible = options.visible !== false;
11500 axis.zoomEnabled = options.zoomEnabled !== false;
11501
11502 // Initial categories
11503 axis.hasNames = type === 'category' || options.categories === true;
11504 axis.categories = options.categories || axis.hasNames;
11505 axis.names = axis.names || []; // Preserve on update (#3830)
11506
11507 // Placeholder for plotlines and plotbands groups
11508 axis.plotLinesAndBandsGroups = {};
11509
11510 // Shorthand types
11511 axis.isLog = type === 'logarithmic';
11512 axis.isDatetimeAxis = isDatetimeAxis;
11513 axis.positiveValuesOnly = axis.isLog && !axis.allowNegativeLog;
11514
11515 // Flag, if axis is linked to another axis
11516 axis.isLinked = defined(options.linkedTo);
11517
11518 // Major ticks
11519 axis.ticks = {};
11520 axis.labelEdge = [];
11521 // Minor ticks
11522 axis.minorTicks = {};
11523
11524 // List of plotLines/Bands
11525 axis.plotLinesAndBands = [];
11526
11527 // Alternate bands
11528 axis.alternateBands = {};
11529
11530 // Axis metrics
11531 axis.len = 0;
11532 axis.minRange = axis.userMinRange = options.minRange || options.maxZoom;
11533 axis.range = options.range;
11534 axis.offset = options.offset || 0;
11535
11536
11537 // Dictionary for stacks
11538 axis.stacks = {};
11539 axis.oldStacks = {};
11540 axis.stacksTouched = 0;
11541
11542
11543 /**
11544 * The maximum value of the axis. In a logarithmic axis, this is the
11545 * logarithm of the real value, and the real value can be obtained from
11546 * {@link Axis#getExtremes}.
11547 *
11548 * @name max
11549 * @memberOf Axis
11550 * @type {Number}
11551 */
11552 axis.max = null;
11553 /**
11554 * The minimum value of the axis. In a logarithmic axis, this is the
11555 * logarithm of the real value, and the real value can be obtained from
11556 * {@link Axis#getExtremes}.
11557 *
11558 * @name min
11559 * @memberOf Axis
11560 * @type {Number}
11561 */
11562 axis.min = null;
11563
11564
11565 /**
11566 * The processed crosshair options.
11567 *
11568 * @name crosshair
11569 * @memberOf Axis
11570 * @type {AxisCrosshairOptions}
11571 */
11572 axis.crosshair = pick(
11573 options.crosshair,
11574 splat(chart.options.tooltip.crosshairs)[isXAxis ? 0 : 1],
11575 false
11576 );
11577
11578 var events = axis.options.events;
11579
11580 // Register. Don't add it again on Axis.update().
11581 if (inArray(axis, chart.axes) === -1) { //
11582 if (isXAxis) { // #2713
11583 chart.axes.splice(chart.xAxis.length, 0, axis);
11584 } else {
11585 chart.axes.push(axis);
11586 }
11587
11588 chart[axis.coll].push(axis);
11589 }
11590
11591 /**
11592 * All series associated to the axis.
11593 *
11594 * @name series
11595 * @memberOf Axis
11596 * @type {Array.<Series>}
11597 */
11598 axis.series = axis.series || []; // populated by Series
11599
11600 // Reversed axis
11601 if (
11602 chart.inverted &&
11603 !axis.isZAxis &&
11604 isXAxis &&
11605 axis.reversed === undefined
11606 ) {
11607 axis.reversed = true;
11608 }
11609
11610 // register event listeners
11611 objectEach(events, function(event, eventType) {
11612 addEvent(axis, eventType, event);
11613 });
11614
11615 // extend logarithmic axis
11616 axis.lin2log = options.linearToLogConverter || axis.lin2log;
11617 if (axis.isLog) {
11618 axis.val2lin = axis.log2lin;
11619 axis.lin2val = axis.lin2log;
11620 }
11621 },
11622
11623 /**
11624 * Merge and set options.
11625 *
11626 * @private
11627 */
11628 setOptions: function(userOptions) {
11629 this.options = merge(
11630 this.defaultOptions,
11631 this.coll === 'yAxis' && this.defaultYAxisOptions, [
11632 this.defaultTopAxisOptions,
11633 this.defaultRightAxisOptions,
11634 this.defaultBottomAxisOptions,
11635 this.defaultLeftAxisOptions
11636 ][this.side],
11637 merge(
11638 defaultOptions[this.coll], // if set in setOptions (#1053)
11639 userOptions
11640 )
11641 );
11642 },
11643
11644 /**
11645 * The default label formatter. The context is a special config object for
11646 * the label. In apps, use the {@link
11647 * https://api.highcharts.com/highcharts/xAxis.labels.formatter|
11648 * labels.formatter} instead except when a modification is needed.
11649 *
11650 * @private
11651 */
11652 defaultLabelFormatter: function() {
11653 var axis = this.axis,
11654 value = this.value,
11655 categories = axis.categories,
11656 dateTimeLabelFormat = this.dateTimeLabelFormat,
11657 lang = defaultOptions.lang,
11658 numericSymbols = lang.numericSymbols,
11659 numSymMagnitude = lang.numericSymbolMagnitude || 1000,
11660 i = numericSymbols && numericSymbols.length,
11661 multi,
11662 ret,
11663 formatOption = axis.options.labels.format,
11664
11665 // make sure the same symbol is added for all labels on a linear
11666 // axis
11667 numericSymbolDetector = axis.isLog ?
11668 Math.abs(value) :
11669 axis.tickInterval;
11670
11671 if (formatOption) {
11672 ret = format(formatOption, this);
11673
11674 } else if (categories) {
11675 ret = value;
11676
11677 } else if (dateTimeLabelFormat) { // datetime axis
11678 ret = H.dateFormat(dateTimeLabelFormat, value);
11679
11680 } else if (i && numericSymbolDetector >= 1000) {
11681 // Decide whether we should add a numeric symbol like k (thousands)
11682 // or M (millions). If we are to enable this in tooltip or other
11683 // places as well, we can move this logic to the numberFormatter and
11684 // enable it by a parameter.
11685 while (i-- && ret === undefined) {
11686 multi = Math.pow(numSymMagnitude, i + 1);
11687 if (
11688 // Only accept a numeric symbol when the distance is more
11689 // than a full unit. So for example if the symbol is k, we
11690 // don't accept numbers like 0.5k.
11691 numericSymbolDetector >= multi &&
11692 // Accept one decimal before the symbol. Accepts 0.5k but
11693 // not 0.25k. How does this work with the previous?
11694 (value * 10) % multi === 0 &&
11695 numericSymbols[i] !== null &&
11696 value !== 0
11697 ) { // #5480
11698 ret = H.numberFormat(value / multi, -1) + numericSymbols[i];
11699 }
11700 }
11701 }
11702
11703 if (ret === undefined) {
11704 if (Math.abs(value) >= 10000) { // add thousands separators
11705 ret = H.numberFormat(value, -1);
11706 } else { // small numbers
11707 ret = H.numberFormat(value, -1, undefined, ''); // #2466
11708 }
11709 }
11710
11711 return ret;
11712 },
11713
11714 /**
11715 * Get the minimum and maximum for the series of each axis. The function
11716 * analyzes the axis series and updates `this.dataMin` and `this.dataMax`.
11717 *
11718 * @private
11719 */
11720 getSeriesExtremes: function() {
11721 var axis = this,
11722 chart = axis.chart;
11723 axis.hasVisibleSeries = false;
11724
11725 // Reset properties in case we're redrawing (#3353)
11726 axis.dataMin = axis.dataMax = axis.threshold = null;
11727 axis.softThreshold = !axis.isXAxis;
11728
11729 if (axis.buildStacks) {
11730 axis.buildStacks();
11731 }
11732
11733 // loop through this axis' series
11734 each(axis.series, function(series) {
11735
11736 if (series.visible || !chart.options.chart.ignoreHiddenSeries) {
11737
11738 var seriesOptions = series.options,
11739 xData,
11740 threshold = seriesOptions.threshold,
11741 seriesDataMin,
11742 seriesDataMax;
11743
11744 axis.hasVisibleSeries = true;
11745
11746 // Validate threshold in logarithmic axes
11747 if (axis.positiveValuesOnly && threshold <= 0) {
11748 threshold = null;
11749 }
11750
11751 // Get dataMin and dataMax for X axes
11752 if (axis.isXAxis) {
11753 xData = series.xData;
11754 if (xData.length) {
11755 // If xData contains values which is not numbers, then
11756 // filter them out. To prevent performance hit, we only
11757 // do this after we have already found seriesDataMin
11758 // because in most cases all data is valid. #5234.
11759 seriesDataMin = arrayMin(xData);
11760 seriesDataMax = arrayMax(xData);
11761
11762 if (!isNumber(seriesDataMin) &&
11763 !(seriesDataMin instanceof Date) // #5010
11764 ) {
11765 xData = grep(xData, isNumber);
11766 // Do it again with valid data
11767 seriesDataMin = arrayMin(xData);
11768 }
11769
11770 axis.dataMin = Math.min(
11771 pick(axis.dataMin, xData[0], seriesDataMin),
11772 seriesDataMin
11773 );
11774 axis.dataMax = Math.max(
11775 pick(axis.dataMax, xData[0], seriesDataMax),
11776 seriesDataMax
11777 );
11778 }
11779
11780 // Get dataMin and dataMax for Y axes, as well as handle
11781 // stacking and processed data
11782 } else {
11783
11784 // Get this particular series extremes
11785 series.getExtremes();
11786 seriesDataMax = series.dataMax;
11787 seriesDataMin = series.dataMin;
11788
11789 // Get the dataMin and dataMax so far. If percentage is
11790 // used, the min and max are always 0 and 100. If
11791 // seriesDataMin and seriesDataMax is null, then series
11792 // doesn't have active y data, we continue with nulls
11793 if (defined(seriesDataMin) && defined(seriesDataMax)) {
11794 axis.dataMin = Math.min(
11795 pick(axis.dataMin, seriesDataMin),
11796 seriesDataMin
11797 );
11798 axis.dataMax = Math.max(
11799 pick(axis.dataMax, seriesDataMax),
11800 seriesDataMax
11801 );
11802 }
11803
11804 // Adjust to threshold
11805 if (defined(threshold)) {
11806 axis.threshold = threshold;
11807 }
11808 // If any series has a hard threshold, it takes precedence
11809 if (!seriesOptions.softThreshold ||
11810 axis.positiveValuesOnly
11811 ) {
11812 axis.softThreshold = false;
11813 }
11814 }
11815 }
11816 });
11817 },
11818
11819 /**
11820 * Translate from axis value to pixel position on the chart, or back. Use
11821 * the `toPixels` and `toValue` functions in applications.
11822 *
11823 * @private
11824 */
11825 translate: function(
11826 val,
11827 backwards,
11828 cvsCoord,
11829 old,
11830 handleLog,
11831 pointPlacement
11832 ) {
11833 var axis = this.linkedParent || this, // #1417
11834 sign = 1,
11835 cvsOffset = 0,
11836 localA = old ? axis.oldTransA : axis.transA,
11837 localMin = old ? axis.oldMin : axis.min,
11838 returnValue,
11839 minPixelPadding = axis.minPixelPadding,
11840 doPostTranslate = (
11841 axis.isOrdinal ||
11842 axis.isBroken ||
11843 (axis.isLog && handleLog)
11844 ) && axis.lin2val;
11845
11846 if (!localA) {
11847 localA = axis.transA;
11848 }
11849
11850 // In vertical axes, the canvas coordinates start from 0 at the top like
11851 // in SVG.
11852 if (cvsCoord) {
11853 sign *= -1; // canvas coordinates inverts the value
11854 cvsOffset = axis.len;
11855 }
11856
11857 // Handle reversed axis
11858 if (axis.reversed) {
11859 sign *= -1;
11860 cvsOffset -= sign * (axis.sector || axis.len);
11861 }
11862
11863 // From pixels to value
11864 if (backwards) { // reverse translation
11865
11866 val = val * sign + cvsOffset;
11867 val -= minPixelPadding;
11868 returnValue = val / localA + localMin; // from chart pixel to value
11869 if (doPostTranslate) { // log and ordinal axes
11870 returnValue = axis.lin2val(returnValue);
11871 }
11872
11873 // From value to pixels
11874 } else {
11875 if (doPostTranslate) { // log and ordinal axes
11876 val = axis.val2lin(val);
11877 }
11878 returnValue = isNumber(localMin) ?
11879 (
11880 sign * (val - localMin) * localA +
11881 cvsOffset +
11882 (sign * minPixelPadding) +
11883 (isNumber(pointPlacement) ? localA * pointPlacement : 0)
11884 ) :
11885 undefined;
11886 }
11887
11888 return returnValue;
11889 },
11890
11891 /**
11892 * Translate a value in terms of axis units into pixels within the chart.
11893 *
11894 * @param {Number} value
11895 * A value in terms of axis units.
11896 * @param {Boolean} paneCoordinates
11897 * Whether to return the pixel coordinate relative to the chart or
11898 * just the axis/pane itself.
11899 * @return {Number} Pixel position of the value on the chart or axis.
11900 */
11901 toPixels: function(value, paneCoordinates) {
11902 return this.translate(value, false, !this.horiz, null, true) +
11903 (paneCoordinates ? 0 : this.pos);
11904 },
11905
11906 /**
11907 * Translate a pixel position along the axis to a value in terms of axis
11908 * units.
11909 * @param {Number} pixel
11910 * The pixel value coordinate.
11911 * @param {Boolean} paneCoordiantes
11912 * Whether the input pixel is relative to the chart or just the
11913 * axis/pane itself.
11914 * @return {Number} The axis value.
11915 */
11916 toValue: function(pixel, paneCoordinates) {
11917 return this.translate(
11918 pixel - (paneCoordinates ? 0 : this.pos),
11919 true, !this.horiz,
11920 null,
11921 true
11922 );
11923 },
11924
11925 /**
11926 * Create the path for a plot line that goes from the given value on
11927 * this axis, across the plot to the opposite side. Also used internally for
11928 * grid lines and crosshairs.
11929 *
11930 * @param {Number} value
11931 * Axis value.
11932 * @param {Number} [lineWidth=1]
11933 * Used for calculation crisp line coordinates.
11934 * @param {Boolean} [old=false]
11935 * Use old coordinates (for resizing and rescaling).
11936 * @param {Boolean} [force=false]
11937 * If `false`, the function will return null when it falls outside
11938 * the axis bounds.
11939 * @param {Number} [translatedValue]
11940 * If given, return the plot line path of a pixel position on the
11941 * axis.
11942 *
11943 * @return {Array.<String|Number>}
11944 * The SVG path definition for the plot line.
11945 */
11946 getPlotLinePath: function(value, lineWidth, old, force, translatedValue) {
11947 var axis = this,
11948 chart = axis.chart,
11949 axisLeft = axis.left,
11950 axisTop = axis.top,
11951 x1,
11952 y1,
11953 x2,
11954 y2,
11955 cHeight = (old && chart.oldChartHeight) || chart.chartHeight,
11956 cWidth = (old && chart.oldChartWidth) || chart.chartWidth,
11957 skip,
11958 transB = axis.transB,
11959 /**
11960 * Check if x is between a and b. If not, either move to a/b
11961 * or skip, depending on the force parameter.
11962 */
11963 between = function(x, a, b) {
11964 if (x < a || x > b) {
11965 if (force) {
11966 x = Math.min(Math.max(a, x), b);
11967 } else {
11968 skip = true;
11969 }
11970 }
11971 return x;
11972 };
11973
11974 translatedValue = pick(
11975 translatedValue,
11976 axis.translate(value, null, null, old)
11977 );
11978 x1 = x2 = Math.round(translatedValue + transB);
11979 y1 = y2 = Math.round(cHeight - translatedValue - transB);
11980 if (!isNumber(translatedValue)) { // no min or max
11981 skip = true;
11982 force = false; // #7175, don't force it when path is invalid
11983 } else if (axis.horiz) {
11984 y1 = axisTop;
11985 y2 = cHeight - axis.bottom;
11986 x1 = x2 = between(x1, axisLeft, axisLeft + axis.width);
11987 } else {
11988 x1 = axisLeft;
11989 x2 = cWidth - axis.right;
11990 y1 = y2 = between(y1, axisTop, axisTop + axis.height);
11991 }
11992 return skip && !force ?
11993 null :
11994 chart.renderer.crispLine(
11995 ['M', x1, y1, 'L', x2, y2],
11996 lineWidth || 1
11997 );
11998 },
11999
12000 /**
12001 * Internal function to et the tick positions of a linear axis to round
12002 * values like whole tens or every five.
12003 *
12004 * @param {Number} tickInterval
12005 * The normalized tick interval
12006 * @param {Number} min
12007 * Axis minimum.
12008 * @param {Number} max
12009 * Axis maximum.
12010 *
12011 * @return {Array.<Number>}
12012 * An array of axis values where ticks should be placed.
12013 */
12014 getLinearTickPositions: function(tickInterval, min, max) {
12015 var pos,
12016 lastPos,
12017 roundedMin =
12018 correctFloat(Math.floor(min / tickInterval) * tickInterval),
12019 roundedMax =
12020 correctFloat(Math.ceil(max / tickInterval) * tickInterval),
12021 tickPositions = [],
12022 precision;
12023
12024 // When the precision is higher than what we filter out in
12025 // correctFloat, skip it (#6183).
12026 if (correctFloat(roundedMin + tickInterval) === roundedMin) {
12027 precision = 20;
12028 }
12029
12030 // For single points, add a tick regardless of the relative position
12031 // (#2662, #6274)
12032 if (this.single) {
12033 return [min];
12034 }
12035
12036 // Populate the intermediate values
12037 pos = roundedMin;
12038 while (pos <= roundedMax) {
12039
12040 // Place the tick on the rounded value
12041 tickPositions.push(pos);
12042
12043 // Always add the raw tickInterval, not the corrected one.
12044 pos = correctFloat(
12045 pos + tickInterval,
12046 precision
12047 );
12048
12049 // If the interval is not big enough in the current min - max range
12050 // to actually increase the loop variable, we need to break out to
12051 // prevent endless loop. Issue #619
12052 if (pos === lastPos) {
12053 break;
12054 }
12055
12056 // Record the last value
12057 lastPos = pos;
12058 }
12059 return tickPositions;
12060 },
12061
12062 /**
12063 * Resolve the new minorTicks/minorTickInterval options into the legacy
12064 * loosely typed minorTickInterval option.
12065 */
12066 getMinorTickInterval: function() {
12067 var options = this.options;
12068
12069 if (options.minorTicks === true) {
12070 return pick(options.minorTickInterval, 'auto');
12071 }
12072 if (options.minorTicks === false) {
12073 return null;
12074 }
12075 return options.minorTickInterval;
12076 },
12077
12078 /**
12079 * Internal function to return the minor tick positions. For logarithmic
12080 * axes, the same logic as for major ticks is reused.
12081 *
12082 * @return {Array.<Number>}
12083 * An array of axis values where ticks should be placed.
12084 */
12085 getMinorTickPositions: function() {
12086 var axis = this,
12087 options = axis.options,
12088 tickPositions = axis.tickPositions,
12089 minorTickInterval = axis.minorTickInterval,
12090 minorTickPositions = [],
12091 pos,
12092 pointRangePadding = axis.pointRangePadding || 0,
12093 min = axis.min - pointRangePadding, // #1498
12094 max = axis.max + pointRangePadding, // #1498
12095 range = max - min;
12096
12097 // If minor ticks get too dense, they are hard to read, and may cause
12098 // long running script. So we don't draw them.
12099 if (range && range / minorTickInterval < axis.len / 3) { // #3875
12100
12101 if (axis.isLog) {
12102 // For each interval in the major ticks, compute the minor ticks
12103 // separately.
12104 each(this.paddedTicks, function(pos, i, paddedTicks) {
12105 if (i) {
12106 minorTickPositions.push.apply(
12107 minorTickPositions,
12108 axis.getLogTickPositions(
12109 minorTickInterval,
12110 paddedTicks[i - 1],
12111 paddedTicks[i],
12112 true
12113 )
12114 );
12115 }
12116 });
12117
12118 } else if (
12119 axis.isDatetimeAxis &&
12120 this.getMinorTickInterval() === 'auto'
12121 ) { // #1314
12122 minorTickPositions = minorTickPositions.concat(
12123 axis.getTimeTicks(
12124 axis.normalizeTimeTickInterval(minorTickInterval),
12125 min,
12126 max,
12127 options.startOfWeek
12128 )
12129 );
12130 } else {
12131 for (
12132 pos = min + (tickPositions[0] - min) % minorTickInterval; pos <= max; pos += minorTickInterval
12133 ) {
12134 // Very, very, tight grid lines (#5771)
12135 if (pos === minorTickPositions[0]) {
12136 break;
12137 }
12138 minorTickPositions.push(pos);
12139 }
12140 }
12141 }
12142
12143 if (minorTickPositions.length !== 0) {
12144 axis.trimTicks(minorTickPositions); // #3652 #3743 #1498 #6330
12145 }
12146 return minorTickPositions;
12147 },
12148
12149 /**
12150 * Adjust the min and max for the minimum range. Keep in mind that the
12151 * series data is not yet processed, so we don't have information on data
12152 * cropping and grouping, or updated axis.pointRange or series.pointRange.
12153 * The data can't be processed until we have finally established min and
12154 * max.
12155 *
12156 * @private
12157 */
12158 adjustForMinRange: function() {
12159 var axis = this,
12160 options = axis.options,
12161 min = axis.min,
12162 max = axis.max,
12163 zoomOffset,
12164 spaceAvailable,
12165 closestDataRange,
12166 i,
12167 distance,
12168 xData,
12169 loopLength,
12170 minArgs,
12171 maxArgs,
12172 minRange;
12173
12174 // Set the automatic minimum range based on the closest point distance
12175 if (axis.isXAxis && axis.minRange === undefined && !axis.isLog) {
12176
12177 if (defined(options.min) || defined(options.max)) {
12178 axis.minRange = null; // don't do this again
12179
12180 } else {
12181
12182 // Find the closest distance between raw data points, as opposed
12183 // to closestPointRange that applies to processed points
12184 // (cropped and grouped)
12185 each(axis.series, function(series) {
12186 xData = series.xData;
12187 loopLength = series.xIncrement ? 1 : xData.length - 1;
12188 for (i = loopLength; i > 0; i--) {
12189 distance = xData[i] - xData[i - 1];
12190 if (
12191 closestDataRange === undefined ||
12192 distance < closestDataRange
12193 ) {
12194 closestDataRange = distance;
12195 }
12196 }
12197 });
12198 axis.minRange = Math.min(
12199 closestDataRange * 5,
12200 axis.dataMax - axis.dataMin
12201 );
12202 }
12203 }
12204
12205 // if minRange is exceeded, adjust
12206 if (max - min < axis.minRange) {
12207
12208 spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange;
12209 minRange = axis.minRange;
12210 zoomOffset = (minRange - max + min) / 2;
12211
12212 // if min and max options have been set, don't go beyond it
12213 minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)];
12214 // If space is available, stay within the data range
12215 if (spaceAvailable) {
12216 minArgs[2] = axis.isLog ?
12217 axis.log2lin(axis.dataMin) :
12218 axis.dataMin;
12219 }
12220 min = arrayMax(minArgs);
12221
12222 maxArgs = [min + minRange, pick(options.max, min + minRange)];
12223 // If space is availabe, stay within the data range
12224 if (spaceAvailable) {
12225 maxArgs[2] = axis.isLog ?
12226 axis.log2lin(axis.dataMax) :
12227 axis.dataMax;
12228 }
12229
12230 max = arrayMin(maxArgs);
12231
12232 // now if the max is adjusted, adjust the min back
12233 if (max - min < minRange) {
12234 minArgs[0] = max - minRange;
12235 minArgs[1] = pick(options.min, max - minRange);
12236 min = arrayMax(minArgs);
12237 }
12238 }
12239
12240 // Record modified extremes
12241 axis.min = min;
12242 axis.max = max;
12243 },
12244
12245 /**
12246 * Find the closestPointRange across all series.
12247 *
12248 * @private
12249 */
12250 getClosest: function() {
12251 var ret;
12252
12253 if (this.categories) {
12254 ret = 1;
12255 } else {
12256 each(this.series, function(series) {
12257 var seriesClosest = series.closestPointRange,
12258 visible = series.visible ||
12259 !series.chart.options.chart.ignoreHiddenSeries;
12260
12261 if (!series.noSharedTooltip &&
12262 defined(seriesClosest) &&
12263 visible
12264 ) {
12265 ret = defined(ret) ?
12266 Math.min(ret, seriesClosest) :
12267 seriesClosest;
12268 }
12269 });
12270 }
12271 return ret;
12272 },
12273
12274 /**
12275 * When a point name is given and no x, search for the name in the existing
12276 * categories, or if categories aren't provided, search names or create a
12277 * new category (#2522).
12278 *
12279 * @private
12280 *
12281 * @param {Point}
12282 * The point to inspect.
12283 *
12284 * @return {Number}
12285 * The X value that the point is given.
12286 */
12287 nameToX: function(point) {
12288 var explicitCategories = isArray(this.categories),
12289 names = explicitCategories ? this.categories : this.names,
12290 nameX = point.options.x,
12291 x;
12292
12293 point.series.requireSorting = false;
12294
12295 if (!defined(nameX)) {
12296 nameX = this.options.uniqueNames === false ?
12297 point.series.autoIncrement() :
12298 inArray(point.name, names);
12299 }
12300 if (nameX === -1) { // The name is not found in currenct categories
12301 if (!explicitCategories) {
12302 x = names.length;
12303 }
12304 } else {
12305 x = nameX;
12306 }
12307
12308 // Write the last point's name to the names array
12309 if (x !== undefined) {
12310 this.names[x] = point.name;
12311 }
12312
12313 return x;
12314 },
12315
12316 /**
12317 * When changes have been done to series data, update the axis.names.
12318 *
12319 * @private
12320 */
12321 updateNames: function() {
12322 var axis = this;
12323
12324 if (this.names.length > 0) {
12325 this.names.length = 0;
12326 this.minRange = this.userMinRange; // Reset
12327 each(this.series || [], function(series) {
12328
12329 // Reset incrementer (#5928)
12330 series.xIncrement = null;
12331
12332 // When adding a series, points are not yet generated
12333 if (!series.points || series.isDirtyData) {
12334 series.processData();
12335 series.generatePoints();
12336 }
12337
12338 each(series.points, function(point, i) {
12339 var x;
12340 if (point.options) {
12341 x = axis.nameToX(point);
12342 if (x !== undefined && x !== point.x) {
12343 point.x = x;
12344 series.xData[i] = x;
12345 }
12346 }
12347 });
12348 });
12349 }
12350 },
12351
12352 /**
12353 * Update translation information.
12354 *
12355 * @private
12356 */
12357 setAxisTranslation: function(saveOld) {
12358 var axis = this,
12359 range = axis.max - axis.min,
12360 pointRange = axis.axisPointRange || 0,
12361 closestPointRange,
12362 minPointOffset = 0,
12363 pointRangePadding = 0,
12364 linkedParent = axis.linkedParent,
12365 ordinalCorrection,
12366 hasCategories = !!axis.categories,
12367 transA = axis.transA,
12368 isXAxis = axis.isXAxis;
12369
12370 // Adjust translation for padding. Y axis with categories need to go
12371 // through the same (#1784).
12372 if (isXAxis || hasCategories || pointRange) {
12373
12374 // Get the closest points
12375 closestPointRange = axis.getClosest();
12376
12377 if (linkedParent) {
12378 minPointOffset = linkedParent.minPointOffset;
12379 pointRangePadding = linkedParent.pointRangePadding;
12380 } else {
12381 each(axis.series, function(series) {
12382 var seriesPointRange = hasCategories ?
12383 1 :
12384 (
12385 isXAxis ?
12386 pick(
12387 series.options.pointRange,
12388 closestPointRange,
12389 0
12390 ) :
12391 (axis.axisPointRange || 0)
12392 ), // #2806
12393 pointPlacement = series.options.pointPlacement;
12394
12395 pointRange = Math.max(pointRange, seriesPointRange);
12396
12397 if (!axis.single) {
12398 // minPointOffset is the value padding to the left of
12399 // the axis in order to make room for points with a
12400 // pointRange, typically columns. When the
12401 // pointPlacement option is 'between' or 'on', this
12402 // padding does not apply.
12403 minPointOffset = Math.max(
12404 minPointOffset,
12405 isString(pointPlacement) ? 0 : seriesPointRange / 2
12406 );
12407
12408 // Determine the total padding needed to the length of
12409 // the axis to make room for the pointRange. If the
12410 // series' pointPlacement is 'on', no padding is added.
12411 pointRangePadding = Math.max(
12412 pointRangePadding,
12413 pointPlacement === 'on' ? 0 : seriesPointRange
12414 );
12415 }
12416 });
12417 }
12418
12419 // Record minPointOffset and pointRangePadding
12420 ordinalCorrection = axis.ordinalSlope && closestPointRange ?
12421 axis.ordinalSlope / closestPointRange :
12422 1; // #988, #1853
12423 axis.minPointOffset = minPointOffset =
12424 minPointOffset * ordinalCorrection;
12425 axis.pointRangePadding =
12426 pointRangePadding = pointRangePadding * ordinalCorrection;
12427
12428 // pointRange means the width reserved for each point, like in a
12429 // column chart
12430 axis.pointRange = Math.min(pointRange, range);
12431
12432 // closestPointRange means the closest distance between points. In
12433 // columns it is mostly equal to pointRange, but in lines pointRange
12434 // is 0 while closestPointRange is some other value
12435 if (isXAxis) {
12436 axis.closestPointRange = closestPointRange;
12437 }
12438 }
12439
12440 // Secondary values
12441 if (saveOld) {
12442 axis.oldTransA = transA;
12443 }
12444 axis.translationSlope = axis.transA = transA =
12445 axis.options.staticScale ||
12446 axis.len / ((range + pointRangePadding) || 1);
12447
12448 // Translation addend
12449 axis.transB = axis.horiz ? axis.left : axis.bottom;
12450 axis.minPixelPadding = transA * minPointOffset;
12451 },
12452
12453 minFromRange: function() {
12454 return this.max - this.range;
12455 },
12456
12457 /**
12458 * Set the tick positions to round values and optionally extend the extremes
12459 * to the nearest tick.
12460 *
12461 * @private
12462 */
12463 setTickInterval: function(secondPass) {
12464 var axis = this,
12465 chart = axis.chart,
12466 options = axis.options,
12467 isLog = axis.isLog,
12468 log2lin = axis.log2lin,
12469 isDatetimeAxis = axis.isDatetimeAxis,
12470 isXAxis = axis.isXAxis,
12471 isLinked = axis.isLinked,
12472 maxPadding = options.maxPadding,
12473 minPadding = options.minPadding,
12474 length,
12475 linkedParentExtremes,
12476 tickIntervalOption = options.tickInterval,
12477 minTickInterval,
12478 tickPixelIntervalOption = options.tickPixelInterval,
12479 categories = axis.categories,
12480 threshold = axis.threshold,
12481 softThreshold = axis.softThreshold,
12482 thresholdMin,
12483 thresholdMax,
12484 hardMin,
12485 hardMax;
12486
12487 if (!isDatetimeAxis && !categories && !isLinked) {
12488 this.getTickAmount();
12489 }
12490
12491 // Min or max set either by zooming/setExtremes or initial options
12492 hardMin = pick(axis.userMin, options.min);
12493 hardMax = pick(axis.userMax, options.max);
12494
12495 // Linked axis gets the extremes from the parent axis
12496 if (isLinked) {
12497 axis.linkedParent = chart[axis.coll][options.linkedTo];
12498 linkedParentExtremes = axis.linkedParent.getExtremes();
12499 axis.min = pick(
12500 linkedParentExtremes.min,
12501 linkedParentExtremes.dataMin
12502 );
12503 axis.max = pick(
12504 linkedParentExtremes.max,
12505 linkedParentExtremes.dataMax
12506 );
12507 if (options.type !== axis.linkedParent.options.type) {
12508 H.error(11, 1); // Can't link axes of different type
12509 }
12510
12511 // Initial min and max from the extreme data values
12512 } else {
12513
12514 // Adjust to hard threshold
12515 if (!softThreshold && defined(threshold)) {
12516 if (axis.dataMin >= threshold) {
12517 thresholdMin = threshold;
12518 minPadding = 0;
12519 } else if (axis.dataMax <= threshold) {
12520 thresholdMax = threshold;
12521 maxPadding = 0;
12522 }
12523 }
12524
12525 axis.min = pick(hardMin, thresholdMin, axis.dataMin);
12526 axis.max = pick(hardMax, thresholdMax, axis.dataMax);
12527
12528 }
12529
12530 if (isLog) {
12531 if (
12532 axis.positiveValuesOnly &&
12533 !secondPass &&
12534 Math.min(axis.min, pick(axis.dataMin, axis.min)) <= 0
12535 ) { // #978
12536 H.error(10, 1); // Can't plot negative values on log axis
12537 }
12538 // The correctFloat cures #934, float errors on full tens. But it
12539 // was too aggressive for #4360 because of conversion back to lin,
12540 // therefore use precision 15.
12541 axis.min = correctFloat(log2lin(axis.min), 15);
12542 axis.max = correctFloat(log2lin(axis.max), 15);
12543 }
12544
12545 // handle zoomed range
12546 if (axis.range && defined(axis.max)) {
12547 axis.userMin = axis.min = hardMin =
12548 Math.max(axis.dataMin, axis.minFromRange()); // #618, #6773
12549 axis.userMax = hardMax = axis.max;
12550
12551 axis.range = null; // don't use it when running setExtremes
12552 }
12553
12554 // Hook for Highstock Scroller. Consider combining with beforePadding.
12555 fireEvent(axis, 'foundExtremes');
12556
12557 // Hook for adjusting this.min and this.max. Used by bubble series.
12558 if (axis.beforePadding) {
12559 axis.beforePadding();
12560 }
12561
12562 // adjust min and max for the minimum range
12563 axis.adjustForMinRange();
12564
12565 // Pad the values to get clear of the chart's edges. To avoid
12566 // tickInterval taking the padding into account, we do this after
12567 // computing tick interval (#1337).
12568 if (!categories &&
12569 !axis.axisPointRange &&
12570 !axis.usePercentage &&
12571 !isLinked &&
12572 defined(axis.min) &&
12573 defined(axis.max)
12574 ) {
12575 length = axis.max - axis.min;
12576 if (length) {
12577 if (!defined(hardMin) && minPadding) {
12578 axis.min -= length * minPadding;
12579 }
12580 if (!defined(hardMax) && maxPadding) {
12581 axis.max += length * maxPadding;
12582 }
12583 }
12584 }
12585
12586 // Handle options for floor, ceiling, softMin and softMax (#6359)
12587 if (isNumber(options.softMin)) {
12588 axis.min = Math.min(axis.min, options.softMin);
12589 }
12590 if (isNumber(options.softMax)) {
12591 axis.max = Math.max(axis.max, options.softMax);
12592 }
12593 if (isNumber(options.floor)) {
12594 axis.min = Math.max(axis.min, options.floor);
12595 }
12596 if (isNumber(options.ceiling)) {
12597 axis.max = Math.min(axis.max, options.ceiling);
12598 }
12599
12600
12601 // When the threshold is soft, adjust the extreme value only if the data
12602 // extreme and the padded extreme land on either side of the threshold.
12603 // For example, a series of [0, 1, 2, 3] would make the yAxis add a tick
12604 // for -1 because of the default minPadding and startOnTick options.
12605 // This is prevented by the softThreshold option.
12606 if (softThreshold && defined(axis.dataMin)) {
12607 threshold = threshold || 0;
12608 if (!defined(hardMin) &&
12609 axis.min < threshold &&
12610 axis.dataMin >= threshold
12611 ) {
12612 axis.min = threshold;
12613
12614 } else if (!defined(hardMax) &&
12615 axis.max > threshold &&
12616 axis.dataMax <= threshold
12617 ) {
12618 axis.max = threshold;
12619 }
12620 }
12621
12622
12623 // get tickInterval
12624 if (
12625 axis.min === axis.max ||
12626 axis.min === undefined ||
12627 axis.max === undefined
12628 ) {
12629 axis.tickInterval = 1;
12630
12631 } else if (
12632 isLinked &&
12633 !tickIntervalOption &&
12634 tickPixelIntervalOption ===
12635 axis.linkedParent.options.tickPixelInterval
12636 ) {
12637 axis.tickInterval = tickIntervalOption =
12638 axis.linkedParent.tickInterval;
12639
12640 } else {
12641 axis.tickInterval = pick(
12642 tickIntervalOption,
12643 this.tickAmount ?
12644 ((axis.max - axis.min) / Math.max(this.tickAmount - 1, 1)) :
12645 undefined,
12646 // For categoried axis, 1 is default, for linear axis use
12647 // tickPix
12648 categories ?
12649 1 :
12650 // don't let it be more than the data range
12651 (axis.max - axis.min) * tickPixelIntervalOption /
12652 Math.max(axis.len, tickPixelIntervalOption)
12653 );
12654 }
12655
12656 /**
12657 * Now we're finished detecting min and max, crop and group series data.
12658 * This is in turn needed in order to find tick positions in
12659 * ordinal axes.
12660 */
12661 if (isXAxis && !secondPass) {
12662 each(axis.series, function(series) {
12663 series.processData(
12664 axis.min !== axis.oldMin || axis.max !== axis.oldMax
12665 );
12666 });
12667 }
12668
12669 // set the translation factor used in translate function
12670 axis.setAxisTranslation(true);
12671
12672 // hook for ordinal axes and radial axes
12673 if (axis.beforeSetTickPositions) {
12674 axis.beforeSetTickPositions();
12675 }
12676
12677 // hook for extensions, used in Highstock ordinal axes
12678 if (axis.postProcessTickInterval) {
12679 axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval);
12680 }
12681
12682 // In column-like charts, don't cramp in more ticks than there are
12683 // points (#1943, #4184)
12684 if (axis.pointRange && !tickIntervalOption) {
12685 axis.tickInterval = Math.max(axis.pointRange, axis.tickInterval);
12686 }
12687
12688 // Before normalizing the tick interval, handle minimum tick interval.
12689 // This applies only if tickInterval is not defined.
12690 minTickInterval = pick(
12691 options.minTickInterval,
12692 axis.isDatetimeAxis && axis.closestPointRange
12693 );
12694 if (!tickIntervalOption && axis.tickInterval < minTickInterval) {
12695 axis.tickInterval = minTickInterval;
12696 }
12697
12698 // for linear axes, get magnitude and normalize the interval
12699 if (!isDatetimeAxis && !isLog && !tickIntervalOption) {
12700 axis.tickInterval = normalizeTickInterval(
12701 axis.tickInterval,
12702 null,
12703 getMagnitude(axis.tickInterval),
12704 // If the tick interval is between 0.5 and 5 and the axis max is
12705 // in the order of thousands, chances are we are dealing with
12706 // years. Don't allow decimals. #3363.
12707 pick(
12708 options.allowDecimals, !(
12709 axis.tickInterval > 0.5 &&
12710 axis.tickInterval < 5 &&
12711 axis.max > 1000 &&
12712 axis.max < 9999
12713 )
12714 ), !!this.tickAmount
12715 );
12716 }
12717
12718 // Prevent ticks from getting so close that we can't draw the labels
12719 if (!this.tickAmount) {
12720 axis.tickInterval = axis.unsquish();
12721 }
12722
12723 this.setTickPositions();
12724 },
12725
12726 /**
12727 * Now we have computed the normalized tickInterval, get the tick positions
12728 */
12729 setTickPositions: function() {
12730
12731 var options = this.options,
12732 tickPositions,
12733 tickPositionsOption = options.tickPositions,
12734 minorTickIntervalOption = this.getMinorTickInterval(),
12735 tickPositioner = options.tickPositioner,
12736 startOnTick = options.startOnTick,
12737 endOnTick = options.endOnTick;
12738
12739 // Set the tickmarkOffset
12740 this.tickmarkOffset = (
12741 this.categories &&
12742 options.tickmarkPlacement === 'between' &&
12743 this.tickInterval === 1
12744 ) ? 0.5 : 0; // #3202
12745
12746
12747 // get minorTickInterval
12748 this.minorTickInterval =
12749 minorTickIntervalOption === 'auto' &&
12750 this.tickInterval ?
12751 this.tickInterval / 5 :
12752 minorTickIntervalOption;
12753
12754 // When there is only one point, or all points have the same value on
12755 // this axis, then min and max are equal and tickPositions.length is 0
12756 // or 1. In this case, add some padding in order to center the point,
12757 // but leave it with one tick. #1337.
12758 this.single =
12759 this.min === this.max &&
12760 defined(this.min) &&
12761 !this.tickAmount &&
12762 (
12763 // Data is on integer (#6563)
12764 parseInt(this.min, 10) === this.min ||
12765
12766 // Between integers and decimals are not allowed (#6274)
12767 options.allowDecimals !== false
12768 );
12769
12770 // Find the tick positions. Work on a copy (#1565)
12771 this.tickPositions = tickPositions =
12772 tickPositionsOption && tickPositionsOption.slice();
12773 if (!tickPositions) {
12774
12775 if (this.isDatetimeAxis) {
12776 tickPositions = this.getTimeTicks(
12777 this.normalizeTimeTickInterval(
12778 this.tickInterval,
12779 options.units
12780 ),
12781 this.min,
12782 this.max,
12783 options.startOfWeek,
12784 this.ordinalPositions,
12785 this.closestPointRange,
12786 true
12787 );
12788 } else if (this.isLog) {
12789 tickPositions = this.getLogTickPositions(
12790 this.tickInterval,
12791 this.min,
12792 this.max
12793 );
12794 } else {
12795 tickPositions = this.getLinearTickPositions(
12796 this.tickInterval,
12797 this.min,
12798 this.max
12799 );
12800 }
12801
12802 // Too dense ticks, keep only the first and last (#4477)
12803 if (tickPositions.length > this.len) {
12804 tickPositions = [tickPositions[0], tickPositions.pop()];
12805 // Reduce doubled value (#7339)
12806 if (tickPositions[0] === tickPositions[1]) {
12807 tickPositions.length = 1;
12808 }
12809 }
12810
12811 this.tickPositions = tickPositions;
12812
12813 // Run the tick positioner callback, that allows modifying auto tick
12814 // positions.
12815 if (tickPositioner) {
12816 tickPositioner = tickPositioner.apply(
12817 this, [this.min, this.max]
12818 );
12819 if (tickPositioner) {
12820 this.tickPositions = tickPositions = tickPositioner;
12821 }
12822 }
12823
12824 }
12825
12826 // Reset min/max or remove extremes based on start/end on tick
12827 this.paddedTicks = tickPositions.slice(0); // Used for logarithmic minor
12828 this.trimTicks(tickPositions, startOnTick, endOnTick);
12829 if (!this.isLinked) {
12830
12831 // Substract half a unit (#2619, #2846, #2515, #3390),
12832 // but not in case of multiple ticks (#6897)
12833 if (this.single && tickPositions.length < 2) {
12834 this.min -= 0.5;
12835 this.max += 0.5;
12836 }
12837 if (!tickPositionsOption && !tickPositioner) {
12838 this.adjustTickAmount();
12839 }
12840 }
12841 },
12842
12843 /**
12844 * Handle startOnTick and endOnTick by either adapting to padding min/max or
12845 * rounded min/max. Also handle single data points.
12846 *
12847 * @private
12848 */
12849 trimTicks: function(tickPositions, startOnTick, endOnTick) {
12850 var roundedMin = tickPositions[0],
12851 roundedMax = tickPositions[tickPositions.length - 1],
12852 minPointOffset = this.minPointOffset || 0;
12853
12854 if (!this.isLinked) {
12855 if (startOnTick && roundedMin !== -Infinity) { // #6502
12856 this.min = roundedMin;
12857 } else {
12858 while (this.min - minPointOffset > tickPositions[0]) {
12859 tickPositions.shift();
12860 }
12861 }
12862
12863 if (endOnTick) {
12864 this.max = roundedMax;
12865 } else {
12866 while (this.max + minPointOffset <
12867 tickPositions[tickPositions.length - 1]) {
12868 tickPositions.pop();
12869 }
12870 }
12871
12872 // If no tick are left, set one tick in the middle (#3195)
12873 if (tickPositions.length === 0 && defined(roundedMin)) {
12874 tickPositions.push((roundedMax + roundedMin) / 2);
12875 }
12876 }
12877 },
12878
12879 /**
12880 * Check if there are multiple axes in the same pane.
12881 *
12882 * @private
12883 * @return {Boolean}
12884 * True if there are other axes.
12885 */
12886 alignToOthers: function() {
12887 var others = {}, // Whether there is another axis to pair with this one
12888 hasOther,
12889 options = this.options;
12890
12891 if (
12892 // Only if alignTicks is true
12893 this.chart.options.chart.alignTicks !== false &&
12894 options.alignTicks !== false &&
12895
12896 // Don't try to align ticks on a log axis, they are not evenly
12897 // spaced (#6021)
12898 !this.isLog
12899 ) {
12900 each(this.chart[this.coll], function(axis) {
12901 var otherOptions = axis.options,
12902 horiz = axis.horiz,
12903 key = [
12904 horiz ? otherOptions.left : otherOptions.top,
12905 otherOptions.width,
12906 otherOptions.height,
12907 otherOptions.pane
12908 ].join(',');
12909
12910
12911 if (axis.series.length) { // #4442
12912 if (others[key]) {
12913 hasOther = true; // #4201
12914 } else {
12915 others[key] = 1;
12916 }
12917 }
12918 });
12919 }
12920 return hasOther;
12921 },
12922
12923 /**
12924 * Find the max ticks of either the x and y axis collection, and record it
12925 * in `this.tickAmount`.
12926 *
12927 * @private
12928 */
12929 getTickAmount: function() {
12930 var options = this.options,
12931 tickAmount = options.tickAmount,
12932 tickPixelInterval = options.tickPixelInterval;
12933
12934 if (!defined(options.tickInterval) &&
12935 this.len < tickPixelInterval &&
12936 !this.isRadial &&
12937 !this.isLog &&
12938 options.startOnTick &&
12939 options.endOnTick
12940 ) {
12941 tickAmount = 2;
12942 }
12943
12944 if (!tickAmount && this.alignToOthers()) {
12945 // Add 1 because 4 tick intervals require 5 ticks (including first
12946 // and last)
12947 tickAmount = Math.ceil(this.len / tickPixelInterval) + 1;
12948 }
12949
12950 // For tick amounts of 2 and 3, compute five ticks and remove the
12951 // intermediate ones. This prevents the axis from adding ticks that are
12952 // too far away from the data extremes.
12953 if (tickAmount < 4) {
12954 this.finalTickAmt = tickAmount;
12955 tickAmount = 5;
12956 }
12957
12958 this.tickAmount = tickAmount;
12959 },
12960
12961 /**
12962 * When using multiple axes, adjust the number of ticks to match the highest
12963 * number of ticks in that group.
12964 *
12965 * @private
12966 */
12967 adjustTickAmount: function() {
12968 var tickInterval = this.tickInterval,
12969 tickPositions = this.tickPositions,
12970 tickAmount = this.tickAmount,
12971 finalTickAmt = this.finalTickAmt,
12972 currentTickAmount = tickPositions && tickPositions.length,
12973 i,
12974 len;
12975
12976 if (currentTickAmount < tickAmount) {
12977 while (tickPositions.length < tickAmount) {
12978 tickPositions.push(correctFloat(
12979 tickPositions[tickPositions.length - 1] + tickInterval
12980 ));
12981 }
12982 this.transA *= (currentTickAmount - 1) / (tickAmount - 1);
12983 this.max = tickPositions[tickPositions.length - 1];
12984
12985 // We have too many ticks, run second pass to try to reduce ticks
12986 } else if (currentTickAmount > tickAmount) {
12987 this.tickInterval *= 2;
12988 this.setTickPositions();
12989 }
12990
12991 // The finalTickAmt property is set in getTickAmount
12992 if (defined(finalTickAmt)) {
12993 i = len = tickPositions.length;
12994 while (i--) {
12995 if (
12996 // Remove every other tick
12997 (finalTickAmt === 3 && i % 2 === 1) ||
12998 // Remove all but first and last
12999 (finalTickAmt <= 2 && i > 0 && i < len - 1)
13000 ) {
13001 tickPositions.splice(i, 1);
13002 }
13003 }
13004 this.finalTickAmt = undefined;
13005 }
13006 },
13007
13008 /**
13009 * Set the scale based on data min and max, user set min and max or options.
13010 *
13011 * @private
13012 */
13013 setScale: function() {
13014 var axis = this,
13015 isDirtyData,
13016 isDirtyAxisLength;
13017
13018 axis.oldMin = axis.min;
13019 axis.oldMax = axis.max;
13020 axis.oldAxisLength = axis.len;
13021
13022 // set the new axisLength
13023 axis.setAxisSize();
13024 isDirtyAxisLength = axis.len !== axis.oldAxisLength;
13025
13026 // is there new data?
13027 each(axis.series, function(series) {
13028 if (
13029 series.isDirtyData ||
13030 series.isDirty ||
13031 // When x axis is dirty, we need new data extremes for y as well
13032 series.xAxis.isDirty
13033 ) {
13034 isDirtyData = true;
13035 }
13036 });
13037
13038 // do we really need to go through all this?
13039 if (
13040 isDirtyAxisLength ||
13041 isDirtyData ||
13042 axis.isLinked ||
13043 axis.forceRedraw ||
13044 axis.userMin !== axis.oldUserMin ||
13045 axis.userMax !== axis.oldUserMax ||
13046 axis.alignToOthers()
13047 ) {
13048
13049 if (axis.resetStacks) {
13050 axis.resetStacks();
13051 }
13052
13053 axis.forceRedraw = false;
13054
13055 // get data extremes if needed
13056 axis.getSeriesExtremes();
13057
13058 // get fixed positions based on tickInterval
13059 axis.setTickInterval();
13060
13061 // record old values to decide whether a rescale is necessary later
13062 // on (#540)
13063 axis.oldUserMin = axis.userMin;
13064 axis.oldUserMax = axis.userMax;
13065
13066 // Mark as dirty if it is not already set to dirty and extremes have
13067 // changed. #595.
13068 if (!axis.isDirty) {
13069 axis.isDirty =
13070 isDirtyAxisLength ||
13071 axis.min !== axis.oldMin ||
13072 axis.max !== axis.oldMax;
13073 }
13074 } else if (axis.cleanStacks) {
13075 axis.cleanStacks();
13076 }
13077 },
13078
13079 /**
13080 * Set the minimum and maximum of the axes after render time. If the
13081 * `startOnTick` and `endOnTick` options are true, the minimum and maximum
13082 * values are rounded off to the nearest tick. To prevent this, these
13083 * options can be set to false before calling setExtremes. Also, setExtremes
13084 * will not allow a range lower than the `minRange` option, which by default
13085 * is the range of five points.
13086 *
13087 * @param {Number} [newMin]
13088 * The new minimum value.
13089 * @param {Number} [newMax]
13090 * The new maximum value.
13091 * @param {Boolean} [redraw=true]
13092 * Whether to redraw the chart or wait for an explicit call to
13093 * {@link Highcharts.Chart#redraw}
13094 * @param {AnimationOptions} [animation=true]
13095 * Enable or modify animations.
13096 * @param {Object} [eventArguments]
13097 * Arguments to be accessed in event handler.
13098 *
13099 * @sample highcharts/members/axis-setextremes/
13100 * Set extremes from a button
13101 * @sample highcharts/members/axis-setextremes-datetime/
13102 * Set extremes on a datetime axis
13103 * @sample highcharts/members/axis-setextremes-off-ticks/
13104 * Set extremes off ticks
13105 * @sample stock/members/axis-setextremes/
13106 * Set extremes in Highstock
13107 * @sample maps/members/axis-setextremes/
13108 * Set extremes in Highmaps
13109 */
13110 setExtremes: function(newMin, newMax, redraw, animation, eventArguments) {
13111 var axis = this,
13112 chart = axis.chart;
13113
13114 redraw = pick(redraw, true); // defaults to true
13115
13116 each(axis.series, function(serie) {
13117 delete serie.kdTree;
13118 });
13119
13120 // Extend the arguments with min and max
13121 eventArguments = extend(eventArguments, {
13122 min: newMin,
13123 max: newMax
13124 });
13125
13126 // Fire the event
13127 fireEvent(axis, 'setExtremes', eventArguments, function() {
13128
13129 axis.userMin = newMin;
13130 axis.userMax = newMax;
13131 axis.eventArgs = eventArguments;
13132
13133 if (redraw) {
13134 chart.redraw(animation);
13135 }
13136 });
13137 },
13138
13139 /**
13140 * Overridable method for zooming chart. Pulled out in a separate method to
13141 * allow overriding in stock charts.
13142 *
13143 * @private
13144 */
13145 zoom: function(newMin, newMax) {
13146 var dataMin = this.dataMin,
13147 dataMax = this.dataMax,
13148 options = this.options,
13149 min = Math.min(dataMin, pick(options.min, dataMin)),
13150 max = Math.max(dataMax, pick(options.max, dataMax));
13151
13152 if (newMin !== this.min || newMax !== this.max) { // #5790
13153
13154 // Prevent pinch zooming out of range. Check for defined is for
13155 // #1946. #1734.
13156 if (!this.allowZoomOutside) {
13157 // #6014, sometimes newMax will be smaller than min (or newMin
13158 // will be larger than max).
13159 if (defined(dataMin)) {
13160 if (newMin < min) {
13161 newMin = min;
13162 }
13163 if (newMin > max) {
13164 newMin = max;
13165 }
13166 }
13167 if (defined(dataMax)) {
13168 if (newMax < min) {
13169 newMax = min;
13170 }
13171 if (newMax > max) {
13172 newMax = max;
13173 }
13174 }
13175 }
13176
13177 // In full view, displaying the reset zoom button is not required
13178 this.displayBtn = newMin !== undefined || newMax !== undefined;
13179
13180 // Do it
13181 this.setExtremes(
13182 newMin,
13183 newMax,
13184 false,
13185 undefined, {
13186 trigger: 'zoom'
13187 }
13188 );
13189 }
13190
13191 return true;
13192 },
13193
13194 /**
13195 * Update the axis metrics.
13196 *
13197 * @private
13198 */
13199 setAxisSize: function() {
13200 var chart = this.chart,
13201 options = this.options,
13202 // [top, right, bottom, left]
13203 offsets = options.offsets || [0, 0, 0, 0],
13204 horiz = this.horiz,
13205
13206 // Check for percentage based input values. Rounding fixes problems
13207 // with column overflow and plot line filtering (#4898, #4899)
13208 width = this.width = Math.round(H.relativeLength(
13209 pick(
13210 options.width,
13211 chart.plotWidth - offsets[3] + offsets[1]
13212 ),
13213 chart.plotWidth
13214 )),
13215 height = this.height = Math.round(H.relativeLength(
13216 pick(
13217 options.height,
13218 chart.plotHeight - offsets[0] + offsets[2]
13219 ),
13220 chart.plotHeight
13221 )),
13222 top = this.top = Math.round(H.relativeLength(
13223 pick(options.top, chart.plotTop + offsets[0]),
13224 chart.plotHeight,
13225 chart.plotTop
13226 )),
13227 left = this.left = Math.round(H.relativeLength(
13228 pick(options.left, chart.plotLeft + offsets[3]),
13229 chart.plotWidth,
13230 chart.plotLeft
13231 ));
13232
13233 // Expose basic values to use in Series object and navigator
13234 this.bottom = chart.chartHeight - height - top;
13235 this.right = chart.chartWidth - width - left;
13236
13237 // Direction agnostic properties
13238 this.len = Math.max(horiz ? width : height, 0); // Math.max fixes #905
13239 this.pos = horiz ? left : top; // distance from SVG origin
13240 },
13241
13242 /**
13243 * The returned object literal from the {@link Highcharts.Axis#getExtremes}
13244 * function.
13245 *
13246 * @typedef {Object} Extremes
13247 * @property {Number} dataMax
13248 * The maximum value of the axis' associated series.
13249 * @property {Number} dataMin
13250 * The minimum value of the axis' associated series.
13251 * @property {Number} max
13252 * The maximum axis value, either automatic or set manually. If
13253 * the `max` option is not set, `maxPadding` is 0 and `endOnTick`
13254 * is false, this value will be the same as `dataMax`.
13255 * @property {Number} min
13256 * The minimum axis value, either automatic or set manually. If
13257 * the `min` option is not set, `minPadding` is 0 and
13258 * `startOnTick` is false, this value will be the same
13259 * as `dataMin`.
13260 */
13261 /**
13262 * Get the current extremes for the axis.
13263 *
13264 * @returns {Extremes}
13265 * An object containing extremes information.
13266 *
13267 * @sample highcharts/members/axis-getextremes/
13268 * Report extremes by click on a button
13269 * @sample maps/members/axis-getextremes/
13270 * Get extremes in Highmaps
13271 */
13272 getExtremes: function() {
13273 var axis = this,
13274 isLog = axis.isLog,
13275 lin2log = axis.lin2log;
13276
13277 return {
13278 min: isLog ? correctFloat(lin2log(axis.min)) : axis.min,
13279 max: isLog ? correctFloat(lin2log(axis.max)) : axis.max,
13280 dataMin: axis.dataMin,
13281 dataMax: axis.dataMax,
13282 userMin: axis.userMin,
13283 userMax: axis.userMax
13284 };
13285 },
13286
13287 /**
13288 * Get the zero plane either based on zero or on the min or max value.
13289 * Used in bar and area plots.
13290 *
13291 * @param {Number} threshold
13292 * The threshold in axis values.
13293 *
13294 * @return {Number}
13295 * The translated threshold position in terms of pixels, and
13296 * corrected to stay within the axis bounds.
13297 */
13298 getThreshold: function(threshold) {
13299 var axis = this,
13300 isLog = axis.isLog,
13301 lin2log = axis.lin2log,
13302 realMin = isLog ? lin2log(axis.min) : axis.min,
13303 realMax = isLog ? lin2log(axis.max) : axis.max;
13304
13305 if (threshold === null) {
13306 threshold = realMin;
13307 } else if (realMin > threshold) {
13308 threshold = realMin;
13309 } else if (realMax < threshold) {
13310 threshold = realMax;
13311 }
13312
13313 return axis.translate(threshold, 0, 1, 0, 1);
13314 },
13315
13316 /**
13317 * Compute auto alignment for the axis label based on which side the axis is
13318 * on and the given rotation for the label.
13319 *
13320 * @param {Number} rotation
13321 * The rotation in degrees as set by either the `rotation` or
13322 * `autoRotation` options.
13323 * @private
13324 */
13325 autoLabelAlign: function(rotation) {
13326 var ret,
13327 angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360;
13328
13329 if (angle > 15 && angle < 165) {
13330 ret = 'right';
13331 } else if (angle > 195 && angle < 345) {
13332 ret = 'left';
13333 } else {
13334 ret = 'center';
13335 }
13336 return ret;
13337 },
13338
13339 /**
13340 * Get the tick length and width for the axis based on axis options.
13341 *
13342 * @private
13343 *
13344 * @param {String} prefix
13345 * 'tick' or 'minorTick'
13346 * @return {Array.<Number>}
13347 * An array of tickLength and tickWidth
13348 */
13349 tickSize: function(prefix) {
13350 var options = this.options,
13351 tickLength = options[prefix + 'Length'],
13352 tickWidth = pick(
13353 options[prefix + 'Width'],
13354 prefix === 'tick' && this.isXAxis ? 1 : 0 // X axis default 1
13355 );
13356
13357 if (tickWidth && tickLength) {
13358 // Negate the length
13359 if (options[prefix + 'Position'] === 'inside') {
13360 tickLength = -tickLength;
13361 }
13362 return [tickLength, tickWidth];
13363 }
13364
13365 },
13366
13367 /**
13368 * Return the size of the labels.
13369 *
13370 * @private
13371 */
13372 labelMetrics: function() {
13373 var index = this.tickPositions && this.tickPositions[0] || 0;
13374 return this.chart.renderer.fontMetrics(
13375 this.options.labels.style && this.options.labels.style.fontSize,
13376 this.ticks[index] && this.ticks[index].label
13377 );
13378 },
13379
13380 /**
13381 * Prevent the ticks from getting so close we can't draw the labels. On a
13382 * horizontal axis, this is handled by rotating the labels, removing ticks
13383 * and adding ellipsis. On a vertical axis remove ticks and add ellipsis.
13384 *
13385 * @private
13386 */
13387 unsquish: function() {
13388 var labelOptions = this.options.labels,
13389 horiz = this.horiz,
13390 tickInterval = this.tickInterval,
13391 newTickInterval = tickInterval,
13392 slotSize = this.len / (
13393 ((this.categories ? 1 : 0) + this.max - this.min) / tickInterval
13394 ),
13395 rotation,
13396 rotationOption = labelOptions.rotation,
13397 labelMetrics = this.labelMetrics(),
13398 step,
13399 bestScore = Number.MAX_VALUE,
13400 autoRotation,
13401 // Return the multiple of tickInterval that is needed to avoid
13402 // collision
13403 getStep = function(spaceNeeded) {
13404 var step = spaceNeeded / (slotSize || 1);
13405 step = step > 1 ? Math.ceil(step) : 1;
13406 return step * tickInterval;
13407 };
13408
13409 if (horiz) {
13410 autoRotation = !labelOptions.staggerLines &&
13411 !labelOptions.step &&
13412 ( // #3971
13413 defined(rotationOption) ? [rotationOption] :
13414 slotSize < pick(labelOptions.autoRotationLimit, 80) &&
13415 labelOptions.autoRotation
13416 );
13417
13418 if (autoRotation) {
13419
13420 // Loop over the given autoRotation options, and determine
13421 // which gives the best score. The best score is that with
13422 // the lowest number of steps and a rotation closest
13423 // to horizontal.
13424 each(autoRotation, function(rot) {
13425 var score;
13426
13427 if (
13428 rot === rotationOption ||
13429 (rot && rot >= -90 && rot <= 90)
13430 ) { // #3891
13431
13432 step = getStep(
13433 Math.abs(labelMetrics.h / Math.sin(deg2rad * rot))
13434 );
13435
13436 score = step + Math.abs(rot / 360);
13437
13438 if (score < bestScore) {
13439 bestScore = score;
13440 rotation = rot;
13441 newTickInterval = step;
13442 }
13443 }
13444 });
13445 }
13446
13447 } else if (!labelOptions.step) { // #4411
13448 newTickInterval = getStep(labelMetrics.h);
13449 }
13450
13451 this.autoRotation = autoRotation;
13452 this.labelRotation = pick(rotation, rotationOption);
13453
13454 return newTickInterval;
13455 },
13456
13457 /**
13458 * Get the general slot width for labels/categories on this axis. This may
13459 * change between the pre-render (from Axis.getOffset) and the final tick
13460 * rendering and placement.
13461 *
13462 * @private
13463 * @return {Number}
13464 * The pixel width allocated to each axis label.
13465 */
13466 getSlotWidth: function() {
13467 // #5086, #1580, #1931
13468 var chart = this.chart,
13469 horiz = this.horiz,
13470 labelOptions = this.options.labels,
13471 slotCount = Math.max(
13472 this.tickPositions.length - (this.categories ? 0 : 1),
13473 1
13474 ),
13475 marginLeft = chart.margin[3];
13476
13477 return (
13478 horiz &&
13479 (labelOptions.step || 0) < 2 &&
13480 !labelOptions.rotation && // #4415
13481 ((this.staggerLines || 1) * this.len) / slotCount
13482 ) || (!horiz && (
13483 // #7028
13484 (
13485 labelOptions.style &&
13486 parseInt(labelOptions.style.width, 10)
13487 ) ||
13488 (
13489 marginLeft &&
13490 (marginLeft - chart.spacing[3])
13491 ) ||
13492 chart.chartWidth * 0.33
13493 ));
13494
13495 },
13496
13497 /**
13498 * Render the axis labels and determine whether ellipsis or rotation need
13499 * to be applied.
13500 *
13501 * @private
13502 */
13503 renderUnsquish: function() {
13504 var chart = this.chart,
13505 renderer = chart.renderer,
13506 tickPositions = this.tickPositions,
13507 ticks = this.ticks,
13508 labelOptions = this.options.labels,
13509 horiz = this.horiz,
13510 slotWidth = this.getSlotWidth(),
13511 innerWidth = Math.max(
13512 1,
13513 Math.round(slotWidth - 2 * (labelOptions.padding || 5))
13514 ),
13515 attr = {},
13516 labelMetrics = this.labelMetrics(),
13517 textOverflowOption = labelOptions.style &&
13518 labelOptions.style.textOverflow,
13519 css,
13520 maxLabelLength = 0,
13521 label,
13522 i,
13523 pos;
13524
13525 // Set rotation option unless it is "auto", like in gauges
13526 if (!isString(labelOptions.rotation)) {
13527 attr.rotation = labelOptions.rotation || 0; // #4443
13528 }
13529
13530 // Get the longest label length
13531 each(tickPositions, function(tick) {
13532 tick = ticks[tick];
13533 if (tick && tick.labelLength > maxLabelLength) {
13534 maxLabelLength = tick.labelLength;
13535 }
13536 });
13537 this.maxLabelLength = maxLabelLength;
13538
13539
13540 // Handle auto rotation on horizontal axis
13541 if (this.autoRotation) {
13542
13543 // Apply rotation only if the label is too wide for the slot, and
13544 // the label is wider than its height.
13545 if (
13546 maxLabelLength > innerWidth &&
13547 maxLabelLength > labelMetrics.h
13548 ) {
13549 attr.rotation = this.labelRotation;
13550 } else {
13551 this.labelRotation = 0;
13552 }
13553
13554 // Handle word-wrap or ellipsis on vertical axis
13555 } else if (slotWidth) {
13556 // For word-wrap or ellipsis
13557 css = {
13558 width: innerWidth + 'px'
13559 };
13560
13561 if (!textOverflowOption) {
13562 css.textOverflow = 'clip';
13563
13564 // On vertical axis, only allow word wrap if there is room
13565 // for more lines.
13566 i = tickPositions.length;
13567 while (!horiz && i--) {
13568 pos = tickPositions[i];
13569 label = ticks[pos].label;
13570 if (label) {
13571 // Reset ellipsis in order to get the correct
13572 // bounding box (#4070)
13573 if (
13574 label.styles &&
13575 label.styles.textOverflow === 'ellipsis'
13576 ) {
13577 label.css({
13578 textOverflow: 'clip'
13579 });
13580
13581 // Set the correct width in order to read
13582 // the bounding box height (#4678, #5034)
13583 } else if (ticks[pos].labelLength > slotWidth) {
13584 label.css({
13585 width: slotWidth + 'px'
13586 });
13587 }
13588
13589 if (
13590 label.getBBox().height > (
13591 this.len / tickPositions.length -
13592 (labelMetrics.h - labelMetrics.f)
13593 )
13594 ) {
13595 label.specCss = {
13596 textOverflow: 'ellipsis'
13597 };
13598 }
13599 }
13600 }
13601 }
13602 }
13603
13604
13605 // Add ellipsis if the label length is significantly longer than ideal
13606 if (attr.rotation) {
13607 css = {
13608 width: (
13609 maxLabelLength > chart.chartHeight * 0.5 ?
13610 chart.chartHeight * 0.33 :
13611 chart.chartHeight
13612 ) + 'px'
13613 };
13614 if (!textOverflowOption) {
13615 css.textOverflow = 'ellipsis';
13616 }
13617 }
13618
13619 // Set the explicit or automatic label alignment
13620 this.labelAlign = labelOptions.align ||
13621 this.autoLabelAlign(this.labelRotation);
13622 if (this.labelAlign) {
13623 attr.align = this.labelAlign;
13624 }
13625
13626 // Apply general and specific CSS
13627 each(tickPositions, function(pos) {
13628 var tick = ticks[pos],
13629 label = tick && tick.label;
13630 if (label) {
13631 // This needs to go before the CSS in old IE (#4502)
13632 label.attr(attr);
13633
13634 if (css) {
13635 label.css(merge(css, label.specCss));
13636 }
13637 delete label.specCss;
13638 tick.rotation = attr.rotation;
13639 }
13640 });
13641
13642 // Note: Why is this not part of getLabelPosition?
13643 this.tickRotCorr = renderer.rotCorr(
13644 labelMetrics.b,
13645 this.labelRotation || 0,
13646 this.side !== 0
13647 );
13648 },
13649
13650 /**
13651 * Return true if the axis has associated data.
13652 *
13653 * @return {Boolean}
13654 * True if the axis has associated visible series and those series
13655 * have either valid data points or explicit `min` and `max`
13656 * settings.
13657 */
13658 hasData: function() {
13659 return (
13660 this.hasVisibleSeries ||
13661 (
13662 defined(this.min) &&
13663 defined(this.max) &&
13664 this.tickPositions &&
13665 this.tickPositions.length > 0
13666 )
13667 );
13668 },
13669
13670 /**
13671 * Adds the title defined in axis.options.title.
13672 * @param {Boolean} display - whether or not to display the title
13673 */
13674 addTitle: function(display) {
13675 var axis = this,
13676 renderer = axis.chart.renderer,
13677 horiz = axis.horiz,
13678 opposite = axis.opposite,
13679 options = axis.options,
13680 axisTitleOptions = options.title,
13681 textAlign;
13682
13683 if (!axis.axisTitle) {
13684 textAlign = axisTitleOptions.textAlign;
13685 if (!textAlign) {
13686 textAlign = (horiz ? {
13687 low: 'left',
13688 middle: 'center',
13689 high: 'right'
13690 } : {
13691 low: opposite ? 'right' : 'left',
13692 middle: 'center',
13693 high: opposite ? 'left' : 'right'
13694 })[axisTitleOptions.align];
13695 }
13696 axis.axisTitle = renderer.text(
13697 axisTitleOptions.text,
13698 0,
13699 0,
13700 axisTitleOptions.useHTML
13701 )
13702 .attr({
13703 zIndex: 7,
13704 rotation: axisTitleOptions.rotation || 0,
13705 align: textAlign
13706 })
13707 .addClass('highcharts-axis-title')
13708
13709 .add(axis.axisGroup);
13710 axis.axisTitle.isNew = true;
13711 }
13712
13713 // Max width defaults to the length of the axis
13714
13715 axis.axisTitle.css({
13716 width: axis.len
13717 });
13718
13719
13720
13721 // hide or show the title depending on whether showEmpty is set
13722 axis.axisTitle[display ? 'show' : 'hide'](true);
13723 },
13724
13725 /**
13726 * Generates a tick for initial positioning.
13727 *
13728 * @private
13729 * @param {number} pos
13730 * The tick position in axis values.
13731 * @param {number} i
13732 * The index of the tick in {@link Axis.tickPositions}.
13733 */
13734 generateTick: function(pos) {
13735 var ticks = this.ticks;
13736
13737 if (!ticks[pos]) {
13738 ticks[pos] = new Tick(this, pos);
13739 } else {
13740 ticks[pos].addLabel(); // update labels depending on tick interval
13741 }
13742 },
13743
13744 /**
13745 * Render the tick labels to a preliminary position to get their sizes.
13746 *
13747 * @private
13748 */
13749 getOffset: function() {
13750 var axis = this,
13751 chart = axis.chart,
13752 renderer = chart.renderer,
13753 options = axis.options,
13754 tickPositions = axis.tickPositions,
13755 ticks = axis.ticks,
13756 horiz = axis.horiz,
13757 side = axis.side,
13758 invertedSide = chart.inverted &&
13759 !axis.isZAxis ? [1, 0, 3, 2][side] : side,
13760 hasData,
13761 showAxis,
13762 titleOffset = 0,
13763 titleOffsetOption,
13764 titleMargin = 0,
13765 axisTitleOptions = options.title,
13766 labelOptions = options.labels,
13767 labelOffset = 0, // reset
13768 labelOffsetPadded,
13769 axisOffset = chart.axisOffset,
13770 clipOffset = chart.clipOffset,
13771 clip,
13772 directionFactor = [-1, 1, 1, -1][side],
13773 className = options.className,
13774 axisParent = axis.axisParent, // Used in color axis
13775 lineHeightCorrection,
13776 tickSize = this.tickSize('tick');
13777
13778 // For reuse in Axis.render
13779 hasData = axis.hasData();
13780 axis.showAxis = showAxis = hasData || pick(options.showEmpty, true);
13781
13782 // Set/reset staggerLines
13783 axis.staggerLines = axis.horiz && labelOptions.staggerLines;
13784
13785 // Create the axisGroup and gridGroup elements on first iteration
13786 if (!axis.axisGroup) {
13787 axis.gridGroup = renderer.g('grid')
13788 .attr({
13789 zIndex: options.gridZIndex || 1
13790 })
13791 .addClass(
13792 'highcharts-' + this.coll.toLowerCase() + '-grid ' +
13793 (className || '')
13794 )
13795 .add(axisParent);
13796 axis.axisGroup = renderer.g('axis')
13797 .attr({
13798 zIndex: options.zIndex || 2
13799 })
13800 .addClass(
13801 'highcharts-' + this.coll.toLowerCase() + ' ' +
13802 (className || '')
13803 )
13804 .add(axisParent);
13805 axis.labelGroup = renderer.g('axis-labels')
13806 .attr({
13807 zIndex: labelOptions.zIndex || 7
13808 })
13809 .addClass(
13810 'highcharts-' + axis.coll.toLowerCase() + '-labels ' +
13811 (className || '')
13812 )
13813 .add(axisParent);
13814 }
13815
13816 if (hasData || axis.isLinked) {
13817
13818 // Generate ticks
13819 each(tickPositions, function(pos, i) {
13820 // i is not used here, but may be used in overrides
13821 axis.generateTick(pos, i);
13822 });
13823
13824 axis.renderUnsquish();
13825
13826
13827 // Left side must be align: right and right side must
13828 // have align: left for labels
13829 if (
13830 labelOptions.reserveSpace !== false &&
13831 (
13832 side === 0 ||
13833 side === 2 || {
13834 1: 'left',
13835 3: 'right'
13836 }[side] === axis.labelAlign ||
13837 axis.labelAlign === 'center'
13838 )
13839 ) {
13840 each(tickPositions, function(pos) {
13841 // get the highest offset
13842 labelOffset = Math.max(
13843 ticks[pos].getLabelSize(),
13844 labelOffset
13845 );
13846 });
13847 }
13848
13849 if (axis.staggerLines) {
13850 labelOffset *= axis.staggerLines;
13851 axis.labelOffset = labelOffset * (axis.opposite ? -1 : 1);
13852 }
13853
13854 } else { // doesn't have data
13855 objectEach(ticks, function(tick, n) {
13856 tick.destroy();
13857 delete ticks[n];
13858 });
13859 }
13860
13861 if (
13862 axisTitleOptions &&
13863 axisTitleOptions.text &&
13864 axisTitleOptions.enabled !== false
13865 ) {
13866 axis.addTitle(showAxis);
13867
13868 if (showAxis && axisTitleOptions.reserveSpace !== false) {
13869 axis.titleOffset = titleOffset =
13870 axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];
13871 titleOffsetOption = axisTitleOptions.offset;
13872 titleMargin = defined(titleOffsetOption) ?
13873 0 :
13874 pick(axisTitleOptions.margin, horiz ? 5 : 10);
13875 }
13876 }
13877
13878 // Render the axis line
13879 axis.renderLine();
13880
13881 // handle automatic or user set offset
13882 axis.offset = directionFactor * pick(options.offset, axisOffset[side]);
13883
13884 axis.tickRotCorr = axis.tickRotCorr || {
13885 x: 0,
13886 y: 0
13887 }; // polar
13888 if (side === 0) {
13889 lineHeightCorrection = -axis.labelMetrics().h;
13890 } else if (side === 2) {
13891 lineHeightCorrection = axis.tickRotCorr.y;
13892 } else {
13893 lineHeightCorrection = 0;
13894 }
13895
13896 // Find the padded label offset
13897 labelOffsetPadded = Math.abs(labelOffset) + titleMargin;
13898 if (labelOffset) {
13899 labelOffsetPadded -= lineHeightCorrection;
13900 labelOffsetPadded += directionFactor * (
13901 horiz ?
13902 pick(
13903 labelOptions.y,
13904 axis.tickRotCorr.y + directionFactor * 8
13905 ) :
13906 labelOptions.x
13907 );
13908 }
13909
13910 axis.axisTitleMargin = pick(titleOffsetOption, labelOffsetPadded);
13911
13912 axisOffset[side] = Math.max(
13913 axisOffset[side],
13914 axis.axisTitleMargin + titleOffset + directionFactor * axis.offset,
13915 labelOffsetPadded, // #3027
13916 hasData && tickPositions.length && tickSize ?
13917 tickSize[0] + directionFactor * axis.offset :
13918 0 // #4866
13919 );
13920
13921 // Decide the clipping needed to keep the graph inside
13922 // the plot area and axis lines
13923 clip = options.offset ?
13924 0 :
13925 Math.floor(axis.axisLine.strokeWidth() / 2) * 2; // #4308, #4371
13926 clipOffset[invertedSide] = Math.max(clipOffset[invertedSide], clip);
13927 },
13928
13929 /**
13930 * Internal function to get the path for the axis line. Extended for polar
13931 * charts.
13932 *
13933 * @param {Number} lineWidth
13934 * The line width in pixels.
13935 * @return {Array}
13936 * The SVG path definition in array form.
13937 */
13938 getLinePath: function(lineWidth) {
13939 var chart = this.chart,
13940 opposite = this.opposite,
13941 offset = this.offset,
13942 horiz = this.horiz,
13943 lineLeft = this.left + (opposite ? this.width : 0) + offset,
13944 lineTop = chart.chartHeight - this.bottom -
13945 (opposite ? this.height : 0) + offset;
13946
13947 if (opposite) {
13948 lineWidth *= -1; // crispify the other way - #1480, #1687
13949 }
13950
13951 return chart.renderer
13952 .crispLine([
13953 'M',
13954 horiz ?
13955 this.left :
13956 lineLeft,
13957 horiz ?
13958 lineTop :
13959 this.top,
13960 'L',
13961 horiz ?
13962 chart.chartWidth - this.right :
13963 lineLeft,
13964 horiz ?
13965 lineTop :
13966 chart.chartHeight - this.bottom
13967 ], lineWidth);
13968 },
13969
13970 /**
13971 * Render the axis line. Called internally when rendering and redrawing the
13972 * axis.
13973 */
13974 renderLine: function() {
13975 if (!this.axisLine) {
13976 this.axisLine = this.chart.renderer.path()
13977 .addClass('highcharts-axis-line')
13978 .add(this.axisGroup);
13979
13980
13981 }
13982 },
13983
13984 /**
13985 * Position the axis title.
13986 *
13987 * @private
13988 *
13989 * @return {Object}
13990 * X and Y positions for the title.
13991 */
13992 getTitlePosition: function() {
13993 // compute anchor points for each of the title align options
13994 var horiz = this.horiz,
13995 axisLeft = this.left,
13996 axisTop = this.top,
13997 axisLength = this.len,
13998 axisTitleOptions = this.options.title,
13999 margin = horiz ? axisLeft : axisTop,
14000 opposite = this.opposite,
14001 offset = this.offset,
14002 xOption = axisTitleOptions.x || 0,
14003 yOption = axisTitleOptions.y || 0,
14004 axisTitle = this.axisTitle,
14005 fontMetrics = this.chart.renderer.fontMetrics(
14006 axisTitleOptions.style && axisTitleOptions.style.fontSize,
14007 axisTitle
14008 ),
14009 // The part of a multiline text that is below the baseline of the
14010 // first line. Subtract 1 to preserve pixel-perfectness from the
14011 // old behaviour (v5.0.12), where only one line was allowed.
14012 textHeightOvershoot = Math.max(
14013 axisTitle.getBBox(null, 0).height - fontMetrics.h - 1,
14014 0
14015 ),
14016
14017 // the position in the length direction of the axis
14018 alongAxis = {
14019 low: margin + (horiz ? 0 : axisLength),
14020 middle: margin + axisLength / 2,
14021 high: margin + (horiz ? axisLength : 0)
14022 }[axisTitleOptions.align],
14023
14024 // the position in the perpendicular direction of the axis
14025 offAxis = (horiz ? axisTop + this.height : axisLeft) +
14026 (horiz ? 1 : -1) * // horizontal axis reverses the margin
14027 (opposite ? -1 : 1) * // so does opposite axes
14028 this.axisTitleMargin + [-textHeightOvershoot, // top
14029 textHeightOvershoot, // right
14030 fontMetrics.f, // bottom
14031 -textHeightOvershoot // left
14032 ][this.side];
14033
14034
14035 return {
14036 x: horiz ?
14037 alongAxis + xOption : offAxis + (opposite ? this.width : 0) + offset + xOption,
14038 y: horiz ?
14039 offAxis + yOption - (opposite ? this.height : 0) + offset : alongAxis + yOption
14040 };
14041 },
14042
14043 /**
14044 * Render a minor tick into the given position. If a minor tick already
14045 * exists in this position, move it.
14046 *
14047 * @param {number} pos
14048 * The position in axis values.
14049 */
14050 renderMinorTick: function(pos) {
14051 var slideInTicks = this.chart.hasRendered && isNumber(this.oldMin),
14052 minorTicks = this.minorTicks;
14053
14054 if (!minorTicks[pos]) {
14055 minorTicks[pos] = new Tick(this, pos, 'minor');
14056 }
14057
14058 // Render new ticks in old position
14059 if (slideInTicks && minorTicks[pos].isNew) {
14060 minorTicks[pos].render(null, true);
14061 }
14062
14063 minorTicks[pos].render(null, false, 1);
14064 },
14065
14066 /**
14067 * Render a major tick into the given position. If a tick already exists
14068 * in this position, move it.
14069 *
14070 * @param {number} pos
14071 * The position in axis values.
14072 * @param {number} i
14073 * The tick index.
14074 */
14075 renderTick: function(pos, i) {
14076 var isLinked = this.isLinked,
14077 ticks = this.ticks,
14078 slideInTicks = this.chart.hasRendered && isNumber(this.oldMin);
14079
14080 // Linked axes need an extra check to find out if
14081 if (!isLinked || (pos >= this.min && pos <= this.max)) {
14082
14083 if (!ticks[pos]) {
14084 ticks[pos] = new Tick(this, pos);
14085 }
14086
14087 // render new ticks in old position
14088 if (slideInTicks && ticks[pos].isNew) {
14089 ticks[pos].render(i, true, 0.1);
14090 }
14091
14092 ticks[pos].render(i);
14093 }
14094 },
14095
14096 /**
14097 * Render the axis.
14098 *
14099 * @private
14100 */
14101 render: function() {
14102 var axis = this,
14103 chart = axis.chart,
14104 renderer = chart.renderer,
14105 options = axis.options,
14106 isLog = axis.isLog,
14107 lin2log = axis.lin2log,
14108 isLinked = axis.isLinked,
14109 tickPositions = axis.tickPositions,
14110 axisTitle = axis.axisTitle,
14111 ticks = axis.ticks,
14112 minorTicks = axis.minorTicks,
14113 alternateBands = axis.alternateBands,
14114 stackLabelOptions = options.stackLabels,
14115 alternateGridColor = options.alternateGridColor,
14116 tickmarkOffset = axis.tickmarkOffset,
14117 axisLine = axis.axisLine,
14118 showAxis = axis.showAxis,
14119 animation = animObject(renderer.globalAnimation),
14120 from,
14121 to;
14122
14123 // Reset
14124 axis.labelEdge.length = 0;
14125 axis.overlap = false;
14126
14127 // Mark all elements inActive before we go over and mark the active ones
14128 each([ticks, minorTicks, alternateBands], function(coll) {
14129 objectEach(coll, function(tick) {
14130 tick.isActive = false;
14131 });
14132 });
14133
14134 // If the series has data draw the ticks. Else only the line and title
14135 if (axis.hasData() || isLinked) {
14136
14137 // minor ticks
14138 if (axis.minorTickInterval && !axis.categories) {
14139 each(axis.getMinorTickPositions(), function(pos) {
14140 axis.renderMinorTick(pos);
14141 });
14142 }
14143
14144 // Major ticks. Pull out the first item and render it last so that
14145 // we can get the position of the neighbour label. #808.
14146 if (tickPositions.length) { // #1300
14147 each(tickPositions, function(pos, i) {
14148 axis.renderTick(pos, i);
14149 });
14150 // In a categorized axis, the tick marks are displayed
14151 // between labels. So we need to add a tick mark and
14152 // grid line at the left edge of the X axis.
14153 if (tickmarkOffset && (axis.min === 0 || axis.single)) {
14154 if (!ticks[-1]) {
14155 ticks[-1] = new Tick(axis, -1, null, true);
14156 }
14157 ticks[-1].render(-1);
14158 }
14159
14160 }
14161
14162 // alternate grid color
14163 if (alternateGridColor) {
14164 each(tickPositions, function(pos, i) {
14165 to = tickPositions[i + 1] !== undefined ?
14166 tickPositions[i + 1] + tickmarkOffset :
14167 axis.max - tickmarkOffset;
14168
14169 if (
14170 i % 2 === 0 &&
14171 pos < axis.max &&
14172 to <= axis.max + (
14173 chart.polar ?
14174 -tickmarkOffset :
14175 tickmarkOffset
14176 )
14177 ) { // #2248, #4660
14178 if (!alternateBands[pos]) {
14179 alternateBands[pos] = new H.PlotLineOrBand(axis);
14180 }
14181 from = pos + tickmarkOffset; // #949
14182 alternateBands[pos].options = {
14183 from: isLog ? lin2log(from) : from,
14184 to: isLog ? lin2log(to) : to,
14185 color: alternateGridColor
14186 };
14187 alternateBands[pos].render();
14188 alternateBands[pos].isActive = true;
14189 }
14190 });
14191 }
14192
14193 // custom plot lines and bands
14194 if (!axis._addedPlotLB) { // only first time
14195 each(
14196 (options.plotLines || []).concat(options.plotBands || []),
14197 function(plotLineOptions) {
14198 axis.addPlotBandOrLine(plotLineOptions);
14199 }
14200 );
14201 axis._addedPlotLB = true;
14202 }
14203
14204 } // end if hasData
14205
14206 // Remove inactive ticks
14207 each([ticks, minorTicks, alternateBands], function(coll) {
14208 var i,
14209 forDestruction = [],
14210 delay = animation.duration,
14211 destroyInactiveItems = function() {
14212 i = forDestruction.length;
14213 while (i--) {
14214 // When resizing rapidly, the same items
14215 // may be destroyed in different timeouts,
14216 // or the may be reactivated
14217 if (
14218 coll[forDestruction[i]] &&
14219 !coll[forDestruction[i]].isActive
14220 ) {
14221 coll[forDestruction[i]].destroy();
14222 delete coll[forDestruction[i]];
14223 }
14224 }
14225
14226 };
14227
14228 objectEach(coll, function(tick, pos) {
14229 if (!tick.isActive) {
14230 // Render to zero opacity
14231 tick.render(pos, false, 0);
14232 tick.isActive = false;
14233 forDestruction.push(pos);
14234 }
14235 });
14236
14237 // When the objects are finished fading out, destroy them
14238 syncTimeout(
14239 destroyInactiveItems,
14240 coll === alternateBands ||
14241 !chart.hasRendered ||
14242 !delay ?
14243 0 :
14244 delay
14245 );
14246 });
14247
14248 // Set the axis line path
14249 if (axisLine) {
14250 axisLine[axisLine.isPlaced ? 'animate' : 'attr']({
14251 d: this.getLinePath(axisLine.strokeWidth())
14252 });
14253 axisLine.isPlaced = true;
14254
14255 // Show or hide the line depending on options.showEmpty
14256 axisLine[showAxis ? 'show' : 'hide'](true);
14257 }
14258
14259 if (axisTitle && showAxis) {
14260 var titleXy = axis.getTitlePosition();
14261 if (isNumber(titleXy.y)) {
14262 axisTitle[axisTitle.isNew ? 'attr' : 'animate'](titleXy);
14263 axisTitle.isNew = false;
14264 } else {
14265 axisTitle.attr('y', -9999);
14266 axisTitle.isNew = true;
14267 }
14268 }
14269
14270 // Stacked totals:
14271 if (stackLabelOptions && stackLabelOptions.enabled) {
14272 axis.renderStackTotals();
14273 }
14274 // End stacked totals
14275
14276 axis.isDirty = false;
14277 },
14278
14279 /**
14280 * Redraw the axis to reflect changes in the data or axis extremes. Called
14281 * internally from {@link Chart#redraw}.
14282 *
14283 * @private
14284 */
14285 redraw: function() {
14286
14287 if (this.visible) {
14288 // render the axis
14289 this.render();
14290
14291 // move plot lines and bands
14292 each(this.plotLinesAndBands, function(plotLine) {
14293 plotLine.render();
14294 });
14295 }
14296
14297 // mark associated series as dirty and ready for redraw
14298 each(this.series, function(series) {
14299 series.isDirty = true;
14300 });
14301
14302 },
14303
14304 // Properties to survive after destroy, needed for Axis.update (#4317,
14305 // #5773, #5881).
14306 keepProps: ['extKey', 'hcEvents', 'names', 'series', 'userMax', 'userMin'],
14307
14308 /**
14309 * Destroys an Axis instance. See {@link Axis#remove} for the API endpoint
14310 * to fully remove the axis.
14311 *
14312 * @private
14313 * @param {Boolean} keepEvents
14314 * Whether to preserve events, used internally in Axis.update.
14315 */
14316 destroy: function(keepEvents) {
14317 var axis = this,
14318 stacks = axis.stacks,
14319 plotLinesAndBands = axis.plotLinesAndBands,
14320 plotGroup,
14321 i;
14322
14323 // Remove the events
14324 if (!keepEvents) {
14325 removeEvent(axis);
14326 }
14327
14328 // Destroy each stack total
14329 objectEach(stacks, function(stack, stackKey) {
14330 destroyObjectProperties(stack);
14331
14332 stacks[stackKey] = null;
14333 });
14334
14335 // Destroy collections
14336 each(
14337 [axis.ticks, axis.minorTicks, axis.alternateBands],
14338 function(coll) {
14339 destroyObjectProperties(coll);
14340 }
14341 );
14342 if (plotLinesAndBands) {
14343 i = plotLinesAndBands.length;
14344 while (i--) { // #1975
14345 plotLinesAndBands[i].destroy();
14346 }
14347 }
14348
14349 // Destroy local variables
14350 each(
14351 ['stackTotalGroup', 'axisLine', 'axisTitle', 'axisGroup',
14352 'gridGroup', 'labelGroup', 'cross'
14353 ],
14354 function(prop) {
14355 if (axis[prop]) {
14356 axis[prop] = axis[prop].destroy();
14357 }
14358 }
14359 );
14360
14361 // Destroy each generated group for plotlines and plotbands
14362 for (plotGroup in axis.plotLinesAndBandsGroups) {
14363 axis.plotLinesAndBandsGroups[plotGroup] =
14364 axis.plotLinesAndBandsGroups[plotGroup].destroy();
14365 }
14366
14367 // Delete all properties and fall back to the prototype.
14368 objectEach(axis, function(val, key) {
14369 if (inArray(key, axis.keepProps) === -1) {
14370 delete axis[key];
14371 }
14372 });
14373 },
14374
14375 /**
14376 * Internal function to draw a crosshair.
14377 *
14378 * @param {PointerEvent} [e]
14379 * The event arguments from the modified pointer event, extended
14380 * with `chartX` and `chartY`
14381 * @param {Point} [point]
14382 * The Point object if the crosshair snaps to points.
14383 */
14384 drawCrosshair: function(e, point) {
14385
14386 var path,
14387 options = this.crosshair,
14388 snap = pick(options.snap, true),
14389 pos,
14390 categorized,
14391 graphic = this.cross;
14392
14393 // Use last available event when updating non-snapped crosshairs without
14394 // mouse interaction (#5287)
14395 if (!e) {
14396 e = this.cross && this.cross.e;
14397 }
14398
14399 if (
14400 // Disabled in options
14401 !this.crosshair ||
14402 // Snap
14403 ((defined(point) || !snap) === false)
14404 ) {
14405 this.hideCrosshair();
14406 } else {
14407
14408 // Get the path
14409 if (!snap) {
14410 pos = e &&
14411 (
14412 this.horiz ?
14413 e.chartX - this.pos :
14414 this.len - e.chartY + this.pos
14415 );
14416 } else if (defined(point)) {
14417 // #3834
14418 pos = this.isXAxis ? point.plotX : this.len - point.plotY;
14419 }
14420
14421 if (defined(pos)) {
14422 path = this.getPlotLinePath(
14423 // First argument, value, only used on radial
14424 point && (this.isXAxis ?
14425 point.x :
14426 pick(point.stackY, point.y)
14427 ),
14428 null,
14429 null,
14430 null,
14431 pos // Translated position
14432 ) || null; // #3189
14433 }
14434
14435 if (!defined(path)) {
14436 this.hideCrosshair();
14437 return;
14438 }
14439
14440 categorized = this.categories && !this.isRadial;
14441
14442 // Draw the cross
14443 if (!graphic) {
14444 this.cross = graphic = this.chart.renderer
14445 .path()
14446 .addClass(
14447 'highcharts-crosshair highcharts-crosshair-' +
14448 (categorized ? 'category ' : 'thin ') +
14449 options.className
14450 )
14451 .attr({
14452 zIndex: pick(options.zIndex, 2)
14453 })
14454 .add();
14455
14456
14457
14458 }
14459
14460 graphic.show().attr({
14461 d: path
14462 });
14463
14464 if (categorized && !options.width) {
14465 graphic.attr({
14466 'stroke-width': this.transA
14467 });
14468 }
14469 this.cross.e = e;
14470 }
14471 },
14472
14473 /**
14474 * Hide the crosshair if visible.
14475 */
14476 hideCrosshair: function() {
14477 if (this.cross) {
14478 this.cross.hide();
14479 }
14480 }
14481 }); // end Axis
14482
14483 H.Axis = Axis;
14484 return Axis;
14485 }(Highcharts));
14486 (function(H) {
14487 /**
14488 * (c) 2010-2017 Torstein Honsi
14489 *
14490 * License: www.highcharts.com/license
14491 */
14492 var Axis = H.Axis,
14493 Date = H.Date,
14494 dateFormat = H.dateFormat,
14495 defaultOptions = H.defaultOptions,
14496 defined = H.defined,
14497 each = H.each,
14498 extend = H.extend,
14499 getMagnitude = H.getMagnitude,
14500 getTZOffset = H.getTZOffset,
14501 normalizeTickInterval = H.normalizeTickInterval,
14502 pick = H.pick,
14503 timeUnits = H.timeUnits;
14504 /**
14505 * Set the tick positions to a time unit that makes sense, for example
14506 * on the first of each month or on every Monday. Return an array
14507 * with the time positions. Used in datetime axes as well as for grouping
14508 * data on a datetime axis.
14509 *
14510 * @param {Object} normalizedInterval The interval in axis values (ms) and the count
14511 * @param {Number} min The minimum in axis values
14512 * @param {Number} max The maximum in axis values
14513 * @param {Number} startOfWeek
14514 */
14515 Axis.prototype.getTimeTicks = function(normalizedInterval, min, max, startOfWeek) {
14516 var tickPositions = [],
14517 i,
14518 higherRanks = {},
14519 useUTC = defaultOptions.global.useUTC,
14520 minYear, // used in months and years as a basis for Date.UTC()
14521 // When crossing DST, use the max. Resolves #6278.
14522 minDate = new Date(min - Math.max(getTZOffset(min), getTZOffset(max))),
14523 makeTime = Date.hcMakeTime,
14524 interval = normalizedInterval.unitRange,
14525 count = normalizedInterval.count,
14526 baseOffset, // #6797
14527 variableDayLength;
14528
14529 if (defined(min)) { // #1300
14530 minDate[Date.hcSetMilliseconds](interval >= timeUnits.second ? 0 : // #3935
14531 count * Math.floor(minDate.getMilliseconds() / count)); // #3652, #3654
14532
14533 if (interval >= timeUnits.second) { // second
14534 minDate[Date.hcSetSeconds](interval >= timeUnits.minute ? 0 : // #3935
14535 count * Math.floor(minDate.getSeconds() / count));
14536 }
14537
14538 if (interval >= timeUnits.minute) { // minute
14539 minDate[Date.hcSetMinutes](interval >= timeUnits.hour ? 0 :
14540 count * Math.floor(minDate[Date.hcGetMinutes]() / count));
14541 }
14542
14543 if (interval >= timeUnits.hour) { // hour
14544 minDate[Date.hcSetHours](interval >= timeUnits.day ? 0 :
14545 count * Math.floor(minDate[Date.hcGetHours]() / count));
14546 }
14547
14548 if (interval >= timeUnits.day) { // day
14549 minDate[Date.hcSetDate](interval >= timeUnits.month ? 1 :
14550 count * Math.floor(minDate[Date.hcGetDate]() / count));
14551 }
14552
14553 if (interval >= timeUnits.month) { // month
14554 minDate[Date.hcSetMonth](interval >= timeUnits.year ? 0 :
14555 count * Math.floor(minDate[Date.hcGetMonth]() / count));
14556 minYear = minDate[Date.hcGetFullYear]();
14557 }
14558
14559 if (interval >= timeUnits.year) { // year
14560 minYear -= minYear % count;
14561 minDate[Date.hcSetFullYear](minYear);
14562 }
14563
14564 // week is a special case that runs outside the hierarchy
14565 if (interval === timeUnits.week) {
14566 // get start of current week, independent of count
14567 minDate[Date.hcSetDate](minDate[Date.hcGetDate]() - minDate[Date.hcGetDay]() +
14568 pick(startOfWeek, 1));
14569 }
14570
14571
14572 // Get basics for variable time spans
14573 minYear = minDate[Date.hcGetFullYear]();
14574 var minMonth = minDate[Date.hcGetMonth](),
14575 minDateDate = minDate[Date.hcGetDate](),
14576 minHours = minDate[Date.hcGetHours]();
14577
14578
14579 // Handle local timezone offset
14580 if (Date.hcTimezoneOffset || Date.hcGetTimezoneOffset) {
14581
14582 // Detect whether we need to take the DST crossover into
14583 // consideration. If we're crossing over DST, the day length may be
14584 // 23h or 25h and we need to compute the exact clock time for each
14585 // tick instead of just adding hours. This comes at a cost, so first
14586 // we found out if it is needed. #4951.
14587 variableDayLength =
14588 (!useUTC || !!Date.hcGetTimezoneOffset) &&
14589 (
14590 // Long range, assume we're crossing over.
14591 max - min > 4 * timeUnits.month ||
14592 // Short range, check if min and max are in different time
14593 // zones.
14594 getTZOffset(min) !== getTZOffset(max)
14595 );
14596
14597 // Adjust minDate to the offset date
14598 minDate = minDate.getTime();
14599 baseOffset = getTZOffset(minDate);
14600 minDate = new Date(minDate + baseOffset);
14601 }
14602
14603
14604 // Iterate and add tick positions at appropriate values
14605 var time = minDate.getTime();
14606 i = 1;
14607 while (time < max) {
14608 tickPositions.push(time);
14609
14610 // if the interval is years, use Date.UTC to increase years
14611 if (interval === timeUnits.year) {
14612 time = makeTime(minYear + i * count, 0);
14613
14614 // if the interval is months, use Date.UTC to increase months
14615 } else if (interval === timeUnits.month) {
14616 time = makeTime(minYear, minMonth + i * count);
14617
14618 // if we're using global time, the interval is not fixed as it jumps
14619 // one hour at the DST crossover
14620 } else if (
14621 variableDayLength &&
14622 (interval === timeUnits.day || interval === timeUnits.week)
14623 ) {
14624 time = makeTime(minYear, minMonth, minDateDate +
14625 i * count * (interval === timeUnits.day ? 1 : 7));
14626
14627 } else if (variableDayLength && interval === timeUnits.hour) {
14628 // corrected by the start date time zone offset (baseOffset)
14629 // to hide duplicated label (#6797)
14630 time = makeTime(minYear, minMonth, minDateDate, minHours +
14631 i * count, 0, 0, baseOffset) - baseOffset;
14632
14633 // else, the interval is fixed and we use simple addition
14634 } else {
14635 time += interval * count;
14636 }
14637
14638 i++;
14639 }
14640
14641 // push the last time
14642 tickPositions.push(time);
14643
14644
14645 // Handle higher ranks. Mark new days if the time is on midnight
14646 // (#950, #1649, #1760, #3349). Use a reasonable dropout threshold to
14647 // prevent looping over dense data grouping (#6156).
14648 if (interval <= timeUnits.hour && tickPositions.length < 10000) {
14649 each(tickPositions, function(time) {
14650 if (
14651 // Speed optimization, no need to run dateFormat unless
14652 // we're on a full or half hour
14653 time % 1800000 === 0 &&
14654 // Check for local or global midnight
14655 dateFormat('%H%M%S%L', time) === '000000000'
14656 ) {
14657 higherRanks[time] = 'day';
14658 }
14659 });
14660 }
14661 }
14662
14663
14664 // record information on the chosen unit - for dynamic label formatter
14665 tickPositions.info = extend(normalizedInterval, {
14666 higherRanks: higherRanks,
14667 totalRange: interval * count
14668 });
14669
14670 return tickPositions;
14671 };
14672
14673 /**
14674 * Get a normalized tick interval for dates. Returns a configuration object with
14675 * unit range (interval), count and name. Used to prepare data for getTimeTicks.
14676 * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs
14677 * of segments in stock charts, the normalizing logic was extracted in order to
14678 * prevent it for running over again for each segment having the same interval.
14679 * #662, #697.
14680 */
14681 Axis.prototype.normalizeTimeTickInterval = function(tickInterval, unitsOption) {
14682 var units = unitsOption || [
14683 [
14684 'millisecond', // unit name
14685 [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
14686 ],
14687 [
14688 'second', [1, 2, 5, 10, 15, 30]
14689 ],
14690 [
14691 'minute', [1, 2, 5, 10, 15, 30]
14692 ],
14693 [
14694 'hour', [1, 2, 3, 4, 6, 8, 12]
14695 ],
14696 [
14697 'day', [1, 2]
14698 ],
14699 [
14700 'week', [1, 2]
14701 ],
14702 [
14703 'month', [1, 2, 3, 4, 6]
14704 ],
14705 [
14706 'year',
14707 null
14708 ]
14709 ],
14710 unit = units[units.length - 1], // default unit is years
14711 interval = timeUnits[unit[0]],
14712 multiples = unit[1],
14713 count,
14714 i;
14715
14716 // loop through the units to find the one that best fits the tickInterval
14717 for (i = 0; i < units.length; i++) {
14718 unit = units[i];
14719 interval = timeUnits[unit[0]];
14720 multiples = unit[1];
14721
14722
14723 if (units[i + 1]) {
14724 // lessThan is in the middle between the highest multiple and the next unit.
14725 var lessThan = (interval * multiples[multiples.length - 1] +
14726 timeUnits[units[i + 1][0]]) / 2;
14727
14728 // break and keep the current unit
14729 if (tickInterval <= lessThan) {
14730 break;
14731 }
14732 }
14733 }
14734
14735 // prevent 2.5 years intervals, though 25, 250 etc. are allowed
14736 if (interval === timeUnits.year && tickInterval < 5 * interval) {
14737 multiples = [1, 2, 5];
14738 }
14739
14740 // get the count
14741 count = normalizeTickInterval(
14742 tickInterval / interval,
14743 multiples,
14744 unit[0] === 'year' ? Math.max(getMagnitude(tickInterval / interval), 1) : 1 // #1913, #2360
14745 );
14746
14747 return {
14748 unitRange: interval,
14749 count: count,
14750 unitName: unit[0]
14751 };
14752 };
14753
14754 }(Highcharts));
14755 (function(H) {
14756 /**
14757 * (c) 2010-2017 Torstein Honsi
14758 *
14759 * License: www.highcharts.com/license
14760 */
14761 var Axis = H.Axis,
14762 getMagnitude = H.getMagnitude,
14763 map = H.map,
14764 normalizeTickInterval = H.normalizeTickInterval,
14765 pick = H.pick;
14766 /**
14767 * Methods defined on the Axis prototype
14768 */
14769
14770 /**
14771 * Set the tick positions of a logarithmic axis
14772 */
14773 Axis.prototype.getLogTickPositions = function(interval, min, max, minor) {
14774 var axis = this,
14775 options = axis.options,
14776 axisLength = axis.len,
14777 lin2log = axis.lin2log,
14778 log2lin = axis.log2lin,
14779 // Since we use this method for both major and minor ticks,
14780 // use a local variable and return the result
14781 positions = [];
14782
14783 // Reset
14784 if (!minor) {
14785 axis._minorAutoInterval = null;
14786 }
14787
14788 // First case: All ticks fall on whole logarithms: 1, 10, 100 etc.
14789 if (interval >= 0.5) {
14790 interval = Math.round(interval);
14791 positions = axis.getLinearTickPositions(interval, min, max);
14792
14793 // Second case: We need intermediary ticks. For example
14794 // 1, 2, 4, 6, 8, 10, 20, 40 etc.
14795 } else if (interval >= 0.08) {
14796 var roundedMin = Math.floor(min),
14797 intermediate,
14798 i,
14799 j,
14800 len,
14801 pos,
14802 lastPos,
14803 break2;
14804
14805 if (interval > 0.3) {
14806 intermediate = [1, 2, 4];
14807 } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc
14808 intermediate = [1, 2, 4, 6, 8];
14809 } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
14810 intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
14811 }
14812
14813 for (i = roundedMin; i < max + 1 && !break2; i++) {
14814 len = intermediate.length;
14815 for (j = 0; j < len && !break2; j++) {
14816 pos = log2lin(lin2log(i) * intermediate[j]);
14817 if (pos > min && (!minor || lastPos <= max) && lastPos !== undefined) { // #1670, lastPos is #3113
14818 positions.push(lastPos);
14819 }
14820
14821 if (lastPos > max) {
14822 break2 = true;
14823 }
14824 lastPos = pos;
14825 }
14826 }
14827
14828 // Third case: We are so deep in between whole logarithmic values that
14829 // we might as well handle the tick positions like a linear axis. For
14830 // example 1.01, 1.02, 1.03, 1.04.
14831 } else {
14832 var realMin = lin2log(min),
14833 realMax = lin2log(max),
14834 tickIntervalOption = minor ?
14835 this.getMinorTickInterval() :
14836 options.tickInterval,
14837 filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption,
14838 tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1),
14839 totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength;
14840
14841 interval = pick(
14842 filteredTickIntervalOption,
14843 axis._minorAutoInterval,
14844 (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)
14845 );
14846
14847 interval = normalizeTickInterval(
14848 interval,
14849 null,
14850 getMagnitude(interval)
14851 );
14852
14853 positions = map(axis.getLinearTickPositions(
14854 interval,
14855 realMin,
14856 realMax
14857 ), log2lin);
14858
14859 if (!minor) {
14860 axis._minorAutoInterval = interval / 5;
14861 }
14862 }
14863
14864 // Set the axis-level tickInterval variable
14865 if (!minor) {
14866 axis.tickInterval = interval;
14867 }
14868 return positions;
14869 };
14870
14871 Axis.prototype.log2lin = function(num) {
14872 return Math.log(num) / Math.LN10;
14873 };
14874
14875 Axis.prototype.lin2log = function(num) {
14876 return Math.pow(10, num);
14877 };
14878
14879 }(Highcharts));
14880 (function(H, Axis) {
14881 /**
14882 * (c) 2010-2017 Torstein Honsi
14883 *
14884 * License: www.highcharts.com/license
14885 */
14886 var arrayMax = H.arrayMax,
14887 arrayMin = H.arrayMin,
14888 defined = H.defined,
14889 destroyObjectProperties = H.destroyObjectProperties,
14890 each = H.each,
14891 erase = H.erase,
14892 merge = H.merge,
14893 pick = H.pick;
14894 /*
14895 * The object wrapper for plot lines and plot bands
14896 * @param {Object} options
14897 */
14898 H.PlotLineOrBand = function(axis, options) {
14899 this.axis = axis;
14900
14901 if (options) {
14902 this.options = options;
14903 this.id = options.id;
14904 }
14905 };
14906
14907 H.PlotLineOrBand.prototype = {
14908
14909 /**
14910 * Render the plot line or plot band. If it is already existing,
14911 * move it.
14912 */
14913 render: function() {
14914 var plotLine = this,
14915 axis = plotLine.axis,
14916 horiz = axis.horiz,
14917 options = plotLine.options,
14918 optionsLabel = options.label,
14919 label = plotLine.label,
14920 to = options.to,
14921 from = options.from,
14922 value = options.value,
14923 isBand = defined(from) && defined(to),
14924 isLine = defined(value),
14925 svgElem = plotLine.svgElem,
14926 isNew = !svgElem,
14927 path = [],
14928 color = options.color,
14929 zIndex = pick(options.zIndex, 0),
14930 events = options.events,
14931 attribs = {
14932 'class': 'highcharts-plot-' + (isBand ? 'band ' : 'line ') +
14933 (options.className || '')
14934 },
14935 groupAttribs = {},
14936 renderer = axis.chart.renderer,
14937 groupName = isBand ? 'bands' : 'lines',
14938 group,
14939 log2lin = axis.log2lin;
14940
14941 // logarithmic conversion
14942 if (axis.isLog) {
14943 from = log2lin(from);
14944 to = log2lin(to);
14945 value = log2lin(value);
14946 }
14947
14948
14949
14950 // Grouping and zIndex
14951 groupAttribs.zIndex = zIndex;
14952 groupName += '-' + zIndex;
14953
14954 group = axis.plotLinesAndBandsGroups[groupName];
14955 if (!group) {
14956 axis.plotLinesAndBandsGroups[groupName] = group =
14957 renderer.g('plot-' + groupName)
14958 .attr(groupAttribs).add();
14959 }
14960
14961 // Create the path
14962 if (isNew) {
14963 plotLine.svgElem = svgElem =
14964 renderer
14965 .path()
14966 .attr(attribs).add(group);
14967 }
14968
14969
14970 // Set the path or return
14971 if (isLine) {
14972 path = axis.getPlotLinePath(value, svgElem.strokeWidth());
14973 } else if (isBand) { // plot band
14974 path = axis.getPlotBandPath(from, to, options);
14975 } else {
14976 return;
14977 }
14978
14979
14980 // common for lines and bands
14981 if (isNew && path && path.length) {
14982 svgElem.attr({
14983 d: path
14984 });
14985
14986 // events
14987 if (events) {
14988 H.objectEach(events, function(event, eventType) {
14989 svgElem.on(eventType, function(e) {
14990 events[eventType].apply(plotLine, [e]);
14991 });
14992 });
14993 }
14994 } else if (svgElem) {
14995 if (path) {
14996 svgElem.show();
14997 svgElem.animate({
14998 d: path
14999 });
15000 } else {
15001 svgElem.hide();
15002 if (label) {
15003 plotLine.label = label = label.destroy();
15004 }
15005 }
15006 }
15007
15008 // the plot band/line label
15009 if (
15010 optionsLabel &&
15011 defined(optionsLabel.text) &&
15012 path &&
15013 path.length &&
15014 axis.width > 0 &&
15015 axis.height > 0 &&
15016 !path.flat
15017 ) {
15018 // apply defaults
15019 optionsLabel = merge({
15020 align: horiz && isBand && 'center',
15021 x: horiz ? !isBand && 4 : 10,
15022 verticalAlign: !horiz && isBand && 'middle',
15023 y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4,
15024 rotation: horiz && !isBand && 90
15025 }, optionsLabel);
15026
15027 this.renderLabel(optionsLabel, path, isBand, zIndex);
15028
15029 } else if (label) { // move out of sight
15030 label.hide();
15031 }
15032
15033 // chainable
15034 return plotLine;
15035 },
15036
15037 /**
15038 * Render and align label for plot line or band.
15039 */
15040 renderLabel: function(optionsLabel, path, isBand, zIndex) {
15041 var plotLine = this,
15042 label = plotLine.label,
15043 renderer = plotLine.axis.chart.renderer,
15044 attribs,
15045 xBounds,
15046 yBounds,
15047 x,
15048 y;
15049
15050 // add the SVG element
15051 if (!label) {
15052 attribs = {
15053 align: optionsLabel.textAlign || optionsLabel.align,
15054 rotation: optionsLabel.rotation,
15055 'class': 'highcharts-plot-' + (isBand ? 'band' : 'line') +
15056 '-label ' + (optionsLabel.className || '')
15057 };
15058
15059 attribs.zIndex = zIndex;
15060
15061 plotLine.label = label = renderer.text(
15062 optionsLabel.text,
15063 0,
15064 0,
15065 optionsLabel.useHTML
15066 )
15067 .attr(attribs)
15068 .add();
15069
15070
15071 }
15072
15073 // get the bounding box and align the label
15074 // #3000 changed to better handle choice between plotband or plotline
15075 xBounds = path.xBounds || [path[1], path[4], (isBand ? path[6] : path[1])];
15076 yBounds = path.yBounds || [path[2], path[5], (isBand ? path[7] : path[2])];
15077
15078 x = arrayMin(xBounds);
15079 y = arrayMin(yBounds);
15080
15081 label.align(optionsLabel, false, {
15082 x: x,
15083 y: y,
15084 width: arrayMax(xBounds) - x,
15085 height: arrayMax(yBounds) - y
15086 });
15087 label.show();
15088 },
15089
15090 /**
15091 * Remove the plot line or band
15092 */
15093 destroy: function() {
15094 // remove it from the lookup
15095 erase(this.axis.plotLinesAndBands, this);
15096
15097 delete this.axis;
15098 destroyObjectProperties(this);
15099 }
15100 };
15101
15102 /**
15103 * Object with members for extending the Axis prototype
15104 * @todo Extend directly instead of adding object to Highcharts first
15105 */
15106
15107 H.extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {
15108
15109 /**
15110 * Internal function to create the SVG path definition for a plot band.
15111 *
15112 * @param {Number} from
15113 * The axis value to start from.
15114 * @param {Number} to
15115 * The axis value to end on.
15116 *
15117 * @return {Array.<String|Number>}
15118 * The SVG path definition in array form.
15119 */
15120 getPlotBandPath: function(from, to) {
15121 var toPath = this.getPlotLinePath(to, null, null, true),
15122 path = this.getPlotLinePath(from, null, null, true),
15123 result = [],
15124 i,
15125 // #4964 check if chart is inverted or plotband is on yAxis
15126 horiz = this.horiz,
15127 plus = 1,
15128 flat,
15129 outside =
15130 (from < this.min && to < this.min) ||
15131 (from > this.max && to > this.max);
15132
15133 if (path && toPath) {
15134
15135 // Flat paths don't need labels (#3836)
15136 if (outside) {
15137 flat = path.toString() === toPath.toString();
15138 plus = 0;
15139 }
15140
15141 // Go over each subpath - for panes in Highstock
15142 for (i = 0; i < path.length; i += 6) {
15143
15144 // Add 1 pixel when coordinates are the same
15145 if (horiz && toPath[i + 1] === path[i + 1]) {
15146 toPath[i + 1] += plus;
15147 toPath[i + 4] += plus;
15148 } else if (!horiz && toPath[i + 2] === path[i + 2]) {
15149 toPath[i + 2] += plus;
15150 toPath[i + 5] += plus;
15151 }
15152
15153 result.push(
15154 'M',
15155 path[i + 1],
15156 path[i + 2],
15157 'L',
15158 path[i + 4],
15159 path[i + 5],
15160 toPath[i + 4],
15161 toPath[i + 5],
15162 toPath[i + 1],
15163 toPath[i + 2],
15164 'z'
15165 );
15166 result.flat = flat;
15167 }
15168
15169 } else { // outside the axis area
15170 path = null;
15171 }
15172
15173 return result;
15174 },
15175
15176 /**
15177 * Add a plot band after render time.
15178 *
15179 * @param {AxisPlotBandsOptions} options
15180 * A configuration object for the plot band, as defined in {@link
15181 * https://api.highcharts.com/highcharts/xAxis.plotBands|
15182 * xAxis.plotBands}.
15183 * @return {Object}
15184 * The added plot band.
15185 * @sample highcharts/members/axis-addplotband/
15186 * Toggle the plot band from a button
15187 */
15188 addPlotBand: function(options) {
15189 return this.addPlotBandOrLine(options, 'plotBands');
15190 },
15191
15192 /**
15193 * Add a plot line after render time.
15194 *
15195 * @param {AxisPlotLinesOptions} options
15196 * A configuration object for the plot line, as defined in {@link
15197 * https://api.highcharts.com/highcharts/xAxis.plotLines|
15198 * xAxis.plotLines}.
15199 * @return {Object}
15200 * The added plot line.
15201 * @sample highcharts/members/axis-addplotline/
15202 * Toggle the plot line from a button
15203 */
15204 addPlotLine: function(options) {
15205 return this.addPlotBandOrLine(options, 'plotLines');
15206 },
15207
15208 /**
15209 * Add a plot band or plot line after render time. Called from addPlotBand
15210 * and addPlotLine internally.
15211 *
15212 * @private
15213 * @param options {AxisPlotLinesOptions|AxisPlotBandsOptions}
15214 * The plotBand or plotLine configuration object.
15215 */
15216 addPlotBandOrLine: function(options, coll) {
15217 var obj = new H.PlotLineOrBand(this, options).render(),
15218 userOptions = this.userOptions;
15219
15220 if (obj) { // #2189
15221 // Add it to the user options for exporting and Axis.update
15222 if (coll) {
15223 userOptions[coll] = userOptions[coll] || [];
15224 userOptions[coll].push(options);
15225 }
15226 this.plotLinesAndBands.push(obj);
15227 }
15228
15229 return obj;
15230 },
15231
15232 /**
15233 * Remove a plot band or plot line from the chart by id. Called internally
15234 * from `removePlotBand` and `removePlotLine`.
15235 *
15236 * @private
15237 * @param {String} id
15238 */
15239 removePlotBandOrLine: function(id) {
15240 var plotLinesAndBands = this.plotLinesAndBands,
15241 options = this.options,
15242 userOptions = this.userOptions,
15243 i = plotLinesAndBands.length;
15244 while (i--) {
15245 if (plotLinesAndBands[i].id === id) {
15246 plotLinesAndBands[i].destroy();
15247 }
15248 }
15249 each([
15250 options.plotLines || [],
15251 userOptions.plotLines || [],
15252 options.plotBands || [],
15253 userOptions.plotBands || []
15254 ], function(arr) {
15255 i = arr.length;
15256 while (i--) {
15257 if (arr[i].id === id) {
15258 erase(arr, arr[i]);
15259 }
15260 }
15261 });
15262 },
15263
15264 /**
15265 * Remove a plot band by its id.
15266 *
15267 * @param {String} id
15268 * The plot band's `id` as given in the original configuration
15269 * object or in the `addPlotBand` option.
15270 * @sample highcharts/members/axis-removeplotband/
15271 * Remove plot band by id
15272 * @sample highcharts/members/axis-addplotband/
15273 * Toggle the plot band from a button
15274 */
15275 removePlotBand: function(id) {
15276 this.removePlotBandOrLine(id);
15277 },
15278
15279 /**
15280 * Remove a plot line by its id.
15281 * @param {String} id
15282 * The plot line's `id` as given in the original configuration
15283 * object or in the `addPlotLine` option.
15284 * @sample highcharts/xaxis/plotlines-id/
15285 * Remove plot line by id
15286 * @sample highcharts/members/axis-addplotline/
15287 * Toggle the plot line from a button
15288 */
15289 removePlotLine: function(id) {
15290 this.removePlotBandOrLine(id);
15291 }
15292 });
15293
15294 }(Highcharts, Axis));
15295 (function(H) {
15296 /**
15297 * (c) 2010-2017 Torstein Honsi
15298 *
15299 * License: www.highcharts.com/license
15300 */
15301 var dateFormat = H.dateFormat,
15302 each = H.each,
15303 extend = H.extend,
15304 format = H.format,
15305 isNumber = H.isNumber,
15306 map = H.map,
15307 merge = H.merge,
15308 pick = H.pick,
15309 splat = H.splat,
15310 syncTimeout = H.syncTimeout,
15311 timeUnits = H.timeUnits;
15312 /**
15313 * The tooltip object
15314 * @param {Object} chart The chart instance
15315 * @param {Object} options Tooltip options
15316 */
15317 H.Tooltip = function() {
15318 this.init.apply(this, arguments);
15319 };
15320
15321 H.Tooltip.prototype = {
15322
15323 init: function(chart, options) {
15324
15325 // Save the chart and options
15326 this.chart = chart;
15327 this.options = options;
15328
15329 // List of crosshairs
15330 this.crosshairs = [];
15331
15332 // Current values of x and y when animating
15333 this.now = {
15334 x: 0,
15335 y: 0
15336 };
15337
15338 // The tooltip is initially hidden
15339 this.isHidden = true;
15340
15341
15342
15343 // Public property for getting the shared state.
15344 this.split = options.split && !chart.inverted;
15345 this.shared = options.shared || this.split;
15346
15347 },
15348
15349 /**
15350 * Destroy the single tooltips in a split tooltip.
15351 * If the tooltip is active then it is not destroyed, unless forced to.
15352 * @param {boolean} force Force destroy all tooltips.
15353 * @return {undefined}
15354 */
15355 cleanSplit: function(force) {
15356 each(this.chart.series, function(series) {
15357 var tt = series && series.tt;
15358 if (tt) {
15359 if (!tt.isActive || force) {
15360 series.tt = tt.destroy();
15361 } else {
15362 tt.isActive = false;
15363 }
15364 }
15365 });
15366 },
15367
15368
15369 /**
15370 * In styled mode, apply the default filter for the tooltip drop-shadow. It
15371 * needs to have an id specific to the chart, otherwise there will be issues
15372 * when one tooltip adopts the filter of a different chart, specifically one
15373 * where the container is hidden.
15374 */
15375 applyFilter: function() {
15376
15377 var chart = this.chart;
15378 chart.renderer.definition({
15379 tagName: 'filter',
15380 id: 'drop-shadow-' + chart.index,
15381 opacity: 0.5,
15382 children: [{
15383 tagName: 'feGaussianBlur',
15384 in: 'SourceAlpha',
15385 stdDeviation: 1
15386 }, {
15387 tagName: 'feOffset',
15388 dx: 1,
15389 dy: 1
15390 }, {
15391 tagName: 'feComponentTransfer',
15392 children: [{
15393 tagName: 'feFuncA',
15394 type: 'linear',
15395 slope: 0.3
15396 }]
15397 }, {
15398 tagName: 'feMerge',
15399 children: [{
15400 tagName: 'feMergeNode'
15401 }, {
15402 tagName: 'feMergeNode',
15403 in: 'SourceGraphic'
15404 }]
15405 }]
15406 });
15407 chart.renderer.definition({
15408 tagName: 'style',
15409 textContent: '.highcharts-tooltip-' + chart.index + '{' +
15410 'filter:url(#drop-shadow-' + chart.index + ')' +
15411 '}'
15412 });
15413 },
15414
15415
15416
15417 /**
15418 * Create the Tooltip label element if it doesn't exist, then return the
15419 * label.
15420 */
15421 getLabel: function() {
15422
15423 var renderer = this.chart.renderer,
15424 options = this.options;
15425
15426 if (!this.label) {
15427 // Create the label
15428 if (this.split) {
15429 this.label = renderer.g('tooltip');
15430 } else {
15431 this.label = renderer.label(
15432 '',
15433 0,
15434 0,
15435 options.shape || 'callout',
15436 null,
15437 null,
15438 options.useHTML,
15439 null,
15440 'tooltip'
15441 )
15442 .attr({
15443 padding: options.padding,
15444 r: options.borderRadius
15445 });
15446
15447
15448 }
15449
15450
15451 // Apply the drop-shadow filter
15452 this.applyFilter();
15453 this.label.addClass('highcharts-tooltip-' + this.chart.index);
15454
15455
15456 this.label
15457 .attr({
15458 zIndex: 8
15459 })
15460 .add();
15461 }
15462 return this.label;
15463 },
15464
15465 update: function(options) {
15466 this.destroy();
15467 // Update user options (#6218)
15468 merge(true, this.chart.options.tooltip.userOptions, options);
15469 this.init(this.chart, merge(true, this.options, options));
15470 },
15471
15472 /**
15473 * Destroy the tooltip and its elements.
15474 */
15475 destroy: function() {
15476 // Destroy and clear local variables
15477 if (this.label) {
15478 this.label = this.label.destroy();
15479 }
15480 if (this.split && this.tt) {
15481 this.cleanSplit(this.chart, true);
15482 this.tt = this.tt.destroy();
15483 }
15484 clearTimeout(this.hideTimer);
15485 clearTimeout(this.tooltipTimeout);
15486 },
15487
15488 /**
15489 * Provide a soft movement for the tooltip
15490 *
15491 * @param {Number} x
15492 * @param {Number} y
15493 * @private
15494 */
15495 move: function(x, y, anchorX, anchorY) {
15496 var tooltip = this,
15497 now = tooltip.now,
15498 animate = tooltip.options.animation !== false &&
15499 !tooltip.isHidden &&
15500 // When we get close to the target position, abort animation and
15501 // land on the right place (#3056)
15502 (Math.abs(x - now.x) > 1 || Math.abs(y - now.y) > 1),
15503 skipAnchor = tooltip.followPointer || tooltip.len > 1;
15504
15505 // Get intermediate values for animation
15506 extend(now, {
15507 x: animate ? (2 * now.x + x) / 3 : x,
15508 y: animate ? (now.y + y) / 2 : y,
15509 anchorX: skipAnchor ?
15510 undefined : animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,
15511 anchorY: skipAnchor ?
15512 undefined : animate ? (now.anchorY + anchorY) / 2 : anchorY
15513 });
15514
15515 // Move to the intermediate value
15516 tooltip.getLabel().attr(now);
15517
15518
15519 // Run on next tick of the mouse tracker
15520 if (animate) {
15521
15522 // Never allow two timeouts
15523 clearTimeout(this.tooltipTimeout);
15524
15525 // Set the fixed interval ticking for the smooth tooltip
15526 this.tooltipTimeout = setTimeout(function() {
15527 // The interval function may still be running during destroy,
15528 // so check that the chart is really there before calling.
15529 if (tooltip) {
15530 tooltip.move(x, y, anchorX, anchorY);
15531 }
15532 }, 32);
15533
15534 }
15535 },
15536
15537 /**
15538 * Hide the tooltip
15539 */
15540 hide: function(delay) {
15541 var tooltip = this;
15542 // disallow duplicate timers (#1728, #1766)
15543 clearTimeout(this.hideTimer);
15544 delay = pick(delay, this.options.hideDelay, 500);
15545 if (!this.isHidden) {
15546 this.hideTimer = syncTimeout(function() {
15547 tooltip.getLabel()[delay ? 'fadeOut' : 'hide']();
15548 tooltip.isHidden = true;
15549 }, delay);
15550 }
15551 },
15552
15553 /**
15554 * Extendable method to get the anchor position of the tooltip
15555 * from a point or set of points
15556 */
15557 getAnchor: function(points, mouseEvent) {
15558 var ret,
15559 chart = this.chart,
15560 inverted = chart.inverted,
15561 plotTop = chart.plotTop,
15562 plotLeft = chart.plotLeft,
15563 plotX = 0,
15564 plotY = 0,
15565 yAxis,
15566 xAxis;
15567
15568 points = splat(points);
15569
15570 // Pie uses a special tooltipPos
15571 ret = points[0].tooltipPos;
15572
15573 // When tooltip follows mouse, relate the position to the mouse
15574 if (this.followPointer && mouseEvent) {
15575 if (mouseEvent.chartX === undefined) {
15576 mouseEvent = chart.pointer.normalize(mouseEvent);
15577 }
15578 ret = [
15579 mouseEvent.chartX - chart.plotLeft,
15580 mouseEvent.chartY - plotTop
15581 ];
15582 }
15583 // When shared, use the average position
15584 if (!ret) {
15585 each(points, function(point) {
15586 yAxis = point.series.yAxis;
15587 xAxis = point.series.xAxis;
15588 plotX += point.plotX +
15589 (!inverted && xAxis ? xAxis.left - plotLeft : 0);
15590 plotY +=
15591 (
15592 point.plotLow ?
15593 (point.plotLow + point.plotHigh) / 2 :
15594 point.plotY
15595 ) +
15596 (!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151
15597 });
15598
15599 plotX /= points.length;
15600 plotY /= points.length;
15601
15602 ret = [
15603 inverted ? chart.plotWidth - plotY : plotX,
15604 this.shared && !inverted && points.length > 1 && mouseEvent ?
15605 // place shared tooltip next to the mouse (#424)
15606 mouseEvent.chartY - plotTop :
15607 inverted ? chart.plotHeight - plotX : plotY
15608 ];
15609 }
15610
15611 return map(ret, Math.round);
15612 },
15613
15614 /**
15615 * Place the tooltip in a chart without spilling over
15616 * and not covering the point it self.
15617 */
15618 getPosition: function(boxWidth, boxHeight, point) {
15619
15620 var chart = this.chart,
15621 distance = this.distance,
15622 ret = {},
15623 // Don't use h if chart isn't inverted (#7242)
15624 h = (chart.inverted && point.h) || 0, // #4117
15625 swapped,
15626 first = ['y', chart.chartHeight, boxHeight,
15627 point.plotY + chart.plotTop, chart.plotTop,
15628 chart.plotTop + chart.plotHeight
15629 ],
15630 second = ['x', chart.chartWidth, boxWidth,
15631 point.plotX + chart.plotLeft, chart.plotLeft,
15632 chart.plotLeft + chart.plotWidth
15633 ],
15634 // The far side is right or bottom
15635 preferFarSide = !this.followPointer && pick(
15636 point.ttBelow, !chart.inverted === !!point.negative
15637 ), // #4984
15638
15639 /**
15640 * Handle the preferred dimension. When the preferred dimension is
15641 * tooltip on top or bottom of the point, it will look for space
15642 * there.
15643 */
15644 firstDimension = function(
15645 dim,
15646 outerSize,
15647 innerSize,
15648 point,
15649 min,
15650 max
15651 ) {
15652 var roomLeft = innerSize < point - distance,
15653 roomRight = point + distance + innerSize < outerSize,
15654 alignedLeft = point - distance - innerSize,
15655 alignedRight = point + distance;
15656
15657 if (preferFarSide && roomRight) {
15658 ret[dim] = alignedRight;
15659 } else if (!preferFarSide && roomLeft) {
15660 ret[dim] = alignedLeft;
15661 } else if (roomLeft) {
15662 ret[dim] = Math.min(
15663 max - innerSize,
15664 alignedLeft - h < 0 ? alignedLeft : alignedLeft - h
15665 );
15666 } else if (roomRight) {
15667 ret[dim] = Math.max(
15668 min,
15669 alignedRight + h + innerSize > outerSize ?
15670 alignedRight :
15671 alignedRight + h
15672 );
15673 } else {
15674 return false;
15675 }
15676 },
15677 /**
15678 * Handle the secondary dimension. If the preferred dimension is
15679 * tooltip on top or bottom of the point, the second dimension is to
15680 * align the tooltip above the point, trying to align center but
15681 * allowing left or right align within the chart box.
15682 */
15683 secondDimension = function(dim, outerSize, innerSize, point) {
15684 var retVal;
15685
15686 // Too close to the edge, return false and swap dimensions
15687 if (point < distance || point > outerSize - distance) {
15688 retVal = false;
15689 // Align left/top
15690 } else if (point < innerSize / 2) {
15691 ret[dim] = 1;
15692 // Align right/bottom
15693 } else if (point > outerSize - innerSize / 2) {
15694 ret[dim] = outerSize - innerSize - 2;
15695 // Align center
15696 } else {
15697 ret[dim] = point - innerSize / 2;
15698 }
15699 return retVal;
15700 },
15701 /**
15702 * Swap the dimensions
15703 */
15704 swap = function(count) {
15705 var temp = first;
15706 first = second;
15707 second = temp;
15708 swapped = count;
15709 },
15710 run = function() {
15711 if (firstDimension.apply(0, first) !== false) {
15712 if (
15713 secondDimension.apply(0, second) === false &&
15714 !swapped
15715 ) {
15716 swap(true);
15717 run();
15718 }
15719 } else if (!swapped) {
15720 swap(true);
15721 run();
15722 } else {
15723 ret.x = ret.y = 0;
15724 }
15725 };
15726
15727 // Under these conditions, prefer the tooltip on the side of the point
15728 if (chart.inverted || this.len > 1) {
15729 swap();
15730 }
15731 run();
15732
15733 return ret;
15734
15735 },
15736
15737 /**
15738 * In case no user defined formatter is given, this will be used. Note that
15739 * the context here is an object holding point, series, x, y etc.
15740 *
15741 * @returns {String|Array<String>}
15742 */
15743 defaultFormatter: function(tooltip) {
15744 var items = this.points || splat(this),
15745 s;
15746
15747 // Build the header
15748 s = [tooltip.tooltipFooterHeaderFormatter(items[0])];
15749
15750 // build the values
15751 s = s.concat(tooltip.bodyFormatter(items));
15752
15753 // footer
15754 s.push(tooltip.tooltipFooterHeaderFormatter(items[0], true));
15755
15756 return s;
15757 },
15758
15759 /**
15760 * Refresh the tooltip's text and position.
15761 * @param {Object|Array} pointOrPoints Rither a point or an array of points
15762 */
15763 refresh: function(pointOrPoints, mouseEvent) {
15764 var tooltip = this,
15765 label,
15766 options = tooltip.options,
15767 x,
15768 y,
15769 point = pointOrPoints,
15770 anchor,
15771 textConfig = {},
15772 text,
15773 pointConfig = [],
15774 formatter = options.formatter || tooltip.defaultFormatter,
15775 shared = tooltip.shared,
15776 currentSeries;
15777
15778 if (!options.enabled) {
15779 return;
15780 }
15781
15782 clearTimeout(this.hideTimer);
15783
15784 // get the reference point coordinates (pie charts use tooltipPos)
15785 tooltip.followPointer = splat(point)[0].series.tooltipOptions
15786 .followPointer;
15787 anchor = tooltip.getAnchor(point, mouseEvent);
15788 x = anchor[0];
15789 y = anchor[1];
15790
15791 // shared tooltip, array is sent over
15792 if (shared && !(point.series && point.series.noSharedTooltip)) {
15793 each(point, function(item) {
15794 item.setState('hover');
15795
15796 pointConfig.push(item.getLabelConfig());
15797 });
15798
15799 textConfig = {
15800 x: point[0].category,
15801 y: point[0].y
15802 };
15803 textConfig.points = pointConfig;
15804 point = point[0];
15805
15806 // single point tooltip
15807 } else {
15808 textConfig = point.getLabelConfig();
15809 }
15810 this.len = pointConfig.length; // #6128
15811 text = formatter.call(textConfig, tooltip);
15812
15813 // register the current series
15814 currentSeries = point.series;
15815 this.distance = pick(currentSeries.tooltipOptions.distance, 16);
15816
15817 // update the inner HTML
15818 if (text === false) {
15819 this.hide();
15820 } else {
15821
15822 label = tooltip.getLabel();
15823
15824 // show it
15825 if (tooltip.isHidden) {
15826 label.attr({
15827 opacity: 1
15828 }).show();
15829 }
15830
15831 // update text
15832 if (tooltip.split) {
15833 this.renderSplit(text, splat(pointOrPoints));
15834 } else {
15835
15836 // Prevent the tooltip from flowing over the chart box (#6659)
15837
15838 label.css({
15839 width: this.chart.spacingBox.width
15840 });
15841
15842
15843 label.attr({
15844 text: text && text.join ? text.join('') : text
15845 });
15846
15847 // Set the stroke color of the box to reflect the point
15848 label.removeClass(/highcharts-color-[\d]+/g)
15849 .addClass(
15850 'highcharts-color-' +
15851 pick(point.colorIndex, currentSeries.colorIndex)
15852 );
15853
15854
15855
15856 tooltip.updatePosition({
15857 plotX: x,
15858 plotY: y,
15859 negative: point.negative,
15860 ttBelow: point.ttBelow,
15861 h: anchor[2] || 0
15862 });
15863 }
15864
15865 this.isHidden = false;
15866 }
15867 },
15868
15869 /**
15870 * Render the split tooltip. Loops over each point's text and adds
15871 * a label next to the point, then uses the distribute function to
15872 * find best non-overlapping positions.
15873 */
15874 renderSplit: function(labels, points) {
15875 var tooltip = this,
15876 boxes = [],
15877 chart = this.chart,
15878 ren = chart.renderer,
15879 rightAligned = true,
15880 options = this.options,
15881 headerHeight = 0,
15882 tooltipLabel = this.getLabel();
15883
15884 // Graceful degradation for legacy formatters
15885 if (H.isString(labels)) {
15886 labels = [false, labels];
15887 }
15888 // Create the individual labels for header and points, ignore footer
15889 each(labels.slice(0, points.length + 1), function(str, i) {
15890 if (str !== false) {
15891 var point = points[i - 1] ||
15892 // Item 0 is the header. Instead of this, we could also
15893 // use the crosshair label
15894 {
15895 isHeader: true,
15896 plotX: points[0].plotX
15897 },
15898 owner = point.series || tooltip,
15899 tt = owner.tt,
15900 series = point.series || {},
15901 colorClass = 'highcharts-color-' + pick(
15902 point.colorIndex,
15903 series.colorIndex,
15904 'none'
15905 ),
15906 target,
15907 x,
15908 bBox,
15909 boxWidth;
15910
15911 // Store the tooltip referance on the series
15912 if (!tt) {
15913 owner.tt = tt = ren.label(
15914 null,
15915 null,
15916 null,
15917 'callout',
15918 null,
15919 null,
15920 options.useHTML
15921 )
15922 .addClass('highcharts-tooltip-box ' + colorClass)
15923 .attr({
15924 'padding': options.padding,
15925 'r': options.borderRadius
15926
15927 })
15928 .add(tooltipLabel);
15929 }
15930
15931 tt.isActive = true;
15932 tt.attr({
15933 text: str
15934 });
15935
15936
15937 // Get X position now, so we can move all to the other side in
15938 // case of overflow
15939 bBox = tt.getBBox();
15940 boxWidth = bBox.width + tt.strokeWidth();
15941 if (point.isHeader) {
15942 headerHeight = bBox.height;
15943 x = Math.max(
15944 0, // No left overflow
15945 Math.min(
15946 point.plotX + chart.plotLeft - boxWidth / 2,
15947 // No right overflow (#5794)
15948 chart.chartWidth - boxWidth
15949 )
15950 );
15951 } else {
15952 x = point.plotX + chart.plotLeft -
15953 pick(options.distance, 16) - boxWidth;
15954 }
15955
15956
15957 // If overflow left, we don't use this x in the next loop
15958 if (x < 0) {
15959 rightAligned = false;
15960 }
15961
15962 // Prepare for distribution
15963 target = (point.series && point.series.yAxis &&
15964 point.series.yAxis.pos) + (point.plotY || 0);
15965 target -= chart.plotTop;
15966 boxes.push({
15967 target: point.isHeader ?
15968 chart.plotHeight + headerHeight : target,
15969 rank: point.isHeader ? 1 : 0,
15970 size: owner.tt.getBBox().height + 1,
15971 point: point,
15972 x: x,
15973 tt: tt
15974 });
15975 }
15976 });
15977
15978 // Clean previous run (for missing points)
15979 this.cleanSplit();
15980
15981 // Distribute and put in place
15982 H.distribute(boxes, chart.plotHeight + headerHeight);
15983 each(boxes, function(box) {
15984 var point = box.point,
15985 series = point.series;
15986
15987 // Put the label in place
15988 box.tt.attr({
15989 visibility: box.pos === undefined ? 'hidden' : 'inherit',
15990 x: (rightAligned || point.isHeader ?
15991 box.x :
15992 point.plotX + chart.plotLeft + pick(options.distance, 16)),
15993 y: box.pos + chart.plotTop,
15994 anchorX: point.isHeader ?
15995 point.plotX + chart.plotLeft : point.plotX + series.xAxis.pos,
15996 anchorY: point.isHeader ?
15997 box.pos + chart.plotTop - 15 : point.plotY + series.yAxis.pos
15998 });
15999 });
16000 },
16001
16002 /**
16003 * Find the new position and perform the move
16004 */
16005 updatePosition: function(point) {
16006 var chart = this.chart,
16007 label = this.getLabel(),
16008 pos = (this.options.positioner || this.getPosition).call(
16009 this,
16010 label.width,
16011 label.height,
16012 point
16013 );
16014
16015 // do the move
16016 this.move(
16017 Math.round(pos.x),
16018 Math.round(pos.y || 0), // can be undefined (#3977)
16019 point.plotX + chart.plotLeft,
16020 point.plotY + chart.plotTop
16021 );
16022 },
16023
16024 /**
16025 * Get the optimal date format for a point, based on a range.
16026 * @param {number} range - The time range
16027 * @param {number|Date} date - The date of the point in question
16028 * @param {number} startOfWeek - An integer representing the first day of
16029 * the week, where 0 is Sunday
16030 * @param {Object} dateTimeLabelFormats - A map of time units to formats
16031 * @return {string} - the optimal date format for a point
16032 */
16033 getDateFormat: function(range, date, startOfWeek, dateTimeLabelFormats) {
16034 var dateStr = dateFormat('%m-%d %H:%M:%S.%L', date),
16035 format,
16036 n,
16037 blank = '01-01 00:00:00.000',
16038 strpos = {
16039 millisecond: 15,
16040 second: 12,
16041 minute: 9,
16042 hour: 6,
16043 day: 3
16044 },
16045 lastN = 'millisecond'; // for sub-millisecond data, #4223
16046 for (n in timeUnits) {
16047
16048 // If the range is exactly one week and we're looking at a
16049 // Sunday/Monday, go for the week format
16050 if (
16051 range === timeUnits.week &&
16052 +dateFormat('%w', date) === startOfWeek &&
16053 dateStr.substr(6) === blank.substr(6)
16054 ) {
16055 n = 'week';
16056 break;
16057 }
16058
16059 // The first format that is too great for the range
16060 if (timeUnits[n] > range) {
16061 n = lastN;
16062 break;
16063 }
16064
16065 // If the point is placed every day at 23:59, we need to show
16066 // the minutes as well. #2637.
16067 if (
16068 strpos[n] &&
16069 dateStr.substr(strpos[n]) !== blank.substr(strpos[n])
16070 ) {
16071 break;
16072 }
16073
16074 // Weeks are outside the hierarchy, only apply them on
16075 // Mondays/Sundays like in the first condition
16076 if (n !== 'week') {
16077 lastN = n;
16078 }
16079 }
16080
16081 if (n) {
16082 format = dateTimeLabelFormats[n];
16083 }
16084
16085 return format;
16086 },
16087
16088 /**
16089 * Get the best X date format based on the closest point range on the axis.
16090 */
16091 getXDateFormat: function(point, options, xAxis) {
16092 var xDateFormat,
16093 dateTimeLabelFormats = options.dateTimeLabelFormats,
16094 closestPointRange = xAxis && xAxis.closestPointRange;
16095
16096 if (closestPointRange) {
16097 xDateFormat = this.getDateFormat(
16098 closestPointRange,
16099 point.x,
16100 xAxis.options.startOfWeek,
16101 dateTimeLabelFormats
16102 );
16103 } else {
16104 xDateFormat = dateTimeLabelFormats.day;
16105 }
16106
16107 return xDateFormat || dateTimeLabelFormats.year; // #2546, 2581
16108 },
16109
16110 /**
16111 * Format the footer/header of the tooltip
16112 * #3397: abstraction to enable formatting of footer and header
16113 */
16114 tooltipFooterHeaderFormatter: function(labelConfig, isFooter) {
16115 var footOrHead = isFooter ? 'footer' : 'header',
16116 series = labelConfig.series,
16117 tooltipOptions = series.tooltipOptions,
16118 xDateFormat = tooltipOptions.xDateFormat,
16119 xAxis = series.xAxis,
16120 isDateTime = (
16121 xAxis &&
16122 xAxis.options.type === 'datetime' &&
16123 isNumber(labelConfig.key)
16124 ),
16125 formatString = tooltipOptions[footOrHead + 'Format'];
16126
16127 // Guess the best date format based on the closest point distance (#568,
16128 // #3418)
16129 if (isDateTime && !xDateFormat) {
16130 xDateFormat = this.getXDateFormat(
16131 labelConfig,
16132 tooltipOptions,
16133 xAxis
16134 );
16135 }
16136
16137 // Insert the footer date format if any
16138 if (isDateTime && xDateFormat) {
16139 each(
16140 (labelConfig.point && labelConfig.point.tooltipDateKeys) || ['key'],
16141 function(key) {
16142 formatString = formatString.replace(
16143 '{point.' + key + '}',
16144 '{point.' + key + ':' + xDateFormat + '}'
16145 );
16146 }
16147 );
16148 }
16149
16150 return format(formatString, {
16151 point: labelConfig,
16152 series: series
16153 });
16154 },
16155
16156 /**
16157 * Build the body (lines) of the tooltip by iterating over the items and
16158 * returning one entry for each item, abstracting this functionality allows
16159 * to easily overwrite and extend it.
16160 */
16161 bodyFormatter: function(items) {
16162 return map(items, function(item) {
16163 var tooltipOptions = item.series.tooltipOptions;
16164 return (
16165 tooltipOptions[
16166 (item.point.formatPrefix || 'point') + 'Formatter'
16167 ] ||
16168 item.point.tooltipFormatter
16169 ).call(
16170 item.point,
16171 tooltipOptions[(item.point.formatPrefix || 'point') + 'Format']
16172 );
16173 });
16174 }
16175
16176 };
16177
16178 }(Highcharts));
16179 (function(Highcharts) {
16180 /**
16181 * (c) 2010-2017 Torstein Honsi
16182 *
16183 * License: www.highcharts.com/license
16184 */
16185 var H = Highcharts,
16186 addEvent = H.addEvent,
16187 attr = H.attr,
16188 charts = H.charts,
16189 color = H.color,
16190 css = H.css,
16191 defined = H.defined,
16192 each = H.each,
16193 extend = H.extend,
16194 find = H.find,
16195 fireEvent = H.fireEvent,
16196 isObject = H.isObject,
16197 offset = H.offset,
16198 pick = H.pick,
16199 splat = H.splat,
16200 Tooltip = H.Tooltip;
16201
16202 /**
16203 * The mouse and touch tracker object. Each {@link Chart} item has one
16204 * assosiated Pointer item that can be accessed from the {@link Chart.pointer}
16205 * property.
16206 *
16207 * @class
16208 * @param {Chart} chart
16209 * The Chart instance.
16210 * @param {Options} options
16211 * The root options object. The pointer uses options from the chart and
16212 * tooltip structures.
16213 */
16214 Highcharts.Pointer = function(chart, options) {
16215 this.init(chart, options);
16216 };
16217
16218 Highcharts.Pointer.prototype = {
16219 /**
16220 * Initialize the Pointer.
16221 *
16222 * @private
16223 */
16224 init: function(chart, options) {
16225
16226 // Store references
16227 this.options = options;
16228 this.chart = chart;
16229
16230 // Do we need to handle click on a touch device?
16231 this.runChartClick = options.chart.events && !!options.chart.events.click;
16232
16233 this.pinchDown = [];
16234 this.lastValidTouch = {};
16235
16236 if (Tooltip) {
16237 chart.tooltip = new Tooltip(chart, options.tooltip);
16238 this.followTouchMove = pick(options.tooltip.followTouchMove, true);
16239 }
16240
16241 this.setDOMEvents();
16242 },
16243
16244 /**
16245 * Resolve the zoomType option, this is reset on all touch start and mouse
16246 * down events.
16247 *
16248 * @private
16249 */
16250 zoomOption: function(e) {
16251 var chart = this.chart,
16252 options = chart.options.chart,
16253 zoomType = options.zoomType || '',
16254 inverted = chart.inverted,
16255 zoomX,
16256 zoomY;
16257
16258 // Look for the pinchType option
16259 if (/touch/.test(e.type)) {
16260 zoomType = pick(options.pinchType, zoomType);
16261 }
16262
16263 this.zoomX = zoomX = /x/.test(zoomType);
16264 this.zoomY = zoomY = /y/.test(zoomType);
16265 this.zoomHor = (zoomX && !inverted) || (zoomY && inverted);
16266 this.zoomVert = (zoomY && !inverted) || (zoomX && inverted);
16267 this.hasZoom = zoomX || zoomY;
16268 },
16269
16270 /**
16271 * @typedef {Object} PointerEvent
16272 * A native browser mouse or touch event, extended with position
16273 * information relative to the {@link Chart.container}.
16274 * @property {Number} chartX
16275 * The X coordinate of the pointer interaction relative to the
16276 * chart.
16277 * @property {Number} chartY
16278 * The Y coordinate of the pointer interaction relative to the
16279 * chart.
16280 *
16281 */
16282 /**
16283 * Takes a browser event object and extends it with custom Highcharts
16284 * properties `chartX` and `chartY` in order to work on the internal
16285 * coordinate system.
16286 *
16287 * @param {Object} e
16288 * The event object in standard browsers.
16289 *
16290 * @return {PointerEvent}
16291 * A browser event with extended properties `chartX` and `chartY`.
16292 */
16293 normalize: function(e, chartPosition) {
16294 var ePos;
16295
16296 // iOS (#2757)
16297 ePos = e.touches ? (e.touches.length ? e.touches.item(0) : e.changedTouches[0]) : e;
16298
16299 // Get mouse position
16300 if (!chartPosition) {
16301 this.chartPosition = chartPosition = offset(this.chart.container);
16302 }
16303
16304 return extend(e, {
16305 chartX: Math.round(ePos.pageX - chartPosition.left),
16306 chartY: Math.round(ePos.pageY - chartPosition.top)
16307 });
16308 },
16309
16310 /**
16311 * Get the click position in terms of axis values.
16312 *
16313 * @param {PointerEvent} e
16314 * A pointer event, extended with `chartX` and `chartY`
16315 * properties.
16316 */
16317 getCoordinates: function(e) {
16318 var coordinates = {
16319 xAxis: [],
16320 yAxis: []
16321 };
16322
16323 each(this.chart.axes, function(axis) {
16324 coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({
16325 axis: axis,
16326 value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY'])
16327 });
16328 });
16329 return coordinates;
16330 },
16331 /**
16332 * Finds the closest point to a set of coordinates, using the k-d-tree
16333 * algorithm.
16334 *
16335 * @param {Array.<Series>} series
16336 * All the series to search in.
16337 * @param {boolean} shared
16338 * Whether it is a shared tooltip or not.
16339 * @param {object} coordinates
16340 * Chart coordinates of the pointer.
16341 * @param {number} coordinates.chartX
16342 * @param {number} coordinates.chartY
16343 *
16344 * @return {Point|undefined} The point closest to given coordinates.
16345 */
16346 findNearestKDPoint: function(series, shared, coordinates) {
16347 var closest,
16348 sort = function(p1, p2) {
16349 var isCloserX = p1.distX - p2.distX,
16350 isCloser = p1.dist - p2.dist,
16351 isAbove =
16352 (p2.series.group && p2.series.group.zIndex) -
16353 (p1.series.group && p1.series.group.zIndex),
16354 result;
16355
16356 // We have two points which are not in the same place on xAxis
16357 // and shared tooltip:
16358 if (isCloserX !== 0 && shared) { // #5721
16359 result = isCloserX;
16360 // Points are not exactly in the same place on x/yAxis:
16361 } else if (isCloser !== 0) {
16362 result = isCloser;
16363 // The same xAxis and yAxis position, sort by z-index:
16364 } else if (isAbove !== 0) {
16365 result = isAbove;
16366 // The same zIndex, sort by array index:
16367 } else {
16368 result = p1.series.index > p2.series.index ? -1 : 1;
16369 }
16370 return result;
16371 };
16372 each(series, function(s) {
16373 var noSharedTooltip = s.noSharedTooltip && shared,
16374 compareX = (!noSharedTooltip &&
16375 s.options.findNearestPointBy.indexOf('y') < 0
16376 ),
16377 point = s.searchPoint(
16378 coordinates,
16379 compareX
16380 );
16381 if (
16382 // Check that we actually found a point on the series.
16383 isObject(point, true) &&
16384 // Use the new point if it is closer.
16385 (!isObject(closest, true) || (sort(closest, point) > 0))
16386 ) {
16387 closest = point;
16388 }
16389 });
16390 return closest;
16391 },
16392 getPointFromEvent: function(e) {
16393 var target = e.target,
16394 point;
16395
16396 while (target && !point) {
16397 point = target.point;
16398 target = target.parentNode;
16399 }
16400 return point;
16401 },
16402
16403 getChartCoordinatesFromPoint: function(point, inverted) {
16404 var series = point.series,
16405 xAxis = series.xAxis,
16406 yAxis = series.yAxis,
16407 plotX = pick(point.clientX, point.plotX);
16408
16409 if (xAxis && yAxis) {
16410 return inverted ? {
16411 chartX: xAxis.len + xAxis.pos - plotX,
16412 chartY: yAxis.len + yAxis.pos - point.plotY
16413 } : {
16414 chartX: plotX + xAxis.pos,
16415 chartY: point.plotY + yAxis.pos
16416 };
16417 }
16418 },
16419
16420 /**
16421 * Calculates what is the current hovered point/points and series.
16422 *
16423 * @private
16424 *
16425 * @param {undefined|Point} existingHoverPoint
16426 * The point currrently beeing hovered.
16427 * @param {undefined|Series} existingHoverSeries
16428 * The series currently beeing hovered.
16429 * @param {Array.<Series>} series
16430 * All the series in the chart.
16431 * @param {boolean} isDirectTouch
16432 * Is the pointer directly hovering the point.
16433 * @param {boolean} shared
16434 * Whether it is a shared tooltip or not.
16435 * @param {object} coordinates
16436 * Chart coordinates of the pointer.
16437 * @param {number} coordinates.chartX
16438 * @param {number} coordinates.chartY
16439 *
16440 * @return {object}
16441 * Object containing resulting hover data.
16442 */
16443 getHoverData: function(
16444 existingHoverPoint,
16445 existingHoverSeries,
16446 series,
16447 isDirectTouch,
16448 shared,
16449 coordinates,
16450 params
16451 ) {
16452 var hoverPoint,
16453 hoverPoints = [],
16454 hoverSeries = existingHoverSeries,
16455 isBoosting = params && params.isBoosting,
16456 useExisting = !!(isDirectTouch && existingHoverPoint),
16457 notSticky = hoverSeries && !hoverSeries.stickyTracking,
16458 filter = function(s) {
16459 return (
16460 s.visible &&
16461 !(!shared && s.directTouch) && // #3821
16462 pick(s.options.enableMouseTracking, true)
16463 );
16464 },
16465 // Which series to look in for the hover point
16466 searchSeries = notSticky ?
16467 // Only search on hovered series if it has stickyTracking false
16468 [hoverSeries] :
16469 // Filter what series to look in.
16470 H.grep(series, function(s) {
16471 return filter(s) && s.stickyTracking;
16472 });
16473
16474 // Use existing hovered point or find the one closest to coordinates.
16475 hoverPoint = useExisting ?
16476 existingHoverPoint :
16477 this.findNearestKDPoint(searchSeries, shared, coordinates);
16478
16479 // Assign hover series
16480 hoverSeries = hoverPoint && hoverPoint.series;
16481
16482 // If we have a hoverPoint, assign hoverPoints.
16483 if (hoverPoint) {
16484 // When tooltip is shared, it displays more than one point
16485 if (shared && !hoverSeries.noSharedTooltip) {
16486 searchSeries = H.grep(series, function(s) {
16487 return filter(s) && !s.noSharedTooltip;
16488 });
16489
16490 // Get all points with the same x value as the hoverPoint
16491 each(searchSeries, function(s) {
16492 var point = find(s.points, function(p) {
16493 return p.x === hoverPoint.x && !p.isNull;
16494 });
16495 if (isObject(point)) {
16496 /*
16497 * Boost returns a minimal point. Convert it to a usable
16498 * point for tooltip and states.
16499 */
16500 if (isBoosting) {
16501 point = s.getPoint(point);
16502 }
16503 hoverPoints.push(point);
16504 }
16505 });
16506 } else {
16507 hoverPoints.push(hoverPoint);
16508 }
16509 }
16510 return {
16511 hoverPoint: hoverPoint,
16512 hoverSeries: hoverSeries,
16513 hoverPoints: hoverPoints
16514 };
16515 },
16516 /**
16517 * With line type charts with a single tracker, get the point closest to the
16518 * mouse. Run Point.onMouseOver and display tooltip for the point or points.
16519 *
16520 * @private
16521 */
16522 runPointActions: function(e, p) {
16523 var pointer = this,
16524 chart = pointer.chart,
16525 series = chart.series,
16526 tooltip = chart.tooltip && chart.tooltip.options.enabled ?
16527 chart.tooltip :
16528 undefined,
16529 shared = tooltip ? tooltip.shared : false,
16530 hoverPoint = p || chart.hoverPoint,
16531 hoverSeries = hoverPoint && hoverPoint.series || chart.hoverSeries,
16532 // onMouseOver or already hovering a series with directTouch
16533 isDirectTouch = !!p || (
16534 (hoverSeries && hoverSeries.directTouch) &&
16535 pointer.isDirectTouch
16536 ),
16537 hoverData = this.getHoverData(
16538 hoverPoint,
16539 hoverSeries,
16540 series,
16541 isDirectTouch,
16542 shared,
16543 e, {
16544 isBoosting: chart.isBoosting
16545 }
16546 ),
16547 useSharedTooltip,
16548 followPointer,
16549 anchor,
16550 points;
16551
16552 // Update variables from hoverData.
16553 hoverPoint = hoverData.hoverPoint;
16554 points = hoverData.hoverPoints;
16555 hoverSeries = hoverData.hoverSeries;
16556 followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer;
16557 useSharedTooltip = shared && hoverSeries && !hoverSeries.noSharedTooltip;
16558
16559 // Refresh tooltip for kdpoint if new hover point or tooltip was hidden
16560 // #3926, #4200
16561 if (
16562 hoverPoint &&
16563 // !(hoverSeries && hoverSeries.directTouch) &&
16564 (hoverPoint !== chart.hoverPoint || (tooltip && tooltip.isHidden))
16565 ) {
16566 each(chart.hoverPoints || [], function(p) {
16567 if (H.inArray(p, points) === -1) {
16568 p.setState();
16569 }
16570 });
16571 // Do mouseover on all points (#3919, #3985, #4410, #5622)
16572 each(points || [], function(p) {
16573 p.setState('hover');
16574 });
16575 // set normal state to previous series
16576 if (chart.hoverSeries !== hoverSeries) {
16577 hoverSeries.onMouseOver();
16578 }
16579
16580 // If tracking is on series in stead of on each point,
16581 // fire mouseOver on hover point. // #4448
16582 if (chart.hoverPoint) {
16583 chart.hoverPoint.firePointEvent('mouseOut');
16584 }
16585
16586 // Hover point may have been destroyed in the event handlers (#7127)
16587 if (!hoverPoint.series) {
16588 return;
16589 }
16590
16591 hoverPoint.firePointEvent('mouseOver');
16592 chart.hoverPoints = points;
16593 chart.hoverPoint = hoverPoint;
16594 // Draw tooltip if necessary
16595 if (tooltip) {
16596 tooltip.refresh(useSharedTooltip ? points : hoverPoint, e);
16597 }
16598 // Update positions (regardless of kdpoint or hoverPoint)
16599 } else if (followPointer && tooltip && !tooltip.isHidden) {
16600 anchor = tooltip.getAnchor([{}], e);
16601 tooltip.updatePosition({
16602 plotX: anchor[0],
16603 plotY: anchor[1]
16604 });
16605 }
16606
16607 // Start the event listener to pick up the tooltip and crosshairs
16608 if (!pointer.unDocMouseMove) {
16609 pointer.unDocMouseMove = addEvent(
16610 chart.container.ownerDocument,
16611 'mousemove',
16612 function(e) {
16613 var chart = charts[H.hoverChartIndex];
16614 if (chart) {
16615 chart.pointer.onDocumentMouseMove(e);
16616 }
16617 }
16618 );
16619 }
16620
16621 // Issues related to crosshair #4927, #5269 #5066, #5658
16622 each(chart.axes, function drawAxisCrosshair(axis) {
16623 var snap = pick(axis.crosshair.snap, true),
16624 point = !snap ?
16625 undefined :
16626 H.find(points, function(p) {
16627 return p.series[axis.coll] === axis;
16628 });
16629
16630 // Axis has snapping crosshairs, and one of the hover points belongs
16631 // to axis. Always call drawCrosshair when it is not snap.
16632 if (point || !snap) {
16633 axis.drawCrosshair(e, point);
16634 // Axis has snapping crosshairs, but no hover point belongs to axis
16635 } else {
16636 axis.hideCrosshair();
16637 }
16638 });
16639 },
16640
16641 /**
16642 * Reset the tracking by hiding the tooltip, the hover series state and the
16643 * hover point
16644 *
16645 * @param allowMove {Boolean}
16646 * Instead of destroying the tooltip altogether, allow moving it if
16647 * possible.
16648 */
16649 reset: function(allowMove, delay) {
16650 var pointer = this,
16651 chart = pointer.chart,
16652 hoverSeries = chart.hoverSeries,
16653 hoverPoint = chart.hoverPoint,
16654 hoverPoints = chart.hoverPoints,
16655 tooltip = chart.tooltip,
16656 tooltipPoints = tooltip && tooltip.shared ? hoverPoints : hoverPoint;
16657
16658 // Check if the points have moved outside the plot area (#1003, #4736, #5101)
16659 if (allowMove && tooltipPoints) {
16660 each(splat(tooltipPoints), function(point) {
16661 if (point.series.isCartesian && point.plotX === undefined) {
16662 allowMove = false;
16663 }
16664 });
16665 }
16666
16667 // Just move the tooltip, #349
16668 if (allowMove) {
16669 if (tooltip && tooltipPoints) {
16670 tooltip.refresh(tooltipPoints);
16671 if (hoverPoint) { // #2500
16672 hoverPoint.setState(hoverPoint.state, true);
16673 each(chart.axes, function(axis) {
16674 if (axis.crosshair) {
16675 axis.drawCrosshair(null, hoverPoint);
16676 }
16677 });
16678 }
16679 }
16680
16681 // Full reset
16682 } else {
16683
16684 if (hoverPoint) {
16685 hoverPoint.onMouseOut();
16686 }
16687
16688 if (hoverPoints) {
16689 each(hoverPoints, function(point) {
16690 point.setState();
16691 });
16692 }
16693
16694 if (hoverSeries) {
16695 hoverSeries.onMouseOut();
16696 }
16697
16698 if (tooltip) {
16699 tooltip.hide(delay);
16700 }
16701
16702 if (pointer.unDocMouseMove) {
16703 pointer.unDocMouseMove = pointer.unDocMouseMove();
16704 }
16705
16706 // Remove crosshairs
16707 each(chart.axes, function(axis) {
16708 axis.hideCrosshair();
16709 });
16710
16711 pointer.hoverX = chart.hoverPoints = chart.hoverPoint = null;
16712 }
16713 },
16714
16715 /**
16716 * Scale series groups to a certain scale and translation.
16717 *
16718 * @private
16719 */
16720 scaleGroups: function(attribs, clip) {
16721
16722 var chart = this.chart,
16723 seriesAttribs;
16724
16725 // Scale each series
16726 each(chart.series, function(series) {
16727 seriesAttribs = attribs || series.getPlotBox(); // #1701
16728 if (series.xAxis && series.xAxis.zoomEnabled && series.group) {
16729 series.group.attr(seriesAttribs);
16730 if (series.markerGroup) {
16731 series.markerGroup.attr(seriesAttribs);
16732 series.markerGroup.clip(clip ? chart.clipRect : null);
16733 }
16734 if (series.dataLabelsGroup) {
16735 series.dataLabelsGroup.attr(seriesAttribs);
16736 }
16737 }
16738 });
16739
16740 // Clip
16741 chart.clipRect.attr(clip || chart.clipBox);
16742 },
16743
16744 /**
16745 * Start a drag operation.
16746 *
16747 * @private
16748 */
16749 dragStart: function(e) {
16750 var chart = this.chart;
16751
16752 // Record the start position
16753 chart.mouseIsDown = e.type;
16754 chart.cancelClick = false;
16755 chart.mouseDownX = this.mouseDownX = e.chartX;
16756 chart.mouseDownY = this.mouseDownY = e.chartY;
16757 },
16758
16759 /**
16760 * Perform a drag operation in response to a mousemove event while the mouse
16761 * is down.
16762 *
16763 * @private
16764 */
16765 drag: function(e) {
16766
16767 var chart = this.chart,
16768 chartOptions = chart.options.chart,
16769 chartX = e.chartX,
16770 chartY = e.chartY,
16771 zoomHor = this.zoomHor,
16772 zoomVert = this.zoomVert,
16773 plotLeft = chart.plotLeft,
16774 plotTop = chart.plotTop,
16775 plotWidth = chart.plotWidth,
16776 plotHeight = chart.plotHeight,
16777 clickedInside,
16778 size,
16779 selectionMarker = this.selectionMarker,
16780 mouseDownX = this.mouseDownX,
16781 mouseDownY = this.mouseDownY,
16782 panKey = chartOptions.panKey && e[chartOptions.panKey + 'Key'];
16783
16784 // If the device supports both touch and mouse (like IE11), and we are touch-dragging
16785 // inside the plot area, don't handle the mouse event. #4339.
16786 if (selectionMarker && selectionMarker.touch) {
16787 return;
16788 }
16789
16790 // If the mouse is outside the plot area, adjust to cooordinates
16791 // inside to prevent the selection marker from going outside
16792 if (chartX < plotLeft) {
16793 chartX = plotLeft;
16794 } else if (chartX > plotLeft + plotWidth) {
16795 chartX = plotLeft + plotWidth;
16796 }
16797
16798 if (chartY < plotTop) {
16799 chartY = plotTop;
16800 } else if (chartY > plotTop + plotHeight) {
16801 chartY = plotTop + plotHeight;
16802 }
16803
16804 // determine if the mouse has moved more than 10px
16805 this.hasDragged = Math.sqrt(
16806 Math.pow(mouseDownX - chartX, 2) +
16807 Math.pow(mouseDownY - chartY, 2)
16808 );
16809
16810 if (this.hasDragged > 10) {
16811 clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop);
16812
16813 // make a selection
16814 if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside && !panKey) {
16815 if (!selectionMarker) {
16816 this.selectionMarker = selectionMarker = chart.renderer.rect(
16817 plotLeft,
16818 plotTop,
16819 zoomHor ? 1 : plotWidth,
16820 zoomVert ? 1 : plotHeight,
16821 0
16822 )
16823 .attr({
16824
16825 'class': 'highcharts-selection-marker',
16826 'zIndex': 7
16827 })
16828 .add();
16829 }
16830 }
16831
16832 // adjust the width of the selection marker
16833 if (selectionMarker && zoomHor) {
16834 size = chartX - mouseDownX;
16835 selectionMarker.attr({
16836 width: Math.abs(size),
16837 x: (size > 0 ? 0 : size) + mouseDownX
16838 });
16839 }
16840 // adjust the height of the selection marker
16841 if (selectionMarker && zoomVert) {
16842 size = chartY - mouseDownY;
16843 selectionMarker.attr({
16844 height: Math.abs(size),
16845 y: (size > 0 ? 0 : size) + mouseDownY
16846 });
16847 }
16848
16849 // panning
16850 if (clickedInside && !selectionMarker && chartOptions.panning) {
16851 chart.pan(e, chartOptions.panning);
16852 }
16853 }
16854 },
16855
16856 /**
16857 * On mouse up or touch end across the entire document, drop the selection.
16858 *
16859 * @private
16860 */
16861 drop: function(e) {
16862 var pointer = this,
16863 chart = this.chart,
16864 hasPinched = this.hasPinched;
16865
16866 if (this.selectionMarker) {
16867 var selectionData = {
16868 originalEvent: e, // #4890
16869 xAxis: [],
16870 yAxis: []
16871 },
16872 selectionBox = this.selectionMarker,
16873 selectionLeft = selectionBox.attr ? selectionBox.attr('x') : selectionBox.x,
16874 selectionTop = selectionBox.attr ? selectionBox.attr('y') : selectionBox.y,
16875 selectionWidth = selectionBox.attr ? selectionBox.attr('width') : selectionBox.width,
16876 selectionHeight = selectionBox.attr ? selectionBox.attr('height') : selectionBox.height,
16877 runZoom;
16878
16879 // a selection has been made
16880 if (this.hasDragged || hasPinched) {
16881
16882 // record each axis' min and max
16883 each(chart.axes, function(axis) {
16884 if (axis.zoomEnabled && defined(axis.min) && (hasPinched || pointer[{
16885 xAxis: 'zoomX',
16886 yAxis: 'zoomY'
16887 }[axis.coll]])) { // #859, #3569
16888 var horiz = axis.horiz,
16889 minPixelPadding = e.type === 'touchend' ? axis.minPixelPadding : 0, // #1207, #3075
16890 selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop) + minPixelPadding),
16891 selectionMax = axis.toValue((horiz ? selectionLeft + selectionWidth : selectionTop + selectionHeight) - minPixelPadding);
16892
16893 selectionData[axis.coll].push({
16894 axis: axis,
16895 min: Math.min(selectionMin, selectionMax), // for reversed axes
16896 max: Math.max(selectionMin, selectionMax)
16897 });
16898 runZoom = true;
16899 }
16900 });
16901 if (runZoom) {
16902 fireEvent(chart, 'selection', selectionData, function(args) {
16903 chart.zoom(extend(args, hasPinched ? {
16904 animation: false
16905 } : null));
16906 });
16907 }
16908
16909 }
16910 this.selectionMarker = this.selectionMarker.destroy();
16911
16912 // Reset scaling preview
16913 if (hasPinched) {
16914 this.scaleGroups();
16915 }
16916 }
16917
16918 // Reset all
16919 if (chart) { // it may be destroyed on mouse up - #877
16920 css(chart.container, {
16921 cursor: chart._cursor
16922 });
16923 chart.cancelClick = this.hasDragged > 10; // #370
16924 chart.mouseIsDown = this.hasDragged = this.hasPinched = false;
16925 this.pinchDown = [];
16926 }
16927 },
16928
16929 onContainerMouseDown: function(e) {
16930
16931 e = this.normalize(e);
16932
16933 this.zoomOption(e);
16934
16935 // issue #295, dragging not always working in Firefox
16936 if (e.preventDefault) {
16937 e.preventDefault();
16938 }
16939
16940 this.dragStart(e);
16941 },
16942
16943
16944
16945 onDocumentMouseUp: function(e) {
16946 if (charts[H.hoverChartIndex]) {
16947 charts[H.hoverChartIndex].pointer.drop(e);
16948 }
16949 },
16950
16951 /**
16952 * Special handler for mouse move that will hide the tooltip when the mouse
16953 * leaves the plotarea. Issue #149 workaround. The mouseleave event does not
16954 * always fire.
16955 *
16956 * @private
16957 */
16958 onDocumentMouseMove: function(e) {
16959 var chart = this.chart,
16960 chartPosition = this.chartPosition;
16961
16962 e = this.normalize(e, chartPosition);
16963
16964 // If we're outside, hide the tooltip
16965 if (chartPosition && !this.inClass(e.target, 'highcharts-tracker') &&
16966 !chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
16967 this.reset();
16968 }
16969 },
16970
16971 /**
16972 * When mouse leaves the container, hide the tooltip.
16973 *
16974 * @private
16975 */
16976 onContainerMouseLeave: function(e) {
16977 var chart = charts[H.hoverChartIndex];
16978 if (chart && (e.relatedTarget || e.toElement)) { // #4886, MS Touch end fires mouseleave but with no related target
16979 chart.pointer.reset();
16980 chart.pointer.chartPosition = null; // also reset the chart position, used in #149 fix
16981 }
16982 },
16983
16984 // The mousemove, touchmove and touchstart event handler
16985 onContainerMouseMove: function(e) {
16986
16987 var chart = this.chart;
16988
16989 if (!defined(H.hoverChartIndex) || !charts[H.hoverChartIndex] || !charts[H.hoverChartIndex].mouseIsDown) {
16990 H.hoverChartIndex = chart.index;
16991 }
16992
16993 e = this.normalize(e);
16994 e.returnValue = false; // #2251, #3224
16995
16996 if (chart.mouseIsDown === 'mousedown') {
16997 this.drag(e);
16998 }
16999
17000 // Show the tooltip and run mouse over events (#977)
17001 if ((this.inClass(e.target, 'highcharts-tracker') ||
17002 chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) && !chart.openMenu) {
17003 this.runPointActions(e);
17004 }
17005 },
17006
17007 /**
17008 * Utility to detect whether an element has, or has a parent with, a specific
17009 * class name. Used on detection of tracker objects and on deciding whether
17010 * hovering the tooltip should cause the active series to mouse out.
17011 *
17012 * @param {SVGDOMElement|HTMLDOMElement} element
17013 * The element to investigate.
17014 * @param {String} className
17015 * The class name to look for.
17016 *
17017 * @return {Boolean}
17018 * True if either the element or one of its parents has the given
17019 * class name.
17020 */
17021 inClass: function(element, className) {
17022 var elemClassName;
17023 while (element) {
17024 elemClassName = attr(element, 'class');
17025 if (elemClassName) {
17026 if (elemClassName.indexOf(className) !== -1) {
17027 return true;
17028 }
17029 if (elemClassName.indexOf('highcharts-container') !== -1) {
17030 return false;
17031 }
17032 }
17033 element = element.parentNode;
17034 }
17035 },
17036
17037 onTrackerMouseOut: function(e) {
17038 var series = this.chart.hoverSeries,
17039 relatedTarget = e.relatedTarget || e.toElement;
17040
17041 this.isDirectTouch = false;
17042
17043 if (
17044 series &&
17045 relatedTarget &&
17046 !series.stickyTracking &&
17047 !this.inClass(relatedTarget, 'highcharts-tooltip') &&
17048 (!this.inClass(
17049 relatedTarget,
17050 'highcharts-series-' + series.index
17051 ) || // #2499, #4465
17052 !this.inClass(relatedTarget, 'highcharts-tracker') // #5553
17053 )
17054 ) {
17055 series.onMouseOut();
17056 }
17057 },
17058
17059 onContainerClick: function(e) {
17060 var chart = this.chart,
17061 hoverPoint = chart.hoverPoint,
17062 plotLeft = chart.plotLeft,
17063 plotTop = chart.plotTop;
17064
17065 e = this.normalize(e);
17066
17067 if (!chart.cancelClick) {
17068
17069 // On tracker click, fire the series and point events. #783, #1583
17070 if (hoverPoint && this.inClass(e.target, 'highcharts-tracker')) {
17071
17072 // the series click event
17073 fireEvent(hoverPoint.series, 'click', extend(e, {
17074 point: hoverPoint
17075 }));
17076
17077 // the point click event
17078 if (chart.hoverPoint) { // it may be destroyed (#1844)
17079 hoverPoint.firePointEvent('click', e);
17080 }
17081
17082 // When clicking outside a tracker, fire a chart event
17083 } else {
17084 extend(e, this.getCoordinates(e));
17085
17086 // fire a click event in the chart
17087 if (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
17088 fireEvent(chart, 'click', e);
17089 }
17090 }
17091
17092
17093 }
17094 },
17095
17096 /**
17097 * Set the JS DOM events on the container and document. This method should contain
17098 * a one-to-one assignment between methods and their handlers. Any advanced logic should
17099 * be moved to the handler reflecting the event's name.
17100 *
17101 * @private
17102 */
17103 setDOMEvents: function() {
17104
17105 var pointer = this,
17106 container = pointer.chart.container,
17107 ownerDoc = container.ownerDocument;
17108
17109 container.onmousedown = function(e) {
17110 pointer.onContainerMouseDown(e);
17111 };
17112 container.onmousemove = function(e) {
17113 pointer.onContainerMouseMove(e);
17114 };
17115 container.onclick = function(e) {
17116 pointer.onContainerClick(e);
17117 };
17118 this.unbindContainerMouseLeave = addEvent(
17119 container,
17120 'mouseleave',
17121 pointer.onContainerMouseLeave
17122 );
17123 if (!H.unbindDocumentMouseUp) {
17124 H.unbindDocumentMouseUp = addEvent(
17125 ownerDoc,
17126 'mouseup',
17127 pointer.onDocumentMouseUp
17128 );
17129 }
17130 if (H.hasTouch) {
17131 container.ontouchstart = function(e) {
17132 pointer.onContainerTouchStart(e);
17133 };
17134 container.ontouchmove = function(e) {
17135 pointer.onContainerTouchMove(e);
17136 };
17137 if (!H.unbindDocumentTouchEnd) {
17138 H.unbindDocumentTouchEnd = addEvent(
17139 ownerDoc,
17140 'touchend',
17141 pointer.onDocumentTouchEnd
17142 );
17143 }
17144 }
17145
17146 },
17147
17148 /**
17149 * Destroys the Pointer object and disconnects DOM events.
17150 */
17151 destroy: function() {
17152 var pointer = this;
17153
17154 if (pointer.unDocMouseMove) {
17155 pointer.unDocMouseMove();
17156 }
17157
17158 this.unbindContainerMouseLeave();
17159
17160 if (!H.chartCount) {
17161 if (H.unbindDocumentMouseUp) {
17162 H.unbindDocumentMouseUp = H.unbindDocumentMouseUp();
17163 }
17164 if (H.unbindDocumentTouchEnd) {
17165 H.unbindDocumentTouchEnd = H.unbindDocumentTouchEnd();
17166 }
17167 }
17168
17169 // memory and CPU leak
17170 clearInterval(pointer.tooltipTimeout);
17171
17172 H.objectEach(pointer, function(val, prop) {
17173 pointer[prop] = null;
17174 });
17175 }
17176 };
17177
17178 }(Highcharts));
17179 (function(H) {
17180 /**
17181 * (c) 2010-2017 Torstein Honsi
17182 *
17183 * License: www.highcharts.com/license
17184 */
17185 var charts = H.charts,
17186 each = H.each,
17187 extend = H.extend,
17188 map = H.map,
17189 noop = H.noop,
17190 pick = H.pick,
17191 Pointer = H.Pointer;
17192
17193 /* Support for touch devices */
17194 extend(Pointer.prototype, /** @lends Pointer.prototype */ {
17195
17196 /**
17197 * Run translation operations
17198 */
17199 pinchTranslate: function(
17200 pinchDown,
17201 touches,
17202 transform,
17203 selectionMarker,
17204 clip,
17205 lastValidTouch
17206 ) {
17207 if (this.zoomHor) {
17208 this.pinchTranslateDirection(
17209 true,
17210 pinchDown,
17211 touches,
17212 transform,
17213 selectionMarker,
17214 clip,
17215 lastValidTouch
17216 );
17217 }
17218 if (this.zoomVert) {
17219 this.pinchTranslateDirection(
17220 false,
17221 pinchDown,
17222 touches,
17223 transform,
17224 selectionMarker,
17225 clip,
17226 lastValidTouch
17227 );
17228 }
17229 },
17230
17231 /**
17232 * Run translation operations for each direction (horizontal and vertical)
17233 * independently
17234 */
17235 pinchTranslateDirection: function(horiz, pinchDown, touches, transform,
17236 selectionMarker, clip, lastValidTouch, forcedScale) {
17237 var chart = this.chart,
17238 xy = horiz ? 'x' : 'y',
17239 XY = horiz ? 'X' : 'Y',
17240 sChartXY = 'chart' + XY,
17241 wh = horiz ? 'width' : 'height',
17242 plotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')],
17243 selectionWH,
17244 selectionXY,
17245 clipXY,
17246 scale = forcedScale || 1,
17247 inverted = chart.inverted,
17248 bounds = chart.bounds[horiz ? 'h' : 'v'],
17249 singleTouch = pinchDown.length === 1,
17250 touch0Start = pinchDown[0][sChartXY],
17251 touch0Now = touches[0][sChartXY],
17252 touch1Start = !singleTouch && pinchDown[1][sChartXY],
17253 touch1Now = !singleTouch && touches[1][sChartXY],
17254 outOfBounds,
17255 transformScale,
17256 scaleKey,
17257 setScale = function() {
17258 // Don't zoom if fingers are too close on this axis
17259 if (!singleTouch && Math.abs(touch0Start - touch1Start) > 20) {
17260 scale = forcedScale ||
17261 Math.abs(touch0Now - touch1Now) /
17262 Math.abs(touch0Start - touch1Start);
17263 }
17264
17265 clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start;
17266 selectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] /
17267 scale;
17268 };
17269
17270 // Set the scale, first pass
17271 setScale();
17272
17273 // The clip position (x or y) is altered if out of bounds, the selection
17274 // position is not
17275 selectionXY = clipXY;
17276
17277 // Out of bounds
17278 if (selectionXY < bounds.min) {
17279 selectionXY = bounds.min;
17280 outOfBounds = true;
17281 } else if (selectionXY + selectionWH > bounds.max) {
17282 selectionXY = bounds.max - selectionWH;
17283 outOfBounds = true;
17284 }
17285
17286 // Is the chart dragged off its bounds, determined by dataMin and
17287 // dataMax?
17288 if (outOfBounds) {
17289
17290 // Modify the touchNow position in order to create an elastic drag
17291 // movement. This indicates to the user that the chart is responsive
17292 // but can't be dragged further.
17293 touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]);
17294 if (!singleTouch) {
17295 touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]);
17296 }
17297
17298 // Set the scale, second pass to adapt to the modified touchNow
17299 // positions
17300 setScale();
17301
17302 } else {
17303 lastValidTouch[xy] = [touch0Now, touch1Now];
17304 }
17305
17306 // Set geometry for clipping, selection and transformation
17307 if (!inverted) {
17308 clip[xy] = clipXY - plotLeftTop;
17309 clip[wh] = selectionWH;
17310 }
17311 scaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY;
17312 transformScale = inverted ? 1 / scale : scale;
17313
17314 selectionMarker[wh] = selectionWH;
17315 selectionMarker[xy] = selectionXY;
17316 transform[scaleKey] = scale;
17317 transform['translate' + XY] = (transformScale * plotLeftTop) +
17318 (touch0Now - (transformScale * touch0Start));
17319 },
17320
17321 /**
17322 * Handle touch events with two touches
17323 */
17324 pinch: function(e) {
17325
17326 var self = this,
17327 chart = self.chart,
17328 pinchDown = self.pinchDown,
17329 touches = e.touches,
17330 touchesLength = touches.length,
17331 lastValidTouch = self.lastValidTouch,
17332 hasZoom = self.hasZoom,
17333 selectionMarker = self.selectionMarker,
17334 transform = {},
17335 fireClickEvent = touchesLength === 1 &&
17336 ((self.inClass(e.target, 'highcharts-tracker') &&
17337 chart.runTrackerClick) || self.runChartClick),
17338 clip = {};
17339
17340 // Don't initiate panning until the user has pinched. This prevents us
17341 // from blocking page scrolling as users scroll down a long page
17342 // (#4210).
17343 if (touchesLength > 1) {
17344 self.initiated = true;
17345 }
17346
17347 // On touch devices, only proceed to trigger click if a handler is
17348 // defined
17349 if (hasZoom && self.initiated && !fireClickEvent) {
17350 e.preventDefault();
17351 }
17352
17353 // Normalize each touch
17354 map(touches, function(e) {
17355 return self.normalize(e);
17356 });
17357
17358 // Register the touch start position
17359 if (e.type === 'touchstart') {
17360 each(touches, function(e, i) {
17361 pinchDown[i] = {
17362 chartX: e.chartX,
17363 chartY: e.chartY
17364 };
17365 });
17366 lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] &&
17367 pinchDown[1].chartX
17368 ];
17369 lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] &&
17370 pinchDown[1].chartY
17371 ];
17372
17373 // Identify the data bounds in pixels
17374 each(chart.axes, function(axis) {
17375 if (axis.zoomEnabled) {
17376 var bounds = chart.bounds[axis.horiz ? 'h' : 'v'],
17377 minPixelPadding = axis.minPixelPadding,
17378 min = axis.toPixels(
17379 pick(axis.options.min, axis.dataMin)
17380 ),
17381 max = axis.toPixels(
17382 pick(axis.options.max, axis.dataMax)
17383 ),
17384 absMin = Math.min(min, max),
17385 absMax = Math.max(min, max);
17386
17387 // Store the bounds for use in the touchmove handler
17388 bounds.min = Math.min(axis.pos, absMin - minPixelPadding);
17389 bounds.max = Math.max(
17390 axis.pos + axis.len,
17391 absMax + minPixelPadding
17392 );
17393 }
17394 });
17395 self.res = true; // reset on next move
17396
17397 // Optionally move the tooltip on touchmove
17398 } else if (self.followTouchMove && touchesLength === 1) {
17399 this.runPointActions(self.normalize(e));
17400
17401 // Event type is touchmove, handle panning and pinching
17402 } else if (pinchDown.length) { // can be 0 when releasing, if touchend
17403 // fires first
17404
17405
17406 // Set the marker
17407 if (!selectionMarker) {
17408 self.selectionMarker = selectionMarker = extend({
17409 destroy: noop,
17410 touch: true
17411 }, chart.plotBox);
17412 }
17413
17414 self.pinchTranslate(
17415 pinchDown,
17416 touches,
17417 transform,
17418 selectionMarker,
17419 clip,
17420 lastValidTouch
17421 );
17422
17423 self.hasPinched = hasZoom;
17424
17425 // Scale and translate the groups to provide visual feedback during
17426 // pinching
17427 self.scaleGroups(transform, clip);
17428
17429 if (self.res) {
17430 self.res = false;
17431 this.reset(false, 0);
17432 }
17433 }
17434 },
17435
17436 /**
17437 * General touch handler shared by touchstart and touchmove.
17438 */
17439 touch: function(e, start) {
17440 var chart = this.chart,
17441 hasMoved,
17442 pinchDown,
17443 isInside;
17444
17445 if (chart.index !== H.hoverChartIndex) {
17446 this.onContainerMouseLeave({
17447 relatedTarget: true
17448 });
17449 }
17450 H.hoverChartIndex = chart.index;
17451
17452 if (e.touches.length === 1) {
17453
17454 e = this.normalize(e);
17455
17456 isInside = chart.isInsidePlot(
17457 e.chartX - chart.plotLeft,
17458 e.chartY - chart.plotTop
17459 );
17460 if (isInside && !chart.openMenu) {
17461
17462 // Run mouse events and display tooltip etc
17463 if (start) {
17464 this.runPointActions(e);
17465 }
17466
17467 // Android fires touchmove events after the touchstart even if
17468 // the finger hasn't moved, or moved only a pixel or two. In iOS
17469 // however, the touchmove doesn't fire unless the finger moves
17470 // more than ~4px. So we emulate this behaviour in Android by
17471 // checking how much it moved, and cancelling on small
17472 // distances. #3450.
17473 if (e.type === 'touchmove') {
17474 pinchDown = this.pinchDown;
17475 hasMoved = pinchDown[0] ? Math.sqrt( // #5266
17476 Math.pow(pinchDown[0].chartX - e.chartX, 2) +
17477 Math.pow(pinchDown[0].chartY - e.chartY, 2)
17478 ) >= 4 : false;
17479 }
17480
17481 if (pick(hasMoved, true)) {
17482 this.pinch(e);
17483 }
17484
17485 } else if (start) {
17486 // Hide the tooltip on touching outside the plot area (#1203)
17487 this.reset();
17488 }
17489
17490 } else if (e.touches.length === 2) {
17491 this.pinch(e);
17492 }
17493 },
17494
17495 onContainerTouchStart: function(e) {
17496 this.zoomOption(e);
17497 this.touch(e, true);
17498 },
17499
17500 onContainerTouchMove: function(e) {
17501 this.touch(e);
17502 },
17503
17504 onDocumentTouchEnd: function(e) {
17505 if (charts[H.hoverChartIndex]) {
17506 charts[H.hoverChartIndex].pointer.drop(e);
17507 }
17508 }
17509
17510 });
17511
17512 }(Highcharts));
17513 (function(H) {
17514 /**
17515 * (c) 2010-2017 Torstein Honsi
17516 *
17517 * License: www.highcharts.com/license
17518 */
17519 var addEvent = H.addEvent,
17520 charts = H.charts,
17521 css = H.css,
17522 doc = H.doc,
17523 extend = H.extend,
17524 hasTouch = H.hasTouch,
17525 noop = H.noop,
17526 Pointer = H.Pointer,
17527 removeEvent = H.removeEvent,
17528 win = H.win,
17529 wrap = H.wrap;
17530
17531 if (!hasTouch && (win.PointerEvent || win.MSPointerEvent)) {
17532
17533 // The touches object keeps track of the points being touched at all times
17534 var touches = {},
17535 hasPointerEvent = !!win.PointerEvent,
17536 getWebkitTouches = function() {
17537 var fake = [];
17538 fake.item = function(i) {
17539 return this[i];
17540 };
17541 H.objectEach(touches, function(touch) {
17542 fake.push({
17543 pageX: touch.pageX,
17544 pageY: touch.pageY,
17545 target: touch.target
17546 });
17547 });
17548 return fake;
17549 },
17550 translateMSPointer = function(e, method, wktype, func) {
17551 var p;
17552 if ((e.pointerType === 'touch' || e.pointerType === e.MSPOINTER_TYPE_TOUCH) && charts[H.hoverChartIndex]) {
17553 func(e);
17554 p = charts[H.hoverChartIndex].pointer;
17555 p[method]({
17556 type: wktype,
17557 target: e.currentTarget,
17558 preventDefault: noop,
17559 touches: getWebkitTouches()
17560 });
17561 }
17562 };
17563
17564 /**
17565 * Extend the Pointer prototype with methods for each event handler and more
17566 */
17567 extend(Pointer.prototype, /** @lends Pointer.prototype */ {
17568 onContainerPointerDown: function(e) {
17569 translateMSPointer(e, 'onContainerTouchStart', 'touchstart', function(e) {
17570 touches[e.pointerId] = {
17571 pageX: e.pageX,
17572 pageY: e.pageY,
17573 target: e.currentTarget
17574 };
17575 });
17576 },
17577 onContainerPointerMove: function(e) {
17578 translateMSPointer(e, 'onContainerTouchMove', 'touchmove', function(e) {
17579 touches[e.pointerId] = {
17580 pageX: e.pageX,
17581 pageY: e.pageY
17582 };
17583 if (!touches[e.pointerId].target) {
17584 touches[e.pointerId].target = e.currentTarget;
17585 }
17586 });
17587 },
17588 onDocumentPointerUp: function(e) {
17589 translateMSPointer(e, 'onDocumentTouchEnd', 'touchend', function(e) {
17590 delete touches[e.pointerId];
17591 });
17592 },
17593
17594 /**
17595 * Add or remove the MS Pointer specific events
17596 */
17597 batchMSEvents: function(fn) {
17598 fn(this.chart.container, hasPointerEvent ? 'pointerdown' : 'MSPointerDown', this.onContainerPointerDown);
17599 fn(this.chart.container, hasPointerEvent ? 'pointermove' : 'MSPointerMove', this.onContainerPointerMove);
17600 fn(doc, hasPointerEvent ? 'pointerup' : 'MSPointerUp', this.onDocumentPointerUp);
17601 }
17602 });
17603
17604 // Disable default IE actions for pinch and such on chart element
17605 wrap(Pointer.prototype, 'init', function(proceed, chart, options) {
17606 proceed.call(this, chart, options);
17607 if (this.hasZoom) { // #4014
17608 css(chart.container, {
17609 '-ms-touch-action': 'none',
17610 'touch-action': 'none'
17611 });
17612 }
17613 });
17614
17615 // Add IE specific touch events to chart
17616 wrap(Pointer.prototype, 'setDOMEvents', function(proceed) {
17617 proceed.apply(this);
17618 if (this.hasZoom || this.followTouchMove) {
17619 this.batchMSEvents(addEvent);
17620 }
17621 });
17622 // Destroy MS events also
17623 wrap(Pointer.prototype, 'destroy', function(proceed) {
17624 this.batchMSEvents(removeEvent);
17625 proceed.call(this);
17626 });
17627 }
17628
17629 }(Highcharts));
17630 (function(Highcharts) {
17631 /**
17632 * (c) 2010-2017 Torstein Honsi
17633 *
17634 * License: www.highcharts.com/license
17635 */
17636 var H = Highcharts,
17637
17638 addEvent = H.addEvent,
17639 css = H.css,
17640 discardElement = H.discardElement,
17641 defined = H.defined,
17642 each = H.each,
17643 isFirefox = H.isFirefox,
17644 marginNames = H.marginNames,
17645 merge = H.merge,
17646 pick = H.pick,
17647 setAnimation = H.setAnimation,
17648 stableSort = H.stableSort,
17649 win = H.win,
17650 wrap = H.wrap;
17651
17652 /**
17653 * The overview of the chart's series. The legend object is instanciated
17654 * internally in the chart constructor, and available from `chart.legend`. Each
17655 * chart has only one legend.
17656 *
17657 * @class
17658 */
17659 Highcharts.Legend = function(chart, options) {
17660 this.init(chart, options);
17661 };
17662
17663 Highcharts.Legend.prototype = {
17664
17665 /**
17666 * Initialize the legend.
17667 *
17668 * @private
17669 */
17670 init: function(chart, options) {
17671
17672 this.chart = chart;
17673
17674 this.setOptions(options);
17675
17676 if (options.enabled) {
17677
17678 // Render it
17679 this.render();
17680
17681 // move checkboxes
17682 addEvent(this.chart, 'endResize', function() {
17683 this.legend.positionCheckboxes();
17684 });
17685 }
17686 },
17687
17688 setOptions: function(options) {
17689
17690 var padding = pick(options.padding, 8);
17691
17692 this.options = options;
17693
17694
17695 this.itemMarginTop = options.itemMarginTop || 0;
17696 this.padding = padding;
17697 this.initialItemY = padding - 5; // 5 is pixels above the text
17698 this.maxItemWidth = 0;
17699 this.itemHeight = 0;
17700 this.symbolWidth = pick(options.symbolWidth, 16);
17701 this.pages = [];
17702
17703 },
17704
17705 /**
17706 * Update the legend with new options. Equivalent to running `chart.update`
17707 * with a legend configuration option.
17708 * @param {LegendOptions} options
17709 * Legend options.
17710 * @param {Boolean} [redraw=true]
17711 * Whether to redraw the chart.
17712 *
17713 * @sample highcharts/legend/legend-update/
17714 * Legend update
17715 */
17716 update: function(options, redraw) {
17717 var chart = this.chart;
17718
17719 this.setOptions(merge(true, this.options, options));
17720 this.destroy();
17721 chart.isDirtyLegend = chart.isDirtyBox = true;
17722 if (pick(redraw, true)) {
17723 chart.redraw();
17724 }
17725 },
17726
17727 /**
17728 * Set the colors for the legend item.
17729 *
17730 * @private
17731 * @param {Series|Point} item
17732 * A Series or Point instance
17733 * @param {Boolean} visible
17734 * Dimmed or colored
17735 */
17736 colorizeItem: function(item, visible) {
17737 item.legendGroup[visible ? 'removeClass' : 'addClass'](
17738 'highcharts-legend-item-hidden'
17739 );
17740
17741
17742 },
17743
17744 /**
17745 * Position the legend item.
17746 *
17747 * @private
17748 * @param {Series|Point} item
17749 * The item to position
17750 */
17751 positionItem: function(item) {
17752 var legend = this,
17753 options = legend.options,
17754 symbolPadding = options.symbolPadding,
17755 ltr = !options.rtl,
17756 legendItemPos = item._legendItemPos,
17757 itemX = legendItemPos[0],
17758 itemY = legendItemPos[1],
17759 checkbox = item.checkbox,
17760 legendGroup = item.legendGroup;
17761
17762 if (legendGroup && legendGroup.element) {
17763 legendGroup.translate(
17764 ltr ?
17765 itemX :
17766 legend.legendWidth - itemX - 2 * symbolPadding - 4,
17767 itemY
17768 );
17769 }
17770
17771 if (checkbox) {
17772 checkbox.x = itemX;
17773 checkbox.y = itemY;
17774 }
17775 },
17776
17777 /**
17778 * Destroy a single legend item, used internally on removing series items.
17779 *
17780 * @param {Series|Point} item
17781 * The item to remove
17782 */
17783 destroyItem: function(item) {
17784 var checkbox = item.checkbox;
17785
17786 // destroy SVG elements
17787 each(
17788 ['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'],
17789 function(key) {
17790 if (item[key]) {
17791 item[key] = item[key].destroy();
17792 }
17793 }
17794 );
17795
17796 if (checkbox) {
17797 discardElement(item.checkbox);
17798 }
17799 },
17800
17801 /**
17802 * Destroy the legend. Used internally. To reflow objects, `chart.redraw`
17803 * must be called after destruction.
17804 */
17805 destroy: function() {
17806 function destroyItems(key) {
17807 if (this[key]) {
17808 this[key] = this[key].destroy();
17809 }
17810 }
17811
17812 // Destroy items
17813 each(this.getAllItems(), function(item) {
17814 each(['legendItem', 'legendGroup'], destroyItems, item);
17815 });
17816
17817 // Destroy legend elements
17818 each([
17819 'clipRect',
17820 'up',
17821 'down',
17822 'pager',
17823 'nav',
17824 'box',
17825 'title',
17826 'group'
17827 ], destroyItems, this);
17828 this.display = null; // Reset in .render on update.
17829 },
17830
17831 /**
17832 * Position the checkboxes after the width is determined.
17833 *
17834 * @private
17835 */
17836 positionCheckboxes: function() {
17837 var alignAttr = this.group && this.group.alignAttr,
17838 translateY,
17839 clipHeight = this.clipHeight || this.legendHeight,
17840 titleHeight = this.titleHeight;
17841
17842 if (alignAttr) {
17843 translateY = alignAttr.translateY;
17844 each(this.allItems, function(item) {
17845 var checkbox = item.checkbox,
17846 top;
17847
17848 if (checkbox) {
17849 top = translateY + titleHeight + checkbox.y +
17850 (this.scrollOffset || 0) + 3;
17851 css(checkbox, {
17852 left: (alignAttr.translateX + item.checkboxOffset +
17853 checkbox.x - 20) + 'px',
17854 top: top + 'px',
17855 display: top > translateY - 6 && top < translateY +
17856 clipHeight - 6 ? '' : 'none'
17857 });
17858 }
17859 }, this);
17860 }
17861 },
17862
17863 /**
17864 * Render the legend title on top of the legend.
17865 *
17866 * @private
17867 */
17868 renderTitle: function() {
17869 var options = this.options,
17870 padding = this.padding,
17871 titleOptions = options.title,
17872 titleHeight = 0,
17873 bBox;
17874
17875 if (titleOptions.text) {
17876 if (!this.title) {
17877 this.title = this.chart.renderer.label(
17878 titleOptions.text,
17879 padding - 3,
17880 padding - 4,
17881 null,
17882 null,
17883 null,
17884 options.useHTML,
17885 null,
17886 'legend-title'
17887 )
17888 .attr({
17889 zIndex: 1
17890 })
17891
17892 .add(this.group);
17893 }
17894 bBox = this.title.getBBox();
17895 titleHeight = bBox.height;
17896 this.offsetWidth = bBox.width; // #1717
17897 this.contentGroup.attr({
17898 translateY: titleHeight
17899 });
17900 }
17901 this.titleHeight = titleHeight;
17902 },
17903
17904 /**
17905 * Set the legend item text.
17906 *
17907 * @param {Series|Point} item
17908 * The item for which to update the text in the legend.
17909 */
17910 setText: function(item) {
17911 var options = this.options;
17912 item.legendItem.attr({
17913 text: options.labelFormat ?
17914 H.format(options.labelFormat, item) : options.labelFormatter.call(item)
17915 });
17916 },
17917
17918 /**
17919 * Render a single specific legend item. Called internally from the `render`
17920 * function.
17921 *
17922 * @private
17923 * @param {Series|Point} item
17924 * The item to render.
17925 */
17926 renderItem: function(item) {
17927 var legend = this,
17928 chart = legend.chart,
17929 renderer = chart.renderer,
17930 options = legend.options,
17931 horizontal = options.layout === 'horizontal',
17932 symbolWidth = legend.symbolWidth,
17933 symbolPadding = options.symbolPadding,
17934
17935 padding = legend.padding,
17936 itemDistance = horizontal ? pick(options.itemDistance, 20) : 0,
17937 ltr = !options.rtl,
17938 itemHeight,
17939 widthOption = options.width,
17940 itemMarginBottom = options.itemMarginBottom || 0,
17941 itemMarginTop = legend.itemMarginTop,
17942 bBox,
17943 itemWidth,
17944 li = item.legendItem,
17945 isSeries = !item.series,
17946 series = !isSeries && item.series.drawLegendSymbol ?
17947 item.series :
17948 item,
17949 seriesOptions = series.options,
17950 showCheckbox = legend.createCheckboxForItem &&
17951 seriesOptions &&
17952 seriesOptions.showCheckbox,
17953 // full width minus text width
17954 itemExtraWidth = symbolWidth + symbolPadding + itemDistance +
17955 (showCheckbox ? 20 : 0),
17956 useHTML = options.useHTML,
17957 fontSize = 12,
17958 itemClassName = item.options.className;
17959
17960 if (!li) { // generate it once, later move it
17961
17962 // Generate the group box, a group to hold the symbol and text. Text
17963 // is to be appended in Legend class.
17964 item.legendGroup = renderer.g('legend-item')
17965 .addClass(
17966 'highcharts-' + series.type + '-series ' +
17967 'highcharts-color-' + item.colorIndex +
17968 (itemClassName ? ' ' + itemClassName : '') +
17969 (isSeries ? ' highcharts-series-' + item.index : '')
17970 )
17971 .attr({
17972 zIndex: 1
17973 })
17974 .add(legend.scrollGroup);
17975
17976 // Generate the list item text and add it to the group
17977 item.legendItem = li = renderer.text(
17978 '',
17979 ltr ? symbolWidth + symbolPadding : -symbolPadding,
17980 legend.baseline || 0,
17981 useHTML
17982 )
17983
17984 .attr({
17985 align: ltr ? 'left' : 'right',
17986 zIndex: 2
17987 })
17988 .add(item.legendGroup);
17989
17990 // Get the baseline for the first item - the font size is equal for
17991 // all
17992 if (!legend.baseline) {
17993
17994 legend.fontMetrics = renderer.fontMetrics(
17995 fontSize,
17996 li
17997 );
17998 legend.baseline = legend.fontMetrics.f + 3 + itemMarginTop;
17999 li.attr('y', legend.baseline);
18000 }
18001
18002 // Draw the legend symbol inside the group box
18003 legend.symbolHeight = options.symbolHeight || legend.fontMetrics.f;
18004 series.drawLegendSymbol(legend, item);
18005
18006 if (legend.setItemEvents) {
18007 legend.setItemEvents(item, li, useHTML);
18008 }
18009
18010 // add the HTML checkbox on top
18011 if (showCheckbox) {
18012 legend.createCheckboxForItem(item);
18013 }
18014 }
18015
18016 // Colorize the items
18017 legend.colorizeItem(item, item.visible);
18018
18019 // Take care of max width and text overflow (#6659)
18020
18021 li.css({
18022 width: (
18023 options.itemWidth ||
18024 options.width ||
18025 chart.spacingBox.width
18026 ) - itemExtraWidth
18027 });
18028
18029
18030 // Always update the text
18031 legend.setText(item);
18032
18033 // calculate the positions for the next line
18034 bBox = li.getBBox();
18035
18036 itemWidth = item.checkboxOffset =
18037 options.itemWidth ||
18038 item.legendItemWidth ||
18039 bBox.width + itemExtraWidth;
18040 legend.itemHeight = itemHeight = Math.round(
18041 item.legendItemHeight || bBox.height || legend.symbolHeight
18042 );
18043
18044 // If the item exceeds the width, start a new line
18045 if (
18046 horizontal &&
18047 legend.itemX - padding + itemWidth > (
18048 widthOption || (
18049 chart.spacingBox.width - 2 * padding - options.x
18050 )
18051 )
18052 ) {
18053 legend.itemX = padding;
18054 legend.itemY += itemMarginTop + legend.lastLineHeight +
18055 itemMarginBottom;
18056 legend.lastLineHeight = 0; // reset for next line (#915, #3976)
18057 }
18058
18059 // If the item exceeds the height, start a new column
18060 /*
18061 if (!horizontal && legend.itemY + options.y +
18062 itemHeight > chart.chartHeight - spacingTop - spacingBottom) {
18063 legend.itemY = legend.initialItemY;
18064 legend.itemX += legend.maxItemWidth;
18065 legend.maxItemWidth = 0;
18066 }
18067 */
18068
18069 // Set the edge positions
18070 legend.maxItemWidth = Math.max(legend.maxItemWidth, itemWidth);
18071 legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom;
18072 legend.lastLineHeight = Math.max( // #915
18073 itemHeight,
18074 legend.lastLineHeight
18075 );
18076
18077 // cache the position of the newly generated or reordered items
18078 item._legendItemPos = [legend.itemX, legend.itemY];
18079
18080 // advance
18081 if (horizontal) {
18082 legend.itemX += itemWidth;
18083
18084 } else {
18085 legend.itemY += itemMarginTop + itemHeight + itemMarginBottom;
18086 legend.lastLineHeight = itemHeight;
18087 }
18088
18089 // the width of the widest item
18090 legend.offsetWidth = widthOption || Math.max(
18091 (
18092 horizontal ? legend.itemX - padding - (item.checkbox ?
18093 // decrease by itemDistance only when no checkbox #4853
18094 0 :
18095 itemDistance
18096 ) : itemWidth
18097 ) + padding,
18098 legend.offsetWidth
18099 );
18100 },
18101
18102 /**
18103 * Get all items, which is one item per series for most series and one
18104 * item per point for pie series and its derivatives.
18105 *
18106 * @return {Array.<Series|Point>}
18107 * The current items in the legend.
18108 */
18109 getAllItems: function() {
18110 var allItems = [];
18111 each(this.chart.series, function(series) {
18112 var seriesOptions = series && series.options;
18113
18114 // Handle showInLegend. If the series is linked to another series,
18115 // defaults to false.
18116 if (series && pick(
18117 seriesOptions.showInLegend, !defined(seriesOptions.linkedTo) ? undefined : false, true
18118 )) {
18119
18120 // Use points or series for the legend item depending on
18121 // legendType
18122 allItems = allItems.concat(
18123 series.legendItems ||
18124 (
18125 seriesOptions.legendType === 'point' ?
18126 series.data :
18127 series
18128 )
18129 );
18130 }
18131 });
18132 return allItems;
18133 },
18134
18135 /**
18136 * Adjust the chart margins by reserving space for the legend on only one
18137 * side of the chart. If the position is set to a corner, top or bottom is
18138 * reserved for horizontal legends and left or right for vertical ones.
18139 *
18140 * @private
18141 */
18142 adjustMargins: function(margin, spacing) {
18143 var chart = this.chart,
18144 options = this.options,
18145 // Use the first letter of each alignment option in order to detect
18146 // the side. (#4189 - use charAt(x) notation instead of [x] for IE7)
18147 alignment = options.align.charAt(0) +
18148 options.verticalAlign.charAt(0) +
18149 options.layout.charAt(0);
18150
18151 if (!options.floating) {
18152
18153 each([
18154 /(lth|ct|rth)/,
18155 /(rtv|rm|rbv)/,
18156 /(rbh|cb|lbh)/,
18157 /(lbv|lm|ltv)/
18158 ], function(alignments, side) {
18159 if (alignments.test(alignment) && !defined(margin[side])) {
18160 // Now we have detected on which side of the chart we should
18161 // reserve space for the legend
18162 chart[marginNames[side]] = Math.max(
18163 chart[marginNames[side]],
18164 (
18165 chart.legend[
18166 (side + 1) % 2 ? 'legendHeight' : 'legendWidth'
18167 ] + [1, -1, -1, 1][side] * options[
18168 (side % 2) ? 'x' : 'y'
18169 ] +
18170 pick(options.margin, 12) +
18171 spacing[side]
18172 )
18173 );
18174 }
18175 });
18176 }
18177 },
18178
18179 /**
18180 * Render the legend. This method can be called both before and after
18181 * `chart.render`. If called after, it will only rearrange items instead
18182 * of creating new ones. Called internally on initial render and after
18183 * redraws.
18184 */
18185 render: function() {
18186 var legend = this,
18187 chart = legend.chart,
18188 renderer = chart.renderer,
18189 legendGroup = legend.group,
18190 allItems,
18191 display,
18192 legendWidth,
18193 legendHeight,
18194 box = legend.box,
18195 options = legend.options,
18196 padding = legend.padding;
18197
18198 legend.itemX = padding;
18199 legend.itemY = legend.initialItemY;
18200 legend.offsetWidth = 0;
18201 legend.lastItemY = 0;
18202
18203 if (!legendGroup) {
18204 legend.group = legendGroup = renderer.g('legend')
18205 .attr({
18206 zIndex: 7
18207 })
18208 .add();
18209 legend.contentGroup = renderer.g()
18210 .attr({
18211 zIndex: 1
18212 }) // above background
18213 .add(legendGroup);
18214 legend.scrollGroup = renderer.g()
18215 .add(legend.contentGroup);
18216 }
18217
18218 legend.renderTitle();
18219
18220 // add each series or point
18221 allItems = legend.getAllItems();
18222
18223 // sort by legendIndex
18224 stableSort(allItems, function(a, b) {
18225 return ((a.options && a.options.legendIndex) || 0) -
18226 ((b.options && b.options.legendIndex) || 0);
18227 });
18228
18229 // reversed legend
18230 if (options.reversed) {
18231 allItems.reverse();
18232 }
18233
18234 legend.allItems = allItems;
18235 legend.display = display = !!allItems.length;
18236
18237 // render the items
18238 legend.lastLineHeight = 0;
18239 each(allItems, function(item) {
18240 legend.renderItem(item);
18241 });
18242
18243 // Get the box
18244 legendWidth = (options.width || legend.offsetWidth) + padding;
18245 legendHeight = legend.lastItemY + legend.lastLineHeight +
18246 legend.titleHeight;
18247 legendHeight = legend.handleOverflow(legendHeight);
18248 legendHeight += padding;
18249
18250 // Draw the border and/or background
18251 if (!box) {
18252 legend.box = box = renderer.rect()
18253 .addClass('highcharts-legend-box')
18254 .attr({
18255 r: options.borderRadius
18256 })
18257 .add(legendGroup);
18258 box.isNew = true;
18259 }
18260
18261
18262
18263 if (legendWidth > 0 && legendHeight > 0) {
18264 box[box.isNew ? 'attr' : 'animate'](
18265 box.crisp.call({}, { // #7260
18266 x: 0,
18267 y: 0,
18268 width: legendWidth,
18269 height: legendHeight
18270 }, box.strokeWidth())
18271 );
18272 box.isNew = false;
18273 }
18274
18275 // hide the border if no items
18276 box[display ? 'show' : 'hide']();
18277
18278
18279 // Open for responsiveness
18280 if (legendGroup.getStyle('display') === 'none') {
18281 legendWidth = legendHeight = 0;
18282 }
18283
18284
18285 legend.legendWidth = legendWidth;
18286 legend.legendHeight = legendHeight;
18287
18288 // Now that the legend width and height are established, put the items
18289 // in the final position
18290 each(allItems, function(item) {
18291 legend.positionItem(item);
18292 });
18293
18294 if (display) {
18295 legendGroup.align(merge(options, {
18296 width: legendWidth,
18297 height: legendHeight
18298 }), true, 'spacingBox');
18299 }
18300
18301 if (!chart.isResizing) {
18302 this.positionCheckboxes();
18303 }
18304 },
18305
18306 /**
18307 * Set up the overflow handling by adding navigation with up and down arrows
18308 * below the legend.
18309 *
18310 * @private
18311 */
18312 handleOverflow: function(legendHeight) {
18313 var legend = this,
18314 chart = this.chart,
18315 renderer = chart.renderer,
18316 options = this.options,
18317 optionsY = options.y,
18318 alignTop = options.verticalAlign === 'top',
18319 padding = this.padding,
18320 spaceHeight = chart.spacingBox.height +
18321 (alignTop ? -optionsY : optionsY) - padding,
18322 maxHeight = options.maxHeight,
18323 clipHeight,
18324 clipRect = this.clipRect,
18325 navOptions = options.navigation,
18326 animation = pick(navOptions.animation, true),
18327 arrowSize = navOptions.arrowSize || 12,
18328 nav = this.nav,
18329 pages = this.pages,
18330 lastY,
18331 allItems = this.allItems,
18332 clipToHeight = function(height) {
18333 if (typeof height === 'number') {
18334 clipRect.attr({
18335 height: height
18336 });
18337 } else if (clipRect) { // Reset (#5912)
18338 legend.clipRect = clipRect.destroy();
18339 legend.contentGroup.clip();
18340 }
18341
18342 // useHTML
18343 if (legend.contentGroup.div) {
18344 legend.contentGroup.div.style.clip = height ?
18345 'rect(' + padding + 'px,9999px,' +
18346 (padding + height) + 'px,0)' :
18347 'auto';
18348 }
18349 };
18350
18351
18352 // Adjust the height
18353 if (
18354 options.layout === 'horizontal' &&
18355 options.verticalAlign !== 'middle' &&
18356 !options.floating
18357 ) {
18358 spaceHeight /= 2;
18359 }
18360 if (maxHeight) {
18361 spaceHeight = Math.min(spaceHeight, maxHeight);
18362 }
18363
18364 // Reset the legend height and adjust the clipping rectangle
18365 pages.length = 0;
18366 if (legendHeight > spaceHeight && navOptions.enabled !== false) {
18367
18368 this.clipHeight = clipHeight =
18369 Math.max(spaceHeight - 20 - this.titleHeight - padding, 0);
18370 this.currentPage = pick(this.currentPage, 1);
18371 this.fullHeight = legendHeight;
18372
18373 // Fill pages with Y positions so that the top of each a legend item
18374 // defines the scroll top for each page (#2098)
18375 each(allItems, function(item, i) {
18376 var y = item._legendItemPos[1],
18377 h = Math.round(item.legendItem.getBBox().height),
18378 len = pages.length;
18379
18380 if (!len || (y - pages[len - 1] > clipHeight &&
18381 (lastY || y) !== pages[len - 1])) {
18382 pages.push(lastY || y);
18383 len++;
18384 }
18385
18386 if (i === allItems.length - 1 &&
18387 y + h - pages[len - 1] > clipHeight) {
18388 pages.push(y);
18389 }
18390 if (y !== lastY) {
18391 lastY = y;
18392 }
18393 });
18394
18395 // Only apply clipping if needed. Clipping causes blurred legend in
18396 // PDF export (#1787)
18397 if (!clipRect) {
18398 clipRect = legend.clipRect =
18399 renderer.clipRect(0, padding, 9999, 0);
18400 legend.contentGroup.clip(clipRect);
18401 }
18402
18403 clipToHeight(clipHeight);
18404
18405 // Add navigation elements
18406 if (!nav) {
18407 this.nav = nav = renderer.g()
18408 .attr({
18409 zIndex: 1
18410 })
18411 .add(this.group);
18412
18413 this.up = renderer
18414 .symbol(
18415 'triangle',
18416 0,
18417 0,
18418 arrowSize,
18419 arrowSize
18420 )
18421 .on('click', function() {
18422 legend.scroll(-1, animation);
18423 })
18424 .add(nav);
18425
18426 this.pager = renderer.text('', 15, 10)
18427 .addClass('highcharts-legend-navigation')
18428
18429 .add(nav);
18430
18431 this.down = renderer
18432 .symbol(
18433 'triangle-down',
18434 0,
18435 0,
18436 arrowSize,
18437 arrowSize
18438 )
18439 .on('click', function() {
18440 legend.scroll(1, animation);
18441 })
18442 .add(nav);
18443 }
18444
18445 // Set initial position
18446 legend.scroll(0);
18447
18448 legendHeight = spaceHeight;
18449
18450 // Reset
18451 } else if (nav) {
18452 clipToHeight();
18453 this.nav = nav.destroy(); // #6322
18454 this.scrollGroup.attr({
18455 translateY: 1
18456 });
18457 this.clipHeight = 0; // #1379
18458 }
18459
18460 return legendHeight;
18461 },
18462
18463 /**
18464 * Scroll the legend by a number of pages.
18465 * @param {Number} scrollBy
18466 * The number of pages to scroll.
18467 * @param {AnimationOptions} animation
18468 * Whether and how to apply animation.
18469 */
18470 scroll: function(scrollBy, animation) {
18471 var pages = this.pages,
18472 pageCount = pages.length,
18473 currentPage = this.currentPage + scrollBy,
18474 clipHeight = this.clipHeight,
18475 navOptions = this.options.navigation,
18476 pager = this.pager,
18477 padding = this.padding;
18478
18479 // When resizing while looking at the last page
18480 if (currentPage > pageCount) {
18481 currentPage = pageCount;
18482 }
18483
18484 if (currentPage > 0) {
18485
18486 if (animation !== undefined) {
18487 setAnimation(animation, this.chart);
18488 }
18489
18490 this.nav.attr({
18491 translateX: padding,
18492 translateY: clipHeight + this.padding + 7 + this.titleHeight,
18493 visibility: 'visible'
18494 });
18495 this.up.attr({
18496 'class': currentPage === 1 ?
18497 'highcharts-legend-nav-inactive' : 'highcharts-legend-nav-active'
18498 });
18499 pager.attr({
18500 text: currentPage + '/' + pageCount
18501 });
18502 this.down.attr({
18503 'x': 18 + this.pager.getBBox().width, // adjust to text width
18504 'class': currentPage === pageCount ?
18505 'highcharts-legend-nav-inactive' : 'highcharts-legend-nav-active'
18506 });
18507
18508
18509
18510 this.scrollOffset = -pages[currentPage - 1] + this.initialItemY;
18511
18512 this.scrollGroup.animate({
18513 translateY: this.scrollOffset
18514 });
18515
18516 this.currentPage = currentPage;
18517 this.positionCheckboxes();
18518 }
18519
18520 }
18521
18522 };
18523
18524 /*
18525 * LegendSymbolMixin
18526 */
18527
18528 H.LegendSymbolMixin = {
18529
18530 /**
18531 * Get the series' symbol in the legend
18532 *
18533 * @param {Object} legend The legend object
18534 * @param {Object} item The series (this) or point
18535 */
18536 drawRectangle: function(legend, item) {
18537 var options = legend.options,
18538 symbolHeight = legend.symbolHeight,
18539 square = options.squareSymbol,
18540 symbolWidth = square ? symbolHeight : legend.symbolWidth;
18541
18542 item.legendSymbol = this.chart.renderer.rect(
18543 square ? (legend.symbolWidth - symbolHeight) / 2 : 0,
18544 legend.baseline - symbolHeight + 1, // #3988
18545 symbolWidth,
18546 symbolHeight,
18547 pick(legend.options.symbolRadius, symbolHeight / 2)
18548 )
18549 .addClass('highcharts-point')
18550 .attr({
18551 zIndex: 3
18552 }).add(item.legendGroup);
18553
18554 },
18555
18556 /**
18557 * Get the series' symbol in the legend. This method should be overridable
18558 * to create custom symbols through
18559 * Highcharts.seriesTypes[type].prototype.drawLegendSymbols.
18560 *
18561 * @param {Object} legend The legend object
18562 */
18563 drawLineMarker: function(legend) {
18564
18565 var options = this.options,
18566 markerOptions = options.marker,
18567 radius,
18568 legendSymbol,
18569 symbolWidth = legend.symbolWidth,
18570 symbolHeight = legend.symbolHeight,
18571 generalRadius = symbolHeight / 2,
18572 renderer = this.chart.renderer,
18573 legendItemGroup = this.legendGroup,
18574 verticalCenter = legend.baseline -
18575 Math.round(legend.fontMetrics.b * 0.3),
18576 attr = {};
18577
18578 // Draw the line
18579
18580
18581 this.legendLine = renderer.path([
18582 'M',
18583 0,
18584 verticalCenter,
18585 'L',
18586 symbolWidth,
18587 verticalCenter
18588 ])
18589 .addClass('highcharts-graph')
18590 .attr(attr)
18591 .add(legendItemGroup);
18592
18593 // Draw the marker
18594 if (markerOptions && markerOptions.enabled !== false) {
18595
18596 // Do not allow the marker to be larger than the symbolHeight
18597 radius = Math.min(
18598 pick(markerOptions.radius, generalRadius),
18599 generalRadius
18600 );
18601
18602 // Restrict symbol markers size
18603 if (this.symbol.indexOf('url') === 0) {
18604 markerOptions = merge(markerOptions, {
18605 width: symbolHeight,
18606 height: symbolHeight
18607 });
18608 radius = 0;
18609 }
18610
18611 this.legendSymbol = legendSymbol = renderer.symbol(
18612 this.symbol,
18613 (symbolWidth / 2) - radius,
18614 verticalCenter - radius,
18615 2 * radius,
18616 2 * radius,
18617 markerOptions
18618 )
18619 .addClass('highcharts-point')
18620 .add(legendItemGroup);
18621 legendSymbol.isMarker = true;
18622 }
18623 }
18624 };
18625
18626 // Workaround for #2030, horizontal legend items not displaying in IE11 Preview,
18627 // and for #2580, a similar drawing flaw in Firefox 26.
18628 // Explore if there's a general cause for this. The problem may be related
18629 // to nested group elements, as the legend item texts are within 4 group
18630 // elements.
18631 if (/Trident\/7\.0/.test(win.navigator.userAgent) || isFirefox) {
18632 wrap(Highcharts.Legend.prototype, 'positionItem', function(proceed, item) {
18633 var legend = this,
18634 // If chart destroyed in sync, this is undefined (#2030)
18635 runPositionItem = function() {
18636 if (item._legendItemPos) {
18637 proceed.call(legend, item);
18638 }
18639 };
18640
18641 // Do it now, for export and to get checkbox placement
18642 runPositionItem();
18643
18644 // Do it after to work around the core issue
18645 setTimeout(runPositionItem);
18646 });
18647 }
18648
18649 }(Highcharts));
18650 (function(H) {
18651 /**
18652 * (c) 2010-2017 Torstein Honsi
18653 *
18654 * License: www.highcharts.com/license
18655 */
18656 var addEvent = H.addEvent,
18657 animate = H.animate,
18658 animObject = H.animObject,
18659 attr = H.attr,
18660 doc = H.doc,
18661 Axis = H.Axis, // @todo add as requirement
18662 createElement = H.createElement,
18663 defaultOptions = H.defaultOptions,
18664 discardElement = H.discardElement,
18665 charts = H.charts,
18666 css = H.css,
18667 defined = H.defined,
18668 each = H.each,
18669 extend = H.extend,
18670 find = H.find,
18671 fireEvent = H.fireEvent,
18672 grep = H.grep,
18673 isNumber = H.isNumber,
18674 isObject = H.isObject,
18675 isString = H.isString,
18676 Legend = H.Legend, // @todo add as requirement
18677 marginNames = H.marginNames,
18678 merge = H.merge,
18679 objectEach = H.objectEach,
18680 Pointer = H.Pointer, // @todo add as requirement
18681 pick = H.pick,
18682 pInt = H.pInt,
18683 removeEvent = H.removeEvent,
18684 seriesTypes = H.seriesTypes,
18685 splat = H.splat,
18686 svg = H.svg,
18687 syncTimeout = H.syncTimeout,
18688 win = H.win;
18689 /**
18690 * The Chart class. The recommended constructor is {@link Highcharts#chart}.
18691 * @class Highcharts.Chart
18692 * @param {String|HTMLDOMElement} renderTo
18693 * The DOM element to render to, or its id.
18694 * @param {Options} options
18695 * The chart options structure.
18696 * @param {Function} [callback]
18697 * Function to run when the chart has loaded and and all external images
18698 * are loaded. Defining a {@link
18699 * https://api.highcharts.com/highcharts/chart.events.load|chart.event.load}
18700 * handler is equivalent.
18701 *
18702 * @example
18703 * var chart = Highcharts.chart('container', {
18704 * title: {
18705 * text: 'My chart'
18706 * },
18707 * series: [{
18708 * data: [1, 3, 2, 4]
18709 * }]
18710 * })
18711 */
18712 var Chart = H.Chart = function() {
18713 this.getArgs.apply(this, arguments);
18714 };
18715
18716 /**
18717 * Factory function for basic charts.
18718 *
18719 * @function #chart
18720 * @memberOf Highcharts
18721 * @param {String|HTMLDOMElement} renderTo - The DOM element to render to, or
18722 * its id.
18723 * @param {Options} options - The chart options structure.
18724 * @param {Function} [callback] - Function to run when the chart has loaded and
18725 * and all external images are loaded. Defining a {@link
18726 * https://api.highcharts.com/highcharts/chart.events.load|chart.event.load}
18727 * handler is equivalent.
18728 * @return {Highcharts.Chart} - Returns the Chart object.
18729 *
18730 * @example
18731 * // Render a chart in to div#container
18732 * var chart = Highcharts.chart('container', {
18733 * title: {
18734 * text: 'My chart'
18735 * },
18736 * series: [{
18737 * data: [1, 3, 2, 4]
18738 * }]
18739 * });
18740 */
18741 H.chart = function(a, b, c) {
18742 return new Chart(a, b, c);
18743 };
18744
18745 extend(Chart.prototype, /** @lends Highcharts.Chart.prototype */ {
18746
18747 // Hook for adding callbacks in modules
18748 callbacks: [],
18749
18750 /**
18751 * Handle the arguments passed to the constructor.
18752 *
18753 * @private
18754 * @returns {Array} Arguments without renderTo
18755 */
18756 getArgs: function() {
18757 var args = [].slice.call(arguments);
18758
18759 // Remove the optional first argument, renderTo, and
18760 // set it on this.
18761 if (isString(args[0]) || args[0].nodeName) {
18762 this.renderTo = args.shift();
18763 }
18764 this.init(args[0], args[1]);
18765 },
18766
18767 /**
18768 * Overridable function that initializes the chart. The constructor's
18769 * arguments are passed on directly.
18770 */
18771 init: function(userOptions, callback) {
18772
18773 // Handle regular options
18774 var options,
18775 type,
18776 seriesOptions = userOptions.series, // skip merging data points to increase performance
18777 userPlotOptions = userOptions.plotOptions || {};
18778
18779 userOptions.series = null;
18780 options = merge(defaultOptions, userOptions); // do the merge
18781
18782 // Override (by copy of user options) or clear tooltip options
18783 // in chart.options.plotOptions (#6218)
18784 for (type in options.plotOptions) {
18785 options.plotOptions[type].tooltip = (
18786 userPlotOptions[type] &&
18787 merge(userPlotOptions[type].tooltip) // override by copy
18788 ) || undefined; // or clear
18789 }
18790 // User options have higher priority than default options (#6218).
18791 // In case of exporting: path is changed
18792 options.tooltip.userOptions = (userOptions.chart &&
18793 userOptions.chart.forExport && userOptions.tooltip.userOptions) ||
18794 userOptions.tooltip;
18795
18796 options.series = userOptions.series = seriesOptions; // set back the series data
18797 this.userOptions = userOptions;
18798
18799 var optionsChart = options.chart;
18800
18801 var chartEvents = optionsChart.events;
18802
18803 this.margin = [];
18804 this.spacing = [];
18805
18806 this.bounds = {
18807 h: {},
18808 v: {}
18809 }; // Pixel data bounds for touch zoom
18810
18811 // An array of functions that returns labels that should be considered
18812 // for anti-collision
18813 this.labelCollectors = [];
18814
18815 this.callback = callback;
18816 this.isResizing = 0;
18817
18818 /**
18819 * The options structure for the chart. It contains members for the sub
18820 * elements like series, legend, tooltip etc.
18821 *
18822 * @memberof Highcharts.Chart
18823 * @name options
18824 * @type {Options}
18825 */
18826 this.options = options;
18827 /**
18828 * All the axes in the chart.
18829 *
18830 * @memberof Highcharts.Chart
18831 * @name axes
18832 * @see Highcharts.Chart.xAxis
18833 * @see Highcharts.Chart.yAxis
18834 * @type {Array.<Highcharts.Axis>}
18835 */
18836 this.axes = [];
18837
18838 /**
18839 * All the current series in the chart.
18840 *
18841 * @memberof Highcharts.Chart
18842 * @name series
18843 * @type {Array.<Highcharts.Series>}
18844 */
18845 this.series = [];
18846
18847 /**
18848 * The chart title. The title has an `update` method that allows
18849 * modifying the options directly or indirectly via `chart.update`.
18850 *
18851 * @memberof Highcharts.Chart
18852 * @name title
18853 * @type Object
18854 *
18855 * @sample highcharts/members/title-update/
18856 * Updating titles
18857 */
18858
18859 /**
18860 * The chart subtitle. The subtitle has an `update` method that allows
18861 * modifying the options directly or indirectly via `chart.update`.
18862 *
18863 * @memberof Highcharts.Chart
18864 * @name subtitle
18865 * @type Object
18866 */
18867
18868
18869
18870 this.hasCartesianSeries = optionsChart.showAxes;
18871
18872 var chart = this;
18873
18874 // Add the chart to the global lookup
18875 chart.index = charts.length;
18876
18877 charts.push(chart);
18878 H.chartCount++;
18879
18880 // Chart event handlers
18881 if (chartEvents) {
18882 objectEach(chartEvents, function(event, eventType) {
18883 addEvent(chart, eventType, event);
18884 });
18885 }
18886
18887 /**
18888 * A collection of the X axes in the chart.
18889 * @type {Array.<Highcharts.Axis>}
18890 * @name xAxis
18891 * @memberOf Highcharts.Chart
18892 */
18893 chart.xAxis = [];
18894 /**
18895 * A collection of the Y axes in the chart.
18896 * @type {Array.<Highcharts.Axis>}
18897 * @name yAxis
18898 * @memberOf Highcharts.Chart
18899 */
18900 chart.yAxis = [];
18901
18902 chart.pointCount = chart.colorCounter = chart.symbolCounter = 0;
18903
18904 chart.firstRender();
18905 },
18906
18907 /**
18908 * Internal function to unitialize an individual series.
18909 *
18910 * @private
18911 */
18912 initSeries: function(options) {
18913 var chart = this,
18914 optionsChart = chart.options.chart,
18915 type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
18916 series,
18917 Constr = seriesTypes[type];
18918
18919 // No such series type
18920 if (!Constr) {
18921 H.error(17, true);
18922 }
18923
18924 series = new Constr();
18925 series.init(this, options);
18926 return series;
18927 },
18928
18929 /**
18930 * Order all series above a given index. When series are added and ordered
18931 * by configuration, only the last series is handled (#248, #1123, #2456,
18932 * #6112). This function is called on series initialization and destroy.
18933 *
18934 * @private
18935 *
18936 * @param {number} fromIndex
18937 * If this is given, only the series above this index are handled.
18938 */
18939 orderSeries: function(fromIndex) {
18940 var series = this.series,
18941 i = fromIndex || 0;
18942 for (; i < series.length; i++) {
18943 if (series[i]) {
18944 series[i].index = i;
18945 series[i].name = series[i].name ||
18946 'Series ' + (series[i].index + 1);
18947 }
18948 }
18949 },
18950
18951 /**
18952 * Check whether a given point is within the plot area.
18953 *
18954 * @param {Number} plotX
18955 * Pixel x relative to the plot area.
18956 * @param {Number} plotY
18957 * Pixel y relative to the plot area.
18958 * @param {Boolean} inverted
18959 * Whether the chart is inverted.
18960 *
18961 * @return {Boolean}
18962 * Returns true if the given point is inside the plot area.
18963 */
18964 isInsidePlot: function(plotX, plotY, inverted) {
18965 var x = inverted ? plotY : plotX,
18966 y = inverted ? plotX : plotY;
18967
18968 return x >= 0 &&
18969 x <= this.plotWidth &&
18970 y >= 0 &&
18971 y <= this.plotHeight;
18972 },
18973
18974 /**
18975 * Redraw the chart after changes have been done to the data, axis extremes
18976 * chart size or chart elements. All methods for updating axes, series or
18977 * points have a parameter for redrawing the chart. This is `true` by
18978 * default. But in many cases you want to do more than one operation on the
18979 * chart before redrawing, for example add a number of points. In those
18980 * cases it is a waste of resources to redraw the chart for each new point
18981 * added. So you add the points and call `chart.redraw()` after.
18982 *
18983 * @param {AnimationOptions} animation
18984 * If or how to apply animation to the redraw.
18985 */
18986 redraw: function(animation) {
18987 var chart = this,
18988 axes = chart.axes,
18989 series = chart.series,
18990 pointer = chart.pointer,
18991 legend = chart.legend,
18992 redrawLegend = chart.isDirtyLegend,
18993 hasStackedSeries,
18994 hasDirtyStacks,
18995 hasCartesianSeries = chart.hasCartesianSeries,
18996 isDirtyBox = chart.isDirtyBox,
18997 i,
18998 serie,
18999 renderer = chart.renderer,
19000 isHiddenChart = renderer.isHidden(),
19001 afterRedraw = [];
19002
19003 // Handle responsive rules, not only on resize (#6130)
19004 if (chart.setResponsive) {
19005 chart.setResponsive(false);
19006 }
19007
19008 H.setAnimation(animation, chart);
19009
19010 if (isHiddenChart) {
19011 chart.temporaryDisplay();
19012 }
19013
19014 // Adjust title layout (reflow multiline text)
19015 chart.layOutTitles();
19016
19017 // link stacked series
19018 i = series.length;
19019 while (i--) {
19020 serie = series[i];
19021
19022 if (serie.options.stacking) {
19023 hasStackedSeries = true;
19024
19025 if (serie.isDirty) {
19026 hasDirtyStacks = true;
19027 break;
19028 }
19029 }
19030 }
19031 if (hasDirtyStacks) { // mark others as dirty
19032 i = series.length;
19033 while (i--) {
19034 serie = series[i];
19035 if (serie.options.stacking) {
19036 serie.isDirty = true;
19037 }
19038 }
19039 }
19040
19041 // Handle updated data in the series
19042 each(series, function(serie) {
19043 if (serie.isDirty) {
19044 if (serie.options.legendType === 'point') {
19045 if (serie.updateTotals) {
19046 serie.updateTotals();
19047 }
19048 redrawLegend = true;
19049 }
19050 }
19051 if (serie.isDirtyData) {
19052 fireEvent(serie, 'updatedData');
19053 }
19054 });
19055
19056 // handle added or removed series
19057 if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed
19058 // draw legend graphics
19059 legend.render();
19060
19061 chart.isDirtyLegend = false;
19062 }
19063
19064 // reset stacks
19065 if (hasStackedSeries) {
19066 chart.getStacks();
19067 }
19068
19069
19070 if (hasCartesianSeries) {
19071 // set axes scales
19072 each(axes, function(axis) {
19073 axis.updateNames();
19074 axis.setScale();
19075 });
19076 }
19077
19078 chart.getMargins(); // #3098
19079
19080 if (hasCartesianSeries) {
19081 // If one axis is dirty, all axes must be redrawn (#792, #2169)
19082 each(axes, function(axis) {
19083 if (axis.isDirty) {
19084 isDirtyBox = true;
19085 }
19086 });
19087
19088 // redraw axes
19089 each(axes, function(axis) {
19090
19091 // Fire 'afterSetExtremes' only if extremes are set
19092 var key = axis.min + ',' + axis.max;
19093 if (axis.extKey !== key) { // #821, #4452
19094 axis.extKey = key;
19095 afterRedraw.push(function() { // prevent a recursive call to chart.redraw() (#1119)
19096 fireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751
19097 delete axis.eventArgs;
19098 });
19099 }
19100 if (isDirtyBox || hasStackedSeries) {
19101 axis.redraw();
19102 }
19103 });
19104 }
19105
19106 // the plot areas size has changed
19107 if (isDirtyBox) {
19108 chart.drawChartBox();
19109 }
19110
19111 // Fire an event before redrawing series, used by the boost module to
19112 // clear previous series renderings.
19113 fireEvent(chart, 'predraw');
19114
19115 // redraw affected series
19116 each(series, function(serie) {
19117 if ((isDirtyBox || serie.isDirty) && serie.visible) {
19118 serie.redraw();
19119 }
19120 // Set it here, otherwise we will have unlimited 'updatedData' calls
19121 // for a hidden series after setData(). Fixes #6012
19122 serie.isDirtyData = false;
19123 });
19124
19125 // move tooltip or reset
19126 if (pointer) {
19127 pointer.reset(true);
19128 }
19129
19130 // redraw if canvas
19131 renderer.draw();
19132
19133 // Fire the events
19134 fireEvent(chart, 'redraw');
19135 fireEvent(chart, 'render');
19136
19137 if (isHiddenChart) {
19138 chart.temporaryDisplay(true);
19139 }
19140
19141 // Fire callbacks that are put on hold until after the redraw
19142 each(afterRedraw, function(callback) {
19143 callback.call();
19144 });
19145 },
19146
19147 /**
19148 * Get an axis, series or point object by `id` as given in the configuration
19149 * options. Returns `undefined` if no item is found.
19150 * @param id {String} The id as given in the configuration options.
19151 * @return {Highcharts.Axis|Highcharts.Series|Highcharts.Point|undefined}
19152 * The retrieved item.
19153 * @sample highcharts/plotoptions/series-id/
19154 * Get series by id
19155 */
19156 get: function(id) {
19157
19158 var ret,
19159 series = this.series,
19160 i;
19161
19162 function itemById(item) {
19163 return item.id === id || (item.options && item.options.id === id);
19164 }
19165
19166 ret =
19167 // Search axes
19168 find(this.axes, itemById) ||
19169
19170 // Search series
19171 find(this.series, itemById);
19172
19173 // Search points
19174 for (i = 0; !ret && i < series.length; i++) {
19175 ret = find(series[i].points || [], itemById);
19176 }
19177
19178 return ret;
19179 },
19180
19181 /**
19182 * Create the Axis instances based on the config options.
19183 *
19184 * @private
19185 */
19186 getAxes: function() {
19187 var chart = this,
19188 options = this.options,
19189 xAxisOptions = options.xAxis = splat(options.xAxis || {}),
19190 yAxisOptions = options.yAxis = splat(options.yAxis || {}),
19191 optionsArray;
19192
19193 // make sure the options are arrays and add some members
19194 each(xAxisOptions, function(axis, i) {
19195 axis.index = i;
19196 axis.isX = true;
19197 });
19198
19199 each(yAxisOptions, function(axis, i) {
19200 axis.index = i;
19201 });
19202
19203 // concatenate all axis options into one array
19204 optionsArray = xAxisOptions.concat(yAxisOptions);
19205
19206 each(optionsArray, function(axisOptions) {
19207 new Axis(chart, axisOptions); // eslint-disable-line no-new
19208 });
19209 },
19210
19211
19212 /**
19213 * Returns an array of all currently selected points in the chart. Points
19214 * can be selected by clicking or programmatically by the {@link
19215 * Highcharts.Point#select} function.
19216 *
19217 * @return {Array.<Highcharts.Point>}
19218 * The currently selected points.
19219 *
19220 * @sample highcharts/plotoptions/series-allowpointselect-line/
19221 * Get selected points
19222 */
19223 getSelectedPoints: function() {
19224 var points = [];
19225 each(this.series, function(serie) {
19226 // series.data - for points outside of viewed range (#6445)
19227 points = points.concat(grep(serie.data || [], function(point) {
19228 return point.selected;
19229 }));
19230 });
19231 return points;
19232 },
19233
19234 /**
19235 * Returns an array of all currently selected series in the chart. Series
19236 * can be selected either programmatically by the {@link
19237 * Highcharts.Series#select} function or by checking the checkbox next to
19238 * the legend item if {@link
19239 * https://api.highcharts.com/highcharts/plotOptions.series.showCheckbox|
19240 * series.showCheckBox} is true.
19241 *
19242 * @return {Array.<Highcharts.Series>}
19243 * The currently selected series.
19244 *
19245 * @sample highcharts/members/chart-getselectedseries/
19246 * Get selected series
19247 */
19248 getSelectedSeries: function() {
19249 return grep(this.series, function(serie) {
19250 return serie.selected;
19251 });
19252 },
19253
19254 /**
19255 * Set a new title or subtitle for the chart.
19256 *
19257 * @param titleOptions {TitleOptions}
19258 * New title options. The title text itself is set by the
19259 * `titleOptions.text` property.
19260 * @param subtitleOptions {SubtitleOptions}
19261 * New subtitle options. The subtitle text itself is set by the
19262 * `subtitleOptions.text` property.
19263 * @param redraw {Boolean}
19264 * Whether to redraw the chart or wait for a later call to
19265 * `chart.redraw()`.
19266 *
19267 * @sample highcharts/members/chart-settitle/ Set title text and styles
19268 *
19269 */
19270 setTitle: function(titleOptions, subtitleOptions, redraw) {
19271 var chart = this,
19272 options = chart.options,
19273 chartTitleOptions,
19274 chartSubtitleOptions;
19275
19276 chartTitleOptions = options.title = merge(
19277
19278 options.title,
19279 titleOptions
19280 );
19281 chartSubtitleOptions = options.subtitle = merge(
19282
19283 options.subtitle,
19284 subtitleOptions
19285 );
19286
19287 // add title and subtitle
19288 each([
19289 ['title', titleOptions, chartTitleOptions],
19290 ['subtitle', subtitleOptions, chartSubtitleOptions]
19291 ], function(arr, i) {
19292 var name = arr[0],
19293 title = chart[name],
19294 titleOptions = arr[1],
19295 chartTitleOptions = arr[2];
19296
19297 if (title && titleOptions) {
19298 chart[name] = title = title.destroy(); // remove old
19299 }
19300
19301 if (chartTitleOptions && !title) {
19302 chart[name] = chart.renderer.text(
19303 chartTitleOptions.text,
19304 0,
19305 0,
19306 chartTitleOptions.useHTML
19307 )
19308 .attr({
19309 align: chartTitleOptions.align,
19310 'class': 'highcharts-' + name,
19311 zIndex: chartTitleOptions.zIndex || 4
19312 })
19313 .add();
19314
19315 // Update methods, shortcut to Chart.setTitle
19316 chart[name].update = function(o) {
19317 chart.setTitle(!i && o, i && o);
19318 };
19319
19320
19321
19322 }
19323 });
19324 chart.layOutTitles(redraw);
19325 },
19326
19327 /**
19328 * Internal function to lay out the chart titles and cache the full offset
19329 * height for use in `getMargins`. The result is stored in
19330 * `this.titleOffset`.
19331 *
19332 * @private
19333 */
19334 layOutTitles: function(redraw) {
19335 var titleOffset = 0,
19336 requiresDirtyBox,
19337 renderer = this.renderer,
19338 spacingBox = this.spacingBox;
19339
19340 // Lay out the title and the subtitle respectively
19341 each(['title', 'subtitle'], function(key) {
19342 var title = this[key],
19343 titleOptions = this.options[key],
19344 offset = key === 'title' ? -3 :
19345 // Floating subtitle (#6574)
19346 titleOptions.verticalAlign ? 0 : titleOffset + 2,
19347 titleSize;
19348
19349 if (title) {
19350
19351 titleSize = renderer.fontMetrics(titleSize, title).b;
19352
19353 title
19354 .css({
19355 width: (titleOptions.width ||
19356 spacingBox.width + titleOptions.widthAdjust) + 'px'
19357 })
19358 .align(extend({
19359 y: offset + titleSize
19360 }, titleOptions), false, 'spacingBox');
19361
19362 if (!titleOptions.floating && !titleOptions.verticalAlign) {
19363 titleOffset = Math.ceil(
19364 titleOffset +
19365 // Skip the cache for HTML (#3481)
19366 title.getBBox(titleOptions.useHTML).height
19367 );
19368 }
19369 }
19370 }, this);
19371
19372 requiresDirtyBox = this.titleOffset !== titleOffset;
19373 this.titleOffset = titleOffset; // used in getMargins
19374
19375 if (!this.isDirtyBox && requiresDirtyBox) {
19376 this.isDirtyBox = requiresDirtyBox;
19377 // Redraw if necessary (#2719, #2744)
19378 if (this.hasRendered && pick(redraw, true) && this.isDirtyBox) {
19379 this.redraw();
19380 }
19381 }
19382 },
19383
19384 /**
19385 * Internal function to get the chart width and height according to options
19386 * and container size. Sets {@link Chart.chartWidth} and {@link
19387 * Chart.chartHeight}.
19388 */
19389 getChartSize: function() {
19390 var chart = this,
19391 optionsChart = chart.options.chart,
19392 widthOption = optionsChart.width,
19393 heightOption = optionsChart.height,
19394 renderTo = chart.renderTo;
19395
19396 // Get inner width and height
19397 if (!defined(widthOption)) {
19398 chart.containerWidth = H.getStyle(renderTo, 'width');
19399 }
19400 if (!defined(heightOption)) {
19401 chart.containerHeight = H.getStyle(renderTo, 'height');
19402 }
19403
19404 /**
19405 * The current pixel width of the chart.
19406 *
19407 * @name chartWidth
19408 * @memberOf Chart
19409 * @type {Number}
19410 */
19411 chart.chartWidth = Math.max( // #1393
19412 0,
19413 widthOption || chart.containerWidth || 600 // #1460
19414 );
19415 /**
19416 * The current pixel height of the chart.
19417 *
19418 * @name chartHeight
19419 * @memberOf Chart
19420 * @type {Number}
19421 */
19422 chart.chartHeight = Math.max(
19423 0,
19424 H.relativeLength(
19425 heightOption,
19426 chart.chartWidth
19427 ) ||
19428 (chart.containerHeight > 1 ? chart.containerHeight : 400)
19429 );
19430 },
19431
19432 /**
19433 * If the renderTo element has no offsetWidth, most likely one or more of
19434 * its parents are hidden. Loop up the DOM tree to temporarily display the
19435 * parents, then save the original display properties, and when the true
19436 * size is retrieved, reset them. Used on first render and on redraws.
19437 *
19438 * @private
19439 *
19440 * @param {Boolean} revert
19441 * Revert to the saved original styles.
19442 */
19443 temporaryDisplay: function(revert) {
19444 var node = this.renderTo,
19445 tempStyle;
19446 if (!revert) {
19447 while (node && node.style) {
19448
19449 // When rendering to a detached node, it needs to be temporarily
19450 // attached in order to read styling and bounding boxes (#5783,
19451 // #7024).
19452 if (!doc.body.contains(node) && !node.parentNode) {
19453 node.hcOrigDetached = true;
19454 doc.body.appendChild(node);
19455 }
19456 if (
19457 H.getStyle(node, 'display', false) === 'none' ||
19458 node.hcOricDetached
19459 ) {
19460 node.hcOrigStyle = {
19461 display: node.style.display,
19462 height: node.style.height,
19463 overflow: node.style.overflow
19464 };
19465 tempStyle = {
19466 display: 'block',
19467 overflow: 'hidden'
19468 };
19469 if (node !== this.renderTo) {
19470 tempStyle.height = 0;
19471 }
19472
19473 H.css(node, tempStyle);
19474
19475 // If it still doesn't have an offset width after setting
19476 // display to block, it probably has an !important priority
19477 // #2631, 6803
19478 if (!node.offsetWidth) {
19479 node.style.setProperty('display', 'block', 'important');
19480 }
19481 }
19482 node = node.parentNode;
19483
19484 if (node === doc.body) {
19485 break;
19486 }
19487 }
19488 } else {
19489 while (node && node.style) {
19490 if (node.hcOrigStyle) {
19491 H.css(node, node.hcOrigStyle);
19492 delete node.hcOrigStyle;
19493 }
19494 if (node.hcOrigDetached) {
19495 doc.body.removeChild(node);
19496 node.hcOrigDetached = false;
19497 }
19498 node = node.parentNode;
19499 }
19500 }
19501 },
19502
19503 /**
19504 * Set the {@link Chart.container|chart container's} class name, in
19505 * addition to `highcharts-container`.
19506 */
19507 setClassName: function(className) {
19508 this.container.className = 'highcharts-container ' + (className || '');
19509 },
19510
19511 /**
19512 * Get the containing element, determine the size and create the inner
19513 * container div to hold the chart.
19514 *
19515 * @private
19516 */
19517 getContainer: function() {
19518 var chart = this,
19519 container,
19520 options = chart.options,
19521 optionsChart = options.chart,
19522 chartWidth,
19523 chartHeight,
19524 renderTo = chart.renderTo,
19525 indexAttrName = 'data-highcharts-chart',
19526 oldChartIndex,
19527 Ren,
19528 containerId = H.uniqueKey(),
19529 containerStyle,
19530 key;
19531
19532 if (!renderTo) {
19533 chart.renderTo = renderTo = optionsChart.renderTo;
19534 }
19535
19536 if (isString(renderTo)) {
19537 chart.renderTo = renderTo = doc.getElementById(renderTo);
19538 }
19539
19540 // Display an error if the renderTo is wrong
19541 if (!renderTo) {
19542 H.error(13, true);
19543 }
19544
19545 // If the container already holds a chart, destroy it. The check for
19546 // hasRendered is there because web pages that are saved to disk from
19547 // the browser, will preserve the data-highcharts-chart attribute and
19548 // the SVG contents, but not an interactive chart. So in this case,
19549 // charts[oldChartIndex] will point to the wrong chart if any (#2609).
19550 oldChartIndex = pInt(attr(renderTo, indexAttrName));
19551 if (
19552 isNumber(oldChartIndex) &&
19553 charts[oldChartIndex] &&
19554 charts[oldChartIndex].hasRendered
19555 ) {
19556 charts[oldChartIndex].destroy();
19557 }
19558
19559 // Make a reference to the chart from the div
19560 attr(renderTo, indexAttrName, chart.index);
19561
19562 // remove previous chart
19563 renderTo.innerHTML = '';
19564
19565 // If the container doesn't have an offsetWidth, it has or is a child of
19566 // a node that has display:none. We need to temporarily move it out to a
19567 // visible state to determine the size, else the legend and tooltips
19568 // won't render properly. The skipClone option is used in sparklines as
19569 // a micro optimization, saving about 1-2 ms each chart.
19570 if (!optionsChart.skipClone && !renderTo.offsetWidth) {
19571 chart.temporaryDisplay();
19572 }
19573
19574 // get the width and height
19575 chart.getChartSize();
19576 chartWidth = chart.chartWidth;
19577 chartHeight = chart.chartHeight;
19578
19579 // Create the inner container
19580
19581
19582 /**
19583 * The containing HTML element of the chart. The container is
19584 * dynamically inserted into the element given as the `renderTo`
19585 * parameterin the {@link Highcharts#chart} constructor.
19586 *
19587 * @memberOf Highcharts.Chart
19588 * @type {HTMLDOMElement}
19589 */
19590 container = createElement(
19591 'div', {
19592 id: containerId
19593 },
19594 containerStyle,
19595 renderTo
19596 );
19597 chart.container = container;
19598
19599 // cache the cursor (#1650)
19600 chart._cursor = container.style.cursor;
19601
19602 // Initialize the renderer
19603 Ren = H[optionsChart.renderer] || H.Renderer;
19604
19605 /**
19606 * The renderer instance of the chart. Each chart instance has only one
19607 * associated renderer.
19608 * @type {SVGRenderer}
19609 * @name renderer
19610 * @memberOf Chart
19611 */
19612 chart.renderer = new Ren(
19613 container,
19614 chartWidth,
19615 chartHeight,
19616 null,
19617 optionsChart.forExport,
19618 options.exporting && options.exporting.allowHTML
19619 );
19620
19621
19622 chart.setClassName(optionsChart.className);
19623
19624 // Initialize definitions
19625 for (key in options.defs) {
19626 this.renderer.definition(options.defs[key]);
19627 }
19628
19629
19630 // Add a reference to the charts index
19631 chart.renderer.chartIndex = chart.index;
19632 },
19633
19634 /**
19635 * Calculate margins by rendering axis labels in a preliminary position.
19636 * Title, subtitle and legend have already been rendered at this stage, but
19637 * will be moved into their final positions.
19638 *
19639 * @private
19640 */
19641 getMargins: function(skipAxes) {
19642 var chart = this,
19643 spacing = chart.spacing,
19644 margin = chart.margin,
19645 titleOffset = chart.titleOffset;
19646
19647 chart.resetMargins();
19648
19649 // Adjust for title and subtitle
19650 if (titleOffset && !defined(margin[0])) {
19651 chart.plotTop = Math.max(
19652 chart.plotTop,
19653 titleOffset + chart.options.title.margin + spacing[0]
19654 );
19655 }
19656
19657 // Adjust for legend
19658 if (chart.legend && chart.legend.display) {
19659 chart.legend.adjustMargins(margin, spacing);
19660 }
19661
19662 // adjust for scroller
19663 if (chart.extraMargin) {
19664 chart[chart.extraMargin.type] =
19665 (chart[chart.extraMargin.type] || 0) + chart.extraMargin.value;
19666 }
19667
19668 // adjust for rangeSelector
19669 if (chart.adjustPlotArea) {
19670 chart.adjustPlotArea();
19671 }
19672
19673 if (!skipAxes) {
19674 this.getAxisMargins();
19675 }
19676 },
19677
19678 getAxisMargins: function() {
19679
19680 var chart = this,
19681 // [top, right, bottom, left]
19682 axisOffset = chart.axisOffset = [0, 0, 0, 0],
19683 margin = chart.margin;
19684
19685 // pre-render axes to get labels offset width
19686 if (chart.hasCartesianSeries) {
19687 each(chart.axes, function(axis) {
19688 if (axis.visible) {
19689 axis.getOffset();
19690 }
19691 });
19692 }
19693
19694 // Add the axis offsets
19695 each(marginNames, function(m, side) {
19696 if (!defined(margin[side])) {
19697 chart[m] += axisOffset[side];
19698 }
19699 });
19700
19701 chart.setChartSize();
19702
19703 },
19704
19705 /**
19706 * Reflows the chart to its container. By default, the chart reflows
19707 * automatically to its container following a `window.resize` event, as per
19708 * the {@link https://api.highcharts/highcharts/chart.reflow|chart.reflow}
19709 * option. However, there are no reliable events for div resize, so if the
19710 * container is resized without a window resize event, this must be called
19711 * explicitly.
19712 *
19713 * @param {Object} e
19714 * Event arguments. Used primarily when the function is called
19715 * internally as a response to window resize.
19716 *
19717 * @sample highcharts/members/chart-reflow/
19718 * Resize div and reflow
19719 * @sample highcharts/chart/events-container/
19720 * Pop up and reflow
19721 */
19722 reflow: function(e) {
19723 var chart = this,
19724 optionsChart = chart.options.chart,
19725 renderTo = chart.renderTo,
19726 hasUserSize = (
19727 defined(optionsChart.width) &&
19728 defined(optionsChart.height)
19729 ),
19730 width = optionsChart.width || H.getStyle(renderTo, 'width'),
19731 height = optionsChart.height || H.getStyle(renderTo, 'height'),
19732 target = e ? e.target : win;
19733
19734 // Width and height checks for display:none. Target is doc in IE8 and
19735 // Opera, win in Firefox, Chrome and IE9.
19736 if (!hasUserSize &&
19737 !chart.isPrinting &&
19738 width &&
19739 height &&
19740 (target === win || target === doc)
19741 ) {
19742 if (
19743 width !== chart.containerWidth ||
19744 height !== chart.containerHeight
19745 ) {
19746 clearTimeout(chart.reflowTimeout);
19747 // When called from window.resize, e is set, else it's called
19748 // directly (#2224)
19749 chart.reflowTimeout = syncTimeout(function() {
19750 // Set size, it may have been destroyed in the meantime
19751 // (#1257)
19752 if (chart.container) {
19753 chart.setSize(undefined, undefined, false);
19754 }
19755 }, e ? 100 : 0);
19756 }
19757 chart.containerWidth = width;
19758 chart.containerHeight = height;
19759 }
19760 },
19761
19762 /**
19763 * Add the event handlers necessary for auto resizing, depending on the
19764 * `chart.events.reflow` option.
19765 *
19766 * @private
19767 */
19768 initReflow: function() {
19769 var chart = this,
19770 unbind;
19771
19772 unbind = addEvent(win, 'resize', function(e) {
19773 chart.reflow(e);
19774 });
19775 addEvent(chart, 'destroy', unbind);
19776
19777 // The following will add listeners to re-fit the chart before and after
19778 // printing (#2284). However it only works in WebKit. Should have worked
19779 // in Firefox, but not supported in IE.
19780 /*
19781 if (win.matchMedia) {
19782 win.matchMedia('print').addListener(function reflow() {
19783 chart.reflow();
19784 });
19785 }
19786 */
19787 },
19788
19789 /**
19790 * Resize the chart to a given width and height. In order to set the width
19791 * only, the height argument may be skipped. To set the height only, pass
19792 * `undefined for the width.
19793 * @param {Number|undefined|null} [width]
19794 * The new pixel width of the chart. Since v4.2.6, the argument can
19795 * be `undefined` in order to preserve the current value (when
19796 * setting height only), or `null` to adapt to the width of the
19797 * containing element.
19798 * @param {Number|undefined|null} [height]
19799 * The new pixel height of the chart. Since v4.2.6, the argument can
19800 * be `undefined` in order to preserve the current value, or `null`
19801 * in order to adapt to the height of the containing element.
19802 * @param {AnimationOptions} [animation=true]
19803 * Whether and how to apply animation.
19804 *
19805 * @sample highcharts/members/chart-setsize-button/
19806 * Test resizing from buttons
19807 * @sample highcharts/members/chart-setsize-jquery-resizable/
19808 * Add a jQuery UI resizable
19809 * @sample stock/members/chart-setsize/
19810 * Highstock with UI resizable
19811 */
19812 setSize: function(width, height, animation) {
19813 var chart = this,
19814 renderer = chart.renderer,
19815 globalAnimation;
19816
19817 // Handle the isResizing counter
19818 chart.isResizing += 1;
19819
19820 // set the animation for the current process
19821 H.setAnimation(animation, chart);
19822
19823 chart.oldChartHeight = chart.chartHeight;
19824 chart.oldChartWidth = chart.chartWidth;
19825 if (width !== undefined) {
19826 chart.options.chart.width = width;
19827 }
19828 if (height !== undefined) {
19829 chart.options.chart.height = height;
19830 }
19831 chart.getChartSize();
19832
19833 // Resize the container with the global animation applied if enabled
19834 // (#2503)
19835
19836
19837 chart.setChartSize(true);
19838 renderer.setSize(chart.chartWidth, chart.chartHeight, animation);
19839
19840 // handle axes
19841 each(chart.axes, function(axis) {
19842 axis.isDirty = true;
19843 axis.setScale();
19844 });
19845
19846 chart.isDirtyLegend = true; // force legend redraw
19847 chart.isDirtyBox = true; // force redraw of plot and chart border
19848
19849 chart.layOutTitles(); // #2857
19850 chart.getMargins();
19851
19852 chart.redraw(animation);
19853
19854
19855 chart.oldChartHeight = null;
19856 fireEvent(chart, 'resize');
19857
19858 // Fire endResize and set isResizing back. If animation is disabled,
19859 // fire without delay
19860 syncTimeout(function() {
19861 if (chart) {
19862 fireEvent(chart, 'endResize', null, function() {
19863 chart.isResizing -= 1;
19864 });
19865 }
19866 }, animObject(globalAnimation).duration);
19867 },
19868
19869 /**
19870 * Set the public chart properties. This is done before and after the
19871 * pre-render to determine margin sizes.
19872 *
19873 * @private
19874 */
19875 setChartSize: function(skipAxes) {
19876 var chart = this,
19877 inverted = chart.inverted,
19878 renderer = chart.renderer,
19879 chartWidth = chart.chartWidth,
19880 chartHeight = chart.chartHeight,
19881 optionsChart = chart.options.chart,
19882 spacing = chart.spacing,
19883 clipOffset = chart.clipOffset,
19884 clipX,
19885 clipY,
19886 plotLeft,
19887 plotTop,
19888 plotWidth,
19889 plotHeight,
19890 plotBorderWidth;
19891
19892 /**
19893 * The current left position of the plot area in pixels.
19894 *
19895 * @name plotLeft
19896 * @memberOf Chart
19897 * @type {Number}
19898 */
19899 chart.plotLeft = plotLeft = Math.round(chart.plotLeft);
19900
19901 /**
19902 * The current top position of the plot area in pixels.
19903 *
19904 * @name plotTop
19905 * @memberOf Chart
19906 * @type {Number}
19907 */
19908 chart.plotTop = plotTop = Math.round(chart.plotTop);
19909
19910 /**
19911 * The current width of the plot area in pixels.
19912 *
19913 * @name plotWidth
19914 * @memberOf Chart
19915 * @type {Number}
19916 */
19917 chart.plotWidth = plotWidth = Math.max(
19918 0,
19919 Math.round(chartWidth - plotLeft - chart.marginRight)
19920 );
19921
19922 /**
19923 * The current height of the plot area in pixels.
19924 *
19925 * @name plotHeight
19926 * @memberOf Chart
19927 * @type {Number}
19928 */
19929 chart.plotHeight = plotHeight = Math.max(
19930 0,
19931 Math.round(chartHeight - plotTop - chart.marginBottom)
19932 );
19933
19934 chart.plotSizeX = inverted ? plotHeight : plotWidth;
19935 chart.plotSizeY = inverted ? plotWidth : plotHeight;
19936
19937 chart.plotBorderWidth = optionsChart.plotBorderWidth || 0;
19938
19939 // Set boxes used for alignment
19940 chart.spacingBox = renderer.spacingBox = {
19941 x: spacing[3],
19942 y: spacing[0],
19943 width: chartWidth - spacing[3] - spacing[1],
19944 height: chartHeight - spacing[0] - spacing[2]
19945 };
19946 chart.plotBox = renderer.plotBox = {
19947 x: plotLeft,
19948 y: plotTop,
19949 width: plotWidth,
19950 height: plotHeight
19951 };
19952
19953 plotBorderWidth = 2 * Math.floor(chart.plotBorderWidth / 2);
19954 clipX = Math.ceil(Math.max(plotBorderWidth, clipOffset[3]) / 2);
19955 clipY = Math.ceil(Math.max(plotBorderWidth, clipOffset[0]) / 2);
19956 chart.clipBox = {
19957 x: clipX,
19958 y: clipY,
19959 width: Math.floor(
19960 chart.plotSizeX -
19961 Math.max(plotBorderWidth, clipOffset[1]) / 2 -
19962 clipX
19963 ),
19964 height: Math.max(
19965 0,
19966 Math.floor(
19967 chart.plotSizeY -
19968 Math.max(plotBorderWidth, clipOffset[2]) / 2 -
19969 clipY
19970 )
19971 )
19972 };
19973
19974 if (!skipAxes) {
19975 each(chart.axes, function(axis) {
19976 axis.setAxisSize();
19977 axis.setAxisTranslation();
19978 });
19979 }
19980 },
19981
19982 /**
19983 * Initial margins before auto size margins are applied.
19984 *
19985 * @private
19986 */
19987 resetMargins: function() {
19988 var chart = this,
19989 chartOptions = chart.options.chart;
19990
19991 // Create margin and spacing array
19992 each(['margin', 'spacing'], function splashArrays(target) {
19993 var value = chartOptions[target],
19994 values = isObject(value) ? value : [value, value, value, value];
19995
19996 each(['Top', 'Right', 'Bottom', 'Left'], function(sideName, side) {
19997 chart[target][side] = pick(
19998 chartOptions[target + sideName],
19999 values[side]
20000 );
20001 });
20002 });
20003
20004 // Set margin names like chart.plotTop, chart.plotLeft,
20005 // chart.marginRight, chart.marginBottom.
20006 each(marginNames, function(m, side) {
20007 chart[m] = pick(chart.margin[side], chart.spacing[side]);
20008 });
20009 chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
20010 chart.clipOffset = [0, 0, 0, 0];
20011 },
20012
20013 /**
20014 * Internal function to draw or redraw the borders and backgrounds for chart
20015 * and plot area.
20016 *
20017 * @private
20018 */
20019 drawChartBox: function() {
20020 var chart = this,
20021 optionsChart = chart.options.chart,
20022 renderer = chart.renderer,
20023 chartWidth = chart.chartWidth,
20024 chartHeight = chart.chartHeight,
20025 chartBackground = chart.chartBackground,
20026 plotBackground = chart.plotBackground,
20027 plotBorder = chart.plotBorder,
20028 chartBorderWidth,
20029
20030 mgn,
20031 bgAttr,
20032 plotLeft = chart.plotLeft,
20033 plotTop = chart.plotTop,
20034 plotWidth = chart.plotWidth,
20035 plotHeight = chart.plotHeight,
20036 plotBox = chart.plotBox,
20037 clipRect = chart.clipRect,
20038 clipBox = chart.clipBox,
20039 verb = 'animate';
20040
20041 // Chart area
20042 if (!chartBackground) {
20043 chart.chartBackground = chartBackground = renderer.rect()
20044 .addClass('highcharts-background')
20045 .add();
20046 verb = 'attr';
20047 }
20048
20049
20050 chartBorderWidth = mgn = chartBackground.strokeWidth();
20051
20052 chartBackground[verb]({
20053 x: mgn / 2,
20054 y: mgn / 2,
20055 width: chartWidth - mgn - chartBorderWidth % 2,
20056 height: chartHeight - mgn - chartBorderWidth % 2,
20057 r: optionsChart.borderRadius
20058 });
20059
20060 // Plot background
20061 verb = 'animate';
20062 if (!plotBackground) {
20063 verb = 'attr';
20064 chart.plotBackground = plotBackground = renderer.rect()
20065 .addClass('highcharts-plot-background')
20066 .add();
20067 }
20068 plotBackground[verb](plotBox);
20069
20070
20071
20072 // Plot clip
20073 if (!clipRect) {
20074 chart.clipRect = renderer.clipRect(clipBox);
20075 } else {
20076 clipRect.animate({
20077 width: clipBox.width,
20078 height: clipBox.height
20079 });
20080 }
20081
20082 // Plot area border
20083 verb = 'animate';
20084 if (!plotBorder) {
20085 verb = 'attr';
20086 chart.plotBorder = plotBorder = renderer.rect()
20087 .addClass('highcharts-plot-border')
20088 .attr({
20089 zIndex: 1 // Above the grid
20090 })
20091 .add();
20092 }
20093
20094
20095
20096 plotBorder[verb](plotBorder.crisp({
20097 x: plotLeft,
20098 y: plotTop,
20099 width: plotWidth,
20100 height: plotHeight
20101 }, -plotBorder.strokeWidth())); // #3282 plotBorder should be negative;
20102
20103 // reset
20104 chart.isDirtyBox = false;
20105 },
20106
20107 /**
20108 * Detect whether a certain chart property is needed based on inspecting its
20109 * options and series. This mainly applies to the chart.inverted property,
20110 * and in extensions to the chart.angular and chart.polar properties.
20111 *
20112 * @private
20113 */
20114 propFromSeries: function() {
20115 var chart = this,
20116 optionsChart = chart.options.chart,
20117 klass,
20118 seriesOptions = chart.options.series,
20119 i,
20120 value;
20121
20122
20123 each(['inverted', 'angular', 'polar'], function(key) {
20124
20125 // The default series type's class
20126 klass = seriesTypes[optionsChart.type ||
20127 optionsChart.defaultSeriesType];
20128
20129 // Get the value from available chart-wide properties
20130 value =
20131 optionsChart[key] || // It is set in the options
20132 (klass && klass.prototype[key]); // The default series class
20133 // requires it
20134
20135 // 4. Check if any the chart's series require it
20136 i = seriesOptions && seriesOptions.length;
20137 while (!value && i--) {
20138 klass = seriesTypes[seriesOptions[i].type];
20139 if (klass && klass.prototype[key]) {
20140 value = true;
20141 }
20142 }
20143
20144 // Set the chart property
20145 chart[key] = value;
20146 });
20147
20148 },
20149
20150 /**
20151 * Internal function to link two or more series together, based on the
20152 * `linkedTo` option. This is done from `Chart.render`, and after
20153 * `Chart.addSeries` and `Series.remove`.
20154 *
20155 * @private
20156 */
20157 linkSeries: function() {
20158 var chart = this,
20159 chartSeries = chart.series;
20160
20161 // Reset links
20162 each(chartSeries, function(series) {
20163 series.linkedSeries.length = 0;
20164 });
20165
20166 // Apply new links
20167 each(chartSeries, function(series) {
20168 var linkedTo = series.options.linkedTo;
20169 if (isString(linkedTo)) {
20170 if (linkedTo === ':previous') {
20171 linkedTo = chart.series[series.index - 1];
20172 } else {
20173 linkedTo = chart.get(linkedTo);
20174 }
20175 // #3341 avoid mutual linking
20176 if (linkedTo && linkedTo.linkedParent !== series) {
20177 linkedTo.linkedSeries.push(series);
20178 series.linkedParent = linkedTo;
20179 series.visible = pick(
20180 series.options.visible,
20181 linkedTo.options.visible,
20182 series.visible
20183 ); // #3879
20184 }
20185 }
20186 });
20187 },
20188
20189 /**
20190 * Render series for the chart.
20191 *
20192 * @private
20193 */
20194 renderSeries: function() {
20195 each(this.series, function(serie) {
20196 serie.translate();
20197 serie.render();
20198 });
20199 },
20200
20201 /**
20202 * Render labels for the chart.
20203 *
20204 * @private
20205 */
20206 renderLabels: function() {
20207 var chart = this,
20208 labels = chart.options.labels;
20209 if (labels.items) {
20210 each(labels.items, function(label) {
20211 var style = extend(labels.style, label.style),
20212 x = pInt(style.left) + chart.plotLeft,
20213 y = pInt(style.top) + chart.plotTop + 12;
20214
20215 // delete to prevent rewriting in IE
20216 delete style.left;
20217 delete style.top;
20218
20219 chart.renderer.text(
20220 label.html,
20221 x,
20222 y
20223 )
20224 .attr({
20225 zIndex: 2
20226 })
20227 .css(style)
20228 .add();
20229
20230 });
20231 }
20232 },
20233
20234 /**
20235 * Render all graphics for the chart. Runs internally on initialization.
20236 *
20237 * @private
20238 */
20239 render: function() {
20240 var chart = this,
20241 axes = chart.axes,
20242 renderer = chart.renderer,
20243 options = chart.options,
20244 tempWidth,
20245 tempHeight,
20246 redoHorizontal,
20247 redoVertical;
20248
20249 // Title
20250 chart.setTitle();
20251
20252
20253 // Legend
20254 chart.legend = new Legend(chart, options.legend);
20255
20256 // Get stacks
20257 if (chart.getStacks) {
20258 chart.getStacks();
20259 }
20260
20261 // Get chart margins
20262 chart.getMargins(true);
20263 chart.setChartSize();
20264
20265 // Record preliminary dimensions for later comparison
20266 tempWidth = chart.plotWidth;
20267 // 21 is the most common correction for X axis labels
20268 // use Math.max to prevent negative plotHeight
20269 tempHeight = chart.plotHeight = Math.max(chart.plotHeight - 21, 0);
20270
20271 // Get margins by pre-rendering axes
20272 each(axes, function(axis) {
20273 axis.setScale();
20274 });
20275 chart.getAxisMargins();
20276
20277 // If the plot area size has changed significantly, calculate tick positions again
20278 redoHorizontal = tempWidth / chart.plotWidth > 1.1;
20279 redoVertical = tempHeight / chart.plotHeight > 1.05; // Height is more sensitive
20280
20281 if (redoHorizontal || redoVertical) {
20282
20283 each(axes, function(axis) {
20284 if ((axis.horiz && redoHorizontal) || (!axis.horiz && redoVertical)) {
20285 axis.setTickInterval(true); // update to reflect the new margins
20286 }
20287 });
20288 chart.getMargins(); // second pass to check for new labels
20289 }
20290
20291 // Draw the borders and backgrounds
20292 chart.drawChartBox();
20293
20294
20295 // Axes
20296 if (chart.hasCartesianSeries) {
20297 each(axes, function(axis) {
20298 if (axis.visible) {
20299 axis.render();
20300 }
20301 });
20302 }
20303
20304 // The series
20305 if (!chart.seriesGroup) {
20306 chart.seriesGroup = renderer.g('series-group')
20307 .attr({
20308 zIndex: 3
20309 })
20310 .add();
20311 }
20312 chart.renderSeries();
20313
20314 // Labels
20315 chart.renderLabels();
20316
20317 // Credits
20318 chart.addCredits();
20319
20320 // Handle responsiveness
20321 if (chart.setResponsive) {
20322 chart.setResponsive();
20323 }
20324
20325 // Set flag
20326 chart.hasRendered = true;
20327
20328 },
20329
20330 /**
20331 * Set a new credits label for the chart.
20332 *
20333 * @param {CreditOptions} options
20334 * A configuration object for the new credits.
20335 * @sample highcharts/credits/credits-update/ Add and update credits
20336 */
20337 addCredits: function(credits) {
20338 var chart = this;
20339
20340 credits = merge(true, this.options.credits, credits);
20341 if (credits.enabled && !this.credits) {
20342
20343 /**
20344 * The chart's credits label. The label has an `update` method that
20345 * allows setting new options as per the {@link
20346 * https://api.highcharts.com/highcharts/credits|
20347 * credits options set}.
20348 *
20349 * @memberof Highcharts.Chart
20350 * @name credits
20351 * @type {Highcharts.SVGElement}
20352 */
20353 this.credits = this.renderer.text(
20354 credits.text + (this.mapCredits || ''),
20355 0,
20356 0
20357 )
20358 .addClass('highcharts-credits')
20359 .on('click', function() {
20360 if (credits.href) {
20361 win.location.href = credits.href;
20362 }
20363 })
20364 .attr({
20365 align: credits.position.align,
20366 zIndex: 8
20367 })
20368
20369 .add()
20370 .align(credits.position);
20371
20372 // Dynamically update
20373 this.credits.update = function(options) {
20374 chart.credits = chart.credits.destroy();
20375 chart.addCredits(options);
20376 };
20377 }
20378 },
20379
20380 /**
20381 * Remove the chart and purge memory. This method is called internally
20382 * before adding a second chart into the same container, as well as on
20383 * window unload to prevent leaks.
20384 *
20385 * @sample highcharts/members/chart-destroy/
20386 * Destroy the chart from a button
20387 * @sample stock/members/chart-destroy/
20388 * Destroy with Highstock
20389 */
20390 destroy: function() {
20391 var chart = this,
20392 axes = chart.axes,
20393 series = chart.series,
20394 container = chart.container,
20395 i,
20396 parentNode = container && container.parentNode;
20397
20398 // fire the chart.destoy event
20399 fireEvent(chart, 'destroy');
20400
20401 // Delete the chart from charts lookup array
20402 if (chart.renderer.forExport) {
20403 H.erase(charts, chart); // #6569
20404 } else {
20405 charts[chart.index] = undefined;
20406 }
20407 H.chartCount--;
20408 chart.renderTo.removeAttribute('data-highcharts-chart');
20409
20410 // remove events
20411 removeEvent(chart);
20412
20413 // ==== Destroy collections:
20414 // Destroy axes
20415 i = axes.length;
20416 while (i--) {
20417 axes[i] = axes[i].destroy();
20418 }
20419
20420 // Destroy scroller & scroller series before destroying base series
20421 if (this.scroller && this.scroller.destroy) {
20422 this.scroller.destroy();
20423 }
20424
20425 // Destroy each series
20426 i = series.length;
20427 while (i--) {
20428 series[i] = series[i].destroy();
20429 }
20430
20431 // ==== Destroy chart properties:
20432 each([
20433 'title', 'subtitle', 'chartBackground', 'plotBackground',
20434 'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits',
20435 'pointer', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip',
20436 'renderer'
20437 ], function(name) {
20438 var prop = chart[name];
20439
20440 if (prop && prop.destroy) {
20441 chart[name] = prop.destroy();
20442 }
20443 });
20444
20445 // remove container and all SVG
20446 if (container) { // can break in IE when destroyed before finished loading
20447 container.innerHTML = '';
20448 removeEvent(container);
20449 if (parentNode) {
20450 discardElement(container);
20451 }
20452
20453 }
20454
20455 // clean it all up
20456 objectEach(chart, function(val, key) {
20457 delete chart[key];
20458 });
20459
20460 },
20461
20462
20463 /**
20464 * VML namespaces can't be added until after complete. Listening
20465 * for Perini's doScroll hack is not enough.
20466 *
20467 * @private
20468 */
20469 isReadyToRender: function() {
20470 var chart = this;
20471
20472 // Note: win == win.top is required
20473 if ((!svg && (win == win.top && doc.readyState !== 'complete'))) { // eslint-disable-line eqeqeq
20474 doc.attachEvent('onreadystatechange', function() {
20475 doc.detachEvent('onreadystatechange', chart.firstRender);
20476 if (doc.readyState === 'complete') {
20477 chart.firstRender();
20478 }
20479 });
20480 return false;
20481 }
20482 return true;
20483 },
20484
20485 /**
20486 * Prepare for first rendering after all data are loaded.
20487 *
20488 * @private
20489 */
20490 firstRender: function() {
20491 var chart = this,
20492 options = chart.options;
20493
20494 // Check whether the chart is ready to render
20495 if (!chart.isReadyToRender()) {
20496 return;
20497 }
20498
20499 // Create the container
20500 chart.getContainer();
20501
20502 // Run an early event after the container and renderer are established
20503 fireEvent(chart, 'init');
20504
20505
20506 chart.resetMargins();
20507 chart.setChartSize();
20508
20509 // Set the common chart properties (mainly invert) from the given series
20510 chart.propFromSeries();
20511
20512 // get axes
20513 chart.getAxes();
20514
20515 // Initialize the series
20516 each(options.series || [], function(serieOptions) {
20517 chart.initSeries(serieOptions);
20518 });
20519
20520 chart.linkSeries();
20521
20522 // Run an event after axes and series are initialized, but before render. At this stage,
20523 // the series data is indexed and cached in the xData and yData arrays, so we can access
20524 // those before rendering. Used in Highstock.
20525 fireEvent(chart, 'beforeRender');
20526
20527 // depends on inverted and on margins being set
20528 if (Pointer) {
20529
20530 /**
20531 * The Pointer that keeps track of mouse and touch interaction.
20532 *
20533 * @memberof Chart
20534 * @name pointer
20535 * @type Pointer
20536 */
20537 chart.pointer = new Pointer(chart, options);
20538 }
20539
20540 chart.render();
20541
20542 // Fire the load event if there are no external images
20543 if (!chart.renderer.imgCount && chart.onload) {
20544 chart.onload();
20545 }
20546
20547 // If the chart was rendered outside the top container, put it back in (#3679)
20548 chart.temporaryDisplay(true);
20549
20550 },
20551
20552 /**
20553 * Internal function that runs on chart load, async if any images are loaded
20554 * in the chart. Runs the callbacks and triggers the `load` and `render`
20555 * events.
20556 *
20557 * @private
20558 */
20559 onload: function() {
20560
20561 // Run callbacks
20562 each([this.callback].concat(this.callbacks), function(fn) {
20563 if (fn && this.index !== undefined) { // Chart destroyed in its own callback (#3600)
20564 fn.apply(this, [this]);
20565 }
20566 }, this);
20567
20568 fireEvent(this, 'load');
20569 fireEvent(this, 'render');
20570
20571
20572 // Set up auto resize, check for not destroyed (#6068)
20573 if (defined(this.index) && this.options.chart.reflow !== false) {
20574 this.initReflow();
20575 }
20576
20577 // Don't run again
20578 this.onload = null;
20579 }
20580
20581 }); // end Chart
20582
20583 }(Highcharts));
20584 (function(Highcharts) {
20585 /**
20586 * (c) 2010-2017 Torstein Honsi
20587 *
20588 * License: www.highcharts.com/license
20589 */
20590 var Point,
20591 H = Highcharts,
20592
20593 each = H.each,
20594 extend = H.extend,
20595 erase = H.erase,
20596 fireEvent = H.fireEvent,
20597 format = H.format,
20598 isArray = H.isArray,
20599 isNumber = H.isNumber,
20600 pick = H.pick,
20601 removeEvent = H.removeEvent;
20602
20603 /**
20604 * The Point object. The point objects are generated from the `series.data`
20605 * configuration objects or raw numbers. They can be accessed from the
20606 * `Series.points` array. Other ways to instaniate points are through {@link
20607 * Highcharts.Series#addPoint} or {@link Highcharts.Series#setData}.
20608 *
20609 * @class
20610 */
20611
20612 Highcharts.Point = Point = function() {};
20613 Highcharts.Point.prototype = {
20614
20615 /**
20616 * Initialize the point. Called internally based on the `series.data`
20617 * option.
20618 * @param {Series} series
20619 * The series object containing this point.
20620 * @param {Number|Array|Object} options
20621 * The data in either number, array or object format.
20622 * @param {Number} x Optionally, the X value of the point.
20623 * @return {Point} The Point instance.
20624 */
20625 init: function(series, options, x) {
20626
20627 var point = this,
20628 colors,
20629 colorCount = series.chart.options.chart.colorCount,
20630 colorIndex;
20631
20632 /**
20633 * The series object associated with the point.
20634 *
20635 * @name series
20636 * @memberof Highcharts.Point
20637 * @type Highcharts.Series
20638 */
20639 point.series = series;
20640
20641
20642 point.applyOptions(options, x);
20643
20644 if (series.options.colorByPoint) {
20645
20646 colorIndex = series.colorCounter;
20647 series.colorCounter++;
20648 // loop back to zero
20649 if (series.colorCounter === colorCount) {
20650 series.colorCounter = 0;
20651 }
20652 } else {
20653 colorIndex = series.colorIndex;
20654 }
20655
20656 /**
20657 * The point's current color index, used in styled mode instead of
20658 * `color`. The color index is inserted in class names used for styling.
20659 * @name colorIndex
20660 * @memberof Highcharts.Point
20661 * @type {Number}
20662 */
20663 point.colorIndex = pick(point.colorIndex, colorIndex);
20664
20665 series.chart.pointCount++;
20666 return point;
20667 },
20668 /**
20669 * Apply the options containing the x and y data and possible some extra
20670 * properties. Called on point init or from point.update.
20671 *
20672 * @private
20673 * @param {Object} options The point options as defined in series.data.
20674 * @param {Number} x Optionally, the X value.
20675 * @returns {Object} The Point instance.
20676 */
20677 applyOptions: function(options, x) {
20678 var point = this,
20679 series = point.series,
20680 pointValKey = series.options.pointValKey || series.pointValKey;
20681
20682 options = Point.prototype.optionsToObject.call(this, options);
20683
20684 // copy options directly to point
20685 extend(point, options);
20686 point.options = point.options ? extend(point.options, options) : options;
20687
20688 // Since options are copied into the Point instance, some accidental options must be shielded (#5681)
20689 if (options.group) {
20690 delete point.group;
20691 }
20692
20693 // For higher dimension series types. For instance, for ranges, point.y is mapped to point.low.
20694 if (pointValKey) {
20695 point.y = point[pointValKey];
20696 }
20697 point.isNull = pick(
20698 point.isValid && !point.isValid(),
20699 point.x === null || !isNumber(point.y, true)
20700 ); // #3571, check for NaN
20701
20702 // The point is initially selected by options (#5777)
20703 if (point.selected) {
20704 point.state = 'select';
20705 }
20706
20707 // If no x is set by now, get auto incremented value. All points must have an
20708 // x value, however the y value can be null to create a gap in the series
20709 if ('name' in point && x === undefined && series.xAxis && series.xAxis.hasNames) {
20710 point.x = series.xAxis.nameToX(point);
20711 }
20712 if (point.x === undefined && series) {
20713 if (x === undefined) {
20714 point.x = series.autoIncrement(point);
20715 } else {
20716 point.x = x;
20717 }
20718 }
20719
20720 return point;
20721 },
20722
20723 /**
20724 * Transform number or array configs into objects. Used internally to unify
20725 * the different configuration formats for points. For example, a simple
20726 * number `10` in a line series will be transformed to `{ y: 10 }`, and an
20727 * array config like `[1, 10]` in a scatter series will be transformed to
20728 * `{ x: 1, y: 10 }`.
20729 *
20730 * @param {Number|Array|Object} options
20731 * The input options
20732 * @return {Object} Transformed options.
20733 */
20734 optionsToObject: function(options) {
20735 var ret = {},
20736 series = this.series,
20737 keys = series.options.keys,
20738 pointArrayMap = keys || series.pointArrayMap || ['y'],
20739 valueCount = pointArrayMap.length,
20740 firstItemType,
20741 i = 0,
20742 j = 0;
20743
20744 if (isNumber(options) || options === null) {
20745 ret[pointArrayMap[0]] = options;
20746
20747 } else if (isArray(options)) {
20748 // with leading x value
20749 if (!keys && options.length > valueCount) {
20750 firstItemType = typeof options[0];
20751 if (firstItemType === 'string') {
20752 ret.name = options[0];
20753 } else if (firstItemType === 'number') {
20754 ret.x = options[0];
20755 }
20756 i++;
20757 }
20758 while (j < valueCount) {
20759 if (!keys || options[i] !== undefined) { // Skip undefined positions for keys
20760 ret[pointArrayMap[j]] = options[i];
20761 }
20762 i++;
20763 j++;
20764 }
20765 } else if (typeof options === 'object') {
20766 ret = options;
20767
20768 // This is the fastest way to detect if there are individual point dataLabels that need
20769 // to be considered in drawDataLabels. These can only occur in object configs.
20770 if (options.dataLabels) {
20771 series._hasPointLabels = true;
20772 }
20773
20774 // Same approach as above for markers
20775 if (options.marker) {
20776 series._hasPointMarkers = true;
20777 }
20778 }
20779 return ret;
20780 },
20781
20782 /**
20783 * Get the CSS class names for individual points. Used internally where the
20784 * returned value is set on every point.
20785 *
20786 * @returns {String} The class names.
20787 */
20788 getClassName: function() {
20789 return 'highcharts-point' +
20790 (this.selected ? ' highcharts-point-select' : '') +
20791 (this.negative ? ' highcharts-negative' : '') +
20792 (this.isNull ? ' highcharts-null-point' : '') +
20793 (this.colorIndex !== undefined ? ' highcharts-color-' +
20794 this.colorIndex : '') +
20795 (this.options.className ? ' ' + this.options.className : '') +
20796 (this.zone && this.zone.className ? ' ' +
20797 this.zone.className.replace('highcharts-negative', '') : '');
20798 },
20799
20800 /**
20801 * In a series with `zones`, return the zone that the point belongs to.
20802 *
20803 * @return {Object}
20804 * The zone item.
20805 */
20806 getZone: function() {
20807 var series = this.series,
20808 zones = series.zones,
20809 zoneAxis = series.zoneAxis || 'y',
20810 i = 0,
20811 zone;
20812
20813 zone = zones[i];
20814 while (this[zoneAxis] >= zone.value) {
20815 zone = zones[++i];
20816 }
20817
20818 if (zone && zone.color && !this.options.color) {
20819 this.color = zone.color;
20820 }
20821
20822 return zone;
20823 },
20824
20825 /**
20826 * Destroy a point to clear memory. Its reference still stays in
20827 * `series.data`.
20828 *
20829 * @private
20830 */
20831 destroy: function() {
20832 var point = this,
20833 series = point.series,
20834 chart = series.chart,
20835 hoverPoints = chart.hoverPoints,
20836 prop;
20837
20838 chart.pointCount--;
20839
20840 if (hoverPoints) {
20841 point.setState();
20842 erase(hoverPoints, point);
20843 if (!hoverPoints.length) {
20844 chart.hoverPoints = null;
20845 }
20846
20847 }
20848 if (point === chart.hoverPoint) {
20849 point.onMouseOut();
20850 }
20851
20852 // remove all events
20853 if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive
20854 removeEvent(point);
20855 point.destroyElements();
20856 }
20857
20858 if (point.legendItem) { // pies have legend items
20859 chart.legend.destroyItem(point);
20860 }
20861
20862 for (prop in point) {
20863 point[prop] = null;
20864 }
20865
20866
20867 },
20868
20869 /**
20870 * Destroy SVG elements associated with the point.
20871 *
20872 * @private
20873 */
20874 destroyElements: function() {
20875 var point = this,
20876 props = ['graphic', 'dataLabel', 'dataLabelUpper', 'connector', 'shadowGroup'],
20877 prop,
20878 i = 6;
20879 while (i--) {
20880 prop = props[i];
20881 if (point[prop]) {
20882 point[prop] = point[prop].destroy();
20883 }
20884 }
20885 },
20886
20887 /**
20888 * Return the configuration hash needed for the data label and tooltip
20889 * formatters.
20890 *
20891 * @returns {Object}
20892 * Abstract object used in formatters and formats.
20893 */
20894 getLabelConfig: function() {
20895 return {
20896 x: this.category,
20897 y: this.y,
20898 color: this.color,
20899 colorIndex: this.colorIndex,
20900 key: this.name || this.category,
20901 series: this.series,
20902 point: this,
20903 percentage: this.percentage,
20904 total: this.total || this.stackTotal
20905 };
20906 },
20907
20908 /**
20909 * Extendable method for formatting each point's tooltip line.
20910 *
20911 * @param {String} pointFormat
20912 * The point format.
20913 * @return {String}
20914 * A string to be concatenated in to the common tooltip text.
20915 */
20916 tooltipFormatter: function(pointFormat) {
20917
20918 // Insert options for valueDecimals, valuePrefix, and valueSuffix
20919 var series = this.series,
20920 seriesTooltipOptions = series.tooltipOptions,
20921 valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''),
20922 valuePrefix = seriesTooltipOptions.valuePrefix || '',
20923 valueSuffix = seriesTooltipOptions.valueSuffix || '';
20924
20925 // Loop over the point array map and replace unformatted values with sprintf formatting markup
20926 each(series.pointArrayMap || ['y'], function(key) {
20927 key = '{point.' + key; // without the closing bracket
20928 if (valuePrefix || valueSuffix) {
20929 pointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix);
20930 }
20931 pointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}');
20932 });
20933
20934 return format(pointFormat, {
20935 point: this,
20936 series: this.series
20937 });
20938 },
20939
20940 /**
20941 * Fire an event on the Point object.
20942 *
20943 * @private
20944 * @param {String} eventType
20945 * @param {Object} eventArgs Additional event arguments
20946 * @param {Function} defaultFunction Default event handler
20947 */
20948 firePointEvent: function(eventType, eventArgs, defaultFunction) {
20949 var point = this,
20950 series = this.series,
20951 seriesOptions = series.options;
20952
20953 // load event handlers on demand to save time on mouseover/out
20954 if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {
20955 this.importEvents();
20956 }
20957
20958 // add default handler if in selection mode
20959 if (eventType === 'click' && seriesOptions.allowPointSelect) {
20960 defaultFunction = function(event) {
20961 // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
20962 if (point.select) { // Could be destroyed by prior event handlers (#2911)
20963 point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
20964 }
20965 };
20966 }
20967
20968 fireEvent(this, eventType, eventArgs, defaultFunction);
20969 },
20970
20971 /**
20972 * For certain series types, like pie charts, where individual points can
20973 * be shown or hidden.
20974 *
20975 * @name visible
20976 * @memberOf Highcharts.Point
20977 * @type {Boolean}
20978 */
20979 visible: true
20980 };
20981
20982 /**
20983 * For categorized axes this property holds the category name for the
20984 * point. For other axes it holds the X value.
20985 *
20986 * @name category
20987 * @memberOf Highcharts.Point
20988 * @type {String|Number}
20989 */
20990
20991 /**
20992 * The name of the point. The name can be given as the first position of the
20993 * point configuration array, or as a `name` property in the configuration:
20994 *
20995 * @example
20996 * // Array config
20997 * data: [
20998 * ['John', 1],
20999 * ['Jane', 2]
21000 * ]
21001 *
21002 * // Object config
21003 * data: [{
21004 * name: 'John',
21005 * y: 1
21006 * }, {
21007 * name: 'Jane',
21008 * y: 2
21009 * }]
21010 *
21011 * @name name
21012 * @memberOf Highcharts.Point
21013 * @type {String}
21014 */
21015
21016
21017 /**
21018 * The percentage for points in a stacked series or pies.
21019 *
21020 * @name percentage
21021 * @memberOf Highcharts.Point
21022 * @type {Number}
21023 */
21024
21025 /**
21026 * The total of values in either a stack for stacked series, or a pie in a pie
21027 * series.
21028 *
21029 * @name total
21030 * @memberOf Highcharts.Point
21031 * @type {Number}
21032 */
21033
21034 /**
21035 * The x value of the point.
21036 *
21037 * @name x
21038 * @memberOf Highcharts.Point
21039 * @type {Number}
21040 */
21041
21042 /**
21043 * The y value of the point.
21044 *
21045 * @name y
21046 * @memberOf Highcharts.Point
21047 * @type {Number}
21048 */
21049
21050 }(Highcharts));
21051 (function(H) {
21052 /**
21053 * (c) 2010-2017 Torstein Honsi
21054 *
21055 * License: www.highcharts.com/license
21056 */
21057 var addEvent = H.addEvent,
21058 animObject = H.animObject,
21059 arrayMax = H.arrayMax,
21060 arrayMin = H.arrayMin,
21061 correctFloat = H.correctFloat,
21062 Date = H.Date,
21063 defaultOptions = H.defaultOptions,
21064 defaultPlotOptions = H.defaultPlotOptions,
21065 defined = H.defined,
21066 each = H.each,
21067 erase = H.erase,
21068 extend = H.extend,
21069 fireEvent = H.fireEvent,
21070 grep = H.grep,
21071 isArray = H.isArray,
21072 isNumber = H.isNumber,
21073 isString = H.isString,
21074 LegendSymbolMixin = H.LegendSymbolMixin, // @todo add as a requirement
21075 merge = H.merge,
21076 objectEach = H.objectEach,
21077 pick = H.pick,
21078 Point = H.Point, // @todo add as a requirement
21079 removeEvent = H.removeEvent,
21080 splat = H.splat,
21081 SVGElement = H.SVGElement,
21082 syncTimeout = H.syncTimeout,
21083 win = H.win;
21084
21085 /**
21086 * This is the base series prototype that all other series types inherit from.
21087 * A new series is initialized either through the
21088 * {@link https://api.highcharts.com/highcharts/series|series} option structure,
21089 * or after the chart is initialized, through
21090 * {@link Highcharts.Chart#addSeries}.
21091 *
21092 * The object can be accessed in a number of ways. All series and point event
21093 * handlers give a reference to the `series` object. The chart object has a
21094 * {@link Highcharts.Chart.series|series} property that is a collection of all
21095 * the chart's series. The point objects and axis objects also have the same
21096 * reference.
21097 *
21098 * Another way to reference the series programmatically is by `id`. Add an id
21099 * in the series configuration options, and get the series object by {@link
21100 * Highcharts.Chart#get}.
21101 *
21102 * Configuration options for the series are given in three levels. Options for
21103 * all series in a chart are given in the
21104 * {@link https://api.highcharts.com/highcharts/plotOptions.series|
21105 * plotOptions.series} object. Then options for all series of a specific type
21106 * are given in the plotOptions of that type, for example `plotOptions.line`.
21107 * Next, options for one single series are given in the series array, or as
21108 * arguements to `chart.addSeries`.
21109 *
21110 * The data in the series is stored in various arrays.
21111 *
21112 * - First, `series.options.data` contains all the original config options for
21113 * each point whether added by options or methods like `series.addPoint`.
21114 * - Next, `series.data` contains those values converted to points, but in case
21115 * the series data length exceeds the `cropThreshold`, or if the data is
21116 * grouped, `series.data` doesn't contain all the points. It only contains the
21117 * points that have been created on demand.
21118 * - Then there's `series.points` that contains all currently visible point
21119 * objects. In case of cropping, the cropped-away points are not part of this
21120 * array. The `series.points` array starts at `series.cropStart` compared to
21121 * `series.data` and `series.options.data`. If however the series data is
21122 * grouped, these can't be correlated one to one.
21123 * - `series.xData` and `series.processedXData` contain clean x values,
21124 * equivalent to `series.data` and `series.points`.
21125 * - `series.yData` and `series.processedYData` contain clean y values,
21126 * equivalent to `series.data` and `series.points`.
21127 *
21128 * @class Highcharts.Series
21129 * @param {Highcharts.Chart} chart
21130 * The chart instance.
21131 * @param {Options.plotOptions.series} options
21132 * The series options.
21133 *
21134 */
21135
21136 /**
21137 * General options for all series types.
21138 * @optionparent plotOptions.series
21139 */
21140 H.Series = H.seriesType('line', null, { // base series options
21141
21142
21143 /**
21144 * For some series, there is a limit that shuts down initial animation
21145 * by default when the total number of points in the chart is too high.
21146 * For example, for a column chart and its derivatives, animation doesn't
21147 * run if there is more than 250 points totally. To disable this cap, set
21148 * `animationLimit` to `Infinity`.
21149 *
21150 * @type {Number}
21151 * @apioption plotOptions.series.animationLimit
21152 */
21153
21154 /**
21155 * Allow this series' points to be selected by clicking on the graphic
21156 * (columns, point markers, pie slices, map areas etc).
21157 *
21158 * @see [Chart#getSelectedPoints]
21159 * (../class-reference/Highcharts.Chart#getSelectedPoints).
21160 *
21161 * @type {Boolean}
21162 * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-line/
21163 * Line
21164 * @sample {highcharts}
21165 * highcharts/plotoptions/series-allowpointselect-column/
21166 * Column
21167 * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-pie/
21168 * Pie
21169 * @sample {highmaps} maps/plotoptions/series-allowpointselect/
21170 * Map area
21171 * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/
21172 * Map bubble
21173 * @default false
21174 * @since 1.2.0
21175 */
21176 allowPointSelect: false,
21177
21178
21179
21180 /**
21181 * If true, a checkbox is displayed next to the legend item to allow
21182 * selecting the series. The state of the checkbox is determined by
21183 * the `selected` option.
21184 *
21185 * @productdesc {highmaps}
21186 * Note that if a `colorAxis` is defined, the color axis is represented in
21187 * the legend, not the series.
21188 *
21189 * @type {Boolean}
21190 * @sample {highcharts} highcharts/plotoptions/series-showcheckbox-true/
21191 * Show select box
21192 * @default false
21193 * @since 1.2.0
21194 */
21195 showCheckbox: false,
21196
21197
21198
21199 /**
21200 * Enable or disable the initial animation when a series is displayed.
21201 * The animation can also be set as a configuration object. Please
21202 * note that this option only applies to the initial animation of the
21203 * series itself. For other animations, see [chart.animation](#chart.
21204 * animation) and the animation parameter under the API methods. The
21205 * following properties are supported:
21206 *
21207 * <dl>
21208 *
21209 * <dt>duration</dt>
21210 *
21211 * <dd>The duration of the animation in milliseconds.</dd>
21212 *
21213 * <dt>easing</dt>
21214 *
21215 * <dd>A string reference to an easing function set on the `Math` object.
21216 * See the _Custom easing function_ demo below.</dd>
21217 *
21218 * </dl>
21219 *
21220 * Due to poor performance, animation is disabled in old IE browsers
21221 * for several chart types.
21222 *
21223 * @type {Boolean}
21224 * @sample {highcharts} highcharts/plotoptions/series-animation-disabled/
21225 * Animation disabled
21226 * @sample {highcharts} highcharts/plotoptions/series-animation-slower/
21227 * Slower animation
21228 * @sample {highcharts} highcharts/plotoptions/series-animation-easing/
21229 * Custom easing function
21230 * @sample {highstock} stock/plotoptions/animation-slower/
21231 * Slower animation
21232 * @sample {highstock} stock/plotoptions/animation-easing/
21233 * Custom easing function
21234 * @sample {highmaps} maps/plotoptions/series-animation-true/
21235 * Animation enabled on map series
21236 * @sample {highmaps} maps/plotoptions/mapbubble-animation-false/
21237 * Disabled on mapbubble series
21238 * @default {highcharts} true
21239 * @default {highstock} true
21240 * @default {highmaps} false
21241 */
21242 animation: {
21243 duration: 1000
21244 },
21245
21246 /**
21247 * A class name to apply to the series' graphical elements.
21248 *
21249 * @type {String}
21250 * @since 5.0.0
21251 * @apioption plotOptions.series.className
21252 */
21253
21254 /**
21255 * The main color of the series. In line type series it applies to the
21256 * line and the point markers unless otherwise specified. In bar type
21257 * series it applies to the bars unless a color is specified per point.
21258 * The default value is pulled from the `options.colors` array.
21259 *
21260 * In styled mode, the color can be defined by the
21261 * [colorIndex](#plotOptions.series.colorIndex) option. Also, the series
21262 * color can be set with the `.highcharts-series`, `.highcharts-color-{n}`,
21263 * `.highcharts-{type}-series` or `.highcharts-series-{n}` class, or
21264 * individual classes given by the `className` option.
21265 *
21266 * @productdesc {highmaps}
21267 * In maps, the series color is rarely used, as most choropleth maps use the
21268 * color to denote the value of each point. The series color can however be
21269 * used in a map with multiple series holding categorized data.
21270 *
21271 * @type {Color}
21272 * @sample {highcharts} highcharts/plotoptions/series-color-general/
21273 * General plot option
21274 * @sample {highcharts} highcharts/plotoptions/series-color-specific/
21275 * One specific series
21276 * @sample {highcharts} highcharts/plotoptions/series-color-area/
21277 * Area color
21278 * @sample {highmaps} maps/demo/category-map/
21279 * Category map by multiple series
21280 * @apioption plotOptions.series.color
21281 */
21282
21283 /**
21284 * Styled mode only. A specific color index to use for the series, so its
21285 * graphic representations are given the class name `highcharts-color-
21286 * {n}`.
21287 *
21288 * @type {Number}
21289 * @since 5.0.0
21290 * @apioption plotOptions.series.colorIndex
21291 */
21292
21293
21294 /**
21295 * Whether to connect a graph line across null points, or render a gap
21296 * between the two points on either side of the null.
21297 *
21298 * @type {Boolean}
21299 * @sample {highcharts} highcharts/plotoptions/series-connectnulls-false/
21300 * False by default
21301 * @sample {highcharts} highcharts/plotoptions/series-connectnulls-true/
21302 * True
21303 * @product highcharts highstock
21304 * @apioption plotOptions.series.connectNulls
21305 */
21306
21307
21308 /**
21309 * You can set the cursor to "pointer" if you have click events attached
21310 * to the series, to signal to the user that the points and lines can
21311 * be clicked.
21312 *
21313 * @validvalue [null, "default", "none", "help", "pointer", "crosshair"]
21314 * @type {String}
21315 * @see In styled mode, the series cursor can be set with the same classes
21316 * as listed under [series.color](#plotOptions.series.color).
21317 * @sample {highcharts} highcharts/plotoptions/series-cursor-line/
21318 * On line graph
21319 * @sample {highcharts} highcharts/plotoptions/series-cursor-column/
21320 * On columns
21321 * @sample {highcharts} highcharts/plotoptions/series-cursor-scatter/
21322 * On scatter markers
21323 * @sample {highstock} stock/plotoptions/cursor/
21324 * Pointer on a line graph
21325 * @sample {highmaps} maps/plotoptions/series-allowpointselect/
21326 * Map area
21327 * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/
21328 * Map bubble
21329 * @apioption plotOptions.series.cursor
21330 */
21331
21332
21333 /**
21334 * A name for the dash style to use for the graph, or for some series types
21335 * the outline of each shape. The value for the `dashStyle` include:
21336 *
21337 * * Solid
21338 * * ShortDash
21339 * * ShortDot
21340 * * ShortDashDot
21341 * * ShortDashDotDot
21342 * * Dot
21343 * * Dash
21344 * * LongDash
21345 * * DashDot
21346 * * LongDashDot
21347 * * LongDashDotDot
21348 *
21349 * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
21350 * "ShortDashDotDot", "Dot", "Dash" ,"LongDash", "DashDot",
21351 * "LongDashDot", "LongDashDotDot"]
21352 * @type {String}
21353 * @see In styled mode, the [stroke dash-array](http://jsfiddle.net/gh/get/
21354 * library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/
21355 * series-dashstyle/) can be set with the same classes as listed under
21356 * [series.color](#plotOptions.series.color).
21357 *
21358 * @sample {highcharts} highcharts/plotoptions/series-dashstyle-all/
21359 * Possible values demonstrated
21360 * @sample {highcharts} highcharts/plotoptions/series-dashstyle/
21361 * Chart suitable for printing in black and white
21362 * @sample {highstock} highcharts/plotoptions/series-dashstyle-all/
21363 * Possible values demonstrated
21364 * @sample {highmaps} highcharts/plotoptions/series-dashstyle-all/
21365 * Possible values demonstrated
21366 * @sample {highmaps} maps/plotoptions/series-dashstyle/
21367 * Dotted borders on a map
21368 * @default Solid
21369 * @since 2.1
21370 * @apioption plotOptions.series.dashStyle
21371 */
21372
21373 /**
21374 * Requires the Accessibility module.
21375 *
21376 * A description of the series to add to the screen reader information
21377 * about the series.
21378 *
21379 * @type {String}
21380 * @default undefined
21381 * @since 5.0.0
21382 * @apioption plotOptions.series.description
21383 */
21384
21385
21386
21387
21388
21389 /**
21390 * Enable or disable the mouse tracking for a specific series. This
21391 * includes point tooltips and click events on graphs and points. For
21392 * large datasets it improves performance.
21393 *
21394 * @type {Boolean}
21395 * @sample {highcharts}
21396 * highcharts/plotoptions/series-enablemousetracking-false/
21397 * No mouse tracking
21398 * @sample {highmaps}
21399 * maps/plotoptions/series-enablemousetracking-false/
21400 * No mouse tracking
21401 * @default true
21402 * @apioption plotOptions.series.enableMouseTracking
21403 */
21404
21405 /**
21406 * By default, series are exposed to screen readers as regions. By enabling
21407 * this option, the series element itself will be exposed in the same
21408 * way as the data points. This is useful if the series is not used
21409 * as a grouping entity in the chart, but you still want to attach a
21410 * description to the series.
21411 *
21412 * Requires the Accessibility module.
21413 *
21414 * @type {Boolean}
21415 * @sample highcharts/accessibility/art-grants/
21416 * Accessible data visualization
21417 * @default undefined
21418 * @since 5.0.12
21419 * @apioption plotOptions.series.exposeElementToA11y
21420 */
21421
21422 /**
21423 * Whether to use the Y extremes of the total chart width or only the
21424 * zoomed area when zooming in on parts of the X axis. By default, the
21425 * Y axis adjusts to the min and max of the visible data. Cartesian
21426 * series only.
21427 *
21428 * @type {Boolean}
21429 * @default false
21430 * @since 4.1.6
21431 * @product highcharts highstock
21432 * @apioption plotOptions.series.getExtremesFromAll
21433 */
21434
21435 /**
21436 * An id for the series. This can be used after render time to get a
21437 * pointer to the series object through `chart.get()`.
21438 *
21439 * @type {String}
21440 * @sample {highcharts} highcharts/plotoptions/series-id/ Get series by id
21441 * @since 1.2.0
21442 * @apioption series.id
21443 */
21444
21445 /**
21446 * The index of the series in the chart, affecting the internal index
21447 * in the `chart.series` array, the visible Z index as well as the order
21448 * in the legend.
21449 *
21450 * @type {Number}
21451 * @default undefined
21452 * @since 2.3.0
21453 * @apioption series.index
21454 */
21455
21456 /**
21457 * An array specifying which option maps to which key in the data point
21458 * array. This makes it convenient to work with unstructured data arrays
21459 * from different sources.
21460 *
21461 * @type {Array<String>}
21462 * @see [series.data](#series.line.data)
21463 * @sample {highcharts|highstock} highcharts/series/data-keys/
21464 * An extended data array with keys
21465 * @since 4.1.6
21466 * @product highcharts highstock
21467 * @apioption plotOptions.series.keys
21468 */
21469
21470 /**
21471 * The sequential index of the series in the legend.
21472 *
21473 * @sample {highcharts|highstock} highcharts/series/legendindex/
21474 * Legend in opposite order
21475 * @type {Number}
21476 * @see [legend.reversed](#legend.reversed), [yAxis.reversedStacks](#yAxis.
21477 * reversedStacks)
21478 * @apioption series.legendIndex
21479 */
21480
21481 /**
21482 * The line cap used for line ends and line joins on the graph.
21483 *
21484 * @validvalue ["round", "square"]
21485 * @type {String}
21486 * @default round
21487 * @product highcharts highstock
21488 * @apioption plotOptions.series.linecap
21489 */
21490
21491 /**
21492 * The [id](#series.id) of another series to link to. Additionally,
21493 * the value can be ":previous" to link to the previous series. When
21494 * two series are linked, only the first one appears in the legend.
21495 * Toggling the visibility of this also toggles the linked series.
21496 *
21497 * @type {String}
21498 * @sample {highcharts} highcharts/demo/arearange-line/ Linked series
21499 * @sample {highstock} highcharts/demo/arearange-line/ Linked series
21500 * @since 3.0
21501 * @product highcharts highstock
21502 * @apioption plotOptions.series.linkedTo
21503 */
21504
21505 /**
21506 * The name of the series as shown in the legend, tooltip etc.
21507 *
21508 * @type {String}
21509 * @sample {highcharts} highcharts/series/name/ Series name
21510 * @sample {highmaps} maps/demo/category-map/ Series name
21511 * @apioption series.name
21512 */
21513
21514 /**
21515 * The color for the parts of the graph or points that are below the
21516 * [threshold](#plotOptions.series.threshold).
21517 *
21518 * @type {Color}
21519 * @see In styled mode, a negative color is applied by setting this
21520 * option to `true` combined with the `.highcharts-negative` class name.
21521 *
21522 * @sample {highcharts} highcharts/plotoptions/series-negative-color/
21523 * Spline, area and column
21524 * @sample {highcharts} highcharts/plotoptions/arearange-negativecolor/
21525 * Arearange
21526 * @sample {highcharts} highcharts/css/series-negative-color/
21527 * Styled mode
21528 * @sample {highstock} highcharts/plotoptions/series-negative-color/
21529 * Spline, area and column
21530 * @sample {highstock} highcharts/plotoptions/arearange-negativecolor/
21531 * Arearange
21532 * @sample {highmaps} highcharts/plotoptions/series-negative-color/
21533 * Spline, area and column
21534 * @sample {highmaps} highcharts/plotoptions/arearange-negativecolor/
21535 * Arearange
21536 * @default null
21537 * @since 3.0
21538 * @apioption plotOptions.series.negativeColor
21539 */
21540
21541 /**
21542 * Same as [accessibility.pointDescriptionFormatter](#accessibility.
21543 * pointDescriptionFormatter), but for an individual series. Overrides
21544 * the chart wide configuration.
21545 *
21546 * @type {Function}
21547 * @since 5.0.12
21548 * @apioption plotOptions.series.pointDescriptionFormatter
21549 */
21550
21551 /**
21552 * If no x values are given for the points in a series, `pointInterval`
21553 * defines the interval of the x values. For example, if a series contains
21554 * one value every decade starting from year 0, set `pointInterval` to
21555 * `10`. In true `datetime` axes, the `pointInterval` is set in
21556 * milliseconds.
21557 *
21558 * It can be also be combined with `pointIntervalUnit` to draw irregular
21559 * time intervals.
21560 *
21561 * @type {Number}
21562 * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/
21563 * Datetime X axis
21564 * @sample {highstock} stock/plotoptions/pointinterval-pointstart/
21565 * Using pointStart and pointInterval
21566 * @default 1
21567 * @product highcharts highstock
21568 * @apioption plotOptions.series.pointInterval
21569 */
21570
21571 /**
21572 * On datetime series, this allows for setting the
21573 * [pointInterval](#plotOptions.series.pointInterval) to irregular time
21574 * units, `day`, `month` and `year`. A day is usually the same as 24 hours,
21575 * but `pointIntervalUnit` also takes the DST crossover into consideration
21576 * when dealing with local time. Combine this option with `pointInterval`
21577 * to draw weeks, quarters, 6 months, 10 years etc.
21578 *
21579 * @validvalue [null, "day", "month", "year"]
21580 * @type {String}
21581 * @sample {highcharts} highcharts/plotoptions/series-pointintervalunit/
21582 * One point a month
21583 * @sample {highstock} highcharts/plotoptions/series-pointintervalunit/
21584 * One point a month
21585 * @since 4.1.0
21586 * @product highcharts highstock
21587 * @apioption plotOptions.series.pointIntervalUnit
21588 */
21589
21590 /**
21591 * Possible values: `null`, `"on"`, `"between"`.
21592 *
21593 * In a column chart, when pointPlacement is `"on"`, the point will
21594 * not create any padding of the X axis. In a polar column chart this
21595 * means that the first column points directly north. If the pointPlacement
21596 * is `"between"`, the columns will be laid out between ticks. This
21597 * is useful for example for visualising an amount between two points
21598 * in time or in a certain sector of a polar chart.
21599 *
21600 * Since Highcharts 3.0.2, the point placement can also be numeric,
21601 * where 0 is on the axis value, -0.5 is between this value and the
21602 * previous, and 0.5 is between this value and the next. Unlike the
21603 * textual options, numeric point placement options won't affect axis
21604 * padding.
21605 *
21606 * Note that pointPlacement needs a [pointRange](#plotOptions.series.
21607 * pointRange) to work. For column series this is computed, but for
21608 * line-type series it needs to be set.
21609 *
21610 * Defaults to `null` in cartesian charts, `"between"` in polar charts.
21611 *
21612 * @validvalue [null, "on", "between"]
21613 * @type {String|Number}
21614 * @see [xAxis.tickmarkPlacement](#xAxis.tickmarkPlacement)
21615 * @sample {highcharts|highstock}
21616 * highcharts/plotoptions/series-pointplacement-between/
21617 * Between in a column chart
21618 * @sample {highcharts|highstock}
21619 * highcharts/plotoptions/series-pointplacement-numeric/
21620 * Numeric placement for custom layout
21621 * @default null
21622 * @since 2.3.0
21623 * @product highcharts highstock
21624 * @apioption plotOptions.series.pointPlacement
21625 */
21626
21627 /**
21628 * If no x values are given for the points in a series, pointStart defines
21629 * on what value to start. For example, if a series contains one yearly
21630 * value starting from 1945, set pointStart to 1945.
21631 *
21632 * @type {Number}
21633 * @sample {highcharts} highcharts/plotoptions/series-pointstart-linear/
21634 * Linear
21635 * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/
21636 * Datetime
21637 * @sample {highstock} stock/plotoptions/pointinterval-pointstart/
21638 * Using pointStart and pointInterval
21639 * @default 0
21640 * @product highcharts highstock
21641 * @apioption plotOptions.series.pointStart
21642 */
21643
21644 /**
21645 * Whether to select the series initially. If `showCheckbox` is true,
21646 * the checkbox next to the series name in the legend will be checked for a
21647 * selected series.
21648 *
21649 * @type {Boolean}
21650 * @sample {highcharts} highcharts/plotoptions/series-selected/
21651 * One out of two series selected
21652 * @default false
21653 * @since 1.2.0
21654 * @apioption plotOptions.series.selected
21655 */
21656
21657 /**
21658 * Whether to apply a drop shadow to the graph line. Since 2.3 the shadow
21659 * can be an object configuration containing `color`, `offsetX`, `offsetY`,
21660 * `opacity` and `width`.
21661 *
21662 * @type {Boolean|Object}
21663 * @sample {highcharts} highcharts/plotoptions/series-shadow/ Shadow enabled
21664 * @default false
21665 * @apioption plotOptions.series.shadow
21666 */
21667
21668 /**
21669 * Whether to display this particular series or series type in the legend.
21670 * The default value is `true` for standalone series, `false` for linked
21671 * series.
21672 *
21673 * @type {Boolean}
21674 * @sample {highcharts} highcharts/plotoptions/series-showinlegend/
21675 * One series in the legend, one hidden
21676 * @default true
21677 * @apioption plotOptions.series.showInLegend
21678 */
21679
21680 /**
21681 * If set to `True`, the accessibility module will skip past the points
21682 * in this series for keyboard navigation.
21683 *
21684 * @type {Boolean}
21685 * @since 5.0.12
21686 * @apioption plotOptions.series.skipKeyboardNavigation
21687 */
21688
21689 /**
21690 * This option allows grouping series in a stacked chart. The stack
21691 * option can be a string or a number or anything else, as long as the
21692 * grouped series' stack options match each other.
21693 *
21694 * @type {String}
21695 * @sample {highcharts} highcharts/series/stack/ Stacked and grouped columns
21696 * @default null
21697 * @since 2.1
21698 * @product highcharts highstock
21699 * @apioption series.stack
21700 */
21701
21702 /**
21703 * Whether to stack the values of each series on top of each other.
21704 * Possible values are `null` to disable, `"normal"` to stack by value or
21705 * `"percent"`. When stacking is enabled, data must be sorted in ascending
21706 * X order. A special stacking option is with the streamgraph series type,
21707 * where the stacking option is set to `"stream"`.
21708 *
21709 * @validvalue [null, "normal", "percent"]
21710 * @type {String}
21711 * @see [yAxis.reversedStacks](#yAxis.reversedStacks)
21712 * @sample {highcharts} highcharts/plotoptions/series-stacking-line/
21713 * Line
21714 * @sample {highcharts} highcharts/plotoptions/series-stacking-column/
21715 * Column
21716 * @sample {highcharts} highcharts/plotoptions/series-stacking-bar/
21717 * Bar
21718 * @sample {highcharts} highcharts/plotoptions/series-stacking-area/
21719 * Area
21720 * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-line/
21721 * Line
21722 * @sample {highcharts}
21723 * highcharts/plotoptions/series-stacking-percent-column/
21724 * Column
21725 * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-bar/
21726 * Bar
21727 * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-area/
21728 * Area
21729 * @sample {highstock} stock/plotoptions/stacking/
21730 * Area
21731 * @default null
21732 * @product highcharts highstock
21733 * @apioption plotOptions.series.stacking
21734 */
21735
21736 /**
21737 * Whether to apply steps to the line. Possible values are `left`, `center`
21738 * and `right`.
21739 *
21740 * @validvalue [null, "left", "center", "right"]
21741 * @type {String}
21742 * @sample {highcharts} highcharts/plotoptions/line-step/
21743 * Different step line options
21744 * @sample {highcharts} highcharts/plotoptions/area-step/
21745 * Stepped, stacked area
21746 * @sample {highstock} stock/plotoptions/line-step/
21747 * Step line
21748 * @default {highcharts} null
21749 * @default {highstock} false
21750 * @since 1.2.5
21751 * @product highcharts highstock
21752 * @apioption plotOptions.series.step
21753 */
21754
21755 /**
21756 * The threshold, also called zero level or base level. For line type
21757 * series this is only used in conjunction with
21758 * [negativeColor](#plotOptions.series.negativeColor).
21759 *
21760 * @type {Number}
21761 * @see [softThreshold](#plotOptions.series.softThreshold).
21762 * @default 0
21763 * @since 3.0
21764 * @product highcharts highstock
21765 * @apioption plotOptions.series.threshold
21766 */
21767
21768 /**
21769 * The type of series, for example `line` or `column`.
21770 *
21771 * @validvalue [null, "line", "spline", "column", "area", "areaspline",
21772 * "pie", "arearange", "areasplinerange", "boxplot", "bubble",
21773 * "columnrange", "errorbar", "funnel", "gauge", "scatter",
21774 * "waterfall"]
21775 * @type {String}
21776 * @sample {highcharts} highcharts/series/type/
21777 * Line and column in the same chart
21778 * @sample {highmaps} maps/demo/mapline-mappoint/
21779 * Multiple types in the same map
21780 * @apioption series.type
21781 */
21782
21783 /**
21784 * Set the initial visibility of the series.
21785 *
21786 * @type {Boolean}
21787 * @sample {highcharts} highcharts/plotoptions/series-visible/
21788 * Two series, one hidden and one visible
21789 * @sample {highstock} stock/plotoptions/series-visibility/
21790 * Hidden series
21791 * @default true
21792 * @apioption plotOptions.series.visible
21793 */
21794
21795 /**
21796 * When using dual or multiple x axes, this number defines which xAxis
21797 * the particular series is connected to. It refers to either the [axis
21798 * id](#xAxis.id) or the index of the axis in the xAxis array, with
21799 * 0 being the first.
21800 *
21801 * @type {Number|String}
21802 * @default 0
21803 * @product highcharts highstock
21804 * @apioption series.xAxis
21805 */
21806
21807 /**
21808 * When using dual or multiple y axes, this number defines which yAxis
21809 * the particular series is connected to. It refers to either the [axis
21810 * id](#yAxis.id) or the index of the axis in the yAxis array, with
21811 * 0 being the first.
21812 *
21813 * @type {Number|String}
21814 * @sample {highcharts} highcharts/series/yaxis/
21815 * Apply the column series to the secondary Y axis
21816 * @default 0
21817 * @product highcharts highstock
21818 * @apioption series.yAxis
21819 */
21820
21821 /**
21822 * Defines the Axis on which the zones are applied.
21823 *
21824 * @type {String}
21825 * @see [zones](#plotOptions.series.zones)
21826 * @sample {highcharts} highcharts/series/color-zones-zoneaxis-x/
21827 * Zones on the X-Axis
21828 * @sample {highstock} highcharts/series/color-zones-zoneaxis-x/
21829 * Zones on the X-Axis
21830 * @default y
21831 * @since 4.1.0
21832 * @product highcharts highstock
21833 * @apioption plotOptions.series.zoneAxis
21834 */
21835
21836 /**
21837 * Define the visual z index of the series.
21838 *
21839 * @type {Number}
21840 * @sample {highcharts} highcharts/plotoptions/series-zindex-default/
21841 * With no z index, the series defined last are on top
21842 * @sample {highcharts} highcharts/plotoptions/series-zindex/
21843 * With a z index, the series with the highest z index is on top
21844 * @sample {highstock} highcharts/plotoptions/series-zindex-default/
21845 * With no z index, the series defined last are on top
21846 * @sample {highstock} highcharts/plotoptions/series-zindex/
21847 * With a z index, the series with the highest z index is on top
21848 * @product highcharts highstock
21849 * @apioption series.zIndex
21850 */
21851
21852 /**
21853 * General event handlers for the series items. These event hooks can also
21854 * be attached to the series at run time using the `Highcharts.addEvent`
21855 * function.
21856 */
21857 events: {
21858
21859 /**
21860 * Fires after the series has finished its initial animation, or in
21861 * case animation is disabled, immediately as the series is displayed.
21862 *
21863 * @type {Function}
21864 * @context Series
21865 * @sample {highcharts}
21866 * highcharts/plotoptions/series-events-afteranimate/
21867 * Show label after animate
21868 * @sample {highstock}
21869 * highcharts/plotoptions/series-events-afteranimate/
21870 * Show label after animate
21871 * @since 4.0
21872 * @product highcharts highstock
21873 * @apioption plotOptions.series.events.afterAnimate
21874 */
21875
21876 /**
21877 * Fires when the checkbox next to the series' name in the legend is
21878 * clicked. One parameter, `event`, is passed to the function. The state
21879 * of the checkbox is found by `event.checked`. The checked item is
21880 * found by `event.item`. Return `false` to prevent the default action
21881 * which is to toggle the select state of the series.
21882 *
21883 * @type {Function}
21884 * @context Series
21885 * @sample {highcharts}
21886 * highcharts/plotoptions/series-events-checkboxclick/
21887 * Alert checkbox status
21888 * @since 1.2.0
21889 * @apioption plotOptions.series.events.checkboxClick
21890 */
21891
21892 /**
21893 * Fires when the series is clicked. One parameter, `event`, is passed
21894 * to the function, containing common event information. Additionally,
21895 * `event.point` holds a pointer to the nearest point on the graph.
21896 *
21897 * @type {Function}
21898 * @context Series
21899 * @sample {highcharts} highcharts/plotoptions/series-events-click/
21900 * Alert click info
21901 * @sample {highstock} stock/plotoptions/series-events-click/
21902 * Alert click info
21903 * @sample {highmaps} maps/plotoptions/series-events-click/
21904 * Display click info in subtitle
21905 * @apioption plotOptions.series.events.click
21906 */
21907
21908 /**
21909 * Fires when the series is hidden after chart generation time, either
21910 * by clicking the legend item or by calling `.hide()`.
21911 *
21912 * @type {Function}
21913 * @context Series
21914 * @sample {highcharts} highcharts/plotoptions/series-events-hide/
21915 * Alert when the series is hidden by clicking the legend item
21916 * @since 1.2.0
21917 * @apioption plotOptions.series.events.hide
21918 */
21919
21920 /**
21921 * Fires when the legend item belonging to the series is clicked. One
21922 * parameter, `event`, is passed to the function. The default action
21923 * is to toggle the visibility of the series. This can be prevented
21924 * by returning `false` or calling `event.preventDefault()`.
21925 *
21926 * @type {Function}
21927 * @context Series
21928 * @sample {highcharts}
21929 * highcharts/plotoptions/series-events-legenditemclick/
21930 * Confirm hiding and showing
21931 * @apioption plotOptions.series.events.legendItemClick
21932 */
21933
21934 /**
21935 * Fires when the mouse leaves the graph. One parameter, `event`, is
21936 * passed to the function, containing common event information. If the
21937 * [stickyTracking](#plotOptions.series) option is true, `mouseOut`
21938 * doesn't happen before the mouse enters another graph or leaves the
21939 * plot area.
21940 *
21941 * @type {Function}
21942 * @context Series
21943 * @sample {highcharts}
21944 * highcharts/plotoptions/series-events-mouseover-sticky/
21945 * With sticky tracking by default
21946 * @sample {highcharts}
21947 * highcharts/plotoptions/series-events-mouseover-no-sticky/
21948 * Without sticky tracking
21949 * @apioption plotOptions.series.events.mouseOut
21950 */
21951
21952 /**
21953 * Fires when the mouse enters the graph. One parameter, `event`, is
21954 * passed to the function, containing common event information.
21955 *
21956 * @type {Function}
21957 * @context Series
21958 * @sample {highcharts}
21959 * highcharts/plotoptions/series-events-mouseover-sticky/
21960 * With sticky tracking by default
21961 * @sample {highcharts}
21962 * highcharts/plotoptions/series-events-mouseover-no-sticky/
21963 * Without sticky tracking
21964 * @apioption plotOptions.series.events.mouseOver
21965 */
21966
21967 /**
21968 * Fires when the series is shown after chart generation time, either
21969 * by clicking the legend item or by calling `.show()`.
21970 *
21971 * @type {Function}
21972 * @context Series
21973 * @sample {highcharts} highcharts/plotoptions/series-events-show/
21974 * Alert when the series is shown by clicking the legend item.
21975 * @since 1.2.0
21976 * @apioption plotOptions.series.events.show
21977 */
21978
21979 },
21980
21981
21982
21983 /**
21984 * Options for the point markers of line-like series. Properties like
21985 * `fillColor`, `lineColor` and `lineWidth` define the visual appearance
21986 * of the markers. Other series types, like column series, don't have
21987 * markers, but have visual options on the series level instead.
21988 *
21989 * In styled mode, the markers can be styled with the `.highcharts-point`,
21990 * `.highcharts-point-hover` and `.highcharts-point-select`
21991 * class names.
21992 *
21993 * @product highcharts highstock
21994 */
21995 marker: {
21996
21997
21998 /**
21999 * Enable or disable the point marker. If `null`, the markers are hidden
22000 * when the data is dense, and shown for more widespread data points.
22001 *
22002 * @type {Boolean}
22003 * @sample {highcharts} highcharts/plotoptions/series-marker-enabled/
22004 * Disabled markers
22005 * @sample {highcharts}
22006 * highcharts/plotoptions/series-marker-enabled-false/
22007 * Disabled in normal state but enabled on hover
22008 * @sample {highstock} stock/plotoptions/series-marker/
22009 * Enabled markers
22010 * @default {highcharts} null
22011 * @default {highstock} false
22012 * @product highcharts highstock
22013 * @apioption plotOptions.series.marker.enabled
22014 */
22015
22016 /**
22017 * Image markers only. Set the image width explicitly. When using this
22018 * option, a `width` must also be set.
22019 *
22020 * @type {Number}
22021 * @sample {highcharts}
22022 * highcharts/plotoptions/series-marker-width-height/
22023 * Fixed width and height
22024 * @sample {highstock}
22025 * highcharts/plotoptions/series-marker-width-height/
22026 * Fixed width and height
22027 * @default null
22028 * @since 4.0.4
22029 * @product highcharts highstock
22030 * @apioption plotOptions.series.marker.height
22031 */
22032
22033 /**
22034 * A predefined shape or symbol for the marker. When null, the symbol
22035 * is pulled from options.symbols. Other possible values are "circle",
22036 * "square", "diamond", "triangle" and "triangle-down".
22037 *
22038 * Additionally, the URL to a graphic can be given on this form:
22039 * "url(graphic.png)". Note that for the image to be applied to exported
22040 * charts, its URL needs to be accessible by the export server.
22041 *
22042 * Custom callbacks for symbol path generation can also be added to
22043 * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
22044 * used by its method name, as shown in the demo.
22045 *
22046 * @validvalue [null, "circle", "square", "diamond", "triangle",
22047 * "triangle-down"]
22048 * @type {String}
22049 * @sample {highcharts} highcharts/plotoptions/series-marker-symbol/
22050 * Predefined, graphic and custom markers
22051 * @sample {highstock} highcharts/plotoptions/series-marker-symbol/
22052 * Predefined, graphic and custom markers
22053 * @default null
22054 * @product highcharts highstock
22055 * @apioption plotOptions.series.marker.symbol
22056 */
22057
22058 /**
22059 * The radius of the point marker.
22060 *
22061 * @type {Number}
22062 * @sample {highcharts} highcharts/plotoptions/series-marker-radius/
22063 * Bigger markers
22064 * @default 4
22065 * @product highcharts highstock
22066 */
22067 radius: 4,
22068
22069 /**
22070 * Image markers only. Set the image width explicitly. When using this
22071 * option, a `height` must also be set.
22072 *
22073 * @type {Number}
22074 * @sample {highcharts}
22075 * highcharts/plotoptions/series-marker-width-height/
22076 * Fixed width and height
22077 * @sample {highstock}
22078 * highcharts/plotoptions/series-marker-width-height/
22079 * Fixed width and height
22080 * @default null
22081 * @since 4.0.4
22082 * @product highcharts highstock
22083 * @apioption plotOptions.series.marker.width
22084 */
22085
22086
22087 /**
22088 * States for a single point marker.
22089 * @product highcharts highstock
22090 */
22091 states: {
22092 /**
22093 * The hover state for a single point marker.
22094 * @product highcharts highstock
22095 */
22096 hover: {
22097
22098 /**
22099 * Animation when hovering over the marker.
22100 * @type {Boolean|Object}
22101 */
22102 animation: {
22103 duration: 50
22104 },
22105
22106 /**
22107 * Enable or disable the point marker.
22108 *
22109 * @type {Boolean}
22110 * @sample {highcharts}
22111 * highcharts/plotoptions/series-marker-states-hover-enabled/
22112 * Disabled hover state
22113 * @default true
22114 * @product highcharts highstock
22115 */
22116 enabled: true,
22117
22118 /**
22119 * The fill color of the marker in hover state.
22120 *
22121 * @type {Color}
22122 * @default null
22123 * @product highcharts highstock
22124 * @apioption plotOptions.series.marker.states.hover.fillColor
22125 */
22126
22127 /**
22128 * The color of the point marker's outline. When `null`, the
22129 * series' or point's color is used.
22130 *
22131 * @type {Color}
22132 * @sample {highcharts}
22133 * highcharts/plotoptions/series-marker-states-hover-linecolor/
22134 * White fill color, black line color
22135 * @default #ffffff
22136 * @product highcharts highstock
22137 * @apioption plotOptions.series.marker.states.hover.lineColor
22138 */
22139
22140 /**
22141 * The width of the point marker's outline.
22142 *
22143 * @type {Number}
22144 * @sample {highcharts}
22145 * highcharts/plotoptions/series-marker-states-hover-linewidth/
22146 * 3px line width
22147 * @default 0
22148 * @product highcharts highstock
22149 * @apioption plotOptions.series.marker.states.hover.lineWidth
22150 */
22151
22152 /**
22153 * The radius of the point marker. In hover state, it defaults to the
22154 * normal state's radius + 2 as per the [radiusPlus](#plotOptions.series.
22155 * marker.states.hover.radiusPlus) option.
22156 *
22157 * @type {Number}
22158 * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-radius/ 10px radius
22159 * @product highcharts highstock
22160 * @apioption plotOptions.series.marker.states.hover.radius
22161 */
22162
22163 /**
22164 * The number of pixels to increase the radius of the hovered point.
22165 *
22166 * @type {Number}
22167 * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/ 5 pixels greater radius on hover
22168 * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/ 5 pixels greater radius on hover
22169 * @default 2
22170 * @since 4.0.3
22171 * @product highcharts highstock
22172 */
22173 radiusPlus: 2
22174
22175
22176 }
22177
22178 }
22179 },
22180
22181
22182
22183 /**
22184 * Properties for each single point.
22185 */
22186 point: {
22187
22188
22189 /**
22190 * Events for each single point.
22191 */
22192 events: {
22193
22194 /**
22195 * Fires when a point is clicked. One parameter, `event`, is passed
22196 * to the function, containing common event information.
22197 *
22198 * If the `series.allowPointSelect` option is true, the default action
22199 * for the point's click event is to toggle the point's select state.
22200 * Returning `false` cancels this action.
22201 *
22202 * @type {Function}
22203 * @context Point
22204 * @sample {highcharts} highcharts/plotoptions/series-point-events-click/ Click marker to alert values
22205 * @sample {highcharts} highcharts/plotoptions/series-point-events-click-column/ Click column
22206 * @sample {highcharts} highcharts/plotoptions/series-point-events-click-url/ Go to URL
22207 * @sample {highmaps} maps/plotoptions/series-point-events-click/ Click marker to display values
22208 * @sample {highmaps} maps/plotoptions/series-point-events-click-url/ Go to URL
22209 * @apioption plotOptions.series.point.events.click
22210 */
22211
22212 /**
22213 * Fires when the mouse leaves the area close to the point. One parameter,
22214 * `event`, is passed to the function, containing common event information.
22215 *
22216 * @type {Function}
22217 * @context Point
22218 * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ Show values in the chart's corner on mouse over
22219 * @apioption plotOptions.series.point.events.mouseOut
22220 */
22221
22222 /**
22223 * Fires when the mouse enters the area close to the point. One parameter,
22224 * `event`, is passed to the function, containing common event information.
22225 *
22226 * @type {Function}
22227 * @context Point
22228 * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ Show values in the chart's corner on mouse over
22229 * @apioption plotOptions.series.point.events.mouseOver
22230 */
22231
22232 /**
22233 * Fires when the point is removed using the `.remove()` method. One
22234 * parameter, `event`, is passed to the function. Returning `false`
22235 * cancels the operation.
22236 *
22237 * @type {Function}
22238 * @context Point
22239 * @sample {highcharts} highcharts/plotoptions/series-point-events-remove/ Remove point and confirm
22240 * @since 1.2.0
22241 * @apioption plotOptions.series.point.events.remove
22242 */
22243
22244 /**
22245 * Fires when the point is selected either programmatically or following
22246 * a click on the point. One parameter, `event`, is passed to the function.
22247 * Returning `false` cancels the operation.
22248 *
22249 * @type {Function}
22250 * @context Point
22251 * @sample {highcharts} highcharts/plotoptions/series-point-events-select/ Report the last selected point
22252 * @sample {highmaps} maps/plotoptions/series-allowpointselect/ Report select and unselect
22253 * @since 1.2.0
22254 * @apioption plotOptions.series.point.events.select
22255 */
22256
22257 /**
22258 * Fires when the point is unselected either programmatically or following
22259 * a click on the point. One parameter, `event`, is passed to the function.
22260 * Returning `false` cancels the operation.
22261 *
22262 * @type {Function}
22263 * @context Point
22264 * @sample {highcharts} highcharts/plotoptions/series-point-events-unselect/ Report the last unselected point
22265 * @sample {highmaps} maps/plotoptions/series-allowpointselect/ Report select and unselect
22266 * @since 1.2.0
22267 * @apioption plotOptions.series.point.events.unselect
22268 */
22269
22270 /**
22271 * Fires when the point is updated programmatically through the `.update()`
22272 * method. One parameter, `event`, is passed to the function. The new
22273 * point options can be accessed through `event.options`. Returning
22274 * `false` cancels the operation.
22275 *
22276 * @type {Function}
22277 * @context Point
22278 * @sample {highcharts} highcharts/plotoptions/series-point-events-update/ Confirm point updating
22279 * @since 1.2.0
22280 * @apioption plotOptions.series.point.events.update
22281 */
22282
22283 }
22284 },
22285
22286
22287
22288 /**
22289 * Options for the series data labels, appearing next to each data
22290 * point.
22291 *
22292 * In styled mode, the data labels can be styled wtih the `.highcharts-data-label-box` and `.highcharts-data-label` class names ([see example](http://jsfiddle.
22293 * net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/series-
22294 * datalabels)).
22295 */
22296 dataLabels: {
22297
22298
22299 /**
22300 * The alignment of the data label compared to the point. If `right`,
22301 * the right side of the label should be touching the point. For
22302 * points with an extent, like columns, the alignments also dictates
22303 * how to align it inside the box, as given with the [inside](#plotOptions.
22304 * column.dataLabels.inside) option. Can be one of "left", "center"
22305 * or "right".
22306 *
22307 * @validvalue ["left", "center", "right"]
22308 * @type {String}
22309 * @sample {highcharts} highcharts/plotoptions/series-datalabels-align-left/ Left aligned
22310 * @default center
22311 */
22312 align: 'center',
22313
22314
22315 /**
22316 * Whether to allow data labels to overlap. To make the labels less
22317 * sensitive for overlapping, the [dataLabels.padding](#plotOptions.
22318 * series.dataLabels.padding) can be set to 0.
22319 *
22320 * @type {Boolean}
22321 * @sample {highcharts} highcharts/plotoptions/series-datalabels-allowoverlap-false/ Don't allow overlap
22322 * @sample {highstock} highcharts/plotoptions/series-datalabels-allowoverlap-false/ Don't allow overlap
22323 * @sample {highmaps} highcharts/plotoptions/series-datalabels-allowoverlap-false/ Don't allow overlap
22324 * @default false
22325 * @since 4.1.0
22326 * @apioption plotOptions.series.dataLabels.allowOverlap
22327 */
22328
22329
22330 /**
22331 * The border radius in pixels for the data label.
22332 *
22333 * @type {Number}
22334 * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
22335 * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
22336 * @sample {highmaps} maps/plotoptions/series-datalabels-box/ Data labels box options
22337 * @default 0
22338 * @since 2.2.1
22339 * @apioption plotOptions.series.dataLabels.borderRadius
22340 */
22341
22342
22343 /**
22344 * The border width in pixels for the data label.
22345 *
22346 * @type {Number}
22347 * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
22348 * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
22349 * @default 0
22350 * @since 2.2.1
22351 * @apioption plotOptions.series.dataLabels.borderWidth
22352 */
22353
22354 /**
22355 * A class name for the data label. Particularly in styled mode, this can
22356 * be used to give each series' or point's data label unique styling.
22357 * In addition to this option, a default color class name is added
22358 * so that we can give the labels a [contrast text shadow](http://jsfiddle.
22359 * net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/data-
22360 * label-contrast/).
22361 *
22362 * @type {String}
22363 * @sample {highcharts} highcharts/css/series-datalabels/ Styling by CSS
22364 * @sample {highstock} highcharts/css/series-datalabels/ Styling by CSS
22365 * @sample {highmaps} highcharts/css/series-datalabels/ Styling by CSS
22366 * @since 5.0.0
22367 * @apioption plotOptions.series.dataLabels.className
22368 */
22369
22370 /**
22371 * The text color for the data labels. Defaults to `null`. For certain series
22372 * types, like column or map, the data labels can be drawn inside the points.
22373 * In this case the data label will be drawn with maximum contrast by default.
22374 * Additionally, it will be given a `text-outline` style with the opposite
22375 * color, to further increase the contrast. This can be overridden by setting
22376 * the `text-outline` style to `none` in the `dataLabels.style` option.
22377 *
22378 * @type {Color}
22379 * @sample {highcharts} highcharts/plotoptions/series-datalabels-color/
22380 * Red data labels
22381 * @sample {highmaps} maps/demo/color-axis/
22382 * White data labels
22383 * @apioption plotOptions.series.dataLabels.color
22384 */
22385
22386 /**
22387 * Whether to hide data labels that are outside the plot area. By default,
22388 * the data label is moved inside the plot area according to the [overflow](#plotOptions.
22389 * series.dataLabels.overflow) option.
22390 *
22391 * @type {Boolean}
22392 * @default true
22393 * @since 2.3.3
22394 * @apioption plotOptions.series.dataLabels.crop
22395 */
22396
22397 /**
22398 * Whether to defer displaying the data labels until the initial series
22399 * animation has finished.
22400 *
22401 * @type {Boolean}
22402 * @default true
22403 * @since 4.0
22404 * @product highcharts highstock
22405 * @apioption plotOptions.series.dataLabels.defer
22406 */
22407
22408 /**
22409 * Enable or disable the data labels.
22410 *
22411 * @type {Boolean}
22412 * @sample {highcharts} highcharts/plotoptions/series-datalabels-enabled/ Data labels enabled
22413 * @sample {highmaps} maps/demo/color-axis/ Data labels enabled
22414 * @default false
22415 * @apioption plotOptions.series.dataLabels.enabled
22416 */
22417
22418 /**
22419 * A [format string](http://www.highcharts.com/docs/chart-concepts/labels-
22420 * and-string-formatting) for the data label. Available variables are
22421 * the same as for `formatter`.
22422 *
22423 * @type {String}
22424 * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ Add a unit
22425 * @sample {highstock} highcharts/plotoptions/series-datalabels-format/ Add a unit
22426 * @sample {highmaps} maps/plotoptions/series-datalabels-format/ Formatted value in the data label
22427 * @default {highcharts} {y}
22428 * @default {highstock} {y}
22429 * @default {highmaps} {point.value}
22430 * @since 3.0
22431 * @apioption plotOptions.series.dataLabels.format
22432 */
22433
22434 /**
22435 * Callback JavaScript function to format the data label. Note that
22436 * if a `format` is defined, the format takes precedence and the formatter
22437 * is ignored. Available data are:
22438 *
22439 * <table>
22440 *
22441 * <tbody>
22442 *
22443 * <tr>
22444 *
22445 * <td>`this.percentage`</td>
22446 *
22447 * <td>Stacked series and pies only. The point's percentage of the
22448 * total.</td>
22449 *
22450 * </tr>
22451 *
22452 * <tr>
22453 *
22454 * <td>`this.point`</td>
22455 *
22456 * <td>The point object. The point name, if defined, is available
22457 * through `this.point.name`.</td>
22458 *
22459 * </tr>
22460 *
22461 * <tr>
22462 *
22463 * <td>`this.series`:</td>
22464 *
22465 * <td>The series object. The series name is available through `this.
22466 * series.name`.</td>
22467 *
22468 * </tr>
22469 *
22470 * <tr>
22471 *
22472 * <td>`this.total`</td>
22473 *
22474 * <td>Stacked series only. The total value at this point's x value.
22475 * </td>
22476 *
22477 * </tr>
22478 *
22479 * <tr>
22480 *
22481 * <td>`this.x`:</td>
22482 *
22483 * <td>The x value.</td>
22484 *
22485 * </tr>
22486 *
22487 * <tr>
22488 *
22489 * <td>`this.y`:</td>
22490 *
22491 * <td>The y value.</td>
22492 *
22493 * </tr>
22494 *
22495 * </tbody>
22496 *
22497 * </table>
22498 *
22499 * @type {Function}
22500 * @sample {highmaps} maps/plotoptions/series-datalabels-format/ Formatted value
22501 */
22502 formatter: function() {
22503 return this.y === null ? '' : H.numberFormat(this.y, -1);
22504 },
22505
22506
22507 /**
22508 * For points with an extent, like columns or map areas, whether to align the data
22509 * label inside the box or to the actual value point. Defaults to `false`
22510 * in most cases, `true` in stacked columns.
22511 *
22512 * @type {Boolean}
22513 * @since 3.0
22514 * @apioption plotOptions.series.dataLabels.inside
22515 */
22516
22517 /**
22518 * How to handle data labels that flow outside the plot area. The default
22519 * is `justify`, which aligns them inside the plot area. For columns
22520 * and bars, this means it will be moved inside the bar. To display
22521 * data labels outside the plot area, set `crop` to `false` and `overflow`
22522 * to `"none"`.
22523 *
22524 * @validvalue ["justify", "none"]
22525 * @type {String}
22526 * @default justify
22527 * @since 3.0.6
22528 * @apioption plotOptions.series.dataLabels.overflow
22529 */
22530
22531 /**
22532 * Text rotation in degrees. Note that due to a more complex structure,
22533 * backgrounds, borders and padding will be lost on a rotated data
22534 * label.
22535 *
22536 * @type {Number}
22537 * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ Vertical labels
22538 * @default 0
22539 * @apioption plotOptions.series.dataLabels.rotation
22540 */
22541
22542 /**
22543 * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
22544 * and-string-formatting#html) to render the labels.
22545 *
22546 * @type {Boolean}
22547 * @default false
22548 * @apioption plotOptions.series.dataLabels.useHTML
22549 */
22550
22551 /**
22552 * The vertical alignment of a data label. Can be one of `top`, `middle`
22553 * or `bottom`. The default value depends on the data, for instance
22554 * in a column chart, the label is above positive values and below
22555 * negative values.
22556 *
22557 * @validvalue ["top", "middle", "bottom"]
22558 * @type {String}
22559 * @since 2.3.3
22560 */
22561 verticalAlign: 'bottom', // above singular point
22562
22563
22564 /**
22565 * The x position offset of the label relative to the point.
22566 *
22567 * @type {Number}
22568 * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ Vertical and positioned
22569 * @default 0
22570 */
22571 x: 0,
22572
22573
22574 /**
22575 * The y position offset of the label relative to the point.
22576 *
22577 * @type {Number}
22578 * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ Vertical and positioned
22579 * @default -6
22580 */
22581 y: 0,
22582
22583
22584 /**
22585 * When either the `borderWidth` or the `backgroundColor` is set,
22586 * this is the padding within the box.
22587 *
22588 * @type {Number}
22589 * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
22590 * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
22591 * @sample {highmaps} maps/plotoptions/series-datalabels-box/ Data labels box options
22592 * @default {highcharts} 5
22593 * @default {highstock} 5
22594 * @default {highmaps} 0
22595 * @since 2.2.1
22596 */
22597 padding: 5
22598
22599 /**
22600 * The name of a symbol to use for the border around the label. Symbols
22601 * are predefined functions on the Renderer object.
22602 *
22603 * @type {String}
22604 * @sample {highcharts} highcharts/plotoptions/series-datalabels-shape/ A callout for annotations
22605 * @sample {highstock} highcharts/plotoptions/series-datalabels-shape/ A callout for annotations
22606 * @sample {highmaps} highcharts/plotoptions/series-datalabels-shape/ A callout for annotations (Highcharts demo)
22607 * @default square
22608 * @since 4.1.2
22609 * @apioption plotOptions.series.dataLabels.shape
22610 */
22611
22612 /**
22613 * The Z index of the data labels. The default Z index puts it above
22614 * the series. Use a Z index of 2 to display it behind the series.
22615 *
22616 * @type {Number}
22617 * @default 6
22618 * @since 2.3.5
22619 * @apioption plotOptions.series.dataLabels.zIndex
22620 */
22621
22622 /**
22623 * A declarative filter for which data labels to display. The
22624 * declarative filter is designed for use when callback functions are
22625 * not available, like when the chart options require a pure JSON
22626 * structure or for use with graphical editors. For programmatic
22627 * control, use the `formatter` instead, and return `false` to disable
22628 * a single data label.
22629 *
22630 * @example
22631 * filter: {
22632 * property: 'percentage',
22633 * operator: '>',
22634 * value: 4
22635 * }
22636 *
22637 * @sample highcharts/demo/pie-monochrome
22638 * Data labels filtered by percentage
22639 *
22640 * @type {Object}
22641 * @since 6.0.3
22642 * @apioption plotOptions.series.dataLabels.filter
22643 */
22644
22645 /**
22646 * The point property to filter by. Point options are passed directly to
22647 * properties, additionally there are `y` value, `percentage` and others
22648 * listed under [Point](https://api.highcharts.com/class-reference/Highcharts.Point)
22649 * members.
22650 *
22651 * @type {String}
22652 * @apioption plotOptions.series.dataLabels.filter.property
22653 */
22654
22655 /**
22656 * The operator to compare by. Can be one of `>`, `<`, `>=`, `<=`, `==`,
22657 * and `===`.
22658 *
22659 * @type {String}
22660 * @validvalue [">", "<", ">=", "<=", "==", "===""]
22661 * @apioption plotOptions.series.dataLabels.filter.operator
22662 */
22663
22664 /**
22665 * The value to compare against.
22666 *
22667 * @type {Mixed}
22668 * @apioption plotOptions.series.dataLabels.filter.value
22669 */
22670 },
22671 // draw points outside the plot area when the number of points is less than
22672 // this
22673
22674
22675
22676 /**
22677 * When the series contains less points than the crop threshold, all
22678 * points are drawn, even if the points fall outside the visible plot
22679 * area at the current zoom. The advantage of drawing all points (including
22680 * markers and columns), is that animation is performed on updates.
22681 * On the other hand, when the series contains more points than the
22682 * crop threshold, the series data is cropped to only contain points
22683 * that fall within the plot area. The advantage of cropping away invisible
22684 * points is to increase performance on large series.
22685 *
22686 * @type {Number}
22687 * @default 300
22688 * @since 2.2
22689 * @product highcharts highstock
22690 */
22691 cropThreshold: 300,
22692
22693
22694
22695 /**
22696 * The width of each point on the x axis. For example in a column chart
22697 * with one value each day, the pointRange would be 1 day (= 24 * 3600
22698 * * 1000 milliseconds). This is normally computed automatically, but
22699 * this option can be used to override the automatic value.
22700 *
22701 * @type {Number}
22702 * @default 0
22703 * @product highstock
22704 */
22705 pointRange: 0,
22706
22707 /**
22708 * When this is true, the series will not cause the Y axis to cross
22709 * the zero plane (or [threshold](#plotOptions.series.threshold) option)
22710 * unless the data actually crosses the plane.
22711 *
22712 * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
22713 * 3 will make the Y axis show negative values according to the `minPadding`
22714 * option. If `softThreshold` is `true`, the Y axis starts at 0.
22715 *
22716 * @type {Boolean}
22717 * @default true
22718 * @since 4.1.9
22719 * @product highcharts highstock
22720 */
22721 softThreshold: true,
22722
22723
22724
22725 /**
22726 * A wrapper object for all the series options in specific states.
22727 *
22728 * @type {plotOptions.series.states}
22729 */
22730 states: {
22731
22732
22733 /**
22734 * Options for the hovered series. These settings override the normal
22735 * state options when a series is moused over or touched.
22736 *
22737 */
22738 hover: {
22739
22740 /**
22741 * Enable separate styles for the hovered series to visualize that the
22742 * user hovers either the series itself or the legend. .
22743 *
22744 * @type {Boolean}
22745 * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled/ Line
22746 * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled-column/ Column
22747 * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled-pie/ Pie
22748 * @default true
22749 * @since 1.2
22750 * @apioption plotOptions.series.states.hover.enabled
22751 */
22752
22753
22754 /**
22755 * Animation setting for hovering the graph in line-type series.
22756 *
22757 * @type {Boolean|Object}
22758 * @default { "duration": 50 }
22759 * @since 5.0.8
22760 * @product highcharts
22761 */
22762 animation: {
22763 /**
22764 * The duration of the hover animation in milliseconds. By
22765 * default the hover state animates quickly in, and slowly back
22766 * to normal.
22767 */
22768 duration: 50
22769 },
22770
22771 /**
22772 * Pixel with of the graph line. By default this property is
22773 * undefined, and the `lineWidthPlus` property dictates how much
22774 * to increase the linewidth from normal state.
22775 *
22776 * @type {Number}
22777 * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidth/
22778 * 5px line on hover
22779 * @default undefined
22780 * @product highcharts highstock
22781 * @apioption plotOptions.series.states.hover.lineWidth
22782 */
22783
22784
22785 /**
22786 * The additional line width for the graph of a hovered series.
22787 *
22788 * @type {Number}
22789 * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/
22790 * 5 pixels wider
22791 * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/
22792 * 5 pixels wider
22793 * @default 1
22794 * @since 4.0.3
22795 * @product highcharts highstock
22796 */
22797 lineWidthPlus: 1,
22798
22799
22800
22801 /**
22802 * In Highcharts 1.0, the appearance of all markers belonging to
22803 * the hovered series. For settings on the hover state of the individual
22804 * point, see [marker.states.hover](#plotOptions.series.marker.states.
22805 * hover).
22806 *
22807 * @extends plotOptions.series.marker
22808 * @deprecated
22809 * @product highcharts highstock
22810 */
22811 marker: {
22812 // lineWidth: base + 1,
22813 // radius: base + 1
22814 },
22815
22816
22817
22818 /**
22819 * Options for the halo appearing around the hovered point in line-
22820 * type series as well as outside the hovered slice in pie charts.
22821 * By default the halo is filled by the current point or series
22822 * color with an opacity of 0.25\. The halo can be disabled by setting
22823 * the `halo` option to `false`.
22824 *
22825 * In styled mode, the halo is styled with the `.highcharts-halo` class, with colors inherited from `.highcharts-color-{n}`.
22826 *
22827 * @type {Object}
22828 * @sample {highcharts} highcharts/plotoptions/halo/ Halo options
22829 * @sample {highstock} highcharts/plotoptions/halo/ Halo options
22830 * @since 4.0
22831 * @product highcharts highstock
22832 */
22833 halo: {
22834
22835 /**
22836 * A collection of SVG attributes to override the appearance of the
22837 * halo, for example `fill`, `stroke` and `stroke-width`.
22838 *
22839 * @type {Object}
22840 * @since 4.0
22841 * @product highcharts highstock
22842 * @apioption plotOptions.series.states.hover.halo.attributes
22843 */
22844
22845
22846 /**
22847 * The pixel size of the halo. For point markers this is the radius
22848 * of the halo. For pie slices it is the width of the halo outside
22849 * the slice. For bubbles it defaults to 5 and is the width of the
22850 * halo outside the bubble.
22851 *
22852 * @type {Number}
22853 * @default 10
22854 * @since 4.0
22855 * @product highcharts highstock
22856 */
22857 size: 10
22858
22859 }
22860 },
22861
22862
22863 /**
22864 * Specific options for point in selected states, after being selected
22865 * by [allowPointSelect](#plotOptions.series.allowPointSelect) or
22866 * programmatically.
22867 *
22868 * @type {Object}
22869 * @extends plotOptions.series.states.hover
22870 * @excluding brightness
22871 * @sample {highmaps} maps/plotoptions/series-allowpointselect/
22872 * Allow point select demo
22873 * @product highmaps
22874 */
22875 select: {
22876 marker: {}
22877 }
22878 },
22879
22880
22881
22882 /**
22883 * Sticky tracking of mouse events. When true, the `mouseOut` event
22884 * on a series isn't triggered until the mouse moves over another series,
22885 * or out of the plot area. When false, the `mouseOut` event on a
22886 * series is triggered when the mouse leaves the area around the series'
22887 * graph or markers. This also implies the tooltip when not shared. When
22888 * `stickyTracking` is false and `tooltip.shared` is false, the tooltip will
22889 * be hidden when moving the mouse between series. Defaults to true for line
22890 * and area type series, but to false for columns, pies etc.
22891 *
22892 * @type {Boolean}
22893 * @sample {highcharts} highcharts/plotoptions/series-stickytracking-true/
22894 * True by default
22895 * @sample {highcharts} highcharts/plotoptions/series-stickytracking-false/
22896 * False
22897 * @default {highcharts} true
22898 * @default {highstock} true
22899 * @default {highmaps} false
22900 * @since 2.0
22901 */
22902 stickyTracking: true,
22903
22904 /**
22905 * A configuration object for the tooltip rendering of each single series.
22906 * Properties are inherited from [tooltip](#tooltip), but only the
22907 * following properties can be defined on a series level.
22908 *
22909 * @type {Object}
22910 * @extends tooltip
22911 * @excluding animation,backgroundColor,borderColor,borderRadius,borderWidth,crosshairs,enabled,formatter,positioner,shadow,shared,shape,snap,style,useHTML
22912 * @since 2.3
22913 * @apioption plotOptions.series.tooltip
22914 */
22915
22916 /**
22917 * When a series contains a data array that is longer than this, only
22918 * one dimensional arrays of numbers, or two dimensional arrays with
22919 * x and y values are allowed. Also, only the first point is tested,
22920 * and the rest are assumed to be the same format. This saves expensive
22921 * data checking and indexing in long series. Set it to `0` disable.
22922 *
22923 * @type {Number}
22924 * @default 1000
22925 * @since 2.2
22926 * @product highcharts highstock
22927 */
22928 turboThreshold: 1000,
22929
22930 /**
22931 * An array defining zones within a series. Zones can be applied to
22932 * the X axis, Y axis or Z axis for bubbles, according to the `zoneAxis`
22933 * option.
22934 *
22935 * In styled mode, the color zones are styled with the `.highcharts-
22936 * zone-{n}` class, or custom classed from the `className` option ([view
22937 * live demo](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/color-
22938 * zones/)).
22939 *
22940 * @type {Array}
22941 * @see [zoneAxis](#plotOptions.series.zoneAxis)
22942 * @sample {highcharts} highcharts/series/color-zones-simple/ Color zones
22943 * @sample {highstock} highcharts/series/color-zones-simple/ Color zones
22944 * @since 4.1.0
22945 * @product highcharts highstock
22946 * @apioption plotOptions.series.zones
22947 */
22948
22949 /**
22950 * Styled mode only. A custom class name for the zone.
22951 *
22952 * @type {String}
22953 * @sample {highcharts} highcharts/css/color-zones/ Zones styled by class name
22954 * @sample {highstock} highcharts/css/color-zones/ Zones styled by class name
22955 * @sample {highmaps} highcharts/css/color-zones/ Zones styled by class name
22956 * @since 5.0.0
22957 * @apioption plotOptions.series.zones.className
22958 */
22959
22960 /**
22961 * Defines the color of the series.
22962 *
22963 * @type {Color}
22964 * @see [series color](#plotOptions.series.color)
22965 * @since 4.1.0
22966 * @product highcharts highstock
22967 * @apioption plotOptions.series.zones.color
22968 */
22969
22970 /**
22971 * A name for the dash style to use for the graph.
22972 *
22973 * @type {String}
22974 * @see [series.dashStyle](#plotOptions.series.dashStyle)
22975 * @sample {highcharts} highcharts/series/color-zones-dashstyle-dot/
22976 * Dashed line indicates prognosis
22977 * @sample {highstock} highcharts/series/color-zones-dashstyle-dot/
22978 * Dashed line indicates prognosis
22979 * @since 4.1.0
22980 * @product highcharts highstock
22981 * @apioption plotOptions.series.zones.dashStyle
22982 */
22983
22984 /**
22985 * Defines the fill color for the series (in area type series)
22986 *
22987 * @type {Color}
22988 * @see [fillColor](#plotOptions.area.fillColor)
22989 * @since 4.1.0
22990 * @product highcharts highstock
22991 * @apioption plotOptions.series.zones.fillColor
22992 */
22993
22994 /**
22995 * The value up to where the zone extends, if undefined the zones stretches
22996 * to the last value in the series.
22997 *
22998 * @type {Number}
22999 * @default undefined
23000 * @since 4.1.0
23001 * @product highcharts highstock
23002 * @apioption plotOptions.series.zones.value
23003 */
23004
23005
23006
23007 /**
23008 * Determines whether the series should look for the nearest point
23009 * in both dimensions or just the x-dimension when hovering the series.
23010 * Defaults to `'xy'` for scatter series and `'x'` for most other
23011 * series. If the data has duplicate x-values, it is recommended to
23012 * set this to `'xy'` to allow hovering over all points.
23013 *
23014 * Applies only to series types using nearest neighbor search (not
23015 * direct hover) for tooltip.
23016 *
23017 * @validvalue ['x', 'xy']
23018 * @type {String}
23019 * @sample {highcharts} highcharts/series/findnearestpointby/
23020 * Different hover behaviors
23021 * @sample {highstock} highcharts/series/findnearestpointby/
23022 * Different hover behaviors
23023 * @sample {highmaps} highcharts/series/findnearestpointby/
23024 * Different hover behaviors
23025 * @since 5.0.10
23026 */
23027 findNearestPointBy: 'x'
23028
23029 }, /** @lends Highcharts.Series.prototype */ {
23030 isCartesian: true,
23031 pointClass: Point,
23032 sorted: true, // requires the data to be sorted
23033 requireSorting: true,
23034 directTouch: false,
23035 axisTypes: ['xAxis', 'yAxis'],
23036 colorCounter: 0,
23037 // each point's x and y values are stored in this.xData and this.yData
23038 parallelArrays: ['x', 'y'],
23039 coll: 'series',
23040 init: function(chart, options) {
23041 var series = this,
23042 events,
23043 chartSeries = chart.series,
23044 lastSeries;
23045
23046 /**
23047 * Read only. The chart that the series belongs to.
23048 *
23049 * @name chart
23050 * @memberOf Series
23051 * @type {Chart}
23052 */
23053 series.chart = chart;
23054
23055 /**
23056 * Read only. The series' type, like "line", "area", "column" etc. The
23057 * type in the series options anc can be altered using {@link
23058 * Series#update}.
23059 *
23060 * @name type
23061 * @memberOf Series
23062 * @type String
23063 */
23064
23065 /**
23066 * Read only. The series' current options. To update, use {@link
23067 * Series#update}.
23068 *
23069 * @name options
23070 * @memberOf Series
23071 * @type SeriesOptions
23072 */
23073 series.options = options = series.setOptions(options);
23074 series.linkedSeries = [];
23075
23076 // bind the axes
23077 series.bindAxes();
23078
23079 // set some variables
23080 extend(series, {
23081 /**
23082 * The series name as given in the options. Defaults to
23083 * "Series {n}".
23084 *
23085 * @name name
23086 * @memberOf Series
23087 * @type {String}
23088 */
23089 name: options.name,
23090 state: '',
23091 /**
23092 * Read only. The series' visibility state as set by {@link
23093 * Series#show}, {@link Series#hide}, or in the initial
23094 * configuration.
23095 *
23096 * @name visible
23097 * @memberOf Series
23098 * @type {Boolean}
23099 */
23100 visible: options.visible !== false, // true by default
23101 /**
23102 * Read only. The series' selected state as set by {@link
23103 * Highcharts.Series#select}.
23104 *
23105 * @name selected
23106 * @memberOf Series
23107 * @type {Boolean}
23108 */
23109 selected: options.selected === true // false by default
23110 });
23111
23112 // register event listeners
23113 events = options.events;
23114
23115 objectEach(events, function(event, eventType) {
23116 addEvent(series, eventType, event);
23117 });
23118 if (
23119 (events && events.click) ||
23120 (
23121 options.point &&
23122 options.point.events &&
23123 options.point.events.click
23124 ) ||
23125 options.allowPointSelect
23126 ) {
23127 chart.runTrackerClick = true;
23128 }
23129
23130 series.getColor();
23131 series.getSymbol();
23132
23133 // Set the data
23134 each(series.parallelArrays, function(key) {
23135 series[key + 'Data'] = [];
23136 });
23137 series.setData(options.data, false);
23138
23139 // Mark cartesian
23140 if (series.isCartesian) {
23141 chart.hasCartesianSeries = true;
23142 }
23143
23144 // Get the index and register the series in the chart. The index is one
23145 // more than the current latest series index (#5960).
23146 if (chartSeries.length) {
23147 lastSeries = chartSeries[chartSeries.length - 1];
23148 }
23149 series._i = pick(lastSeries && lastSeries._i, -1) + 1;
23150
23151 // Insert the series and re-order all series above the insertion point.
23152 chart.orderSeries(this.insert(chartSeries));
23153 },
23154
23155 /**
23156 * Insert the series in a collection with other series, either the chart
23157 * series or yAxis series, in the correct order according to the index
23158 * option. Used internally when adding series.
23159 *
23160 * @private
23161 * @param {Array.<Series>} collection
23162 * A collection of series, like `chart.series` or `xAxis.series`.
23163 * @returns {Number} The index of the series in the collection.
23164 */
23165 insert: function(collection) {
23166 var indexOption = this.options.index,
23167 i;
23168
23169 // Insert by index option
23170 if (isNumber(indexOption)) {
23171 i = collection.length;
23172 while (i--) {
23173 // Loop down until the interted element has higher index
23174 if (indexOption >=
23175 pick(collection[i].options.index, collection[i]._i)) {
23176 collection.splice(i + 1, 0, this);
23177 break;
23178 }
23179 }
23180 if (i === -1) {
23181 collection.unshift(this);
23182 }
23183 i = i + 1;
23184
23185 // Or just push it to the end
23186 } else {
23187 collection.push(this);
23188 }
23189 return pick(i, collection.length - 1);
23190 },
23191
23192 /**
23193 * Set the xAxis and yAxis properties of cartesian series, and register the
23194 * series in the `axis.series` array.
23195 *
23196 * @private
23197 */
23198 bindAxes: function() {
23199 var series = this,
23200 seriesOptions = series.options,
23201 chart = series.chart,
23202 axisOptions;
23203
23204 // repeat for xAxis and yAxis
23205 each(series.axisTypes || [], function(AXIS) {
23206
23207 // loop through the chart's axis objects
23208 each(chart[AXIS], function(axis) {
23209 axisOptions = axis.options;
23210
23211 // apply if the series xAxis or yAxis option mathches the number
23212 // of the axis, or if undefined, use the first axis
23213 if (
23214 seriesOptions[AXIS] === axisOptions.index ||
23215 (
23216 seriesOptions[AXIS] !== undefined &&
23217 seriesOptions[AXIS] === axisOptions.id
23218 ) ||
23219 (
23220 seriesOptions[AXIS] === undefined &&
23221 axisOptions.index === 0
23222 )
23223 ) {
23224
23225 // register this series in the axis.series lookup
23226 series.insert(axis.series);
23227
23228 // set this series.xAxis or series.yAxis reference
23229 /**
23230 * Read only. The unique xAxis object associated with the
23231 * series.
23232 *
23233 * @name xAxis
23234 * @memberOf Series
23235 * @type Axis
23236 */
23237 /**
23238 * Read only. The unique yAxis object associated with the
23239 * series.
23240 *
23241 * @name yAxis
23242 * @memberOf Series
23243 * @type Axis
23244 */
23245 series[AXIS] = axis;
23246
23247 // mark dirty for redraw
23248 axis.isDirty = true;
23249 }
23250 });
23251
23252 // The series needs an X and an Y axis
23253 if (!series[AXIS] && series.optionalAxis !== AXIS) {
23254 H.error(18, true);
23255 }
23256
23257 });
23258 },
23259
23260 /**
23261 * For simple series types like line and column, the data values are held in
23262 * arrays like xData and yData for quick lookup to find extremes and more.
23263 * For multidimensional series like bubble and map, this can be extended
23264 * with arrays like zData and valueData by adding to the
23265 * `series.parallelArrays` array.
23266 *
23267 * @private
23268 */
23269 updateParallelArrays: function(point, i) {
23270 var series = point.series,
23271 args = arguments,
23272 fn = isNumber(i) ?
23273 // Insert the value in the given position
23274 function(key) {
23275 var val = key === 'y' && series.toYData ?
23276 series.toYData(point) :
23277 point[key];
23278 series[key + 'Data'][i] = val;
23279 } :
23280 // Apply the method specified in i with the following arguments
23281 // as arguments
23282 function(key) {
23283 Array.prototype[i].apply(
23284 series[key + 'Data'],
23285 Array.prototype.slice.call(args, 2)
23286 );
23287 };
23288
23289 each(series.parallelArrays, fn);
23290 },
23291
23292 /**
23293 * Return an auto incremented x value based on the pointStart and
23294 * pointInterval options. This is only used if an x value is not given for
23295 * the point that calls autoIncrement.
23296 *
23297 * @private
23298 */
23299 autoIncrement: function() {
23300
23301 var options = this.options,
23302 xIncrement = this.xIncrement,
23303 date,
23304 pointInterval,
23305 pointIntervalUnit = options.pointIntervalUnit;
23306
23307 xIncrement = pick(xIncrement, options.pointStart, 0);
23308
23309 this.pointInterval = pointInterval = pick(
23310 this.pointInterval,
23311 options.pointInterval,
23312 1
23313 );
23314
23315 // Added code for pointInterval strings
23316 if (pointIntervalUnit) {
23317 date = new Date(xIncrement);
23318
23319 if (pointIntervalUnit === 'day') {
23320 date = +date[Date.hcSetDate](
23321 date[Date.hcGetDate]() + pointInterval
23322 );
23323 } else if (pointIntervalUnit === 'month') {
23324 date = +date[Date.hcSetMonth](
23325 date[Date.hcGetMonth]() + pointInterval
23326 );
23327 } else if (pointIntervalUnit === 'year') {
23328 date = +date[Date.hcSetFullYear](
23329 date[Date.hcGetFullYear]() + pointInterval
23330 );
23331 }
23332 pointInterval = date - xIncrement;
23333
23334 }
23335
23336 this.xIncrement = xIncrement + pointInterval;
23337 return xIncrement;
23338 },
23339
23340 /**
23341 * Set the series options by merging from the options tree. Called
23342 * internally on initiating and updating series. This function will not
23343 * redraw the series. For API usage, use {@link Series#update}.
23344 *
23345 * @param {Options.plotOptions.series} itemOptions
23346 * The series options.
23347 */
23348 setOptions: function(itemOptions) {
23349 var chart = this.chart,
23350 chartOptions = chart.options,
23351 plotOptions = chartOptions.plotOptions,
23352 userOptions = chart.userOptions || {},
23353 userPlotOptions = userOptions.plotOptions || {},
23354 typeOptions = plotOptions[this.type],
23355 options,
23356 zones;
23357
23358 this.userOptions = itemOptions;
23359
23360 // General series options take precedence over type options because
23361 // otherwise, default type options like column.animation would be
23362 // overwritten by the general option. But issues have been raised here
23363 // (#3881), and the solution may be to distinguish between default
23364 // option and userOptions like in the tooltip below.
23365 options = merge(
23366 typeOptions,
23367 plotOptions.series,
23368 itemOptions
23369 );
23370
23371 // The tooltip options are merged between global and series specific
23372 // options. Importance order asscendingly:
23373 // globals: (1)tooltip, (2)plotOptions.series, (3)plotOptions[this.type]
23374 // init userOptions with possible later updates: 4-6 like 1-3 and
23375 // (7)this series options
23376 this.tooltipOptions = merge(
23377 defaultOptions.tooltip, // 1
23378 defaultOptions.plotOptions.series &&
23379 defaultOptions.plotOptions.series.tooltip, // 2
23380 defaultOptions.plotOptions[this.type].tooltip, // 3
23381 chartOptions.tooltip.userOptions, // 4
23382 plotOptions.series && plotOptions.series.tooltip, // 5
23383 plotOptions[this.type].tooltip, // 6
23384 itemOptions.tooltip // 7
23385 );
23386
23387 // When shared tooltip, stickyTracking is true by default,
23388 // unless user says otherwise.
23389 this.stickyTracking = pick(
23390 itemOptions.stickyTracking,
23391 userPlotOptions[this.type] &&
23392 userPlotOptions[this.type].stickyTracking,
23393 userPlotOptions.series && userPlotOptions.series.stickyTracking,
23394 (
23395 this.tooltipOptions.shared && !this.noSharedTooltip ?
23396 true :
23397 options.stickyTracking
23398 )
23399 );
23400
23401 // Delete marker object if not allowed (#1125)
23402 if (typeOptions.marker === null) {
23403 delete options.marker;
23404 }
23405
23406 // Handle color zones
23407 this.zoneAxis = options.zoneAxis;
23408 zones = this.zones = (options.zones || []).slice();
23409 if (
23410 (options.negativeColor || options.negativeFillColor) &&
23411 !options.zones
23412 ) {
23413 zones.push({
23414 value: options[this.zoneAxis + 'Threshold'] ||
23415 options.threshold ||
23416 0,
23417 className: 'highcharts-negative'
23418
23419 });
23420 }
23421 if (zones.length) { // Push one extra zone for the rest
23422 if (defined(zones[zones.length - 1].value)) {
23423 zones.push({
23424
23425 });
23426 }
23427 }
23428 return options;
23429 },
23430
23431 getCyclic: function(prop, value, defaults) {
23432 var i,
23433 chart = this.chart,
23434 userOptions = this.userOptions,
23435 indexName = prop + 'Index',
23436 counterName = prop + 'Counter',
23437 len = defaults ? defaults.length : pick(
23438 chart.options.chart[prop + 'Count'],
23439 chart[prop + 'Count']
23440 ),
23441 setting;
23442
23443 if (!value) {
23444 // Pick up either the colorIndex option, or the _colorIndex after
23445 // Series.update()
23446 setting = pick(
23447 userOptions[indexName],
23448 userOptions['_' + indexName]
23449 );
23450 if (defined(setting)) { // after Series.update()
23451 i = setting;
23452 } else {
23453 // #6138
23454 if (!chart.series.length) {
23455 chart[counterName] = 0;
23456 }
23457 userOptions['_' + indexName] = i = chart[counterName] % len;
23458 chart[counterName] += 1;
23459 }
23460 if (defaults) {
23461 value = defaults[i];
23462 }
23463 }
23464 // Set the colorIndex
23465 if (i !== undefined) {
23466 this[indexName] = i;
23467 }
23468 this[prop] = value;
23469 },
23470
23471 /**
23472 * Get the series' color based on either the options or pulled from global
23473 * options.
23474 *
23475 * @return {Color} The series color.
23476 */
23477
23478 getColor: function() {
23479 this.getCyclic('color');
23480 },
23481
23482
23483 /**
23484 * Get the series' symbol based on either the options or pulled from global
23485 * options.
23486 */
23487 getSymbol: function() {
23488 var seriesMarkerOption = this.options.marker;
23489
23490 this.getCyclic(
23491 'symbol',
23492 seriesMarkerOption.symbol,
23493 this.chart.options.symbols
23494 );
23495 },
23496
23497 drawLegendSymbol: LegendSymbolMixin.drawLineMarker,
23498
23499 /**
23500 * Apply a new set of data to the series and optionally redraw it. The new
23501 * data array is passed by reference (except in case of `updatePoints`), and
23502 * may later be mutated when updating the chart data.
23503 *
23504 * Note the difference in behaviour when setting the same amount of points,
23505 * or a different amount of points, as handled by the `updatePoints`
23506 * parameter.
23507 *
23508 * @param {SeriesDataOptions} data
23509 * Takes an array of data in the same format as described under
23510 * `series.typedata` for the given series type.
23511 * @param {Boolean} [redraw=true]
23512 * Whether to redraw the chart after the series is altered. If doing
23513 * more operations on the chart, it is a good idea to set redraw to
23514 * false and call {@link Chart#redraw} after.
23515 * @param {AnimationOptions} [animation]
23516 * When the updated data is the same length as the existing data,
23517 * points will be updated by default, and animation visualizes how
23518 * the points are changed. Set false to disable animation, or a
23519 * configuration object to set duration or easing.
23520 * @param {Boolean} [updatePoints=true]
23521 * When the updated data is the same length as the existing data,
23522 * points will be updated instead of replaced. This allows updating
23523 * with animation and performs better. In this case, the original
23524 * array is not passed by reference. Set false to prevent.
23525 *
23526 * @sample highcharts/members/series-setdata/
23527 * Set new data from a button
23528 * @sample highcharts/members/series-setdata-pie/
23529 * Set data in a pie
23530 * @sample stock/members/series-setdata/
23531 * Set new data in Highstock
23532 * @sample maps/members/series-setdata/
23533 * Set new data in Highmaps
23534 */
23535 setData: function(data, redraw, animation, updatePoints) {
23536 var series = this,
23537 oldData = series.points,
23538 oldDataLength = (oldData && oldData.length) || 0,
23539 dataLength,
23540 options = series.options,
23541 chart = series.chart,
23542 firstPoint = null,
23543 xAxis = series.xAxis,
23544 i,
23545 turboThreshold = options.turboThreshold,
23546 pt,
23547 xData = this.xData,
23548 yData = this.yData,
23549 pointArrayMap = series.pointArrayMap,
23550 valueCount = pointArrayMap && pointArrayMap.length;
23551
23552 data = data || [];
23553 dataLength = data.length;
23554 redraw = pick(redraw, true);
23555
23556 // If the point count is the same as is was, just run Point.update which
23557 // is cheaper, allows animation, and keeps references to points.
23558 if (
23559 updatePoints !== false &&
23560 dataLength &&
23561 oldDataLength === dataLength &&
23562 !series.cropped &&
23563 !series.hasGroupedData &&
23564 series.visible
23565 ) {
23566 each(data, function(point, i) {
23567 // .update doesn't exist on a linked, hidden series (#3709)
23568 if (oldData[i].update && point !== options.data[i]) {
23569 oldData[i].update(point, false, null, false);
23570 }
23571 });
23572
23573 } else {
23574
23575 // Reset properties
23576 series.xIncrement = null;
23577
23578 series.colorCounter = 0; // for series with colorByPoint (#1547)
23579
23580 // Update parallel arrays
23581 each(this.parallelArrays, function(key) {
23582 series[key + 'Data'].length = 0;
23583 });
23584
23585 // In turbo mode, only one- or twodimensional arrays of numbers are
23586 // allowed. The first value is tested, and we assume that all the
23587 // rest are defined the same way. Although the 'for' loops are
23588 // similar, they are repeated inside each if-else conditional for
23589 // max performance.
23590 if (turboThreshold && dataLength > turboThreshold) {
23591
23592 // find the first non-null point
23593 i = 0;
23594 while (firstPoint === null && i < dataLength) {
23595 firstPoint = data[i];
23596 i++;
23597 }
23598
23599
23600 if (isNumber(firstPoint)) { // assume all points are numbers
23601 for (i = 0; i < dataLength; i++) {
23602 xData[i] = this.autoIncrement();
23603 yData[i] = data[i];
23604 }
23605
23606 // Assume all points are arrays when first point is
23607 } else if (isArray(firstPoint)) {
23608 if (valueCount) { // [x, low, high] or [x, o, h, l, c]
23609 for (i = 0; i < dataLength; i++) {
23610 pt = data[i];
23611 xData[i] = pt[0];
23612 yData[i] = pt.slice(1, valueCount + 1);
23613 }
23614 } else { // [x, y]
23615 for (i = 0; i < dataLength; i++) {
23616 pt = data[i];
23617 xData[i] = pt[0];
23618 yData[i] = pt[1];
23619 }
23620 }
23621 } else {
23622 // Highcharts expects configs to be numbers or arrays in
23623 // turbo mode
23624 H.error(12);
23625 }
23626 } else {
23627 for (i = 0; i < dataLength; i++) {
23628 if (data[i] !== undefined) { // stray commas in oldIE
23629 pt = {
23630 series: series
23631 };
23632 series.pointClass.prototype.applyOptions.apply(
23633 pt, [data[i]]
23634 );
23635 series.updateParallelArrays(pt, i);
23636 }
23637 }
23638 }
23639
23640 // Forgetting to cast strings to numbers is a common caveat when
23641 // handling CSV or JSON
23642 if (yData && isString(yData[0])) {
23643 H.error(14, true);
23644 }
23645
23646 series.data = [];
23647 series.options.data = series.userOptions.data = data;
23648
23649 // destroy old points
23650 i = oldDataLength;
23651 while (i--) {
23652 if (oldData[i] && oldData[i].destroy) {
23653 oldData[i].destroy();
23654 }
23655 }
23656
23657 // reset minRange (#878)
23658 if (xAxis) {
23659 xAxis.minRange = xAxis.userMinRange;
23660 }
23661
23662 // redraw
23663 series.isDirty = chart.isDirtyBox = true;
23664 series.isDirtyData = !!oldData;
23665 animation = false;
23666 }
23667
23668 // Typically for pie series, points need to be processed and generated
23669 // prior to rendering the legend
23670 if (options.legendType === 'point') {
23671 this.processData();
23672 this.generatePoints();
23673 }
23674
23675 if (redraw) {
23676 chart.redraw(animation);
23677 }
23678 },
23679
23680 /**
23681 * Internal function to process the data by cropping away unused data points
23682 * if the series is longer than the crop threshold. This saves computing
23683 * time for large series. In Highstock, this function is extended to
23684 * provide data grouping.
23685 *
23686 * @private
23687 * @param {Boolean} force
23688 * Force data grouping.
23689 */
23690 processData: function(force) {
23691 var series = this,
23692 processedXData = series.xData, // copied during slice operation
23693 processedYData = series.yData,
23694 dataLength = processedXData.length,
23695 croppedData,
23696 cropStart = 0,
23697 cropped,
23698 distance,
23699 closestPointRange,
23700 xAxis = series.xAxis,
23701 i, // loop variable
23702 options = series.options,
23703 cropThreshold = options.cropThreshold,
23704 getExtremesFromAll =
23705 series.getExtremesFromAll ||
23706 options.getExtremesFromAll, // #4599
23707 isCartesian = series.isCartesian,
23708 xExtremes,
23709 val2lin = xAxis && xAxis.val2lin,
23710 isLog = xAxis && xAxis.isLog,
23711 throwOnUnsorted = series.requireSorting,
23712 min,
23713 max;
23714
23715 // If the series data or axes haven't changed, don't go through this.
23716 // Return false to pass the message on to override methods like in data
23717 // grouping.
23718 if (
23719 isCartesian &&
23720 !series.isDirty &&
23721 !xAxis.isDirty &&
23722 !series.yAxis.isDirty &&
23723 !force
23724 ) {
23725 return false;
23726 }
23727
23728 if (xAxis) {
23729 xExtremes = xAxis.getExtremes(); // corrected for log axis (#3053)
23730 min = xExtremes.min;
23731 max = xExtremes.max;
23732 }
23733
23734 // optionally filter out points outside the plot area
23735 if (
23736 isCartesian &&
23737 series.sorted &&
23738 !getExtremesFromAll &&
23739 (!cropThreshold || dataLength > cropThreshold || series.forceCrop)
23740 ) {
23741
23742 // it's outside current extremes
23743 if (
23744 processedXData[dataLength - 1] < min ||
23745 processedXData[0] > max
23746 ) {
23747 processedXData = [];
23748 processedYData = [];
23749
23750 // only crop if it's actually spilling out
23751 } else if (
23752 processedXData[0] < min ||
23753 processedXData[dataLength - 1] > max
23754 ) {
23755 croppedData = this.cropData(
23756 series.xData,
23757 series.yData,
23758 min,
23759 max
23760 );
23761 processedXData = croppedData.xData;
23762 processedYData = croppedData.yData;
23763 cropStart = croppedData.start;
23764 cropped = true;
23765 }
23766 }
23767
23768
23769 // Find the closest distance between processed points
23770 i = processedXData.length || 1;
23771 while (--i) {
23772 distance = isLog ?
23773 val2lin(processedXData[i]) - val2lin(processedXData[i - 1]) :
23774 processedXData[i] - processedXData[i - 1];
23775
23776 if (
23777 distance > 0 &&
23778 (
23779 closestPointRange === undefined ||
23780 distance < closestPointRange
23781 )
23782 ) {
23783 closestPointRange = distance;
23784
23785 // Unsorted data is not supported by the line tooltip, as well as
23786 // data grouping and navigation in Stock charts (#725) and width
23787 // calculation of columns (#1900)
23788 } else if (distance < 0 && throwOnUnsorted) {
23789 H.error(15);
23790 throwOnUnsorted = false; // Only once
23791 }
23792 }
23793
23794 // Record the properties
23795 series.cropped = cropped; // undefined or true
23796 series.cropStart = cropStart;
23797 series.processedXData = processedXData;
23798 series.processedYData = processedYData;
23799
23800 series.closestPointRange = closestPointRange;
23801
23802 },
23803
23804 /**
23805 * Iterate over xData and crop values between min and max. Returns object
23806 * containing crop start/end cropped xData with corresponding part of yData,
23807 * dataMin and dataMax within the cropped range.
23808 *
23809 * @private
23810 */
23811 cropData: function(xData, yData, min, max) {
23812 var dataLength = xData.length,
23813 cropStart = 0,
23814 cropEnd = dataLength,
23815 // line-type series need one point outside
23816 cropShoulder = pick(this.cropShoulder, 1),
23817 i,
23818 j;
23819
23820 // iterate up to find slice start
23821 for (i = 0; i < dataLength; i++) {
23822 if (xData[i] >= min) {
23823 cropStart = Math.max(0, i - cropShoulder);
23824 break;
23825 }
23826 }
23827
23828 // proceed to find slice end
23829 for (j = i; j < dataLength; j++) {
23830 if (xData[j] > max) {
23831 cropEnd = j + cropShoulder;
23832 break;
23833 }
23834 }
23835
23836 return {
23837 xData: xData.slice(cropStart, cropEnd),
23838 yData: yData.slice(cropStart, cropEnd),
23839 start: cropStart,
23840 end: cropEnd
23841 };
23842 },
23843
23844
23845 /**
23846 * Generate the data point after the data has been processed by cropping
23847 * away unused points and optionally grouped in Highcharts Stock.
23848 *
23849 * @private
23850 */
23851 generatePoints: function() {
23852 var series = this,
23853 options = series.options,
23854 dataOptions = options.data,
23855 data = series.data,
23856 dataLength,
23857 processedXData = series.processedXData,
23858 processedYData = series.processedYData,
23859 PointClass = series.pointClass,
23860 processedDataLength = processedXData.length,
23861 cropStart = series.cropStart || 0,
23862 cursor,
23863 hasGroupedData = series.hasGroupedData,
23864 keys = options.keys,
23865 point,
23866 points = [],
23867 i;
23868
23869 if (!data && !hasGroupedData) {
23870 var arr = [];
23871 arr.length = dataOptions.length;
23872 data = series.data = arr;
23873 }
23874
23875 if (keys && hasGroupedData) {
23876 // grouped data has already applied keys (#6590)
23877 series.options.keys = false;
23878 }
23879
23880 for (i = 0; i < processedDataLength; i++) {
23881 cursor = cropStart + i;
23882 if (!hasGroupedData) {
23883 point = data[cursor];
23884 if (!point && dataOptions[cursor] !== undefined) { // #970
23885 data[cursor] = point = (new PointClass()).init(
23886 series,
23887 dataOptions[cursor],
23888 processedXData[i]
23889 );
23890 }
23891 } else {
23892 // splat the y data in case of ohlc data array
23893 point = (new PointClass()).init(
23894 series, [processedXData[i]].concat(splat(processedYData[i]))
23895 );
23896
23897 /**
23898 * Highstock only. If a point object is created by data
23899 * grouping, it doesn't reflect actual points in the raw data.
23900 * In this case, the `dataGroup` property holds information
23901 * that points back to the raw data.
23902 *
23903 * - `dataGroup.start` is the index of the first raw data point
23904 * in the group.
23905 * - `dataGroup.length` is the amount of points in the group.
23906 *
23907 * @name dataGroup
23908 * @memberOf Point
23909 * @type {Object}
23910 *
23911 */
23912 point.dataGroup = series.groupMap[i];
23913 }
23914 if (point) { // #6279
23915 point.index = cursor; // For faster access in Point.update
23916 points[i] = point;
23917 }
23918 }
23919
23920 // restore keys options (#6590)
23921 series.options.keys = keys;
23922
23923 // Hide cropped-away points - this only runs when the number of points
23924 // is above cropThreshold, or when swithching view from non-grouped
23925 // data to grouped data (#637)
23926 if (
23927 data &&
23928 (
23929 processedDataLength !== (dataLength = data.length) ||
23930 hasGroupedData
23931 )
23932 ) {
23933 for (i = 0; i < dataLength; i++) {
23934 // when has grouped data, clear all points
23935 if (i === cropStart && !hasGroupedData) {
23936 i += processedDataLength;
23937 }
23938 if (data[i]) {
23939 data[i].destroyElements();
23940 data[i].plotX = undefined; // #1003
23941 }
23942 }
23943 }
23944
23945 /**
23946 * Read only. An array containing those values converted to points, but
23947 * in case the series data length exceeds the `cropThreshold`, or if the
23948 * data is grouped, `series.data` doesn't contain all the points. It
23949 * only contains the points that have been created on demand. To
23950 * modify the data, use {@link Highcharts.Series#setData} or {@link
23951 * Highcharts.Point#update}.
23952 *
23953 * @name data
23954 * @memberOf Highcharts.Series
23955 * @see Series.points
23956 * @type {Array.<Highcharts.Point>}
23957 */
23958 series.data = data;
23959
23960 /**
23961 * An array containing all currently visible point objects. In case of
23962 * cropping, the cropped-away points are not part of this array. The
23963 * `series.points` array starts at `series.cropStart` compared to
23964 * `series.data` and `series.options.data`. If however the series data
23965 * is grouped, these can't be correlated one to one. To
23966 * modify the data, use {@link Highcharts.Series#setData} or {@link
23967 * Highcharts.Point#update}.
23968 * @name points
23969 * @memberof Series
23970 * @type {Array.<Point>}
23971 */
23972 series.points = points;
23973 },
23974
23975 /**
23976 * Calculate Y extremes for the visible data. The result is set as
23977 * `dataMin` and `dataMax` on the Series item.
23978 *
23979 * @param {Array.<Number>} [yData]
23980 * The data to inspect. Defaults to the current data within the
23981 * visible range.
23982 *
23983 */
23984 getExtremes: function(yData) {
23985 var xAxis = this.xAxis,
23986 yAxis = this.yAxis,
23987 xData = this.processedXData,
23988 yDataLength,
23989 activeYData = [],
23990 activeCounter = 0,
23991 // #2117, need to compensate for log X axis
23992 xExtremes = xAxis.getExtremes(),
23993 xMin = xExtremes.min,
23994 xMax = xExtremes.max,
23995 validValue,
23996 withinRange,
23997 x,
23998 y,
23999 i,
24000 j;
24001
24002 yData = yData || this.stackedYData || this.processedYData || [];
24003 yDataLength = yData.length;
24004
24005 for (i = 0; i < yDataLength; i++) {
24006
24007 x = xData[i];
24008 y = yData[i];
24009
24010 // For points within the visible range, including the first point
24011 // outside the visible range (#7061), consider y extremes.
24012 validValue =
24013 (isNumber(y, true) || isArray(y)) &&
24014 (!yAxis.positiveValuesOnly || (y.length || y > 0));
24015 withinRange =
24016 this.getExtremesFromAll ||
24017 this.options.getExtremesFromAll ||
24018 this.cropped ||
24019 ((xData[i + 1] || x) >= xMin && (xData[i - 1] || x) <= xMax);
24020
24021 if (validValue && withinRange) {
24022
24023 j = y.length;
24024 if (j) { // array, like ohlc or range data
24025 while (j--) {
24026 if (y[j] !== null) {
24027 activeYData[activeCounter++] = y[j];
24028 }
24029 }
24030 } else {
24031 activeYData[activeCounter++] = y;
24032 }
24033 }
24034 }
24035
24036 this.dataMin = arrayMin(activeYData);
24037 this.dataMax = arrayMax(activeYData);
24038 },
24039
24040 /**
24041 * Translate data points from raw data values to chart specific positioning
24042 * data needed later in the `drawPoints` and `drawGraph` functions. This
24043 * function can be overridden in plugins and custom series type
24044 * implementations.
24045 */
24046 translate: function() {
24047 if (!this.processedXData) { // hidden series
24048 this.processData();
24049 }
24050 this.generatePoints();
24051 var series = this,
24052 options = series.options,
24053 stacking = options.stacking,
24054 xAxis = series.xAxis,
24055 categories = xAxis.categories,
24056 yAxis = series.yAxis,
24057 points = series.points,
24058 dataLength = points.length,
24059 hasModifyValue = !!series.modifyValue,
24060 i,
24061 pointPlacement = options.pointPlacement,
24062 dynamicallyPlaced =
24063 pointPlacement === 'between' ||
24064 isNumber(pointPlacement),
24065 threshold = options.threshold,
24066 stackThreshold = options.startFromThreshold ? threshold : 0,
24067 plotX,
24068 plotY,
24069 lastPlotX,
24070 stackIndicator,
24071 closestPointRangePx = Number.MAX_VALUE;
24072
24073 // Point placement is relative to each series pointRange (#5889)
24074 if (pointPlacement === 'between') {
24075 pointPlacement = 0.5;
24076 }
24077 if (isNumber(pointPlacement)) {
24078 pointPlacement *= pick(options.pointRange || xAxis.pointRange);
24079 }
24080
24081 // Translate each point
24082 for (i = 0; i < dataLength; i++) {
24083 var point = points[i],
24084 xValue = point.x,
24085 yValue = point.y,
24086 yBottom = point.low,
24087 stack = stacking && yAxis.stacks[(
24088 series.negStacks &&
24089 yValue < (stackThreshold ? 0 : threshold) ? '-' : ''
24090 ) + series.stackKey],
24091 pointStack,
24092 stackValues;
24093
24094 // Discard disallowed y values for log axes (#3434)
24095 if (yAxis.positiveValuesOnly && yValue !== null && yValue <= 0) {
24096 point.isNull = true;
24097 }
24098
24099 // Get the plotX translation
24100 point.plotX = plotX = correctFloat( // #5236
24101 Math.min(Math.max(-1e5, xAxis.translate(
24102 xValue,
24103 0,
24104 0,
24105 0,
24106 1,
24107 pointPlacement,
24108 this.type === 'flags'
24109 )), 1e5) // #3923
24110 );
24111
24112 // Calculate the bottom y value for stacked series
24113 if (
24114 stacking &&
24115 series.visible &&
24116 !point.isNull &&
24117 stack &&
24118 stack[xValue]
24119 ) {
24120 stackIndicator = series.getStackIndicator(
24121 stackIndicator,
24122 xValue,
24123 series.index
24124 );
24125 pointStack = stack[xValue];
24126 stackValues = pointStack.points[stackIndicator.key];
24127 yBottom = stackValues[0];
24128 yValue = stackValues[1];
24129
24130 if (
24131 yBottom === stackThreshold &&
24132 stackIndicator.key === stack[xValue].base
24133 ) {
24134 yBottom = pick(threshold, yAxis.min);
24135 }
24136 if (yAxis.positiveValuesOnly && yBottom <= 0) { // #1200, #1232
24137 yBottom = null;
24138 }
24139
24140 point.total = point.stackTotal = pointStack.total;
24141 point.percentage =
24142 pointStack.total &&
24143 (point.y / pointStack.total * 100);
24144 point.stackY = yValue;
24145
24146 // Place the stack label
24147 pointStack.setOffset(
24148 series.pointXOffset || 0,
24149 series.barW || 0
24150 );
24151
24152 }
24153
24154 // Set translated yBottom or remove it
24155 point.yBottom = defined(yBottom) ?
24156 yAxis.translate(yBottom, 0, 1, 0, 1) :
24157 null;
24158
24159 // general hook, used for Highstock compare mode
24160 if (hasModifyValue) {
24161 yValue = series.modifyValue(yValue, point);
24162 }
24163
24164 // Set the the plotY value, reset it for redraws
24165 point.plotY = plotY =
24166 (typeof yValue === 'number' && yValue !== Infinity) ?
24167 Math.min(Math.max(-1e5,
24168 yAxis.translate(yValue, 0, 1, 0, 1)), 1e5) : // #3201
24169 undefined;
24170
24171 point.isInside =
24172 plotY !== undefined &&
24173 plotY >= 0 &&
24174 plotY <= yAxis.len && // #3519
24175 plotX >= 0 &&
24176 plotX <= xAxis.len;
24177
24178
24179 // Set client related positions for mouse tracking
24180 point.clientX = dynamicallyPlaced ?
24181 correctFloat(
24182 xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement)
24183 ) :
24184 plotX; // #1514, #5383, #5518
24185
24186 point.negative = point.y < (threshold || 0);
24187
24188 // some API data
24189 point.category = categories && categories[point.x] !== undefined ?
24190 categories[point.x] : point.x;
24191
24192 // Determine auto enabling of markers (#3635, #5099)
24193 if (!point.isNull) {
24194 if (lastPlotX !== undefined) {
24195 closestPointRangePx = Math.min(
24196 closestPointRangePx,
24197 Math.abs(plotX - lastPlotX)
24198 );
24199 }
24200 lastPlotX = plotX;
24201 }
24202
24203 // Find point zone
24204 point.zone = this.zones.length && point.getZone();
24205 }
24206 series.closestPointRangePx = closestPointRangePx;
24207 },
24208
24209 /**
24210 * Return the series points with null points filtered out.
24211 *
24212 * @param {Array.<Point>} [points]
24213 * The points to inspect, defaults to {@link Series.points}.
24214 * @param {Boolean} [insideOnly=false]
24215 * Whether to inspect only the points that are inside the visible
24216 * view.
24217 *
24218 * @return {Array.<Point>}
24219 * The valid points.
24220 */
24221 getValidPoints: function(points, insideOnly) {
24222 var chart = this.chart;
24223 // #3916, #5029, #5085
24224 return grep(points || this.points || [], function isValidPoint(point) {
24225 if (insideOnly && !chart.isInsidePlot(
24226 point.plotX,
24227 point.plotY,
24228 chart.inverted
24229 )) {
24230 return false;
24231 }
24232 return !point.isNull;
24233 });
24234 },
24235
24236 /**
24237 * Set the clipping for the series. For animated series it is called twice,
24238 * first to initiate animating the clip then the second time without the
24239 * animation to set the final clip.
24240 *
24241 * @private
24242 */
24243 setClip: function(animation) {
24244 var chart = this.chart,
24245 options = this.options,
24246 renderer = chart.renderer,
24247 inverted = chart.inverted,
24248 seriesClipBox = this.clipBox,
24249 clipBox = seriesClipBox || chart.clipBox,
24250 sharedClipKey =
24251 this.sharedClipKey || [
24252 '_sharedClip',
24253 animation && animation.duration,
24254 animation && animation.easing,
24255 clipBox.height,
24256 options.xAxis,
24257 options.yAxis
24258 ].join(','), // #4526
24259 clipRect = chart[sharedClipKey],
24260 markerClipRect = chart[sharedClipKey + 'm'];
24261
24262 // If a clipping rectangle with the same properties is currently present
24263 // in the chart, use that.
24264 if (!clipRect) {
24265
24266 // When animation is set, prepare the initial positions
24267 if (animation) {
24268 clipBox.width = 0;
24269 if (inverted) {
24270 clipBox.x = chart.plotSizeX;
24271 }
24272
24273 chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect(
24274 inverted ? chart.plotSizeX + 99 : -99, // include the width of the first marker
24275 inverted ? -chart.plotLeft : -chart.plotTop,
24276 99,
24277 inverted ? chart.chartWidth : chart.chartHeight
24278 );
24279 }
24280 chart[sharedClipKey] = clipRect = renderer.clipRect(clipBox);
24281 // Create hashmap for series indexes
24282 clipRect.count = {
24283 length: 0
24284 };
24285
24286 }
24287 if (animation) {
24288 if (!clipRect.count[this.index]) {
24289 clipRect.count[this.index] = true;
24290 clipRect.count.length += 1;
24291 }
24292 }
24293
24294 if (options.clip !== false) {
24295 this.group.clip(animation || seriesClipBox ? clipRect : chart.clipRect);
24296 this.markerGroup.clip(markerClipRect);
24297 this.sharedClipKey = sharedClipKey;
24298 }
24299
24300 // Remove the shared clipping rectangle when all series are shown
24301 if (!animation) {
24302 if (clipRect.count[this.index]) {
24303 delete clipRect.count[this.index];
24304 clipRect.count.length -= 1;
24305 }
24306
24307 if (clipRect.count.length === 0 && sharedClipKey && chart[sharedClipKey]) {
24308 if (!seriesClipBox) {
24309 chart[sharedClipKey] = chart[sharedClipKey].destroy();
24310 }
24311 if (chart[sharedClipKey + 'm']) {
24312 chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();
24313 }
24314 }
24315 }
24316 },
24317
24318 /**
24319 * Animate in the series. Called internally twice. First with the `init`
24320 * parameter set to true, which sets up the initial state of the animation.
24321 * Then when ready, it is called with the `init` parameter undefined, in
24322 * order to perform the actual animation. After the second run, the function
24323 * is removed.
24324 *
24325 * @param {Boolean} init
24326 * Initialize the animation.
24327 */
24328 animate: function(init) {
24329 var series = this,
24330 chart = series.chart,
24331 clipRect,
24332 animation = animObject(series.options.animation),
24333 sharedClipKey;
24334
24335 // Initialize the animation. Set up the clipping rectangle.
24336 if (init) {
24337
24338 series.setClip(animation);
24339
24340 // Run the animation
24341 } else {
24342 sharedClipKey = this.sharedClipKey;
24343 clipRect = chart[sharedClipKey];
24344 if (clipRect) {
24345 clipRect.animate({
24346 width: chart.plotSizeX,
24347 x: 0
24348 }, animation);
24349 }
24350 if (chart[sharedClipKey + 'm']) {
24351 chart[sharedClipKey + 'm'].animate({
24352 width: chart.plotSizeX + 99,
24353 x: 0
24354 }, animation);
24355 }
24356
24357 // Delete this function to allow it only once
24358 series.animate = null;
24359
24360 }
24361 },
24362
24363 /**
24364 * This runs after animation to land on the final plot clipping.
24365 *
24366 * @private
24367 */
24368 afterAnimate: function() {
24369 this.setClip();
24370 fireEvent(this, 'afterAnimate');
24371 this.finishedAnimating = true;
24372 },
24373
24374 /**
24375 * Draw the markers for line-like series types, and columns or other
24376 * graphical representation for {@link Point} objects for other series
24377 * types. The resulting element is typically stored as {@link
24378 * Point.graphic}, and is created on the first call and updated and moved on
24379 * subsequent calls.
24380 */
24381 drawPoints: function() {
24382 var series = this,
24383 points = series.points,
24384 chart = series.chart,
24385 i,
24386 point,
24387 symbol,
24388 graphic,
24389 options = series.options,
24390 seriesMarkerOptions = options.marker,
24391 pointMarkerOptions,
24392 hasPointMarker,
24393 enabled,
24394 isInside,
24395 markerGroup = series[series.specialGroup] || series.markerGroup,
24396 xAxis = series.xAxis,
24397 markerAttribs,
24398 globallyEnabled = pick(
24399 seriesMarkerOptions.enabled,
24400 xAxis.isRadial ? true : null,
24401 // Use larger or equal as radius is null in bubbles (#6321)
24402 series.closestPointRangePx >= 2 * seriesMarkerOptions.radius
24403 );
24404
24405 if (seriesMarkerOptions.enabled !== false || series._hasPointMarkers) {
24406
24407 for (i = 0; i < points.length; i++) {
24408 point = points[i];
24409 graphic = point.graphic;
24410 pointMarkerOptions = point.marker || {};
24411 hasPointMarker = !!point.marker;
24412 enabled = (globallyEnabled && pointMarkerOptions.enabled === undefined) || pointMarkerOptions.enabled;
24413 isInside = point.isInside;
24414
24415 // only draw the point if y is defined
24416 if (enabled && !point.isNull) {
24417
24418 // Shortcuts
24419 symbol = pick(pointMarkerOptions.symbol, series.symbol);
24420 point.hasImage = symbol.indexOf('url') === 0;
24421
24422 markerAttribs = series.markerAttribs(
24423 point,
24424 point.selected && 'select'
24425 );
24426
24427 if (graphic) { // update
24428 graphic[isInside ? 'show' : 'hide'](true) // Since the marker group isn't clipped, each individual marker must be toggled
24429 .animate(markerAttribs);
24430 } else if (isInside && (markerAttribs.width > 0 || point.hasImage)) {
24431
24432 /**
24433 * The graphic representation of the point. Typically
24434 * this is a simple shape, like a `rect` for column
24435 * charts or `path` for line markers, but for some
24436 * complex series types like boxplot or 3D charts, the
24437 * graphic may be a `g` element containing other shapes.
24438 * The graphic is generated the first time {@link
24439 * Series#drawPoints} runs, and updated and moved on
24440 * subsequent runs.
24441 *
24442 * @memberof Point
24443 * @name graphic
24444 * @type {SVGElement}
24445 */
24446 point.graphic = graphic = chart.renderer.symbol(
24447 symbol,
24448 markerAttribs.x,
24449 markerAttribs.y,
24450 markerAttribs.width,
24451 markerAttribs.height,
24452 hasPointMarker ? pointMarkerOptions : seriesMarkerOptions
24453 )
24454 .add(markerGroup);
24455 }
24456
24457
24458
24459 if (graphic) {
24460 graphic.addClass(point.getClassName(), true);
24461 }
24462
24463 } else if (graphic) {
24464 point.graphic = graphic.destroy(); // #1269
24465 }
24466 }
24467 }
24468
24469 },
24470
24471 /**
24472 * Get non-presentational attributes for a point. Used internally for both
24473 * styled mode and classic. Can be overridden for different series types.
24474 *
24475 * @see Series#pointAttribs
24476 *
24477 * @param {Point} point
24478 * The Point to inspect.
24479 * @param {String} [state]
24480 * The state, can be either `hover`, `select` or undefined.
24481 *
24482 * @return {SVGAttributes}
24483 * A hash containing those attributes that are not settable from
24484 * CSS.
24485 */
24486 markerAttribs: function(point, state) {
24487 var seriesMarkerOptions = this.options.marker,
24488 seriesStateOptions,
24489 pointMarkerOptions = point.marker || {},
24490 pointStateOptions,
24491 radius = pick(
24492 pointMarkerOptions.radius,
24493 seriesMarkerOptions.radius
24494 ),
24495 attribs;
24496
24497 // Handle hover and select states
24498 if (state) {
24499 seriesStateOptions = seriesMarkerOptions.states[state];
24500 pointStateOptions = pointMarkerOptions.states &&
24501 pointMarkerOptions.states[state];
24502
24503 radius = pick(
24504 pointStateOptions && pointStateOptions.radius,
24505 seriesStateOptions && seriesStateOptions.radius,
24506 radius + (seriesStateOptions && seriesStateOptions.radiusPlus || 0)
24507 );
24508 }
24509
24510 if (point.hasImage) {
24511 radius = 0; // and subsequently width and height is not set
24512 }
24513
24514 attribs = {
24515 x: Math.floor(point.plotX) - radius, // Math.floor for #1843
24516 y: point.plotY - radius
24517 };
24518
24519 if (radius) {
24520 attribs.width = attribs.height = 2 * radius;
24521 }
24522
24523 return attribs;
24524
24525 },
24526
24527
24528 /**
24529 * Clear DOM objects and free up memory.
24530 *
24531 * @private
24532 */
24533 destroy: function() {
24534 var series = this,
24535 chart = series.chart,
24536 issue134 = /AppleWebKit\/533/.test(win.navigator.userAgent),
24537 destroy,
24538 i,
24539 data = series.data || [],
24540 point,
24541 axis;
24542
24543 // add event hook
24544 fireEvent(series, 'destroy');
24545
24546 // remove all events
24547 removeEvent(series);
24548
24549 // erase from axes
24550 each(series.axisTypes || [], function(AXIS) {
24551 axis = series[AXIS];
24552 if (axis && axis.series) {
24553 erase(axis.series, series);
24554 axis.isDirty = axis.forceRedraw = true;
24555 }
24556 });
24557
24558 // remove legend items
24559 if (series.legendItem) {
24560 series.chart.legend.destroyItem(series);
24561 }
24562
24563 // destroy all points with their elements
24564 i = data.length;
24565 while (i--) {
24566 point = data[i];
24567 if (point && point.destroy) {
24568 point.destroy();
24569 }
24570 }
24571 series.points = null;
24572
24573 // Clear the animation timeout if we are destroying the series during initial animation
24574 clearTimeout(series.animationTimeout);
24575
24576 // Destroy all SVGElements associated to the series
24577 objectEach(series, function(val, prop) {
24578 if (val instanceof SVGElement && !val.survive) { // Survive provides a hook for not destroying
24579
24580 // issue 134 workaround
24581 destroy = issue134 && prop === 'group' ?
24582 'hide' :
24583 'destroy';
24584
24585 val[destroy]();
24586 }
24587 });
24588
24589 // remove from hoverSeries
24590 if (chart.hoverSeries === series) {
24591 chart.hoverSeries = null;
24592 }
24593 erase(chart.series, series);
24594 chart.orderSeries();
24595
24596 // clear all members
24597 objectEach(series, function(val, prop) {
24598 delete series[prop];
24599 });
24600 },
24601
24602 /**
24603 * Get the graph path.
24604 *
24605 * @private
24606 */
24607 getGraphPath: function(points, nullsAsZeroes, connectCliffs) {
24608 var series = this,
24609 options = series.options,
24610 step = options.step,
24611 reversed,
24612 graphPath = [],
24613 xMap = [],
24614 gap;
24615
24616 points = points || series.points;
24617
24618 // Bottom of a stack is reversed
24619 reversed = points.reversed;
24620 if (reversed) {
24621 points.reverse();
24622 }
24623 // Reverse the steps (#5004)
24624 step = {
24625 right: 1,
24626 center: 2
24627 }[step] || (step && 3);
24628 if (step && reversed) {
24629 step = 4 - step;
24630 }
24631
24632 // Remove invalid points, especially in spline (#5015)
24633 if (options.connectNulls && !nullsAsZeroes && !connectCliffs) {
24634 points = this.getValidPoints(points);
24635 }
24636
24637 // Build the line
24638 each(points, function(point, i) {
24639
24640 var plotX = point.plotX,
24641 plotY = point.plotY,
24642 lastPoint = points[i - 1],
24643 pathToPoint; // the path to this point from the previous
24644
24645 if ((point.leftCliff || (lastPoint && lastPoint.rightCliff)) && !connectCliffs) {
24646 gap = true; // ... and continue
24647 }
24648
24649 // Line series, nullsAsZeroes is not handled
24650 if (point.isNull && !defined(nullsAsZeroes) && i > 0) {
24651 gap = !options.connectNulls;
24652
24653 // Area series, nullsAsZeroes is set
24654 } else if (point.isNull && !nullsAsZeroes) {
24655 gap = true;
24656
24657 } else {
24658
24659 if (i === 0 || gap) {
24660 pathToPoint = ['M', point.plotX, point.plotY];
24661
24662 } else if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
24663
24664 pathToPoint = series.getPointSpline(points, point, i);
24665
24666 } else if (step) {
24667
24668 if (step === 1) { // right
24669 pathToPoint = [
24670 'L',
24671 lastPoint.plotX,
24672 plotY
24673 ];
24674
24675 } else if (step === 2) { // center
24676 pathToPoint = [
24677 'L',
24678 (lastPoint.plotX + plotX) / 2,
24679 lastPoint.plotY,
24680 'L',
24681 (lastPoint.plotX + plotX) / 2,
24682 plotY
24683 ];
24684
24685 } else {
24686 pathToPoint = [
24687 'L',
24688 plotX,
24689 lastPoint.plotY
24690 ];
24691 }
24692 pathToPoint.push('L', plotX, plotY);
24693
24694 } else {
24695 // normal line to next point
24696 pathToPoint = [
24697 'L',
24698 plotX,
24699 plotY
24700 ];
24701 }
24702
24703 // Prepare for animation. When step is enabled, there are two path nodes for each x value.
24704 xMap.push(point.x);
24705 if (step) {
24706 xMap.push(point.x);
24707 }
24708
24709 graphPath.push.apply(graphPath, pathToPoint);
24710 gap = false;
24711 }
24712 });
24713
24714 graphPath.xMap = xMap;
24715 series.graphPath = graphPath;
24716
24717 return graphPath;
24718
24719 },
24720
24721 /**
24722 * Draw the graph. Called internally when rendering line-like series types.
24723 * The first time it generates the `series.graph` item and optionally other
24724 * series-wide items like `series.area` for area charts. On subsequent calls
24725 * these items are updated with new positions and attributes.
24726 */
24727 drawGraph: function() {
24728 var series = this,
24729 options = this.options,
24730 graphPath = (this.gappedPath || this.getGraphPath).call(this),
24731 props = [
24732 [
24733 'graph',
24734 'highcharts-graph'
24735
24736 ]
24737 ];
24738
24739 // Add the zone properties if any
24740 each(this.zones, function(zone, i) {
24741 props.push([
24742 'zone-graph-' + i,
24743 'highcharts-graph highcharts-zone-graph-' + i + ' ' + (zone.className || '')
24744
24745 ]);
24746 });
24747
24748 // Draw the graph
24749 each(props, function(prop, i) {
24750 var graphKey = prop[0],
24751 graph = series[graphKey],
24752 attribs;
24753
24754 if (graph) {
24755 graph.endX = series.preventGraphAnimation ?
24756 null :
24757 graphPath.xMap;
24758 graph.animate({
24759 d: graphPath
24760 });
24761
24762 } else if (graphPath.length) { // #1487
24763
24764 series[graphKey] = series.chart.renderer.path(graphPath)
24765 .addClass(prop[1])
24766 .attr({
24767 zIndex: 1
24768 }) // #1069
24769 .add(series.group);
24770
24771
24772 }
24773
24774 // Helpers for animation
24775 if (graph) {
24776 graph.startX = graphPath.xMap;
24777 graph.isArea = graphPath.isArea; // For arearange animation
24778 }
24779 });
24780 },
24781
24782 /**
24783 * Clip the graphs into zones for colors and styling.
24784 *
24785 * @private
24786 */
24787 applyZones: function() {
24788 var series = this,
24789 chart = this.chart,
24790 renderer = chart.renderer,
24791 zones = this.zones,
24792 translatedFrom,
24793 translatedTo,
24794 clips = this.clips || [],
24795 clipAttr,
24796 graph = this.graph,
24797 area = this.area,
24798 chartSizeMax = Math.max(chart.chartWidth, chart.chartHeight),
24799 axis = this[(this.zoneAxis || 'y') + 'Axis'],
24800 extremes,
24801 reversed,
24802 inverted = chart.inverted,
24803 horiz,
24804 pxRange,
24805 pxPosMin,
24806 pxPosMax,
24807 ignoreZones = false;
24808
24809 if (zones.length && (graph || area) && axis && axis.min !== undefined) {
24810 reversed = axis.reversed;
24811 horiz = axis.horiz;
24812 // The use of the Color Threshold assumes there are no gaps
24813 // so it is safe to hide the original graph and area
24814 if (graph) {
24815 graph.hide();
24816 }
24817 if (area) {
24818 area.hide();
24819 }
24820
24821 // Create the clips
24822 extremes = axis.getExtremes();
24823 each(zones, function(threshold, i) {
24824
24825 translatedFrom = reversed ?
24826 (horiz ? chart.plotWidth : 0) :
24827 (horiz ? 0 : axis.toPixels(extremes.min));
24828 translatedFrom = Math.min(Math.max(pick(translatedTo, translatedFrom), 0), chartSizeMax);
24829 translatedTo = Math.min(Math.max(Math.round(axis.toPixels(pick(threshold.value, extremes.max), true)), 0), chartSizeMax);
24830
24831 if (ignoreZones) {
24832 translatedFrom = translatedTo = axis.toPixels(extremes.max);
24833 }
24834
24835 pxRange = Math.abs(translatedFrom - translatedTo);
24836 pxPosMin = Math.min(translatedFrom, translatedTo);
24837 pxPosMax = Math.max(translatedFrom, translatedTo);
24838 if (axis.isXAxis) {
24839 clipAttr = {
24840 x: inverted ? pxPosMax : pxPosMin,
24841 y: 0,
24842 width: pxRange,
24843 height: chartSizeMax
24844 };
24845 if (!horiz) {
24846 clipAttr.x = chart.plotHeight - clipAttr.x;
24847 }
24848 } else {
24849 clipAttr = {
24850 x: 0,
24851 y: inverted ? pxPosMax : pxPosMin,
24852 width: chartSizeMax,
24853 height: pxRange
24854 };
24855 if (horiz) {
24856 clipAttr.y = chart.plotWidth - clipAttr.y;
24857 }
24858 }
24859
24860
24861
24862 if (clips[i]) {
24863 clips[i].animate(clipAttr);
24864 } else {
24865 clips[i] = renderer.clipRect(clipAttr);
24866
24867 if (graph) {
24868 series['zone-graph-' + i].clip(clips[i]);
24869 }
24870
24871 if (area) {
24872 series['zone-area-' + i].clip(clips[i]);
24873 }
24874 }
24875 // if this zone extends out of the axis, ignore the others
24876 ignoreZones = threshold.value > extremes.max;
24877 });
24878 this.clips = clips;
24879 }
24880 },
24881
24882 /**
24883 * Initialize and perform group inversion on series.group and
24884 * series.markerGroup.
24885 *
24886 * @private
24887 */
24888 invertGroups: function(inverted) {
24889 var series = this,
24890 chart = series.chart,
24891 remover;
24892
24893 function setInvert() {
24894 each(['group', 'markerGroup'], function(groupName) {
24895 if (series[groupName]) {
24896
24897 // VML/HTML needs explicit attributes for flipping
24898 if (chart.renderer.isVML) {
24899 series[groupName].attr({
24900 width: series.yAxis.len,
24901 height: series.xAxis.len
24902 });
24903 }
24904
24905 series[groupName].width = series.yAxis.len;
24906 series[groupName].height = series.xAxis.len;
24907 series[groupName].invert(inverted);
24908 }
24909 });
24910 }
24911
24912 // Pie, go away (#1736)
24913 if (!series.xAxis) {
24914 return;
24915 }
24916
24917 // A fixed size is needed for inversion to work
24918 remover = addEvent(chart, 'resize', setInvert);
24919 addEvent(series, 'destroy', remover);
24920
24921 // Do it now
24922 setInvert(inverted); // do it now
24923
24924 // On subsequent render and redraw, just do setInvert without setting up events again
24925 series.invertGroups = setInvert;
24926 },
24927
24928 /**
24929 * General abstraction for creating plot groups like series.group,
24930 * series.dataLabelsGroup and series.markerGroup. On subsequent calls, the
24931 * group will only be adjusted to the updated plot size.
24932 *
24933 * @private
24934 */
24935 plotGroup: function(prop, name, visibility, zIndex, parent) {
24936 var group = this[prop],
24937 isNew = !group;
24938
24939 // Generate it on first call
24940 if (isNew) {
24941 this[prop] = group = this.chart.renderer.g()
24942 .attr({
24943 zIndex: zIndex || 0.1 // IE8 and pointer logic use this
24944 })
24945 .add(parent);
24946
24947 }
24948
24949 // Add the class names, and replace existing ones as response to
24950 // Series.update (#6660)
24951 group.addClass(
24952 (
24953 'highcharts-' + name +
24954 ' highcharts-series-' + this.index +
24955 ' highcharts-' + this.type + '-series ' +
24956 (
24957 defined(this.colorIndex) ?
24958 'highcharts-color-' + this.colorIndex + ' ' :
24959 ''
24960 ) +
24961 (this.options.className || '') +
24962 (group.hasClass('highcharts-tracker') ? ' highcharts-tracker' : '')
24963 ),
24964 true
24965 );
24966
24967 // Place it on first and subsequent (redraw) calls
24968 group.attr({
24969 visibility: visibility
24970 })[isNew ? 'attr' : 'animate'](
24971 this.getPlotBox()
24972 );
24973 return group;
24974 },
24975
24976 /**
24977 * Get the translation and scale for the plot area of this series.
24978 */
24979 getPlotBox: function() {
24980 var chart = this.chart,
24981 xAxis = this.xAxis,
24982 yAxis = this.yAxis;
24983
24984 // Swap axes for inverted (#2339)
24985 if (chart.inverted) {
24986 xAxis = yAxis;
24987 yAxis = this.xAxis;
24988 }
24989 return {
24990 translateX: xAxis ? xAxis.left : chart.plotLeft,
24991 translateY: yAxis ? yAxis.top : chart.plotTop,
24992 scaleX: 1, // #1623
24993 scaleY: 1
24994 };
24995 },
24996
24997 /**
24998 * Render the graph and markers. Called internally when first rendering and
24999 * later when redrawing the chart. This function can be extended in plugins,
25000 * but normally shouldn't be called directly.
25001 */
25002 render: function() {
25003 var series = this,
25004 chart = series.chart,
25005 group,
25006 options = series.options,
25007 // Animation doesn't work in IE8 quirks when the group div is
25008 // hidden, and looks bad in other oldIE
25009 animDuration = (!!series.animate &&
25010 chart.renderer.isSVG &&
25011 animObject(options.animation).duration
25012 ),
25013 visibility = series.visible ? 'inherit' : 'hidden', // #2597
25014 zIndex = options.zIndex,
25015 hasRendered = series.hasRendered,
25016 chartSeriesGroup = chart.seriesGroup,
25017 inverted = chart.inverted;
25018
25019 // the group
25020 group = series.plotGroup(
25021 'group',
25022 'series',
25023 visibility,
25024 zIndex,
25025 chartSeriesGroup
25026 );
25027
25028 series.markerGroup = series.plotGroup(
25029 'markerGroup',
25030 'markers',
25031 visibility,
25032 zIndex,
25033 chartSeriesGroup
25034 );
25035
25036 // initiate the animation
25037 if (animDuration) {
25038 series.animate(true);
25039 }
25040
25041 // SVGRenderer needs to know this before drawing elements (#1089, #1795)
25042 group.inverted = series.isCartesian ? inverted : false;
25043
25044 // draw the graph if any
25045 if (series.drawGraph) {
25046 series.drawGraph();
25047 series.applyZones();
25048 }
25049
25050 /* each(series.points, function (point) {
25051 if (point.redraw) {
25052 point.redraw();
25053 }
25054 });*/
25055
25056 // draw the data labels (inn pies they go before the points)
25057 if (series.drawDataLabels) {
25058 series.drawDataLabels();
25059 }
25060
25061 // draw the points
25062 if (series.visible) {
25063 series.drawPoints();
25064 }
25065
25066
25067 // draw the mouse tracking area
25068 if (
25069 series.drawTracker &&
25070 series.options.enableMouseTracking !== false
25071 ) {
25072 series.drawTracker();
25073 }
25074
25075 // Handle inverted series and tracker groups
25076 series.invertGroups(inverted);
25077
25078 // Initial clipping, must be defined after inverting groups for VML.
25079 // Applies to columns etc. (#3839).
25080 if (options.clip !== false && !series.sharedClipKey && !hasRendered) {
25081 group.clip(chart.clipRect);
25082 }
25083
25084 // Run the animation
25085 if (animDuration) {
25086 series.animate();
25087 }
25088
25089 // Call the afterAnimate function on animation complete (but don't
25090 // overwrite the animation.complete option which should be available to
25091 // the user).
25092 if (!hasRendered) {
25093 series.animationTimeout = syncTimeout(function() {
25094 series.afterAnimate();
25095 }, animDuration);
25096 }
25097
25098 series.isDirty = false; // means data is in accordance with what you see
25099 // (See #322) series.isDirty = series.isDirtyData = false; // means
25100 // data is in accordance with what you see
25101 series.hasRendered = true;
25102 },
25103
25104 /**
25105 * Redraw the series. This function is called internally from `chart.redraw`
25106 * and normally shouldn't be called directly.
25107 *
25108 * @private
25109 */
25110 redraw: function() {
25111 var series = this,
25112 chart = series.chart,
25113 // cache it here as it is set to false in render, but used after
25114 wasDirty = series.isDirty || series.isDirtyData,
25115 group = series.group,
25116 xAxis = series.xAxis,
25117 yAxis = series.yAxis;
25118
25119 // reposition on resize
25120 if (group) {
25121 if (chart.inverted) {
25122 group.attr({
25123 width: chart.plotWidth,
25124 height: chart.plotHeight
25125 });
25126 }
25127
25128 group.animate({
25129 translateX: pick(xAxis && xAxis.left, chart.plotLeft),
25130 translateY: pick(yAxis && yAxis.top, chart.plotTop)
25131 });
25132 }
25133
25134 series.translate();
25135 series.render();
25136 if (wasDirty) { // #3868, #3945
25137 delete this.kdTree;
25138 }
25139 },
25140
25141 kdAxisArray: ['clientX', 'plotY'],
25142
25143 searchPoint: function(e, compareX) {
25144 var series = this,
25145 xAxis = series.xAxis,
25146 yAxis = series.yAxis,
25147 inverted = series.chart.inverted;
25148
25149 return this.searchKDTree({
25150 clientX: inverted ?
25151 xAxis.len - e.chartY + xAxis.pos : e.chartX - xAxis.pos,
25152 plotY: inverted ?
25153 yAxis.len - e.chartX + yAxis.pos : e.chartY - yAxis.pos
25154 }, compareX);
25155 },
25156
25157 /**
25158 * Build the k-d-tree that is used by mouse and touch interaction to get the
25159 * closest point. Line-like series typically have a one-dimensional tree
25160 * where points are searched along the X axis, while scatter-like series
25161 * typically search in two dimensions, X and Y.
25162 *
25163 * @private
25164 */
25165 buildKDTree: function() {
25166
25167 // Prevent multiple k-d-trees from being built simultaneously (#6235)
25168 this.buildingKdTree = true;
25169
25170 var series = this,
25171 dimensions = series.options.findNearestPointBy.indexOf('y') > -1 ?
25172 2 : 1;
25173
25174 // Internal function
25175 function _kdtree(points, depth, dimensions) {
25176 var axis,
25177 median,
25178 length = points && points.length;
25179
25180 if (length) {
25181
25182 // alternate between the axis
25183 axis = series.kdAxisArray[depth % dimensions];
25184
25185 // sort point array
25186 points.sort(function(a, b) {
25187 return a[axis] - b[axis];
25188 });
25189
25190 median = Math.floor(length / 2);
25191
25192 // build and return nod
25193 return {
25194 point: points[median],
25195 left: _kdtree(
25196 points.slice(0, median), depth + 1, dimensions
25197 ),
25198 right: _kdtree(
25199 points.slice(median + 1), depth + 1, dimensions
25200 )
25201 };
25202
25203 }
25204 }
25205
25206 // Start the recursive build process with a clone of the points array
25207 // and null points filtered out (#3873)
25208 function startRecursive() {
25209 series.kdTree = _kdtree(
25210 series.getValidPoints(
25211 null,
25212 // For line-type series restrict to plot area, but
25213 // column-type series not (#3916, #4511)
25214 !series.directTouch
25215 ),
25216 dimensions,
25217 dimensions
25218 );
25219 series.buildingKdTree = false;
25220 }
25221 delete series.kdTree;
25222
25223 // For testing tooltips, don't build async
25224 syncTimeout(startRecursive, series.options.kdNow ? 0 : 1);
25225 },
25226
25227 searchKDTree: function(point, compareX) {
25228 var series = this,
25229 kdX = this.kdAxisArray[0],
25230 kdY = this.kdAxisArray[1],
25231 kdComparer = compareX ? 'distX' : 'dist',
25232 kdDimensions = series.options.findNearestPointBy.indexOf('y') > -1 ?
25233 2 : 1;
25234
25235 // Set the one and two dimensional distance on the point object
25236 function setDistance(p1, p2) {
25237 var x = (defined(p1[kdX]) && defined(p2[kdX])) ?
25238 Math.pow(p1[kdX] - p2[kdX], 2) :
25239 null,
25240 y = (defined(p1[kdY]) && defined(p2[kdY])) ?
25241 Math.pow(p1[kdY] - p2[kdY], 2) :
25242 null,
25243 r = (x || 0) + (y || 0);
25244
25245 p2.dist = defined(r) ? Math.sqrt(r) : Number.MAX_VALUE;
25246 p2.distX = defined(x) ? Math.sqrt(x) : Number.MAX_VALUE;
25247 }
25248
25249 function _search(search, tree, depth, dimensions) {
25250 var point = tree.point,
25251 axis = series.kdAxisArray[depth % dimensions],
25252 tdist,
25253 sideA,
25254 sideB,
25255 ret = point,
25256 nPoint1,
25257 nPoint2;
25258
25259 setDistance(search, point);
25260
25261 // Pick side based on distance to splitting point
25262 tdist = search[axis] - point[axis];
25263 sideA = tdist < 0 ? 'left' : 'right';
25264 sideB = tdist < 0 ? 'right' : 'left';
25265
25266 // End of tree
25267 if (tree[sideA]) {
25268 nPoint1 = _search(search, tree[sideA], depth + 1, dimensions);
25269
25270 ret = (nPoint1[kdComparer] < ret[kdComparer] ? nPoint1 : point);
25271 }
25272 if (tree[sideB]) {
25273 // compare distance to current best to splitting point to decide
25274 // wether to check side B or not
25275 if (Math.sqrt(tdist * tdist) < ret[kdComparer]) {
25276 nPoint2 = _search(
25277 search,
25278 tree[sideB],
25279 depth + 1,
25280 dimensions
25281 );
25282 ret = nPoint2[kdComparer] < ret[kdComparer] ?
25283 nPoint2 :
25284 ret;
25285 }
25286 }
25287
25288 return ret;
25289 }
25290
25291 if (!this.kdTree && !this.buildingKdTree) {
25292 this.buildKDTree();
25293 }
25294
25295 if (this.kdTree) {
25296 return _search(point, this.kdTree, kdDimensions, kdDimensions);
25297 }
25298 }
25299
25300 }); // end Series prototype
25301
25302 /**
25303 * A line series displays information as a series of data points connected by
25304 * straight line segments.
25305 *
25306 * @sample {highcharts} highcharts/demo/line-basic/ Line chart
25307 * @sample {highstock} stock/demo/basic-line/ Line chart
25308 *
25309 * @extends plotOptions.series
25310 * @product highcharts highstock
25311 * @apioption plotOptions.line
25312 */
25313
25314 /**
25315 * A `line` series. If the [type](#series.line.type) option is not
25316 * specified, it is inherited from [chart.type](#chart.type).
25317 *
25318 * For options that apply to multiple series, it is recommended to add
25319 * them to the [plotOptions.series](#plotOptions.series) options structure.
25320 * To apply to all series of this specific type, apply it to [plotOptions.
25321 * line](#plotOptions.line).
25322 *
25323 * @type {Object}
25324 * @extends series,plotOptions.line
25325 * @excluding dataParser,dataURL
25326 * @product highcharts highstock
25327 * @apioption series.line
25328 */
25329
25330 /**
25331 * An array of data points for the series. For the `line` series type,
25332 * points can be given in the following ways:
25333 *
25334 * 1. An array of numerical values. In this case, the numerical values
25335 * will be interpreted as `y` options. The `x` values will be automatically
25336 * calculated, either starting at 0 and incremented by 1, or from `pointStart`
25337 * and `pointInterval` given in the series options. If the axis has
25338 * categories, these will be used. Example:
25339 *
25340 * ```js
25341 * data: [0, 5, 3, 5]
25342 * ```
25343 *
25344 * 2. An array of arrays with 2 values. In this case, the values correspond
25345 * to `x,y`. If the first value is a string, it is applied as the name
25346 * of the point, and the `x` value is inferred.
25347 *
25348 * ```js
25349 * data: [
25350 * [0, 1],
25351 * [1, 2],
25352 * [2, 8]
25353 * ]
25354 * ```
25355 *
25356 * 3. An array of objects with named values. The objects are point
25357 * configuration objects as seen below. If the total number of data
25358 * points exceeds the series' [turboThreshold](#series.line.turboThreshold),
25359 * this option is not available.
25360 *
25361 * ```js
25362 * data: [{
25363 * x: 1,
25364 * y: 9,
25365 * name: "Point2",
25366 * color: "#00FF00"
25367 * }, {
25368 * x: 1,
25369 * y: 6,
25370 * name: "Point1",
25371 * color: "#FF00FF"
25372 * }]
25373 * ```
25374 *
25375 * @type {Array<Object|Array|Number>}
25376 * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
25377 * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
25378 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
25379 * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
25380 * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
25381 * @apioption series.line.data
25382 */
25383
25384 /**
25385 * An additional, individual class name for the data point's graphic
25386 * representation.
25387 *
25388 * @type {String}
25389 * @since 5.0.0
25390 * @product highcharts
25391 * @apioption series.line.data.className
25392 */
25393
25394 /**
25395 * Individual color for the point. By default the color is pulled from
25396 * the global `colors` array.
25397 *
25398 * In styled mode, the `color` option doesn't take effect. Instead, use
25399 * `colorIndex`.
25400 *
25401 * @type {Color}
25402 * @sample {highcharts} highcharts/point/color/ Mark the highest point
25403 * @default undefined
25404 * @product highcharts highstock
25405 * @apioption series.line.data.color
25406 */
25407
25408 /**
25409 * Styled mode only. A specific color index to use for the point, so its
25410 * graphic representations are given the class name
25411 * `highcharts-color-{n}`.
25412 *
25413 * @type {Number}
25414 * @since 5.0.0
25415 * @product highcharts
25416 * @apioption series.line.data.colorIndex
25417 */
25418
25419 /**
25420 * Individual data label for each point. The options are the same as
25421 * the ones for [plotOptions.series.dataLabels](#plotOptions.series.
25422 * dataLabels)
25423 *
25424 * @type {Object}
25425 * @sample {highcharts} highcharts/point/datalabels/ Show a label for the last value
25426 * @sample {highstock} highcharts/point/datalabels/ Show a label for the last value
25427 * @product highcharts highstock
25428 * @apioption series.line.data.dataLabels
25429 */
25430
25431 /**
25432 * A description of the point to add to the screen reader information
25433 * about the point. Requires the Accessibility module.
25434 *
25435 * @type {String}
25436 * @default undefined
25437 * @since 5.0.0
25438 * @apioption series.line.data.description
25439 */
25440
25441 /**
25442 * An id for the point. This can be used after render time to get a
25443 * pointer to the point object through `chart.get()`.
25444 *
25445 * @type {String}
25446 * @sample {highcharts} highcharts/point/id/ Remove an id'd point
25447 * @default null
25448 * @since 1.2.0
25449 * @product highcharts highstock
25450 * @apioption series.line.data.id
25451 */
25452
25453 /**
25454 * The rank for this point's data label in case of collision. If two
25455 * data labels are about to overlap, only the one with the highest `labelrank`
25456 * will be drawn.
25457 *
25458 * @type {Number}
25459 * @apioption series.line.data.labelrank
25460 */
25461
25462 /**
25463 * The name of the point as shown in the legend, tooltip, dataLabel
25464 * etc.
25465 *
25466 * @type {String}
25467 * @sample {highcharts} highcharts/series/data-array-of-objects/ Point names
25468 * @see [xAxis.uniqueNames](#xAxis.uniqueNames)
25469 * @apioption series.line.data.name
25470 */
25471
25472 /**
25473 * Whether the data point is selected initially.
25474 *
25475 * @type {Boolean}
25476 * @default false
25477 * @product highcharts highstock
25478 * @apioption series.line.data.selected
25479 */
25480
25481 /**
25482 * The x value of the point. For datetime axes, the X value is the timestamp
25483 * in milliseconds since 1970.
25484 *
25485 * @type {Number}
25486 * @product highcharts highstock
25487 * @apioption series.line.data.x
25488 */
25489
25490 /**
25491 * The y value of the point.
25492 *
25493 * @type {Number}
25494 * @default null
25495 * @product highcharts highstock
25496 * @apioption series.line.data.y
25497 */
25498
25499 /**
25500 * Individual point events
25501 *
25502 * @extends plotOptions.series.point.events
25503 * @product highcharts highstock
25504 * @apioption series.line.data.events
25505 */
25506
25507 /**
25508 * @extends plotOptions.series.marker
25509 * @product highcharts highstock
25510 * @apioption series.line.data.marker
25511 */
25512
25513 }(Highcharts));
25514 (function(H) {
25515 /**
25516 * (c) 2010-2017 Torstein Honsi
25517 *
25518 * License: www.highcharts.com/license
25519 */
25520 var Axis = H.Axis,
25521 Chart = H.Chart,
25522 correctFloat = H.correctFloat,
25523 defined = H.defined,
25524 destroyObjectProperties = H.destroyObjectProperties,
25525 each = H.each,
25526 format = H.format,
25527 objectEach = H.objectEach,
25528 pick = H.pick,
25529 Series = H.Series;
25530
25531 /**
25532 * The class for stacks. Each stack, on a specific X value and either negative
25533 * or positive, has its own stack item.
25534 *
25535 * @class
25536 */
25537 H.StackItem = function(axis, options, isNegative, x, stackOption) {
25538
25539 var inverted = axis.chart.inverted;
25540
25541 this.axis = axis;
25542
25543 // Tells if the stack is negative
25544 this.isNegative = isNegative;
25545
25546 // Save the options to be able to style the label
25547 this.options = options;
25548
25549 // Save the x value to be able to position the label later
25550 this.x = x;
25551
25552 // Initialize total value
25553 this.total = null;
25554
25555 // This will keep each points' extremes stored by series.index and point
25556 // index
25557 this.points = {};
25558
25559 // Save the stack option on the series configuration object, and whether to
25560 // treat it as percent
25561 this.stack = stackOption;
25562 this.leftCliff = 0;
25563 this.rightCliff = 0;
25564
25565 // The align options and text align varies on whether the stack is negative
25566 // and if the chart is inverted or not.
25567 // First test the user supplied value, then use the dynamic.
25568 this.alignOptions = {
25569 align: options.align ||
25570 (inverted ? (isNegative ? 'left' : 'right') : 'center'),
25571 verticalAlign: options.verticalAlign ||
25572 (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
25573 y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),
25574 x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)
25575 };
25576
25577 this.textAlign = options.textAlign ||
25578 (inverted ? (isNegative ? 'right' : 'left') : 'center');
25579 };
25580
25581 H.StackItem.prototype = {
25582 destroy: function() {
25583 destroyObjectProperties(this, this.axis);
25584 },
25585
25586 /**
25587 * Renders the stack total label and adds it to the stack label group.
25588 */
25589 render: function(group) {
25590 var options = this.options,
25591 formatOption = options.format,
25592 str = formatOption ?
25593 format(formatOption, this) :
25594 options.formatter.call(this); // format the text in the label
25595
25596 // Change the text to reflect the new total and set visibility to hidden
25597 // in case the serie is hidden
25598 if (this.label) {
25599 this.label.attr({
25600 text: str,
25601 visibility: 'hidden'
25602 });
25603 // Create new label
25604 } else {
25605 this.label =
25606 this.axis.chart.renderer.text(str, null, null, options.useHTML)
25607 .css(options.style)
25608 .attr({
25609 align: this.textAlign,
25610 rotation: options.rotation,
25611 visibility: 'hidden' // hidden until setOffset is called
25612 })
25613 .add(group); // add to the labels-group
25614 }
25615 },
25616
25617 /**
25618 * Sets the offset that the stack has from the x value and repositions the
25619 * label.
25620 */
25621 setOffset: function(xOffset, xWidth) {
25622 var stackItem = this,
25623 axis = stackItem.axis,
25624 chart = axis.chart,
25625 // stack value translated mapped to chart coordinates
25626 y = axis.translate(
25627 axis.usePercentage ? 100 : stackItem.total,
25628 0,
25629 0,
25630 0,
25631 1
25632 ),
25633 yZero = axis.translate(0), // stack origin
25634 h = Math.abs(y - yZero), // stack height
25635 x = chart.xAxis[0].translate(stackItem.x) + xOffset, // x position
25636 stackBox = stackItem.getStackBox(chart, stackItem, x, y, xWidth, h),
25637 label = stackItem.label,
25638 alignAttr;
25639
25640 if (label) {
25641 // Align the label to the box
25642 label.align(stackItem.alignOptions, null, stackBox);
25643
25644 // Set visibility (#678)
25645 alignAttr = label.alignAttr;
25646 label[
25647 stackItem.options.crop === false || chart.isInsidePlot(
25648 alignAttr.x,
25649 alignAttr.y
25650 ) ? 'show' : 'hide'](true);
25651 }
25652 },
25653 getStackBox: function(chart, stackItem, x, y, xWidth, h) {
25654 var reversed = stackItem.axis.reversed,
25655 inverted = chart.inverted,
25656 plotHeight = chart.plotHeight,
25657 neg = (stackItem.isNegative && !reversed) ||
25658 (!stackItem.isNegative && reversed); // #4056
25659
25660 return { // this is the box for the complete stack
25661 x: inverted ? (neg ? y : y - h) : x,
25662 y: inverted ?
25663 plotHeight - x - xWidth :
25664 (neg ?
25665 (plotHeight - y - h) :
25666 plotHeight - y
25667 ),
25668 width: inverted ? h : xWidth,
25669 height: inverted ? xWidth : h
25670 };
25671 }
25672 };
25673
25674 /**
25675 * Generate stacks for each series and calculate stacks total values
25676 */
25677 Chart.prototype.getStacks = function() {
25678 var chart = this;
25679
25680 // reset stacks for each yAxis
25681 each(chart.yAxis, function(axis) {
25682 if (axis.stacks && axis.hasVisibleSeries) {
25683 axis.oldStacks = axis.stacks;
25684 }
25685 });
25686
25687 each(chart.series, function(series) {
25688 if (series.options.stacking && (series.visible === true ||
25689 chart.options.chart.ignoreHiddenSeries === false)) {
25690 series.stackKey = series.type + pick(series.options.stack, '');
25691 }
25692 });
25693 };
25694
25695
25696 // Stacking methods defined on the Axis prototype
25697
25698 /**
25699 * Build the stacks from top down
25700 */
25701 Axis.prototype.buildStacks = function() {
25702 var axisSeries = this.series,
25703 reversedStacks = pick(this.options.reversedStacks, true),
25704 len = axisSeries.length,
25705 i;
25706 if (!this.isXAxis) {
25707 this.usePercentage = false;
25708 i = len;
25709 while (i--) {
25710 axisSeries[reversedStacks ? i : len - i - 1].setStackedPoints();
25711 }
25712
25713 // Loop up again to compute percent and stream stack
25714 for (i = 0; i < len; i++) {
25715 axisSeries[i].modifyStacks();
25716 }
25717 }
25718 };
25719
25720 Axis.prototype.renderStackTotals = function() {
25721 var axis = this,
25722 chart = axis.chart,
25723 renderer = chart.renderer,
25724 stacks = axis.stacks,
25725 stackTotalGroup = axis.stackTotalGroup;
25726
25727 // Create a separate group for the stack total labels
25728 if (!stackTotalGroup) {
25729 axis.stackTotalGroup = stackTotalGroup =
25730 renderer.g('stack-labels')
25731 .attr({
25732 visibility: 'visible',
25733 zIndex: 6
25734 })
25735 .add();
25736 }
25737
25738 // plotLeft/Top will change when y axis gets wider so we need to translate
25739 // the stackTotalGroup at every render call. See bug #506 and #516
25740 stackTotalGroup.translate(chart.plotLeft, chart.plotTop);
25741
25742 // Render each stack total
25743 objectEach(stacks, function(type) {
25744 objectEach(type, function(stack) {
25745 stack.render(stackTotalGroup);
25746 });
25747 });
25748 };
25749
25750 /**
25751 * Set all the stacks to initial states and destroy unused ones.
25752 */
25753 Axis.prototype.resetStacks = function() {
25754 var axis = this,
25755 stacks = axis.stacks;
25756 if (!axis.isXAxis) {
25757 objectEach(stacks, function(type) {
25758 objectEach(type, function(stack, key) {
25759 // Clean up memory after point deletion (#1044, #4320)
25760 if (stack.touched < axis.stacksTouched) {
25761 stack.destroy();
25762 delete type[key];
25763
25764 // Reset stacks
25765 } else {
25766 stack.total = null;
25767 stack.cum = null;
25768 }
25769 });
25770 });
25771 }
25772 };
25773
25774 Axis.prototype.cleanStacks = function() {
25775 var stacks;
25776
25777 if (!this.isXAxis) {
25778 if (this.oldStacks) {
25779 stacks = this.stacks = this.oldStacks;
25780 }
25781
25782 // reset stacks
25783 objectEach(stacks, function(type) {
25784 objectEach(type, function(stack) {
25785 stack.cum = stack.total;
25786 });
25787 });
25788 }
25789 };
25790
25791
25792 // Stacking methods defnied for Series prototype
25793
25794 /**
25795 * Adds series' points value to corresponding stack
25796 */
25797 Series.prototype.setStackedPoints = function() {
25798 if (!this.options.stacking || (this.visible !== true &&
25799 this.chart.options.chart.ignoreHiddenSeries !== false)) {
25800 return;
25801 }
25802
25803 var series = this,
25804 xData = series.processedXData,
25805 yData = series.processedYData,
25806 stackedYData = [],
25807 yDataLength = yData.length,
25808 seriesOptions = series.options,
25809 threshold = seriesOptions.threshold,
25810 stackThreshold = seriesOptions.startFromThreshold ? threshold : 0,
25811 stackOption = seriesOptions.stack,
25812 stacking = seriesOptions.stacking,
25813 stackKey = series.stackKey,
25814 negKey = '-' + stackKey,
25815 negStacks = series.negStacks,
25816 yAxis = series.yAxis,
25817 stacks = yAxis.stacks,
25818 oldStacks = yAxis.oldStacks,
25819 stackIndicator,
25820 isNegative,
25821 stack,
25822 other,
25823 key,
25824 pointKey,
25825 i,
25826 x,
25827 y;
25828
25829
25830 yAxis.stacksTouched += 1;
25831
25832 // loop over the non-null y values and read them into a local array
25833 for (i = 0; i < yDataLength; i++) {
25834 x = xData[i];
25835 y = yData[i];
25836 stackIndicator = series.getStackIndicator(
25837 stackIndicator,
25838 x,
25839 series.index
25840 );
25841 pointKey = stackIndicator.key;
25842 // Read stacked values into a stack based on the x value,
25843 // the sign of y and the stack key. Stacking is also handled for null
25844 // values (#739)
25845 isNegative = negStacks && y < (stackThreshold ? 0 : threshold);
25846 key = isNegative ? negKey : stackKey;
25847
25848 // Create empty object for this stack if it doesn't exist yet
25849 if (!stacks[key]) {
25850 stacks[key] = {};
25851 }
25852
25853 // Initialize StackItem for this x
25854 if (!stacks[key][x]) {
25855 if (oldStacks[key] && oldStacks[key][x]) {
25856 stacks[key][x] = oldStacks[key][x];
25857 stacks[key][x].total = null;
25858 } else {
25859 stacks[key][x] = new H.StackItem(
25860 yAxis,
25861 yAxis.options.stackLabels,
25862 isNegative,
25863 x,
25864 stackOption
25865 );
25866 }
25867 }
25868
25869 // If the StackItem doesn't exist, create it first
25870 stack = stacks[key][x];
25871 if (y !== null) {
25872 stack.points[pointKey] = stack.points[series.index] = [pick(stack.cum, stackThreshold)];
25873
25874 // Record the base of the stack
25875 if (!defined(stack.cum)) {
25876 stack.base = pointKey;
25877 }
25878 stack.touched = yAxis.stacksTouched;
25879
25880
25881 // In area charts, if there are multiple points on the same X value,
25882 // let the area fill the full span of those points
25883 if (stackIndicator.index > 0 && series.singleStacks === false) {
25884 stack.points[pointKey][0] =
25885 stack.points[series.index + ',' + x + ',0'][0];
25886 }
25887 }
25888
25889 // Add value to the stack total
25890 if (stacking === 'percent') {
25891
25892 // Percent stacked column, totals are the same for the positive and
25893 // negative stacks
25894 other = isNegative ? stackKey : negKey;
25895 if (negStacks && stacks[other] && stacks[other][x]) {
25896 other = stacks[other][x];
25897 stack.total = other.total =
25898 Math.max(other.total, stack.total) + Math.abs(y) || 0;
25899
25900 // Percent stacked areas
25901 } else {
25902 stack.total = correctFloat(stack.total + (Math.abs(y) || 0));
25903 }
25904 } else {
25905 stack.total = correctFloat(stack.total + (y || 0));
25906 }
25907
25908 stack.cum = pick(stack.cum, stackThreshold) + (y || 0);
25909
25910 if (y !== null) {
25911 stack.points[pointKey].push(stack.cum);
25912 stackedYData[i] = stack.cum;
25913 }
25914
25915 }
25916
25917 if (stacking === 'percent') {
25918 yAxis.usePercentage = true;
25919 }
25920
25921 this.stackedYData = stackedYData; // To be used in getExtremes
25922
25923 // Reset old stacks
25924 yAxis.oldStacks = {};
25925 };
25926
25927 /**
25928 * Iterate over all stacks and compute the absolute values to percent
25929 */
25930 Series.prototype.modifyStacks = function() {
25931 var series = this,
25932 stackKey = series.stackKey,
25933 stacks = series.yAxis.stacks,
25934 processedXData = series.processedXData,
25935 stackIndicator,
25936 stacking = series.options.stacking;
25937
25938 if (series[stacking + 'Stacker']) { // Modifier function exists
25939 each([stackKey, '-' + stackKey], function(key) {
25940 var i = processedXData.length,
25941 x,
25942 stack,
25943 pointExtremes;
25944
25945 while (i--) {
25946 x = processedXData[i];
25947 stackIndicator = series.getStackIndicator(
25948 stackIndicator,
25949 x,
25950 series.index,
25951 key
25952 );
25953 stack = stacks[key] && stacks[key][x];
25954 pointExtremes = stack && stack.points[stackIndicator.key];
25955 if (pointExtremes) {
25956 series[stacking + 'Stacker'](pointExtremes, stack, i);
25957 }
25958 }
25959 });
25960 }
25961 };
25962
25963 /**
25964 * Modifier function for percent stacks. Blows up the stack to 100%.
25965 */
25966 Series.prototype.percentStacker = function(pointExtremes, stack, i) {
25967 var totalFactor = stack.total ? 100 / stack.total : 0;
25968 // Y bottom value
25969 pointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor);
25970 // Y value
25971 pointExtremes[1] = correctFloat(pointExtremes[1] * totalFactor);
25972 this.stackedYData[i] = pointExtremes[1];
25973 };
25974
25975 /**
25976 * Get stack indicator, according to it's x-value, to determine points with the
25977 * same x-value
25978 */
25979 Series.prototype.getStackIndicator = function(stackIndicator, x, index, key) {
25980 // Update stack indicator, when:
25981 // first point in a stack || x changed || stack type (negative vs positive)
25982 // changed:
25983 if (!defined(stackIndicator) || stackIndicator.x !== x ||
25984 (key && stackIndicator.key !== key)) {
25985 stackIndicator = {
25986 x: x,
25987 index: 0,
25988 key: key
25989 };
25990 } else {
25991 stackIndicator.index++;
25992 }
25993
25994 stackIndicator.key = [index, x, stackIndicator.index].join(',');
25995
25996 return stackIndicator;
25997 };
25998
25999 }(Highcharts));
26000 (function(H) {
26001 /**
26002 * (c) 2010-2017 Torstein Honsi
26003 *
26004 * License: www.highcharts.com/license
26005 */
26006 var addEvent = H.addEvent,
26007 animate = H.animate,
26008 Axis = H.Axis,
26009 Chart = H.Chart,
26010 createElement = H.createElement,
26011 css = H.css,
26012 defined = H.defined,
26013 each = H.each,
26014 erase = H.erase,
26015 extend = H.extend,
26016 fireEvent = H.fireEvent,
26017 inArray = H.inArray,
26018 isNumber = H.isNumber,
26019 isObject = H.isObject,
26020 isArray = H.isArray,
26021 merge = H.merge,
26022 objectEach = H.objectEach,
26023 pick = H.pick,
26024 Point = H.Point,
26025 Series = H.Series,
26026 seriesTypes = H.seriesTypes,
26027 setAnimation = H.setAnimation,
26028 splat = H.splat;
26029
26030 // Extend the Chart prototype for dynamic methods
26031 extend(Chart.prototype, /** @lends Highcharts.Chart.prototype */ {
26032
26033 /**
26034 * Add a series to the chart after render time. Note that this method should
26035 * never be used when adding data synchronously at chart render time, as it
26036 * adds expense to the calculations and rendering. When adding data at the
26037 * same time as the chart is initialized, add the series as a configuration
26038 * option instead. With multiple axes, the `offset` is dynamically adjusted.
26039 *
26040 * @param {SeriesOptions} options
26041 * The config options for the series.
26042 * @param {Boolean} [redraw=true]
26043 * Whether to redraw the chart after adding.
26044 * @param {AnimationOptions} animation
26045 * Whether to apply animation, and optionally animation
26046 * configuration.
26047 *
26048 * @return {Highcharts.Series}
26049 * The newly created series object.
26050 *
26051 * @sample highcharts/members/chart-addseries/
26052 * Add a series from a button
26053 * @sample stock/members/chart-addseries/
26054 * Add a series in Highstock
26055 */
26056 addSeries: function(options, redraw, animation) {
26057 var series,
26058 chart = this;
26059
26060 if (options) {
26061 redraw = pick(redraw, true); // defaults to true
26062
26063 fireEvent(chart, 'addSeries', {
26064 options: options
26065 }, function() {
26066 series = chart.initSeries(options);
26067
26068 chart.isDirtyLegend = true; // the series array is out of sync with the display
26069 chart.linkSeries();
26070 if (redraw) {
26071 chart.redraw(animation);
26072 }
26073 });
26074 }
26075
26076 return series;
26077 },
26078
26079 /**
26080 * Add an axis to the chart after render time. Note that this method should
26081 * never be used when adding data synchronously at chart render time, as it
26082 * adds expense to the calculations and rendering. When adding data at the
26083 * same time as the chart is initialized, add the axis as a configuration
26084 * option instead.
26085 * @param {AxisOptions} options
26086 * The axis options.
26087 * @param {Boolean} [isX=false]
26088 * Whether it is an X axis or a value axis.
26089 * @param {Boolean} [redraw=true]
26090 * Whether to redraw the chart after adding.
26091 * @param {AnimationOptions} [animation=true]
26092 * Whether and how to apply animation in the redraw.
26093 *
26094 * @sample highcharts/members/chart-addaxis/ Add and remove axes
26095 *
26096 * @return {Axis}
26097 * The newly generated Axis object.
26098 */
26099 addAxis: function(options, isX, redraw, animation) {
26100 var key = isX ? 'xAxis' : 'yAxis',
26101 chartOptions = this.options,
26102 userOptions = merge(options, {
26103 index: this[key].length,
26104 isX: isX
26105 }),
26106 axis;
26107
26108 axis = new Axis(this, userOptions);
26109
26110 // Push the new axis options to the chart options
26111 chartOptions[key] = splat(chartOptions[key] || {});
26112 chartOptions[key].push(userOptions);
26113
26114 if (pick(redraw, true)) {
26115 this.redraw(animation);
26116 }
26117
26118 return axis;
26119 },
26120
26121 /**
26122 * Dim the chart and show a loading text or symbol. Options for the loading
26123 * screen are defined in {@link
26124 * https://api.highcharts.com/highcharts/loading|the loading options}.
26125 *
26126 * @param {String} str
26127 * An optional text to show in the loading label instead of the
26128 * default one. The default text is set in {@link
26129 * http://api.highcharts.com/highcharts/lang.loading|lang.loading}.
26130 *
26131 * @sample highcharts/members/chart-hideloading/
26132 * Show and hide loading from a button
26133 * @sample highcharts/members/chart-showloading/
26134 * Apply different text labels
26135 * @sample stock/members/chart-show-hide-loading/
26136 * Toggle loading in Highstock
26137 */
26138 showLoading: function(str) {
26139 var chart = this,
26140 options = chart.options,
26141 loadingDiv = chart.loadingDiv,
26142 loadingOptions = options.loading,
26143 setLoadingSize = function() {
26144 if (loadingDiv) {
26145 css(loadingDiv, {
26146 left: chart.plotLeft + 'px',
26147 top: chart.plotTop + 'px',
26148 width: chart.plotWidth + 'px',
26149 height: chart.plotHeight + 'px'
26150 });
26151 }
26152 };
26153
26154 // create the layer at the first call
26155 if (!loadingDiv) {
26156 chart.loadingDiv = loadingDiv = createElement('div', {
26157 className: 'highcharts-loading highcharts-loading-hidden'
26158 }, null, chart.container);
26159
26160 chart.loadingSpan = createElement(
26161 'span', {
26162 className: 'highcharts-loading-inner'
26163 },
26164 null,
26165 loadingDiv
26166 );
26167 addEvent(chart, 'redraw', setLoadingSize); // #1080
26168 }
26169
26170 loadingDiv.className = 'highcharts-loading';
26171
26172 // Update text
26173 chart.loadingSpan.innerHTML = str || options.lang.loading;
26174
26175
26176
26177 chart.loadingShown = true;
26178 setLoadingSize();
26179 },
26180
26181 /**
26182 * Hide the loading layer.
26183 *
26184 * @see Highcharts.Chart#showLoading
26185 * @sample highcharts/members/chart-hideloading/
26186 * Show and hide loading from a button
26187 * @sample stock/members/chart-show-hide-loading/
26188 * Toggle loading in Highstock
26189 */
26190 hideLoading: function() {
26191 var options = this.options,
26192 loadingDiv = this.loadingDiv;
26193
26194 if (loadingDiv) {
26195 loadingDiv.className = 'highcharts-loading highcharts-loading-hidden';
26196
26197 }
26198 this.loadingShown = false;
26199 },
26200
26201 /**
26202 * These properties cause isDirtyBox to be set to true when updating. Can be extended from plugins.
26203 */
26204 propsRequireDirtyBox: ['backgroundColor', 'borderColor', 'borderWidth', 'margin', 'marginTop', 'marginRight',
26205 'marginBottom', 'marginLeft', 'spacing', 'spacingTop', 'spacingRight', 'spacingBottom', 'spacingLeft',
26206 'borderRadius', 'plotBackgroundColor', 'plotBackgroundImage', 'plotBorderColor', 'plotBorderWidth',
26207 'plotShadow', 'shadow'
26208 ],
26209
26210 /**
26211 * These properties cause all series to be updated when updating. Can be
26212 * extended from plugins.
26213 */
26214 propsRequireUpdateSeries: ['chart.inverted', 'chart.polar',
26215 'chart.ignoreHiddenSeries', 'chart.type', 'colors', 'plotOptions',
26216 'tooltip'
26217 ],
26218
26219 /**
26220 * A generic function to update any element of the chart. Elements can be
26221 * enabled and disabled, moved, re-styled, re-formatted etc.
26222 *
26223 * A special case is configuration objects that take arrays, for example
26224 * {@link https://api.highcharts.com/highcharts/xAxis|xAxis},
26225 * {@link https://api.highcharts.com/highcharts/yAxis|yAxis} or
26226 * {@link https://api.highcharts.com/highcharts/series|series}. For these
26227 * collections, an `id` option is used to map the new option set to an
26228 * existing object. If an existing object of the same id is not found, the
26229 * corresponding item is updated. So for example, running `chart.update`
26230 * with a series item without an id, will cause the existing chart's series
26231 * with the same index in the series array to be updated. When the
26232 * `oneToOne` parameter is true, `chart.update` will also take care of
26233 * adding and removing items from the collection. Read more under the
26234 * parameter description below.
26235 *
26236 * See also the {@link https://api.highcharts.com/highcharts/responsive|
26237 * responsive option set}. Switching between `responsive.rules` basically
26238 * runs `chart.update` under the hood.
26239 *
26240 * @param {Options} options
26241 * A configuration object for the new chart options.
26242 * @param {Boolean} [redraw=true]
26243 * Whether to redraw the chart.
26244 * @param {Boolean} [oneToOne=false]
26245 * When `true`, the `series`, `xAxis` and `yAxis` collections will
26246 * be updated one to one, and items will be either added or removed
26247 * to match the new updated options. For example, if the chart has
26248 * two series and we call `chart.update` with a configuration
26249 * containing three series, one will be added. If we call
26250 * `chart.update` with one series, one will be removed. Setting an
26251 * empty `series` array will remove all series, but leaving out the
26252 * `series` property will leave all series untouched. If the series
26253 * have id's, the new series options will be matched by id, and the
26254 * remaining ones removed.
26255 *
26256 * @sample highcharts/members/chart-update/
26257 * Update chart geometry
26258 */
26259 update: function(options, redraw, oneToOne) {
26260 var chart = this,
26261 adders = {
26262 credits: 'addCredits',
26263 title: 'setTitle',
26264 subtitle: 'setSubtitle'
26265 },
26266 optionsChart = options.chart,
26267 updateAllAxes,
26268 updateAllSeries,
26269 newWidth,
26270 newHeight,
26271 itemsForRemoval = [];
26272
26273 // If the top-level chart option is present, some special updates are required
26274 if (optionsChart) {
26275 merge(true, chart.options.chart, optionsChart);
26276
26277 // Setter function
26278 if ('className' in optionsChart) {
26279 chart.setClassName(optionsChart.className);
26280 }
26281
26282 if ('inverted' in optionsChart || 'polar' in optionsChart) {
26283 // Parse options.chart.inverted and options.chart.polar together
26284 // with the available series.
26285 chart.propFromSeries();
26286 updateAllAxes = true;
26287 }
26288
26289 if ('alignTicks' in optionsChart) { // #6452
26290 updateAllAxes = true;
26291 }
26292
26293 objectEach(optionsChart, function(val, key) {
26294 if (inArray('chart.' + key, chart.propsRequireUpdateSeries) !== -1) {
26295 updateAllSeries = true;
26296 }
26297 // Only dirty box
26298 if (inArray(key, chart.propsRequireDirtyBox) !== -1) {
26299 chart.isDirtyBox = true;
26300 }
26301 });
26302
26303
26304 }
26305
26306 // Moved up, because tooltip needs updated plotOptions (#6218)
26307
26308
26309 if (options.plotOptions) {
26310 merge(true, this.options.plotOptions, options.plotOptions);
26311 }
26312
26313 // Some option stuctures correspond one-to-one to chart objects that
26314 // have update methods, for example
26315 // options.credits => chart.credits
26316 // options.legend => chart.legend
26317 // options.title => chart.title
26318 // options.tooltip => chart.tooltip
26319 // options.subtitle => chart.subtitle
26320 // options.mapNavigation => chart.mapNavigation
26321 // options.navigator => chart.navigator
26322 // options.scrollbar => chart.scrollbar
26323 objectEach(options, function(val, key) {
26324 if (chart[key] && typeof chart[key].update === 'function') {
26325 chart[key].update(val, false);
26326
26327 // If a one-to-one object does not exist, look for an adder function
26328 } else if (typeof chart[adders[key]] === 'function') {
26329 chart[adders[key]](val);
26330 }
26331
26332 if (
26333 key !== 'chart' &&
26334 inArray(key, chart.propsRequireUpdateSeries) !== -1
26335 ) {
26336 updateAllSeries = true;
26337 }
26338 });
26339
26340 // Setters for collections. For axes and series, each item is referred
26341 // by an id. If the id is not found, it defaults to the corresponding
26342 // item in the collection, so setting one series without an id, will
26343 // update the first series in the chart. Setting two series without
26344 // an id will update the first and the second respectively (#6019)
26345 // chart.update and responsive.
26346 each([
26347 'xAxis',
26348 'yAxis',
26349 'zAxis',
26350 'series',
26351 'colorAxis',
26352 'pane'
26353 ], function(coll) {
26354 if (options[coll]) {
26355 each(splat(options[coll]), function(newOptions, i) {
26356 var item = (
26357 defined(newOptions.id) &&
26358 chart.get(newOptions.id)
26359 ) || chart[coll][i];
26360 if (item && item.coll === coll) {
26361 item.update(newOptions, false);
26362
26363 if (oneToOne) {
26364 item.touched = true;
26365 }
26366 }
26367
26368 // If oneToOne and no matching item is found, add one
26369 if (!item && oneToOne) {
26370 if (coll === 'series') {
26371 chart.addSeries(newOptions, false)
26372 .touched = true;
26373 } else if (coll === 'xAxis' || coll === 'yAxis') {
26374 chart.addAxis(newOptions, coll === 'xAxis', false)
26375 .touched = true;
26376 }
26377 }
26378
26379 });
26380
26381 // Add items for removal
26382 if (oneToOne) {
26383 each(chart[coll], function(item) {
26384 if (!item.touched) {
26385 itemsForRemoval.push(item);
26386 } else {
26387 delete item.touched;
26388 }
26389 });
26390 }
26391
26392
26393 }
26394 });
26395
26396 each(itemsForRemoval, function(item) {
26397 item.remove(false);
26398 });
26399
26400 if (updateAllAxes) {
26401 each(chart.axes, function(axis) {
26402 axis.update({}, false);
26403 });
26404 }
26405
26406 // Certain options require the whole series structure to be thrown away
26407 // and rebuilt
26408 if (updateAllSeries) {
26409 each(chart.series, function(series) {
26410 series.update({}, false);
26411 });
26412 }
26413
26414 // For loading, just update the options, do not redraw
26415 if (options.loading) {
26416 merge(true, chart.options.loading, options.loading);
26417 }
26418
26419 // Update size. Redraw is forced.
26420 newWidth = optionsChart && optionsChart.width;
26421 newHeight = optionsChart && optionsChart.height;
26422 if ((isNumber(newWidth) && newWidth !== chart.chartWidth) ||
26423 (isNumber(newHeight) && newHeight !== chart.chartHeight)) {
26424 chart.setSize(newWidth, newHeight);
26425 } else if (pick(redraw, true)) {
26426 chart.redraw();
26427 }
26428 },
26429
26430 /**
26431 * Shortcut to set the subtitle options. This can also be done from {@link
26432 * Chart#update} or {@link Chart#setTitle}.
26433 *
26434 * @param {SubtitleOptions} options
26435 * New subtitle options. The subtitle text itself is set by the
26436 * `options.text` property.
26437 */
26438 setSubtitle: function(options) {
26439 this.setTitle(undefined, options);
26440 }
26441
26442
26443 });
26444
26445 // extend the Point prototype for dynamic methods
26446 extend(Point.prototype, /** @lends Highcharts.Point.prototype */ {
26447 /**
26448 * Update point with new options (typically x/y data) and optionally redraw
26449 * the series.
26450 *
26451 * @param {Object} options
26452 * The point options. Point options are handled as described under
26453 * the `series.type.data` item for each series type. For example
26454 * for a line series, if options is a single number, the point will
26455 * be given that number as the main y value. If it is an array, it
26456 * will be interpreted as x and y values respectively. If it is an
26457 * object, advanced options are applied.
26458 * @param {Boolean} [redraw=true]
26459 * Whether to redraw the chart after the point is updated. If doing
26460 * more operations on the chart, it is best practice to set
26461 * `redraw` to false and call `chart.redraw()` after.
26462 * @param {AnimationOptions} [animation=true]
26463 * Whether to apply animation, and optionally animation
26464 * configuration.
26465 *
26466 * @sample highcharts/members/point-update-column/
26467 * Update column value
26468 * @sample highcharts/members/point-update-pie/
26469 * Update pie slice
26470 * @sample maps/members/point-update/
26471 * Update map area value in Highmaps
26472 */
26473 update: function(options, redraw, animation, runEvent) {
26474 var point = this,
26475 series = point.series,
26476 graphic = point.graphic,
26477 i,
26478 chart = series.chart,
26479 seriesOptions = series.options;
26480
26481 redraw = pick(redraw, true);
26482
26483 function update() {
26484
26485 point.applyOptions(options);
26486
26487 // Update visuals
26488 if (point.y === null && graphic) { // #4146
26489 point.graphic = graphic.destroy();
26490 }
26491 if (isObject(options, true)) {
26492 // Destroy so we can get new elements
26493 if (graphic && graphic.element) {
26494 // "null" is also a valid symbol
26495 if (options && options.marker && options.marker.symbol !== undefined) {
26496 point.graphic = graphic.destroy();
26497 }
26498 }
26499 if (options && options.dataLabels && point.dataLabel) { // #2468
26500 point.dataLabel = point.dataLabel.destroy();
26501 }
26502 if (point.connector) {
26503 point.connector = point.connector.destroy(); // #7243
26504 }
26505 }
26506
26507 // record changes in the parallel arrays
26508 i = point.index;
26509 series.updateParallelArrays(point, i);
26510
26511 // Record the options to options.data. If the old or the new config
26512 // is an object, use point options, otherwise use raw options
26513 // (#4701, #4916).
26514 seriesOptions.data[i] = (
26515 isObject(seriesOptions.data[i], true) ||
26516 isObject(options, true)
26517 ) ?
26518 point.options :
26519 options;
26520
26521 // redraw
26522 series.isDirty = series.isDirtyData = true;
26523 if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320
26524 chart.isDirtyBox = true;
26525 }
26526
26527 if (seriesOptions.legendType === 'point') { // #1831, #1885
26528 chart.isDirtyLegend = true;
26529 }
26530 if (redraw) {
26531 chart.redraw(animation);
26532 }
26533 }
26534
26535 // Fire the event with a default handler of doing the update
26536 if (runEvent === false) { // When called from setData
26537 update();
26538 } else {
26539 point.firePointEvent('update', {
26540 options: options
26541 }, update);
26542 }
26543 },
26544
26545 /**
26546 * Remove a point and optionally redraw the series and if necessary the axes
26547 * @param {Boolean} redraw
26548 * Whether to redraw the chart or wait for an explicit call. When
26549 * doing more operations on the chart, for example running
26550 * `point.remove()` in a loop, it is best practice to set `redraw`
26551 * to false and call `chart.redraw()` after.
26552 * @param {AnimationOptions} [animation=false]
26553 * Whether to apply animation, and optionally animation
26554 * configuration.
26555 *
26556 * @sample highcharts/plotoptions/series-point-events-remove/
26557 * Remove point and confirm
26558 * @sample highcharts/members/point-remove/
26559 * Remove pie slice
26560 * @sample maps/members/point-remove/
26561 * Remove selected points in Highmaps
26562 */
26563 remove: function(redraw, animation) {
26564 this.series.removePoint(inArray(this, this.series.data), redraw, animation);
26565 }
26566 });
26567
26568 // Extend the series prototype for dynamic methods
26569 extend(Series.prototype, /** @lends Series.prototype */ {
26570 /**
26571 * Add a point to the series after render time. The point can be added at
26572 * the end, or by giving it an X value, to the start or in the middle of the
26573 * series.
26574 *
26575 * @param {Number|Array|Object} options
26576 * The point options. If options is a single number, a point with
26577 * that y value is appended to the series.If it is an array, it will
26578 * be interpreted as x and y values respectively. If it is an
26579 * object, advanced options as outlined under `series.data` are
26580 * applied.
26581 * @param {Boolean} [redraw=true]
26582 * Whether to redraw the chart after the point is added. When adding
26583 * more than one point, it is highly recommended that the redraw
26584 * option be set to false, and instead {@link Chart#redraw}
26585 * is explicitly called after the adding of points is finished.
26586 * Otherwise, the chart will redraw after adding each point.
26587 * @param {Boolean} [shift=false]
26588 * If true, a point is shifted off the start of the series as one is
26589 * appended to the end.
26590 * @param {AnimationOptions} [animation]
26591 * Whether to apply animation, and optionally animation
26592 * configuration.
26593 *
26594 * @sample highcharts/members/series-addpoint-append/
26595 * Append point
26596 * @sample highcharts/members/series-addpoint-append-and-shift/
26597 * Append and shift
26598 * @sample highcharts/members/series-addpoint-x-and-y/
26599 * Both X and Y values given
26600 * @sample highcharts/members/series-addpoint-pie/
26601 * Append pie slice
26602 * @sample stock/members/series-addpoint/
26603 * Append 100 points in Highstock
26604 * @sample stock/members/series-addpoint-shift/
26605 * Append and shift in Highstock
26606 * @sample maps/members/series-addpoint/
26607 * Add a point in Highmaps
26608 */
26609 addPoint: function(options, redraw, shift, animation) {
26610 var series = this,
26611 seriesOptions = series.options,
26612 data = series.data,
26613 chart = series.chart,
26614 xAxis = series.xAxis,
26615 names = xAxis && xAxis.hasNames && xAxis.names,
26616 dataOptions = seriesOptions.data,
26617 point,
26618 isInTheMiddle,
26619 xData = series.xData,
26620 i,
26621 x;
26622
26623 // Optional redraw, defaults to true
26624 redraw = pick(redraw, true);
26625
26626 // Get options and push the point to xData, yData and series.options. In series.generatePoints
26627 // the Point instance will be created on demand and pushed to the series.data array.
26628 point = {
26629 series: series
26630 };
26631 series.pointClass.prototype.applyOptions.apply(point, [options]);
26632 x = point.x;
26633
26634 // Get the insertion point
26635 i = xData.length;
26636 if (series.requireSorting && x < xData[i - 1]) {
26637 isInTheMiddle = true;
26638 while (i && xData[i - 1] > x) {
26639 i--;
26640 }
26641 }
26642
26643 series.updateParallelArrays(point, 'splice', i, 0, 0); // insert undefined item
26644 series.updateParallelArrays(point, i); // update it
26645
26646 if (names && point.name) {
26647 names[x] = point.name;
26648 }
26649 dataOptions.splice(i, 0, options);
26650
26651 if (isInTheMiddle) {
26652 series.data.splice(i, 0, null);
26653 series.processData();
26654 }
26655
26656 // Generate points to be added to the legend (#1329)
26657 if (seriesOptions.legendType === 'point') {
26658 series.generatePoints();
26659 }
26660
26661 // Shift the first point off the parallel arrays
26662 if (shift) {
26663 if (data[0] && data[0].remove) {
26664 data[0].remove(false);
26665 } else {
26666 data.shift();
26667 series.updateParallelArrays(point, 'shift');
26668
26669 dataOptions.shift();
26670 }
26671 }
26672
26673 // redraw
26674 series.isDirty = true;
26675 series.isDirtyData = true;
26676
26677 if (redraw) {
26678 chart.redraw(animation); // Animation is set anyway on redraw, #5665
26679 }
26680 },
26681
26682 /**
26683 * Remove a point from the series. Unlike the {@link Highcharts.Point#remove}
26684 * method, this can also be done on a point that is not instanciated because
26685 * it is outside the view or subject to Highstock data grouping.
26686 *
26687 * @param {Number} i
26688 * The index of the point in the {@link Highcharts.Series.data|data}
26689 * array.
26690 * @param {Boolean} [redraw=true]
26691 * Whether to redraw the chart after the point is added. When
26692 * removing more than one point, it is highly recommended that the
26693 * `redraw` option be set to `false`, and instead {@link
26694 * Highcharts.Chart#redraw} is explicitly called after the adding of
26695 * points is finished.
26696 * @param {AnimationOptions} [animation]
26697 * Whether and optionally how the series should be animated.
26698 *
26699 * @sample highcharts/members/series-removepoint/
26700 * Remove cropped point
26701 */
26702 removePoint: function(i, redraw, animation) {
26703
26704 var series = this,
26705 data = series.data,
26706 point = data[i],
26707 points = series.points,
26708 chart = series.chart,
26709 remove = function() {
26710
26711 if (points && points.length === data.length) { // #4935
26712 points.splice(i, 1);
26713 }
26714 data.splice(i, 1);
26715 series.options.data.splice(i, 1);
26716 series.updateParallelArrays(point || {
26717 series: series
26718 }, 'splice', i, 1);
26719
26720 if (point) {
26721 point.destroy();
26722 }
26723
26724 // redraw
26725 series.isDirty = true;
26726 series.isDirtyData = true;
26727 if (redraw) {
26728 chart.redraw();
26729 }
26730 };
26731
26732 setAnimation(animation, chart);
26733 redraw = pick(redraw, true);
26734
26735 // Fire the event with a default handler of removing the point
26736 if (point) {
26737 point.firePointEvent('remove', null, remove);
26738 } else {
26739 remove();
26740 }
26741 },
26742
26743 /**
26744 * Remove a series and optionally redraw the chart.
26745 *
26746 * @param {Boolean} [redraw=true]
26747 * Whether to redraw the chart or wait for an explicit call to
26748 * {@link Highcharts.Chart#redraw}.
26749 * @param {AnimationOptions} [animation]
26750 * Whether to apply animation, and optionally animation
26751 * configuration
26752 * @param {Boolean} [withEvent=true]
26753 * Used internally, whether to fire the series `remove` event.
26754 *
26755 * @sample highcharts/members/series-remove/
26756 * Remove first series from a button
26757 */
26758 remove: function(redraw, animation, withEvent) {
26759 var series = this,
26760 chart = series.chart;
26761
26762 function remove() {
26763
26764 // Destroy elements
26765 series.destroy();
26766
26767 // Redraw
26768 chart.isDirtyLegend = chart.isDirtyBox = true;
26769 chart.linkSeries();
26770
26771 if (pick(redraw, true)) {
26772 chart.redraw(animation);
26773 }
26774 }
26775
26776 // Fire the event with a default handler of removing the point
26777 if (withEvent !== false) {
26778 fireEvent(series, 'remove', null, remove);
26779 } else {
26780 remove();
26781 }
26782 },
26783
26784 /**
26785 * Update the series with a new set of options. For a clean and precise
26786 * handling of new options, all methods and elements from the series are
26787 * removed, and it is initiated from scratch. Therefore, this method is more
26788 * performance expensive than some other utility methods like {@link
26789 * Series#setData} or {@link Series#setVisible}.
26790 *
26791 * @param {SeriesOptions} options
26792 * New options that will be merged with the series' existing
26793 * options.
26794 * @param {Boolean} [redraw=true]
26795 * Whether to redraw the chart after the series is altered. If doing
26796 * more operations on the chart, it is a good idea to set redraw to
26797 * false and call {@link Chart#redraw} after.
26798 *
26799 * @sample highcharts/members/series-update/
26800 * Updating series options
26801 * @sample maps/members/series-update/
26802 * Update series options in Highmaps
26803 */
26804 update: function(newOptions, redraw) {
26805 var series = this,
26806 chart = series.chart,
26807 // must use user options when changing type because series.options
26808 // is merged in with type specific plotOptions
26809 oldOptions = series.userOptions,
26810 oldType = series.oldType || series.type,
26811 newType = newOptions.type || oldOptions.type || chart.options.chart.type,
26812 proto = seriesTypes[oldType].prototype,
26813 n,
26814 preserveGroups = [
26815 'group',
26816 'markerGroup',
26817 'dataLabelsGroup'
26818 ],
26819 preserve = [
26820 'navigatorSeries',
26821 'baseSeries'
26822 ],
26823
26824 // Animation must be enabled when calling update before the initial
26825 // animation has first run. This happens when calling update
26826 // directly after chart initialization, or when applying responsive
26827 // rules (#6912).
26828 animation = series.finishedAnimating && {
26829 animation: false
26830 };
26831
26832 // Running Series.update to update the data only is an intuitive usage,
26833 // so we want to make sure that when used like this, we run the
26834 // cheaper setData function and allow animation instead of completely
26835 // recreating the series instance.
26836 if (Object.keys && Object.keys(newOptions).toString() === 'data') {
26837 return this.setData(newOptions.data, redraw);
26838 }
26839
26840 // If we're changing type or zIndex, create new groups (#3380, #3404)
26841 // Also create new groups for navigator series.
26842 if (
26843 (newType && newType !== oldType) ||
26844 newOptions.zIndex !== undefined
26845 ) {
26846 preserveGroups.length = 0;
26847 }
26848
26849 // Make sure preserved properties are not destroyed (#3094)
26850 preserve = preserveGroups.concat(preserve);
26851 each(preserve, function(prop) {
26852 preserve[prop] = series[prop];
26853 delete series[prop];
26854 });
26855
26856 // Do the merge, with some forced options
26857 newOptions = merge(oldOptions, animation, {
26858 index: series.index,
26859 pointStart: series.xData[0] // when updating after addPoint
26860 }, {
26861 data: series.options.data
26862 }, newOptions);
26863
26864 // Destroy the series and delete all properties. Reinsert all methods
26865 // and properties from the new type prototype (#2270, #3719)
26866 series.remove(false, null, false);
26867 for (n in proto) {
26868 series[n] = undefined;
26869 }
26870 extend(series, seriesTypes[newType || oldType].prototype);
26871
26872 // Re-register groups (#3094) and other preserved properties
26873 each(preserve, function(prop) {
26874 series[prop] = preserve[prop];
26875 });
26876
26877 series.init(chart, newOptions);
26878 series.oldType = oldType;
26879 chart.linkSeries(); // Links are lost in series.remove (#3028)
26880 if (pick(redraw, true)) {
26881 chart.redraw(false);
26882 }
26883 }
26884 });
26885
26886 // Extend the Axis.prototype for dynamic methods
26887 extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {
26888
26889 /**
26890 * Update an axis object with a new set of options. The options are merged
26891 * with the existing options, so only new or altered options need to be
26892 * specified.
26893 *
26894 * @param {Object} options
26895 * The new options that will be merged in with existing options on
26896 * the axis.
26897 * @sample highcharts/members/axis-update/ Axis update demo
26898 */
26899 update: function(options, redraw) {
26900 var chart = this.chart;
26901
26902 options = chart.options[this.coll][this.options.index] =
26903 merge(this.userOptions, options);
26904
26905 this.destroy(true);
26906
26907 this.init(chart, extend(options, {
26908 events: undefined
26909 }));
26910
26911 chart.isDirtyBox = true;
26912 if (pick(redraw, true)) {
26913 chart.redraw();
26914 }
26915 },
26916
26917 /**
26918 * Remove the axis from the chart.
26919 *
26920 * @param {Boolean} [redraw=true] Whether to redraw the chart following the
26921 * remove.
26922 *
26923 * @sample highcharts/members/chart-addaxis/ Add and remove axes
26924 */
26925 remove: function(redraw) {
26926 var chart = this.chart,
26927 key = this.coll, // xAxis or yAxis
26928 axisSeries = this.series,
26929 i = axisSeries.length;
26930
26931 // Remove associated series (#2687)
26932 while (i--) {
26933 if (axisSeries[i]) {
26934 axisSeries[i].remove(false);
26935 }
26936 }
26937
26938 // Remove the axis
26939 erase(chart.axes, this);
26940 erase(chart[key], this);
26941
26942 if (isArray(chart.options[key])) {
26943 chart.options[key].splice(this.options.index, 1);
26944 } else { // color axis, #6488
26945 delete chart.options[key];
26946 }
26947
26948 each(chart[key], function(axis, i) { // Re-index, #1706
26949 axis.options.index = i;
26950 });
26951 this.destroy();
26952 chart.isDirtyBox = true;
26953
26954 if (pick(redraw, true)) {
26955 chart.redraw();
26956 }
26957 },
26958
26959 /**
26960 * Update the axis title by options after render time.
26961 *
26962 * @param {TitleOptions} titleOptions
26963 * The additional title options.
26964 * @param {Boolean} [redraw=true]
26965 * Whether to redraw the chart after setting the title.
26966 * @sample highcharts/members/axis-settitle/ Set a new Y axis title
26967 */
26968 setTitle: function(titleOptions, redraw) {
26969 this.update({
26970 title: titleOptions
26971 }, redraw);
26972 },
26973
26974 /**
26975 * Set new axis categories and optionally redraw.
26976 * @param {Array.<String>} categories - The new categories.
26977 * @param {Boolean} [redraw=true] - Whether to redraw the chart.
26978 * @sample highcharts/members/axis-setcategories/ Set categories by click on
26979 * a button
26980 */
26981 setCategories: function(categories, redraw) {
26982 this.update({
26983 categories: categories
26984 }, redraw);
26985 }
26986
26987 });
26988
26989 }(Highcharts));
26990 (function(H) {
26991 /**
26992 * (c) 2010-2017 Torstein Honsi
26993 *
26994 * License: www.highcharts.com/license
26995 */
26996 var color = H.color,
26997 each = H.each,
26998 LegendSymbolMixin = H.LegendSymbolMixin,
26999 map = H.map,
27000 pick = H.pick,
27001 Series = H.Series,
27002 seriesType = H.seriesType;
27003
27004 /**
27005 * Area series type.
27006 * @constructor seriesTypes.area
27007 * @extends {Series}
27008 */
27009 /**
27010 * The area series type.
27011 * @extends {plotOptions.line}
27012 * @product highcharts highstock
27013 * @sample {highcharts} highcharts/demo/area-basic/
27014 * Area chart
27015 * @sample {highstock} stock/demo/area/
27016 * Area chart
27017 * @optionparent plotOptions.area
27018 */
27019 seriesType('area', 'line', {
27020
27021 /**
27022 * Fill color or gradient for the area. When `null`, the series' `color`
27023 * is used with the series' `fillOpacity`.
27024 *
27025 * @type {Color}
27026 * @see In styled mode, the fill color can be set with the `.highcharts-area` class name.
27027 * @sample {highcharts} highcharts/plotoptions/area-fillcolor-default/ Null by default
27028 * @sample {highcharts} highcharts/plotoptions/area-fillcolor-gradient/ Gradient
27029 * @default null
27030 * @product highcharts highstock
27031 * @apioption plotOptions.area.fillColor
27032 */
27033
27034 /**
27035 * Fill opacity for the area. When you set an explicit `fillColor`,
27036 * the `fillOpacity` is not applied. Instead, you should define the
27037 * opacity in the `fillColor` with an rgba color definition. The `fillOpacity`
27038 * setting, also the default setting, overrides the alpha component
27039 * of the `color` setting.
27040 *
27041 * @type {Number}
27042 * @see In styled mode, the fill opacity can be set with the `.highcharts-area` class name.
27043 * @sample {highcharts} highcharts/plotoptions/area-fillopacity/ Automatic fill color and fill opacity of 0.1
27044 * @default {highcharts} 0.75
27045 * @default {highstock} .75
27046 * @product highcharts highstock
27047 * @apioption plotOptions.area.fillOpacity
27048 */
27049
27050 /**
27051 * A separate color for the graph line. By default the line takes the
27052 * `color` of the series, but the lineColor setting allows setting a
27053 * separate color for the line without altering the `fillColor`.
27054 *
27055 * @type {Color}
27056 * @see In styled mode, the line stroke can be set with the `.highcharts-graph` class name.
27057 * @sample {highcharts} highcharts/plotoptions/area-linecolor/ Dark gray line
27058 * @default null
27059 * @product highcharts highstock
27060 * @apioption plotOptions.area.lineColor
27061 */
27062
27063 /**
27064 * A separate color for the negative part of the area.
27065 *
27066 * @type {Color}
27067 * @see [negativeColor](#plotOptions.area.negativeColor). In styled mode, a negative
27068 * color is set with the `.highcharts-negative` class name ([view live
27069 * demo](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/series-
27070 * negative-color/)).
27071 * @since 3.0
27072 * @product highcharts
27073 * @apioption plotOptions.area.negativeFillColor
27074 */
27075
27076 /**
27077 * When this is true, the series will not cause the Y axis to cross
27078 * the zero plane (or [threshold](#plotOptions.series.threshold) option)
27079 * unless the data actually crosses the plane.
27080 *
27081 * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
27082 * 3 will make the Y axis show negative values according to the `minPadding`
27083 * option. If `softThreshold` is `true`, the Y axis starts at 0.
27084 *
27085 * @type {Boolean}
27086 * @default false
27087 * @since 4.1.9
27088 * @product highcharts highstock
27089 */
27090 softThreshold: false,
27091
27092 /**
27093 * The Y axis value to serve as the base for the area, for distinguishing
27094 * between values above and below a threshold. If `null`, the area
27095 * behaves like a line series with fill between the graph and the Y
27096 * axis minimum.
27097 *
27098 * @type {Number}
27099 * @sample {highcharts} highcharts/plotoptions/area-threshold/ A threshold of 100
27100 * @default 0
27101 * @since 2.0
27102 * @product highcharts highstock
27103 */
27104 threshold: 0
27105
27106 /**
27107 * Whether the whole area or just the line should respond to mouseover
27108 * tooltips and other mouse or touch events.
27109 *
27110 * @type {Boolean}
27111 * @sample {highcharts} highcharts/plotoptions/area-trackbyarea/ Display the tooltip when the area is hovered
27112 * @sample {highstock} highcharts/plotoptions/area-trackbyarea/ Display the tooltip when the area is hovered
27113 * @default false
27114 * @since 1.1.6
27115 * @product highcharts highstock
27116 * @apioption plotOptions.area.trackByArea
27117 */
27118
27119
27120 }, /** @lends seriesTypes.area.prototype */ {
27121 singleStacks: false,
27122 /**
27123 * Return an array of stacked points, where null and missing points are replaced by
27124 * dummy points in order for gaps to be drawn correctly in stacks.
27125 */
27126 getStackPoints: function(points) {
27127 var series = this,
27128 segment = [],
27129 keys = [],
27130 xAxis = this.xAxis,
27131 yAxis = this.yAxis,
27132 stack = yAxis.stacks[this.stackKey],
27133 pointMap = {},
27134 seriesIndex = series.index,
27135 yAxisSeries = yAxis.series,
27136 seriesLength = yAxisSeries.length,
27137 visibleSeries,
27138 upOrDown = pick(yAxis.options.reversedStacks, true) ? 1 : -1,
27139 i;
27140
27141
27142 points = points || this.points;
27143
27144 if (this.options.stacking) {
27145
27146 for (i = 0; i < points.length; i++) {
27147 // Reset after point update (#7326)
27148 points[i].leftNull = points[i].rightNull = null;
27149
27150 // Create a map where we can quickly look up the points by their
27151 // X values.
27152 pointMap[points[i].x] = points[i];
27153 }
27154
27155 // Sort the keys (#1651)
27156 H.objectEach(stack, function(stackX, x) {
27157 if (stackX.total !== null) { // nulled after switching between grouping and not (#1651, #2336)
27158 keys.push(x);
27159 }
27160 });
27161 keys.sort(function(a, b) {
27162 return a - b;
27163 });
27164
27165 visibleSeries = map(yAxisSeries, function() {
27166 return this.visible;
27167 });
27168
27169 each(keys, function(x, idx) {
27170 var y = 0,
27171 stackPoint,
27172 stackedValues;
27173
27174 if (pointMap[x] && !pointMap[x].isNull) {
27175 segment.push(pointMap[x]);
27176
27177 // Find left and right cliff. -1 goes left, 1 goes right.
27178 each([-1, 1], function(direction) {
27179 var nullName = direction === 1 ? 'rightNull' : 'leftNull',
27180 cliffName = direction === 1 ? 'rightCliff' : 'leftCliff',
27181 cliff = 0,
27182 otherStack = stack[keys[idx + direction]];
27183
27184 // If there is a stack next to this one, to the left or to the right...
27185 if (otherStack) {
27186 i = seriesIndex;
27187 while (i >= 0 && i < seriesLength) { // Can go either up or down, depending on reversedStacks
27188 stackPoint = otherStack.points[i];
27189 if (!stackPoint) {
27190 // If the next point in this series is missing, mark the point
27191 // with point.leftNull or point.rightNull = true.
27192 if (i === seriesIndex) {
27193 pointMap[x][nullName] = true;
27194
27195 // If there are missing points in the next stack in any of the
27196 // series below this one, we need to substract the missing values
27197 // and add a hiatus to the left or right.
27198 } else if (visibleSeries[i]) {
27199 stackedValues = stack[x].points[i];
27200 if (stackedValues) {
27201 cliff -= stackedValues[1] - stackedValues[0];
27202 }
27203 }
27204 }
27205 // When reversedStacks is true, loop up, else loop down
27206 i += upOrDown;
27207 }
27208 }
27209 pointMap[x][cliffName] = cliff;
27210 });
27211
27212
27213 // There is no point for this X value in this series, so we
27214 // insert a dummy point in order for the areas to be drawn
27215 // correctly.
27216 } else {
27217
27218 // Loop down the stack to find the series below this one that has
27219 // a value (#1991)
27220 i = seriesIndex;
27221 while (i >= 0 && i < seriesLength) {
27222 stackPoint = stack[x].points[i];
27223 if (stackPoint) {
27224 y = stackPoint[1];
27225 break;
27226 }
27227 // When reversedStacks is true, loop up, else loop down
27228 i += upOrDown;
27229 }
27230 y = yAxis.translate(y, 0, 1, 0, 1); // #6272
27231 segment.push({
27232 isNull: true,
27233 plotX: xAxis.translate(x, 0, 0, 0, 1), // #6272
27234 x: x,
27235 plotY: y,
27236 yBottom: y
27237 });
27238 }
27239 });
27240
27241 }
27242
27243 return segment;
27244 },
27245
27246 getGraphPath: function(points) {
27247 var getGraphPath = Series.prototype.getGraphPath,
27248 graphPath,
27249 options = this.options,
27250 stacking = options.stacking,
27251 yAxis = this.yAxis,
27252 topPath,
27253 bottomPath,
27254 bottomPoints = [],
27255 graphPoints = [],
27256 seriesIndex = this.index,
27257 i,
27258 areaPath,
27259 plotX,
27260 stacks = yAxis.stacks[this.stackKey],
27261 threshold = options.threshold,
27262 translatedThreshold = yAxis.getThreshold(options.threshold),
27263 isNull,
27264 yBottom,
27265 connectNulls = options.connectNulls || stacking === 'percent',
27266 /**
27267 * To display null points in underlying stacked series, this series graph must be
27268 * broken, and the area also fall down to fill the gap left by the null point. #2069
27269 */
27270 addDummyPoints = function(i, otherI, side) {
27271 var point = points[i],
27272 stackedValues = stacking && stacks[point.x].points[seriesIndex],
27273 nullVal = point[side + 'Null'] || 0,
27274 cliffVal = point[side + 'Cliff'] || 0,
27275 top,
27276 bottom,
27277 isNull = true;
27278
27279 if (cliffVal || nullVal) {
27280
27281 top = (nullVal ? stackedValues[0] : stackedValues[1]) + cliffVal;
27282 bottom = stackedValues[0] + cliffVal;
27283 isNull = !!nullVal;
27284
27285 } else if (!stacking && points[otherI] && points[otherI].isNull) {
27286 top = bottom = threshold;
27287 }
27288
27289 // Add to the top and bottom line of the area
27290 if (top !== undefined) {
27291 graphPoints.push({
27292 plotX: plotX,
27293 plotY: top === null ? translatedThreshold : yAxis.getThreshold(top),
27294 isNull: isNull,
27295 isCliff: true
27296 });
27297 bottomPoints.push({
27298 plotX: plotX,
27299 plotY: bottom === null ? translatedThreshold : yAxis.getThreshold(bottom),
27300 doCurve: false // #1041, gaps in areaspline areas
27301 });
27302 }
27303 };
27304
27305 // Find what points to use
27306 points = points || this.points;
27307
27308 // Fill in missing points
27309 if (stacking) {
27310 points = this.getStackPoints(points);
27311 }
27312
27313 for (i = 0; i < points.length; i++) {
27314 isNull = points[i].isNull;
27315 plotX = pick(points[i].rectPlotX, points[i].plotX);
27316 yBottom = pick(points[i].yBottom, translatedThreshold);
27317
27318 if (!isNull || connectNulls) {
27319
27320 if (!connectNulls) {
27321 addDummyPoints(i, i - 1, 'left');
27322 }
27323
27324 if (!(isNull && !stacking && connectNulls)) { // Skip null point when stacking is false and connectNulls true
27325 graphPoints.push(points[i]);
27326 bottomPoints.push({
27327 x: i,
27328 plotX: plotX,
27329 plotY: yBottom
27330 });
27331 }
27332
27333 if (!connectNulls) {
27334 addDummyPoints(i, i + 1, 'right');
27335 }
27336 }
27337 }
27338
27339 topPath = getGraphPath.call(this, graphPoints, true, true);
27340
27341 bottomPoints.reversed = true;
27342 bottomPath = getGraphPath.call(this, bottomPoints, true, true);
27343 if (bottomPath.length) {
27344 bottomPath[0] = 'L';
27345 }
27346
27347 areaPath = topPath.concat(bottomPath);
27348 graphPath = getGraphPath.call(this, graphPoints, false, connectNulls); // TODO: don't set leftCliff and rightCliff when connectNulls?
27349
27350 areaPath.xMap = topPath.xMap;
27351 this.areaPath = areaPath;
27352
27353 return graphPath;
27354 },
27355
27356 /**
27357 * Draw the graph and the underlying area. This method calls the Series base
27358 * function and adds the area. The areaPath is calculated in the getSegmentPath
27359 * method called from Series.prototype.drawGraph.
27360 */
27361 drawGraph: function() {
27362
27363 // Define or reset areaPath
27364 this.areaPath = [];
27365
27366 // Call the base method
27367 Series.prototype.drawGraph.apply(this);
27368
27369 // Define local variables
27370 var series = this,
27371 areaPath = this.areaPath,
27372 options = this.options,
27373 zones = this.zones,
27374 props = [
27375 [
27376 'area',
27377 'highcharts-area'
27378
27379 ]
27380 ]; // area name, main color, fill color
27381
27382 each(zones, function(zone, i) {
27383 props.push([
27384 'zone-area-' + i,
27385 'highcharts-area highcharts-zone-area-' + i + ' ' + zone.className
27386
27387 ]);
27388 });
27389
27390 each(props, function(prop) {
27391 var areaKey = prop[0],
27392 area = series[areaKey];
27393
27394 // Create or update the area
27395 if (area) { // update
27396 area.endX = series.preventGraphAnimation ? null : areaPath.xMap;
27397 area.animate({
27398 d: areaPath
27399 });
27400
27401 } else { // create
27402 area = series[areaKey] = series.chart.renderer.path(areaPath)
27403 .addClass(prop[1])
27404 .attr({
27405
27406 zIndex: 0 // #1069
27407 }).add(series.group);
27408 area.isArea = true;
27409 }
27410 area.startX = areaPath.xMap;
27411 area.shiftUnit = options.step ? 2 : 1;
27412 });
27413 },
27414
27415 drawLegendSymbol: LegendSymbolMixin.drawRectangle
27416 });
27417
27418 /**
27419 * A `area` series. If the [type](#series.area.type) option is not
27420 * specified, it is inherited from [chart.type](#chart.type).
27421 *
27422 * For options that apply to multiple series, it is recommended to add
27423 * them to the [plotOptions.series](#plotOptions.series) options structure.
27424 * To apply to all series of this specific type, apply it to [plotOptions.
27425 * area](#plotOptions.area).
27426 *
27427 * @type {Object}
27428 * @extends series,plotOptions.area
27429 * @excluding dataParser,dataURL
27430 * @product highcharts highstock
27431 * @apioption series.area
27432 */
27433
27434 /**
27435 * An array of data points for the series. For the `area` series type,
27436 * points can be given in the following ways:
27437 *
27438 * 1. An array of numerical values. In this case, the numerical values
27439 * will be interpreted as `y` options. The `x` values will be automatically
27440 * calculated, either starting at 0 and incremented by 1, or from `pointStart`
27441 * and `pointInterval` given in the series options. If the axis has
27442 * categories, these will be used. Example:
27443 *
27444 * ```js
27445 * data: [0, 5, 3, 5]
27446 * ```
27447 *
27448 * 2. An array of arrays with 2 values. In this case, the values correspond
27449 * to `x,y`. If the first value is a string, it is applied as the name
27450 * of the point, and the `x` value is inferred.
27451 *
27452 * ```js
27453 * data: [
27454 * [0, 9],
27455 * [1, 7],
27456 * [2, 6]
27457 * ]
27458 * ```
27459 *
27460 * 3. An array of objects with named values. The objects are point
27461 * configuration objects as seen below. If the total number of data
27462 * points exceeds the series' [turboThreshold](#series.area.turboThreshold),
27463 * this option is not available.
27464 *
27465 * ```js
27466 * data: [{
27467 * x: 1,
27468 * y: 9,
27469 * name: "Point2",
27470 * color: "#00FF00"
27471 * }, {
27472 * x: 1,
27473 * y: 6,
27474 * name: "Point1",
27475 * color: "#FF00FF"
27476 * }]
27477 * ```
27478 *
27479 * @type {Array<Object|Array|Number>}
27480 * @extends series.line.data
27481 * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
27482 * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
27483 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
27484 * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
27485 * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
27486 * @product highcharts highstock
27487 * @apioption series.area.data
27488 */
27489
27490 }(Highcharts));
27491 (function(H) {
27492 /**
27493 * (c) 2010-2017 Torstein Honsi
27494 *
27495 * License: www.highcharts.com/license
27496 */
27497 var pick = H.pick,
27498 seriesType = H.seriesType;
27499
27500 /**
27501 * A spline series is a special type of line series, where the segments between
27502 * the data points are smoothed.
27503 *
27504 * @sample {highcharts} highcharts/demo/spline-irregular-time/ Spline chart
27505 * @sample {highstock} stock/demo/spline/ Spline chart
27506 *
27507 * @extends plotOptions.series
27508 * @excluding step
27509 * @product highcharts highstock
27510 * @apioption plotOptions.spline
27511 */
27512
27513 /**
27514 * Spline series type.
27515 * @constructor seriesTypes.spline
27516 * @extends {Series}
27517 */
27518 seriesType('spline', 'line', {}, /** @lends seriesTypes.spline.prototype */ {
27519 /**
27520 * Get the spline segment from a given point's previous neighbour to the
27521 * given point
27522 */
27523 getPointSpline: function(points, point, i) {
27524 var
27525 // 1 means control points midway between points, 2 means 1/3 from
27526 // the point, 3 is 1/4 etc
27527 smoothing = 1.5,
27528 denom = smoothing + 1,
27529 plotX = point.plotX,
27530 plotY = point.plotY,
27531 lastPoint = points[i - 1],
27532 nextPoint = points[i + 1],
27533 leftContX,
27534 leftContY,
27535 rightContX,
27536 rightContY,
27537 ret;
27538
27539 function doCurve(otherPoint) {
27540 return otherPoint &&
27541 !otherPoint.isNull &&
27542 otherPoint.doCurve !== false &&
27543 !point.isCliff; // #6387, area splines next to null
27544 }
27545
27546 // Find control points
27547 if (doCurve(lastPoint) && doCurve(nextPoint)) {
27548 var lastX = lastPoint.plotX,
27549 lastY = lastPoint.plotY,
27550 nextX = nextPoint.plotX,
27551 nextY = nextPoint.plotY,
27552 correction = 0;
27553
27554 leftContX = (smoothing * plotX + lastX) / denom;
27555 leftContY = (smoothing * plotY + lastY) / denom;
27556 rightContX = (smoothing * plotX + nextX) / denom;
27557 rightContY = (smoothing * plotY + nextY) / denom;
27558
27559 // Have the two control points make a straight line through main
27560 // point
27561 if (rightContX !== leftContX) { // #5016, division by zero
27562 correction = ((rightContY - leftContY) * (rightContX - plotX)) /
27563 (rightContX - leftContX) + plotY - rightContY;
27564 }
27565
27566 leftContY += correction;
27567 rightContY += correction;
27568
27569 // to prevent false extremes, check that control points are between
27570 // neighbouring points' y values
27571 if (leftContY > lastY && leftContY > plotY) {
27572 leftContY = Math.max(lastY, plotY);
27573 // mirror of left control point
27574 rightContY = 2 * plotY - leftContY;
27575 } else if (leftContY < lastY && leftContY < plotY) {
27576 leftContY = Math.min(lastY, plotY);
27577 rightContY = 2 * plotY - leftContY;
27578 }
27579 if (rightContY > nextY && rightContY > plotY) {
27580 rightContY = Math.max(nextY, plotY);
27581 leftContY = 2 * plotY - rightContY;
27582 } else if (rightContY < nextY && rightContY < plotY) {
27583 rightContY = Math.min(nextY, plotY);
27584 leftContY = 2 * plotY - rightContY;
27585 }
27586
27587 // record for drawing in next point
27588 point.rightContX = rightContX;
27589 point.rightContY = rightContY;
27590
27591
27592 }
27593
27594 // Visualize control points for debugging
27595 /*
27596 if (leftContX) {
27597 this.chart.renderer.circle(
27598 leftContX + this.chart.plotLeft,
27599 leftContY + this.chart.plotTop,
27600 2
27601 )
27602 .attr({
27603 stroke: 'red',
27604 'stroke-width': 2,
27605 fill: 'none',
27606 zIndex: 9
27607 })
27608 .add();
27609 this.chart.renderer.path(['M', leftContX + this.chart.plotLeft,
27610 leftContY + this.chart.plotTop,
27611 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
27612 .attr({
27613 stroke: 'red',
27614 'stroke-width': 2,
27615 zIndex: 9
27616 })
27617 .add();
27618 }
27619 if (rightContX) {
27620 this.chart.renderer.circle(
27621 rightContX + this.chart.plotLeft,
27622 rightContY + this.chart.plotTop,
27623 2
27624 )
27625 .attr({
27626 stroke: 'green',
27627 'stroke-width': 2,
27628 fill: 'none',
27629 zIndex: 9
27630 })
27631 .add();
27632 this.chart.renderer.path(['M', rightContX + this.chart.plotLeft,
27633 rightContY + this.chart.plotTop,
27634 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
27635 .attr({
27636 stroke: 'green',
27637 'stroke-width': 2,
27638 zIndex: 9
27639 })
27640 .add();
27641 }
27642 // */
27643 ret = [
27644 'C',
27645 pick(lastPoint.rightContX, lastPoint.plotX),
27646 pick(lastPoint.rightContY, lastPoint.plotY),
27647 pick(leftContX, plotX),
27648 pick(leftContY, plotY),
27649 plotX,
27650 plotY
27651 ];
27652 // reset for updating series later
27653 lastPoint.rightContX = lastPoint.rightContY = null;
27654 return ret;
27655 }
27656 });
27657
27658 /**
27659 * A `spline` series. If the [type](#series.spline.type) option is
27660 * not specified, it is inherited from [chart.type](#chart.type).
27661 *
27662 * For options that apply to multiple series, it is recommended to add
27663 * them to the [plotOptions.series](#plotOptions.series) options structure.
27664 * To apply to all series of this specific type, apply it to [plotOptions.
27665 * spline](#plotOptions.spline).
27666 *
27667 * @type {Object}
27668 * @extends series,plotOptions.spline
27669 * @excluding dataParser,dataURL
27670 * @product highcharts highstock
27671 * @apioption series.spline
27672 */
27673
27674 /**
27675 * An array of data points for the series. For the `spline` series type,
27676 * points can be given in the following ways:
27677 *
27678 * 1. An array of numerical values. In this case, the numerical values
27679 * will be interpreted as `y` options. The `x` values will be automatically
27680 * calculated, either starting at 0 and incremented by 1, or from `pointStart`
27681 * and `pointInterval` given in the series options. If the axis has
27682 * categories, these will be used. Example:
27683 *
27684 * ```js
27685 * data: [0, 5, 3, 5]
27686 * ```
27687 *
27688 * 2. An array of arrays with 2 values. In this case, the values correspond
27689 * to `x,y`. If the first value is a string, it is applied as the name
27690 * of the point, and the `x` value is inferred.
27691 *
27692 * ```js
27693 * data: [
27694 * [0, 9],
27695 * [1, 2],
27696 * [2, 8]
27697 * ]
27698 * ```
27699 *
27700 * 3. An array of objects with named values. The objects are point
27701 * configuration objects as seen below. If the total number of data
27702 * points exceeds the series' [turboThreshold](#series.spline.turboThreshold),
27703 * this option is not available.
27704 *
27705 * ```js
27706 * data: [{
27707 * x: 1,
27708 * y: 9,
27709 * name: "Point2",
27710 * color: "#00FF00"
27711 * }, {
27712 * x: 1,
27713 * y: 0,
27714 * name: "Point1",
27715 * color: "#FF00FF"
27716 * }]
27717 * ```
27718 *
27719 * @type {Array<Object|Array|Number>}
27720 * @extends series.line.data
27721 * @sample {highcharts} highcharts/chart/reflow-true/
27722 * Numerical values
27723 * @sample {highcharts} highcharts/series/data-array-of-arrays/
27724 * Arrays of numeric x and y
27725 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
27726 * Arrays of datetime x and y
27727 * @sample {highcharts} highcharts/series/data-array-of-name-value/
27728 * Arrays of point.name and y
27729 * @sample {highcharts} highcharts/series/data-array-of-objects/
27730 * Config objects
27731 * @product highcharts highstock
27732 * @apioption series.spline.data
27733 */
27734
27735 }(Highcharts));
27736 (function(H) {
27737 /**
27738 * (c) 2010-2017 Torstein Honsi
27739 *
27740 * License: www.highcharts.com/license
27741 */
27742 var areaProto = H.seriesTypes.area.prototype,
27743 defaultPlotOptions = H.defaultPlotOptions,
27744 LegendSymbolMixin = H.LegendSymbolMixin,
27745 seriesType = H.seriesType;
27746 /**
27747 * AreaSplineSeries object
27748 */
27749 /**
27750 * The area spline series is an area series where the graph between the points
27751 * is smoothed into a spline.
27752 *
27753 * @extends plotOptions.area
27754 * @excluding step
27755 * @sample {highcharts} highcharts/demo/areaspline/ Area spline chart
27756 * @sample {highstock} stock/demo/areaspline/ Area spline chart
27757 * @product highcharts highstock
27758 * @apioption plotOptions.areaspline
27759 */
27760 seriesType('areaspline', 'spline', defaultPlotOptions.area, {
27761 getStackPoints: areaProto.getStackPoints,
27762 getGraphPath: areaProto.getGraphPath,
27763 drawGraph: areaProto.drawGraph,
27764 drawLegendSymbol: LegendSymbolMixin.drawRectangle
27765 });
27766 /**
27767 * A `areaspline` series. If the [type](#series.areaspline.type) option
27768 * is not specified, it is inherited from [chart.type](#chart.type).
27769 *
27770 *
27771 * For options that apply to multiple series, it is recommended to add
27772 * them to the [plotOptions.series](#plotOptions.series) options structure.
27773 * To apply to all series of this specific type, apply it to [plotOptions.
27774 * areaspline](#plotOptions.areaspline).
27775 *
27776 * @type {Object}
27777 * @extends series,plotOptions.areaspline
27778 * @excluding dataParser,dataURL
27779 * @product highcharts highstock
27780 * @apioption series.areaspline
27781 */
27782
27783
27784 /**
27785 * An array of data points for the series. For the `areaspline` series
27786 * type, points can be given in the following ways:
27787 *
27788 * 1. An array of numerical values. In this case, the numerical values
27789 * will be interpreted as `y` options. The `x` values will be automatically
27790 * calculated, either starting at 0 and incremented by 1, or from `pointStart`
27791 * and `pointInterval` given in the series options. If the axis has
27792 * categories, these will be used. Example:
27793 *
27794 * ```js
27795 * data: [0, 5, 3, 5]
27796 * ```
27797 *
27798 * 2. An array of arrays with 2 values. In this case, the values correspond
27799 * to `x,y`. If the first value is a string, it is applied as the name
27800 * of the point, and the `x` value is inferred.
27801 *
27802 * ```js
27803 * data: [
27804 * [0, 10],
27805 * [1, 9],
27806 * [2, 3]
27807 * ]
27808 * ```
27809 *
27810 * 3. An array of objects with named values. The objects are point
27811 * configuration objects as seen below. If the total number of data
27812 * points exceeds the series' [turboThreshold](#series.areaspline.turboThreshold),
27813 * this option is not available.
27814 *
27815 * ```js
27816 * data: [{
27817 * x: 1,
27818 * y: 4,
27819 * name: "Point2",
27820 * color: "#00FF00"
27821 * }, {
27822 * x: 1,
27823 * y: 4,
27824 * name: "Point1",
27825 * color: "#FF00FF"
27826 * }]
27827 * ```
27828 *
27829 * @type {Array<Object|Array|Number>}
27830 * @extends series.line.data
27831 * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
27832 * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
27833 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
27834 * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
27835 * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
27836 * @product highcharts highstock
27837 * @apioption series.areaspline.data
27838 */
27839
27840
27841
27842 }(Highcharts));
27843 (function(H) {
27844 /**
27845 * (c) 2010-2017 Torstein Honsi
27846 *
27847 * License: www.highcharts.com/license
27848 */
27849 var animObject = H.animObject,
27850 color = H.color,
27851 each = H.each,
27852 extend = H.extend,
27853 isNumber = H.isNumber,
27854 LegendSymbolMixin = H.LegendSymbolMixin,
27855 merge = H.merge,
27856 noop = H.noop,
27857 pick = H.pick,
27858 Series = H.Series,
27859 seriesType = H.seriesType,
27860 svg = H.svg;
27861 /**
27862 * The column series type.
27863 *
27864 * @constructor seriesTypes.column
27865 * @augments Series
27866 */
27867
27868 /**
27869 * Column series display one column per value along an X axis.
27870 *
27871 * @sample {highcharts} highcharts/demo/column-basic/ Column chart
27872 * @sample {highstock} stock/demo/column/ Column chart
27873 *
27874 * @extends {plotOptions.line}
27875 * @product highcharts highstock
27876 * @excluding connectNulls,dashStyle,gapSize,gapUnit,linecap,lineWidth,marker,
27877 * connectEnds,step
27878 * @optionparent plotOptions.column
27879 */
27880 seriesType('column', 'line', {
27881
27882 /**
27883 * The corner radius of the border surrounding each column or bar.
27884 *
27885 * @type {Number}
27886 * @sample {highcharts} highcharts/plotoptions/column-borderradius/
27887 * Rounded columns
27888 * @default 0
27889 * @product highcharts highstock
27890 */
27891 borderRadius: 0,
27892
27893 /**
27894 * The width of the border surrounding each column or bar.
27895 *
27896 * In styled mode, the stroke width can be set with the `.highcharts-point`
27897 * rule.
27898 *
27899 * @type {Number}
27900 * @sample {highcharts} highcharts/plotoptions/column-borderwidth/
27901 * 2px black border
27902 * @default 1
27903 * @product highcharts highstock
27904 * @apioption plotOptions.column.borderWidth
27905 */
27906
27907 /**
27908 * When using automatic point colors pulled from the `options.colors`
27909 * collection, this option determines whether the chart should receive
27910 * one color per series or one color per point.
27911 *
27912 * @type {Boolean}
27913 * @see [series colors](#plotOptions.column.colors)
27914 * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-false/
27915 * False by default
27916 * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-true/
27917 * True
27918 * @default false
27919 * @since 2.0
27920 * @product highcharts highstock
27921 * @apioption plotOptions.column.colorByPoint
27922 */
27923
27924 /**
27925 * A series specific or series type specific color set to apply instead
27926 * of the global [colors](#colors) when [colorByPoint](#plotOptions.
27927 * column.colorByPoint) is true.
27928 *
27929 * @type {Array<Color>}
27930 * @since 3.0
27931 * @product highcharts highstock
27932 * @apioption plotOptions.column.colors
27933 */
27934
27935 /**
27936 * When true, each column edge is rounded to its nearest pixel in order
27937 * to render sharp on screen. In some cases, when there are a lot of
27938 * densely packed columns, this leads to visible difference in column
27939 * widths or distance between columns. In these cases, setting `crisp`
27940 * to `false` may look better, even though each column is rendered
27941 * blurry.
27942 *
27943 * @type {Boolean}
27944 * @sample {highcharts} highcharts/plotoptions/column-crisp-false/
27945 * Crisp is false
27946 * @default true
27947 * @since 5.0.10
27948 * @product highcharts highstock
27949 */
27950 crisp: true,
27951
27952 /**
27953 * Padding between each value groups, in x axis units.
27954 *
27955 * @type {Number}
27956 * @sample {highcharts} highcharts/plotoptions/column-grouppadding-default/
27957 * 0.2 by default
27958 * @sample {highcharts} highcharts/plotoptions/column-grouppadding-none/
27959 * No group padding - all columns are evenly spaced
27960 * @default 0.2
27961 * @product highcharts highstock
27962 */
27963 groupPadding: 0.2,
27964
27965 /**
27966 * Whether to group non-stacked columns or to let them render independent
27967 * of each other. Non-grouped columns will be laid out individually
27968 * and overlap each other.
27969 *
27970 * @type {Boolean}
27971 * @sample {highcharts} highcharts/plotoptions/column-grouping-false/
27972 * Grouping disabled
27973 * @sample {highstock} highcharts/plotoptions/column-grouping-false/
27974 * Grouping disabled
27975 * @default true
27976 * @since 2.3.0
27977 * @product highcharts highstock
27978 * @apioption plotOptions.column.grouping
27979 */
27980
27981 marker: null, // point options are specified in the base options
27982
27983 /**
27984 * The maximum allowed pixel width for a column, translated to the height
27985 * of a bar in a bar chart. This prevents the columns from becoming
27986 * too wide when there is a small number of points in the chart.
27987 *
27988 * @type {Number}
27989 * @see [pointWidth](#plotOptions.column.pointWidth)
27990 * @sample {highcharts} highcharts/plotoptions/column-maxpointwidth-20/
27991 * Limited to 50
27992 * @sample {highstock} highcharts/plotoptions/column-maxpointwidth-20/
27993 * Limited to 50
27994 * @default null
27995 * @since 4.1.8
27996 * @product highcharts highstock
27997 * @apioption plotOptions.column.maxPointWidth
27998 */
27999
28000 /**
28001 * Padding between each column or bar, in x axis units.
28002 *
28003 * @type {Number}
28004 * @sample {highcharts} highcharts/plotoptions/column-pointpadding-default/
28005 * 0.1 by default
28006 * @sample {highcharts} highcharts/plotoptions/column-pointpadding-025/
28007 * 0.25
28008 * @sample {highcharts} highcharts/plotoptions/column-pointpadding-none/
28009 * 0 for tightly packed columns
28010 * @default 0.1
28011 * @product highcharts highstock
28012 */
28013 pointPadding: 0.1,
28014
28015 /**
28016 * A pixel value specifying a fixed width for each column or bar. When
28017 * `null`, the width is calculated from the `pointPadding` and
28018 * `groupPadding`.
28019 *
28020 * @type {Number}
28021 * @see [maxPointWidth](#plotOptions.column.maxPointWidth)
28022 * @sample {highcharts} highcharts/plotoptions/column-pointwidth-20/
28023 * 20px wide columns regardless of chart width or the amount of data
28024 * points
28025 * @default null
28026 * @since 1.2.5
28027 * @product highcharts highstock
28028 * @apioption plotOptions.column.pointWidth
28029 */
28030
28031 /**
28032 * The minimal height for a column or width for a bar. By default,
28033 * 0 values are not shown. To visualize a 0 (or close to zero) point,
28034 * set the minimal point length to a pixel value like 3\. In stacked
28035 * column charts, minPointLength might not be respected for tightly
28036 * packed values.
28037 *
28038 * @type {Number}
28039 * @sample {highcharts} highcharts/plotoptions/column-minpointlength/
28040 * Zero base value
28041 * @sample {highcharts} highcharts/plotoptions/column-minpointlength-pos-and-neg/
28042 * Positive and negative close to zero values
28043 * @default 0
28044 * @product highcharts highstock
28045 */
28046 minPointLength: 0,
28047
28048 /**
28049 * When the series contains less points than the crop threshold, all
28050 * points are drawn, event if the points fall outside the visible plot
28051 * area at the current zoom. The advantage of drawing all points (including
28052 * markers and columns), is that animation is performed on updates.
28053 * On the other hand, when the series contains more points than the
28054 * crop threshold, the series data is cropped to only contain points
28055 * that fall within the plot area. The advantage of cropping away invisible
28056 * points is to increase performance on large series. .
28057 *
28058 * @type {Number}
28059 * @default 50
28060 * @product highcharts highstock
28061 */
28062 cropThreshold: 50,
28063
28064 /**
28065 * The X axis range that each point is valid for. This determines the
28066 * width of the column. On a categorized axis, the range will be 1
28067 * by default (one category unit). On linear and datetime axes, the
28068 * range will be computed as the distance between the two closest data
28069 * points.
28070 *
28071 * The default `null` means it is computed automatically, but this option
28072 * can be used to override the automatic value.
28073 *
28074 * @type {Number}
28075 * @sample {highcharts} highcharts/plotoptions/column-pointrange/
28076 * Set the point range to one day on a data set with one week
28077 * between the points
28078 * @default null
28079 * @since 2.3
28080 * @product highcharts highstock
28081 */
28082 pointRange: null,
28083
28084 states: {
28085
28086 /**
28087 * @extends plotOptions.series.states.hover
28088 * @excluding halo,lineWidth,lineWidthPlus,marker
28089 * @product highcharts highstock
28090 */
28091 hover: {
28092
28093 /**
28094 * @ignore-option
28095 */
28096 halo: false,
28097 /**
28098 * A specific border color for the hovered point. Defaults to
28099 * inherit the normal state border color.
28100 *
28101 * @type {Color}
28102 * @product highcharts
28103 * @apioption plotOptions.column.states.hover.borderColor
28104 */
28105
28106 /**
28107 * A specific color for the hovered point.
28108 *
28109 * @type {Color}
28110 * @default undefined
28111 * @product highcharts
28112 * @apioption plotOptions.column.states.hover.color
28113 */
28114
28115
28116 }
28117
28118 },
28119
28120 dataLabels: {
28121 align: null, // auto
28122 verticalAlign: null, // auto
28123 y: null
28124 },
28125
28126 /**
28127 * When this is true, the series will not cause the Y axis to cross
28128 * the zero plane (or [threshold](#plotOptions.series.threshold) option)
28129 * unless the data actually crosses the plane.
28130 *
28131 * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
28132 * 3 will make the Y axis show negative values according to the `minPadding`
28133 * option. If `softThreshold` is `true`, the Y axis starts at 0.
28134 *
28135 * @type {Boolean}
28136 * @default {highcharts} true
28137 * @default {highstock} false
28138 * @since 4.1.9
28139 * @product highcharts highstock
28140 */
28141 softThreshold: false,
28142
28143 // false doesn't work well: http://jsfiddle.net/highcharts/hz8fopan/14/
28144 /** @ignore */
28145 startFromThreshold: true,
28146
28147 stickyTracking: false,
28148
28149 tooltip: {
28150 distance: 6
28151 },
28152
28153 /**
28154 * The Y axis value to serve as the base for the columns, for distinguishing
28155 * between values above and below a threshold. If `null`, the columns
28156 * extend from the padding Y axis minimum.
28157 *
28158 * @type {Number}
28159 * @default 0
28160 * @since 2.0
28161 * @product highcharts
28162 */
28163 threshold: 0
28164
28165
28166 }, /** @lends seriesTypes.column.prototype */ {
28167 cropShoulder: 0,
28168 // When tooltip is not shared, this series (and derivatives) requires direct
28169 // touch/hover. KD-tree does not apply.
28170 directTouch: true,
28171 trackerGroups: ['group', 'dataLabelsGroup'],
28172 // use separate negative stacks, unlike area stacks where a negative point
28173 // is substracted from previous (#1910)
28174 negStacks: true,
28175
28176 /**
28177 * Initialize the series. Extends the basic Series.init method by
28178 * marking other series of the same type as dirty.
28179 *
28180 * @function #init
28181 * @memberOf seriesTypes.column
28182 *
28183 */
28184 init: function() {
28185 Series.prototype.init.apply(this, arguments);
28186
28187 var series = this,
28188 chart = series.chart;
28189
28190 // if the series is added dynamically, force redraw of other
28191 // series affected by a new column
28192 if (chart.hasRendered) {
28193 each(chart.series, function(otherSeries) {
28194 if (otherSeries.type === series.type) {
28195 otherSeries.isDirty = true;
28196 }
28197 });
28198 }
28199 },
28200
28201 /**
28202 * Return the width and x offset of the columns adjusted for grouping,
28203 * groupPadding, pointPadding, pointWidth etc.
28204 */
28205 getColumnMetrics: function() {
28206
28207 var series = this,
28208 options = series.options,
28209 xAxis = series.xAxis,
28210 yAxis = series.yAxis,
28211 reversedXAxis = xAxis.reversed,
28212 stackKey,
28213 stackGroups = {},
28214 columnCount = 0;
28215
28216 // Get the total number of column type series. This is called on every
28217 // series. Consider moving this logic to a chart.orderStacks() function
28218 // and call it on init, addSeries and removeSeries
28219 if (options.grouping === false) {
28220 columnCount = 1;
28221 } else {
28222 each(series.chart.series, function(otherSeries) {
28223 var otherOptions = otherSeries.options,
28224 otherYAxis = otherSeries.yAxis,
28225 columnIndex;
28226 if (
28227 otherSeries.type === series.type &&
28228 (
28229 otherSeries.visible ||
28230 !series.chart.options.chart.ignoreHiddenSeries
28231 ) &&
28232 yAxis.len === otherYAxis.len &&
28233 yAxis.pos === otherYAxis.pos
28234 ) { // #642, #2086
28235 if (otherOptions.stacking) {
28236 stackKey = otherSeries.stackKey;
28237 if (stackGroups[stackKey] === undefined) {
28238 stackGroups[stackKey] = columnCount++;
28239 }
28240 columnIndex = stackGroups[stackKey];
28241 } else if (otherOptions.grouping !== false) { // #1162
28242 columnIndex = columnCount++;
28243 }
28244 otherSeries.columnIndex = columnIndex;
28245 }
28246 });
28247 }
28248
28249 var categoryWidth = Math.min(
28250 Math.abs(xAxis.transA) * (
28251 xAxis.ordinalSlope ||
28252 options.pointRange ||
28253 xAxis.closestPointRange ||
28254 xAxis.tickInterval ||
28255 1
28256 ), // #2610
28257 xAxis.len // #1535
28258 ),
28259 groupPadding = categoryWidth * options.groupPadding,
28260 groupWidth = categoryWidth - 2 * groupPadding,
28261 pointOffsetWidth = groupWidth / (columnCount || 1),
28262 pointWidth = Math.min(
28263 options.maxPointWidth || xAxis.len,
28264 pick(
28265 options.pointWidth,
28266 pointOffsetWidth * (1 - 2 * options.pointPadding)
28267 )
28268 ),
28269 pointPadding = (pointOffsetWidth - pointWidth) / 2,
28270 // #1251, #3737
28271 colIndex = (series.columnIndex || 0) + (reversedXAxis ? 1 : 0),
28272 pointXOffset =
28273 pointPadding +
28274 (
28275 groupPadding +
28276 colIndex * pointOffsetWidth -
28277 (categoryWidth / 2)
28278 ) * (reversedXAxis ? -1 : 1);
28279
28280 // Save it for reading in linked series (Error bars particularly)
28281 series.columnMetrics = {
28282 width: pointWidth,
28283 offset: pointXOffset
28284 };
28285 return series.columnMetrics;
28286
28287 },
28288
28289 /**
28290 * Make the columns crisp. The edges are rounded to the nearest full pixel.
28291 */
28292 crispCol: function(x, y, w, h) {
28293 var chart = this.chart,
28294 borderWidth = this.borderWidth,
28295 xCrisp = -(borderWidth % 2 ? 0.5 : 0),
28296 yCrisp = borderWidth % 2 ? 0.5 : 1,
28297 right,
28298 bottom,
28299 fromTop;
28300
28301 if (chart.inverted && chart.renderer.isVML) {
28302 yCrisp += 1;
28303 }
28304
28305 // Horizontal. We need to first compute the exact right edge, then round
28306 // it and compute the width from there.
28307 if (this.options.crisp) {
28308 right = Math.round(x + w) + xCrisp;
28309 x = Math.round(x) + xCrisp;
28310 w = right - x;
28311 }
28312
28313 // Vertical
28314 bottom = Math.round(y + h) + yCrisp;
28315 fromTop = Math.abs(y) <= 0.5 && bottom > 0.5; // #4504, #4656
28316 y = Math.round(y) + yCrisp;
28317 h = bottom - y;
28318
28319 // Top edges are exceptions
28320 if (fromTop && h) { // #5146
28321 y -= 1;
28322 h += 1;
28323 }
28324
28325 return {
28326 x: x,
28327 y: y,
28328 width: w,
28329 height: h
28330 };
28331 },
28332
28333 /**
28334 * Translate each point to the plot area coordinate system and find shape
28335 * positions
28336 */
28337 translate: function() {
28338 var series = this,
28339 chart = series.chart,
28340 options = series.options,
28341 dense = series.dense =
28342 series.closestPointRange * series.xAxis.transA < 2,
28343 borderWidth = series.borderWidth = pick(
28344 options.borderWidth,
28345 dense ? 0 : 1 // #3635
28346 ),
28347 yAxis = series.yAxis,
28348 threshold = options.threshold,
28349 translatedThreshold = series.translatedThreshold =
28350 yAxis.getThreshold(threshold),
28351 minPointLength = pick(options.minPointLength, 5),
28352 metrics = series.getColumnMetrics(),
28353 pointWidth = metrics.width,
28354 // postprocessed for border width
28355 seriesBarW = series.barW =
28356 Math.max(pointWidth, 1 + 2 * borderWidth),
28357 pointXOffset = series.pointXOffset = metrics.offset;
28358
28359 if (chart.inverted) {
28360 translatedThreshold -= 0.5; // #3355
28361 }
28362
28363 // When the pointPadding is 0, we want the columns to be packed tightly,
28364 // so we allow individual columns to have individual sizes. When
28365 // pointPadding is greater, we strive for equal-width columns (#2694).
28366 if (options.pointPadding) {
28367 seriesBarW = Math.ceil(seriesBarW);
28368 }
28369
28370 Series.prototype.translate.apply(series);
28371
28372 // Record the new values
28373 each(series.points, function(point) {
28374 var yBottom = pick(point.yBottom, translatedThreshold),
28375 safeDistance = 999 + Math.abs(yBottom),
28376 plotY = Math.min(
28377 Math.max(-safeDistance, point.plotY),
28378 yAxis.len + safeDistance
28379 ), // Don't draw too far outside plot area (#1303, #2241, #4264)
28380 barX = point.plotX + pointXOffset,
28381 barW = seriesBarW,
28382 barY = Math.min(plotY, yBottom),
28383 up,
28384 barH = Math.max(plotY, yBottom) - barY;
28385
28386 // Handle options.minPointLength
28387 if (minPointLength && Math.abs(barH) < minPointLength) {
28388 barH = minPointLength;
28389 up = (!yAxis.reversed && !point.negative) ||
28390 (yAxis.reversed && point.negative);
28391
28392 // Reverse zeros if there's no positive value in the series
28393 // in visible range (#7046)
28394 if (
28395 point.y === threshold &&
28396 series.dataMax <= threshold &&
28397 yAxis.min < threshold // and if there's room for it (#7311)
28398 ) {
28399 up = !up;
28400 }
28401
28402 // If stacked...
28403 barY = Math.abs(barY - translatedThreshold) > minPointLength ?
28404 // ...keep position
28405 yBottom - minPointLength :
28406 // #1485, #4051
28407 translatedThreshold - (up ? minPointLength : 0);
28408 }
28409
28410 // Cache for access in polar
28411 point.barX = barX;
28412 point.pointWidth = pointWidth;
28413
28414 // Fix the tooltip on center of grouped columns (#1216, #424, #3648)
28415 point.tooltipPos = chart.inverted ? [
28416 yAxis.len + yAxis.pos - chart.plotLeft - plotY,
28417 series.xAxis.len - barX - barW / 2, barH
28418 ] : [barX + barW / 2, plotY + yAxis.pos - chart.plotTop, barH];
28419
28420 // Register shape type and arguments to be used in drawPoints
28421 point.shapeType = 'rect';
28422 point.shapeArgs = series.crispCol.apply(
28423 series,
28424 point.isNull ?
28425 // #3169, drilldown from null must have a position to work
28426 // from #6585, dataLabel should be placed on xAxis, not
28427 // floating in the middle of the chart
28428 [barX, translatedThreshold, barW, 0] : [barX, barY, barW, barH]
28429 );
28430 });
28431
28432 },
28433
28434 getSymbol: noop,
28435
28436 /**
28437 * Use a solid rectangle like the area series types
28438 */
28439 drawLegendSymbol: LegendSymbolMixin.drawRectangle,
28440
28441
28442 /**
28443 * Columns have no graph
28444 */
28445 drawGraph: function() {
28446 this.group[
28447 this.dense ? 'addClass' : 'removeClass'
28448 ]('highcharts-dense-data');
28449 },
28450
28451
28452
28453 /**
28454 * Draw the columns. For bars, the series.group is rotated, so the same
28455 * coordinates apply for columns and bars. This method is inherited by
28456 * scatter series.
28457 */
28458 drawPoints: function() {
28459 var series = this,
28460 chart = this.chart,
28461 options = series.options,
28462 renderer = chart.renderer,
28463 animationLimit = options.animationLimit || 250,
28464 shapeArgs;
28465
28466 // draw the columns
28467 each(series.points, function(point) {
28468 var plotY = point.plotY,
28469 graphic = point.graphic;
28470
28471 if (isNumber(plotY) && point.y !== null) {
28472 shapeArgs = point.shapeArgs;
28473
28474 if (graphic) { // update
28475 graphic[
28476 chart.pointCount < animationLimit ? 'animate' : 'attr'
28477 ](
28478 merge(shapeArgs)
28479 );
28480
28481 } else {
28482 point.graphic = graphic =
28483 renderer[point.shapeType](shapeArgs)
28484 .add(point.group || series.group);
28485 }
28486
28487 // Border radius is not stylable (#6900)
28488 if (options.borderRadius) {
28489 graphic.attr({
28490 r: options.borderRadius
28491 });
28492 }
28493
28494
28495
28496 graphic.addClass(point.getClassName(), true);
28497
28498
28499 } else if (graphic) {
28500 point.graphic = graphic.destroy(); // #1269
28501 }
28502 });
28503 },
28504
28505 /**
28506 * Animate the column heights one by one from zero
28507 * @param {Boolean} init Whether to initialize the animation or run it
28508 */
28509 animate: function(init) {
28510 var series = this,
28511 yAxis = this.yAxis,
28512 options = series.options,
28513 inverted = this.chart.inverted,
28514 attr = {},
28515 translateProp = inverted ? 'translateX' : 'translateY',
28516 translateStart,
28517 translatedThreshold;
28518
28519 if (svg) { // VML is too slow anyway
28520 if (init) {
28521 attr.scaleY = 0.001;
28522 translatedThreshold = Math.min(
28523 yAxis.pos + yAxis.len,
28524 Math.max(yAxis.pos, yAxis.toPixels(options.threshold))
28525 );
28526 if (inverted) {
28527 attr.translateX = translatedThreshold - yAxis.len;
28528 } else {
28529 attr.translateY = translatedThreshold;
28530 }
28531 series.group.attr(attr);
28532
28533 } else { // run the animation
28534 translateStart = series.group.attr(translateProp);
28535 series.group.animate({
28536 scaleY: 1
28537 },
28538 extend(animObject(series.options.animation), {
28539 // Do the scale synchronously to ensure smooth updating
28540 // (#5030, #7228)
28541 step: function(val, fx) {
28542
28543 attr[translateProp] =
28544 translateStart +
28545 fx.pos * (yAxis.pos - translateStart);
28546 series.group.attr(attr);
28547 }
28548 }));
28549
28550 // delete this function to allow it only once
28551 series.animate = null;
28552 }
28553 }
28554 },
28555
28556 /**
28557 * Remove this series from the chart
28558 */
28559 remove: function() {
28560 var series = this,
28561 chart = series.chart;
28562
28563 // column and bar series affects other series of the same type
28564 // as they are either stacked or grouped
28565 if (chart.hasRendered) {
28566 each(chart.series, function(otherSeries) {
28567 if (otherSeries.type === series.type) {
28568 otherSeries.isDirty = true;
28569 }
28570 });
28571 }
28572
28573 Series.prototype.remove.apply(series, arguments);
28574 }
28575 });
28576
28577
28578 /**
28579 * A `column` series. If the [type](#series.column.type) option is
28580 * not specified, it is inherited from [chart.type](#chart.type).
28581 *
28582 * For options that apply to multiple series, it is recommended to add
28583 * them to the [plotOptions.series](#plotOptions.series) options structure.
28584 * To apply to all series of this specific type, apply it to [plotOptions.
28585 * column](#plotOptions.column).
28586 *
28587 * @type {Object}
28588 * @extends series,plotOptions.column
28589 * @excluding dataParser,dataURL,marker
28590 * @product highcharts highstock
28591 * @apioption series.column
28592 */
28593
28594 /**
28595 * An array of data points for the series. For the `column` series type,
28596 * points can be given in the following ways:
28597 *
28598 * 1. An array of numerical values. In this case, the numerical values
28599 * will be interpreted as `y` options. The `x` values will be automatically
28600 * calculated, either starting at 0 and incremented by 1, or from `pointStart`
28601 * and `pointInterval` given in the series options. If the axis has
28602 * categories, these will be used. Example:
28603 *
28604 * ```js
28605 * data: [0, 5, 3, 5]
28606 * ```
28607 *
28608 * 2. An array of arrays with 2 values. In this case, the values correspond
28609 * to `x,y`. If the first value is a string, it is applied as the name
28610 * of the point, and the `x` value is inferred.
28611 *
28612 * ```js
28613 * data: [
28614 * [0, 6],
28615 * [1, 2],
28616 * [2, 6]
28617 * ]
28618 * ```
28619 *
28620 * 3. An array of objects with named values. The objects are point
28621 * configuration objects as seen below. If the total number of data
28622 * points exceeds the series' [turboThreshold](#series.column.turboThreshold),
28623 * this option is not available.
28624 *
28625 * ```js
28626 * data: [{
28627 * x: 1,
28628 * y: 9,
28629 * name: "Point2",
28630 * color: "#00FF00"
28631 * }, {
28632 * x: 1,
28633 * y: 6,
28634 * name: "Point1",
28635 * color: "#FF00FF"
28636 * }]
28637 * ```
28638 *
28639 * @type {Array<Object|Array|Number>}
28640 * @extends series.line.data
28641 * @excluding marker
28642 * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
28643 * @sample {highcharts} highcharts/series/data-array-of-arrays/
28644 * Arrays of numeric x and y
28645 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
28646 * Arrays of datetime x and y
28647 * @sample {highcharts} highcharts/series/data-array-of-name-value/
28648 * Arrays of point.name and y
28649 * @sample {highcharts} highcharts/series/data-array-of-objects/
28650 * Config objects
28651 * @product highcharts highstock
28652 * @apioption series.column.data
28653 */
28654
28655
28656 }(Highcharts));
28657 (function(H) {
28658 /**
28659 * (c) 2010-2017 Torstein Honsi
28660 *
28661 * License: www.highcharts.com/license
28662 */
28663
28664 var seriesType = H.seriesType;
28665
28666 /**
28667 * The Bar series class
28668 */
28669 seriesType('bar', 'column', null, {
28670 inverted: true
28671 });
28672 /**
28673 * A bar series is a special type of column series where the columns are
28674 * horizontal.
28675 *
28676 * @sample highcharts/demo/bar-basic/ Bar chart
28677 * @extends {plotOptions.column}
28678 * @product highcharts
28679 * @optionparent plotOptions.bar
28680 */
28681
28682
28683 /**
28684 * A `bar` series. If the [type](#series.bar.type) option is not specified,
28685 * it is inherited from [chart.type](#chart.type).
28686 *
28687 * For options that apply to multiple series, it is recommended to add
28688 * them to the [plotOptions.series](#plotOptions.series) options structure.
28689 * To apply to all series of this specific type, apply it to [plotOptions.
28690 * bar](#plotOptions.bar).
28691 *
28692 * @type {Object}
28693 * @extends series,plotOptions.bar
28694 * @excluding dataParser,dataURL
28695 * @product highcharts
28696 * @apioption series.bar
28697 */
28698
28699 /**
28700 * An array of data points for the series. For the `bar` series type,
28701 * points can be given in the following ways:
28702 *
28703 * 1. An array of numerical values. In this case, the numerical values
28704 * will be interpreted as `y` options. The `x` values will be automatically
28705 * calculated, either starting at 0 and incremented by 1, or from `pointStart`
28706 * and `pointInterval` given in the series options. If the axis has
28707 * categories, these will be used. Example:
28708 *
28709 * ```js
28710 * data: [0, 5, 3, 5]
28711 * ```
28712 *
28713 * 2. An array of arrays with 2 values. In this case, the values correspond
28714 * to `x,y`. If the first value is a string, it is applied as the name
28715 * of the point, and the `x` value is inferred.
28716 *
28717 * ```js
28718 * data: [
28719 * [0, 5],
28720 * [1, 10],
28721 * [2, 3]
28722 * ]
28723 * ```
28724 *
28725 * 3. An array of objects with named values. The objects are point
28726 * configuration objects as seen below. If the total number of data
28727 * points exceeds the series' [turboThreshold](#series.bar.turboThreshold),
28728 * this option is not available.
28729 *
28730 * ```js
28731 * data: [{
28732 * x: 1,
28733 * y: 1,
28734 * name: "Point2",
28735 * color: "#00FF00"
28736 * }, {
28737 * x: 1,
28738 * y: 10,
28739 * name: "Point1",
28740 * color: "#FF00FF"
28741 * }]
28742 * ```
28743 *
28744 * @type {Array<Object|Array|Number>}
28745 * @extends series.column.data
28746 * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
28747 * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
28748 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
28749 * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
28750 * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
28751 * @product highcharts
28752 * @apioption series.bar.data
28753 */
28754
28755 /**
28756 * Alignment of the data label relative to the data point.
28757 *
28758 * @type {String}
28759 * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/
28760 * Data labels inside the bar
28761 * @default left
28762 * @product highcharts
28763 * @apioption plotOptions.bar.dataLabels.align
28764 */
28765
28766 /**
28767 * The x position of the data label relative to the data point.
28768 *
28769 * @type {Number}
28770 * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/
28771 * Data labels inside the bar
28772 * @default 5
28773 * @product highcharts
28774 * @apioption plotOptions.bar.dataLabels.x
28775 */
28776
28777 }(Highcharts));
28778 (function(H) {
28779 /**
28780 * (c) 2010-2017 Torstein Honsi
28781 *
28782 * License: www.highcharts.com/license
28783 */
28784 var Series = H.Series,
28785 seriesType = H.seriesType;
28786
28787 /**
28788 * A scatter plot uses cartesian coordinates to display values for two variables
28789 * for a set of data.
28790 *
28791 * @sample {highcharts} highcharts/demo/scatter/ Scatter plot
28792 *
28793 * @extends {plotOptions.line}
28794 * @product highcharts highstock
28795 * @optionparent plotOptions.scatter
28796 */
28797 seriesType('scatter', 'line', {
28798
28799 /**
28800 * The width of the line connecting the data points.
28801 *
28802 * @type {Number}
28803 * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-none/
28804 * 0 by default
28805 * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-1/
28806 * 1px
28807 * @default 0
28808 * @product highcharts highstock
28809 */
28810 lineWidth: 0,
28811
28812 findNearestPointBy: 'xy',
28813 marker: {
28814 enabled: true // Overrides auto-enabling in line series (#3647)
28815 },
28816
28817 /**
28818 * Sticky tracking of mouse events. When true, the `mouseOut` event
28819 * on a series isn't triggered until the mouse moves over another series,
28820 * or out of the plot area. When false, the `mouseOut` event on a series
28821 * is triggered when the mouse leaves the area around the series' graph
28822 * or markers. This also implies the tooltip. When `stickyTracking`
28823 * is false and `tooltip.shared` is false, the tooltip will be hidden
28824 * when moving the mouse between series.
28825 *
28826 * @type {Boolean}
28827 * @default false
28828 * @product highcharts highstock
28829 * @apioption plotOptions.scatter.stickyTracking
28830 */
28831
28832 /**
28833 * A configuration object for the tooltip rendering of each single
28834 * series. Properties are inherited from <a class="internal">#tooltip</a>.
28835 * Overridable properties are `headerFormat`, `pointFormat`, `yDecimals`,
28836 * `xDateFormat`, `yPrefix` and `ySuffix`. Unlike other series, in
28837 * a scatter plot the series.name by default shows in the headerFormat
28838 * and point.x and point.y in the pointFormat.
28839 *
28840 * @product highcharts highstock
28841 */
28842 tooltip: {
28843
28844
28845 headerFormat: '<span class="highcharts-color-{point.colorIndex}">\u25CF</span> <span class="highcharts-header"> {series.name}</span><br/>', // eslint-disable-line max-len
28846
28847
28848 pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>'
28849 }
28850
28851 // Prototype members
28852 }, {
28853 sorted: false,
28854 requireSorting: false,
28855 noSharedTooltip: true,
28856 trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],
28857 takeOrdinalPosition: false, // #2342
28858 drawGraph: function() {
28859 if (this.options.lineWidth) {
28860 Series.prototype.drawGraph.call(this);
28861 }
28862 }
28863 });
28864
28865 /**
28866 * A `scatter` series. If the [type](#series.scatter.type) option is
28867 * not specified, it is inherited from [chart.type](#chart.type).
28868 *
28869 * For options that apply to multiple series, it is recommended to add
28870 * them to the [plotOptions.series](#plotOptions.series) options structure.
28871 * To apply to all series of this specific type, apply it to [plotOptions.
28872 * scatter](#plotOptions.scatter).
28873 *
28874 * @type {Object}
28875 * @extends series,plotOptions.scatter
28876 * @excluding dataParser,dataURL,stack
28877 * @product highcharts highstock
28878 * @apioption series.scatter
28879 */
28880
28881 /**
28882 * An array of data points for the series. For the `scatter` series
28883 * type, points can be given in the following ways:
28884 *
28885 * 1. An array of numerical values. In this case, the numerical values
28886 * will be interpreted as `y` options. The `x` values will be automatically
28887 * calculated, either starting at 0 and incremented by 1, or from `pointStart`
28888 * and `pointInterval` given in the series options. If the axis has
28889 * categories, these will be used. Example:
28890 *
28891 * ```js
28892 * data: [0, 5, 3, 5]
28893 * ```
28894 *
28895 * 2. An array of arrays with 2 values. In this case, the values correspond
28896 * to `x,y`. If the first value is a string, it is applied as the name
28897 * of the point, and the `x` value is inferred.
28898 *
28899 * ```js
28900 * data: [
28901 * [0, 0],
28902 * [1, 8],
28903 * [2, 9]
28904 * ]
28905 * ```
28906 *
28907 * 3. An array of objects with named values. The objects are point
28908 * configuration objects as seen below. If the total number of data
28909 * points exceeds the series' [turboThreshold](#series.scatter.turboThreshold),
28910 * this option is not available.
28911 *
28912 * ```js
28913 * data: [{
28914 * x: 1,
28915 * y: 2,
28916 * name: "Point2",
28917 * color: "#00FF00"
28918 * }, {
28919 * x: 1,
28920 * y: 4,
28921 * name: "Point1",
28922 * color: "#FF00FF"
28923 * }]
28924 * ```
28925 *
28926 * @type {Array<Object|Array|Number>}
28927 * @extends series.line.data
28928 * @sample {highcharts} highcharts/chart/reflow-true/
28929 * Numerical values
28930 * @sample {highcharts} highcharts/series/data-array-of-arrays/
28931 * Arrays of numeric x and y
28932 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
28933 * Arrays of datetime x and y
28934 * @sample {highcharts} highcharts/series/data-array-of-name-value/
28935 * Arrays of point.name and y
28936 * @sample {highcharts} highcharts/series/data-array-of-objects/
28937 * Config objects
28938 * @product highcharts highstock
28939 * @apioption series.scatter.data
28940 */
28941
28942
28943 }(Highcharts));
28944 (function(H) {
28945 /**
28946 * (c) 2010-2017 Torstein Honsi
28947 *
28948 * License: www.highcharts.com/license
28949 */
28950 var deg2rad = H.deg2rad,
28951 isNumber = H.isNumber,
28952 pick = H.pick,
28953 relativeLength = H.relativeLength;
28954 H.CenteredSeriesMixin = {
28955 /**
28956 * Get the center of the pie based on the size and center options relative to the
28957 * plot area. Borrowed by the polar and gauge series types.
28958 */
28959 getCenter: function() {
28960
28961 var options = this.options,
28962 chart = this.chart,
28963 slicingRoom = 2 * (options.slicedOffset || 0),
28964 handleSlicingRoom,
28965 plotWidth = chart.plotWidth - 2 * slicingRoom,
28966 plotHeight = chart.plotHeight - 2 * slicingRoom,
28967 centerOption = options.center,
28968 positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0],
28969 smallestSize = Math.min(plotWidth, plotHeight),
28970 i,
28971 value;
28972
28973 for (i = 0; i < 4; ++i) {
28974 value = positions[i];
28975 handleSlicingRoom = i < 2 || (i === 2 && /%$/.test(value));
28976
28977 // i == 0: centerX, relative to width
28978 // i == 1: centerY, relative to height
28979 // i == 2: size, relative to smallestSize
28980 // i == 3: innerSize, relative to size
28981 positions[i] = relativeLength(value, [plotWidth, plotHeight, smallestSize, positions[2]][i]) +
28982 (handleSlicingRoom ? slicingRoom : 0);
28983
28984 }
28985 // innerSize cannot be larger than size (#3632)
28986 if (positions[3] > positions[2]) {
28987 positions[3] = positions[2];
28988 }
28989 return positions;
28990 },
28991 /**
28992 * getStartAndEndRadians - Calculates start and end angles in radians.
28993 * Used in series types such as pie and sunburst.
28994 *
28995 * @param {Number} start Start angle in degrees.
28996 * @param {Number} end Start angle in degrees.
28997 * @return {object} Returns an object containing start and end angles as
28998 * radians.
28999 */
29000 getStartAndEndRadians: function getStartAndEndRadians(start, end) {
29001 var startAngle = isNumber(start) ? start : 0, // must be a number
29002 endAngle = (
29003 (
29004 isNumber(end) && // must be a number
29005 end > startAngle && // must be larger than the start angle
29006 // difference must be less than 360 degrees
29007 (end - startAngle) < 360
29008 ) ?
29009 end :
29010 startAngle + 360
29011 ),
29012 correction = -90;
29013 return {
29014 start: deg2rad * (startAngle + correction),
29015 end: deg2rad * (endAngle + correction)
29016 };
29017 }
29018 };
29019
29020 }(Highcharts));
29021 (function(H) {
29022 /**
29023 * (c) 2010-2017 Torstein Honsi
29024 *
29025 * License: www.highcharts.com/license
29026 */
29027 var addEvent = H.addEvent,
29028 CenteredSeriesMixin = H.CenteredSeriesMixin,
29029 defined = H.defined,
29030 each = H.each,
29031 extend = H.extend,
29032 getStartAndEndRadians = CenteredSeriesMixin.getStartAndEndRadians,
29033 inArray = H.inArray,
29034 LegendSymbolMixin = H.LegendSymbolMixin,
29035 noop = H.noop,
29036 pick = H.pick,
29037 Point = H.Point,
29038 Series = H.Series,
29039 seriesType = H.seriesType,
29040 seriesTypes = H.seriesTypes,
29041 setAnimation = H.setAnimation;
29042
29043 /**
29044 * The pie series type.
29045 *
29046 * @constructor seriesTypes.pie
29047 * @augments Series
29048 */
29049
29050 /**
29051 * A pie chart is a circular graphic which is divided into slices to illustrate
29052 * numerical proportion.
29053 *
29054 * @sample highcharts/demo/pie-basic/ Pie chart
29055 *
29056 * @extends {plotOptions.line}
29057 * @excluding animationLimit,boostThreshold,connectEnds,connectNulls,
29058 * cropThreshold,dashStyle,findNearestPointBy,getExtremesFromAll,
29059 * lineWidth,marker,negativeColor,pointInterval,pointIntervalUnit,
29060 * pointPlacement,pointStart,softThreshold,stacking,step,threshold,
29061 * turboThreshold,zoneAxis,zones
29062 * @product highcharts
29063 * @optionparent plotOptions.pie
29064 */
29065 seriesType('pie', 'line', {
29066
29067 /**
29068 * The center of the pie chart relative to the plot area. Can be percentages
29069 * or pixel values. The default behaviour (as of 3.0) is to center
29070 * the pie so that all slices and data labels are within the plot area.
29071 * As a consequence, the pie may actually jump around in a chart with
29072 * dynamic values, as the data labels move. In that case, the center
29073 * should be explicitly set, for example to `["50%", "50%"]`.
29074 *
29075 * @type {Array<String|Number>}
29076 * @sample {highcharts} highcharts/plotoptions/pie-center/ Centered at 100, 100
29077 * @default [null, null]
29078 * @product highcharts
29079 */
29080 center: [null, null],
29081
29082 clip: false,
29083
29084 /** @ignore */
29085 colorByPoint: true, // always true for pies
29086
29087 /**
29088 * A series specific or series type specific color set to use instead
29089 * of the global [colors](#colors).
29090 *
29091 * @type {Array<Color>}
29092 * @sample {highcharts} highcharts/demo/pie-monochrome/ Set default colors for all pies
29093 * @since 3.0
29094 * @product highcharts
29095 * @apioption plotOptions.pie.colors
29096 */
29097
29098 /**
29099 * @extends plotOptions.series.dataLabels
29100 * @excluding align,allowOverlap,staggerLines,step
29101 * @product highcharts
29102 */
29103 dataLabels: {
29104 /**
29105 * The color of the line connecting the data label to the pie slice.
29106 * The default color is the same as the point's color.
29107 *
29108 * In styled mode, the connector stroke is given in the
29109 * `.highcharts-data-label-connector` class.
29110 *
29111 * @type {String}
29112 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorcolor/ Blue connectors
29113 * @sample {highcharts} highcharts/css/pie-point/ Styled connectors
29114 * @default {point.color}
29115 * @since 2.1
29116 * @product highcharts
29117 * @apioption plotOptions.pie.dataLabels.connectorColor
29118 */
29119
29120 /**
29121 * The distance from the data label to the connector.
29122 *
29123 * @type {Number}
29124 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorpadding/ No padding
29125 * @default 5
29126 * @since 2.1
29127 * @product highcharts
29128 * @apioption plotOptions.pie.dataLabels.connectorPadding
29129 */
29130
29131 /**
29132 * The width of the line connecting the data label to the pie slice.
29133 *
29134 *
29135 * In styled mode, the connector stroke width is given in the
29136 * `.highcharts-data-label-connector` class.
29137 *
29138 * @type {Number}
29139 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorwidth-disabled/ Disable the connector
29140 * @sample {highcharts} highcharts/css/pie-point/ Styled connectors
29141 * @default 1
29142 * @since 2.1
29143 * @product highcharts
29144 * @apioption plotOptions.pie.dataLabels.connectorWidth
29145 */
29146
29147 /**
29148 * The distance of the data label from the pie's edge. Negative numbers
29149 * put the data label on top of the pie slices. Connectors are only
29150 * shown for data labels outside the pie.
29151 *
29152 * @type {Number}
29153 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-distance/ Data labels on top of the pie
29154 * @default 30
29155 * @since 2.1
29156 * @product highcharts
29157 */
29158 distance: 30,
29159
29160 /**
29161 * Enable or disable the data labels.
29162 *
29163 * @type {Boolean}
29164 * @since 2.1
29165 * @product highcharts
29166 */
29167 enabled: true,
29168
29169 formatter: function() { // #2945
29170 return this.point.isNull ? undefined : this.point.name;
29171 },
29172
29173 /**
29174 * Whether to render the connector as a soft arc or a line with sharp
29175 * break.
29176 *
29177 * @type {Number}
29178 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-true/ Soft
29179 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-false/ Non soft
29180 * @since 2.1.7
29181 * @product highcharts
29182 * @apioption plotOptions.pie.dataLabels.softConnector
29183 */
29184
29185 x: 0
29186 // y: 0
29187 },
29188
29189 /**
29190 * The end angle of the pie in degrees where 0 is top and 90 is right.
29191 * Defaults to `startAngle` plus 360.
29192 *
29193 * @type {Number}
29194 * @sample {highcharts} highcharts/demo/pie-semi-circle/ Semi-circle donut
29195 * @default null
29196 * @since 1.3.6
29197 * @product highcharts
29198 * @apioption plotOptions.pie.endAngle
29199 */
29200
29201 /**
29202 * Equivalent to [chart.ignoreHiddenSeries](#chart.ignoreHiddenSeries),
29203 * this option tells whether the series shall be redrawn as if the
29204 * hidden point were `null`.
29205 *
29206 * The default value changed from `false` to `true` with Highcharts
29207 * 3.0.
29208 *
29209 * @type {Boolean}
29210 * @sample {highcharts} highcharts/plotoptions/pie-ignorehiddenpoint/ True, the hiddden point is ignored
29211 * @default true
29212 * @since 2.3.0
29213 * @product highcharts
29214 */
29215 ignoreHiddenPoint: true,
29216
29217 /**
29218 * The size of the inner diameter for the pie. A size greater than 0
29219 * renders a donut chart. Can be a percentage or pixel value. Percentages
29220 * are relative to the pie size. Pixel values are given as integers.
29221 *
29222 *
29223 * Note: in Highcharts < 4.1.2, the percentage was relative to the plot
29224 * area, not the pie size.
29225 *
29226 * @type {String|Number}
29227 * @sample {highcharts} highcharts/plotoptions/pie-innersize-80px/ 80px inner size
29228 * @sample {highcharts} highcharts/plotoptions/pie-innersize-50percent/ 50% of the plot area
29229 * @sample {highcharts} highcharts/demo/3d-pie-donut/ 3D donut
29230 * @default 0
29231 * @since 2.0
29232 * @product highcharts
29233 * @apioption plotOptions.pie.innerSize
29234 */
29235
29236 /** @ignore */
29237 legendType: 'point',
29238
29239 /** @ignore */
29240 marker: null, // point options are specified in the base options
29241
29242 /**
29243 * The minimum size for a pie in response to auto margins. The pie will
29244 * try to shrink to make room for data labels in side the plot area,
29245 * but only to this size.
29246 *
29247 * @type {Number}
29248 * @default 80
29249 * @since 3.0
29250 * @product highcharts
29251 * @apioption plotOptions.pie.minSize
29252 */
29253
29254 /**
29255 * The diameter of the pie relative to the plot area. Can be a percentage
29256 * or pixel value. Pixel values are given as integers. The default
29257 * behaviour (as of 3.0) is to scale to the plot area and give room
29258 * for data labels within the plot area. As a consequence, the size
29259 * of the pie may vary when points are updated and data labels more
29260 * around. In that case it is best to set a fixed value, for example
29261 * `"75%"`.
29262 *
29263 * @type {String|Number}
29264 * @sample {highcharts} highcharts/plotoptions/pie-size/ Smaller pie
29265 * @default
29266 * @product highcharts
29267 */
29268 size: null,
29269
29270 /**
29271 * Whether to display this particular series or series type in the
29272 * legend. Since 2.1, pies are not shown in the legend by default.
29273 *
29274 * @type {Boolean}
29275 * @sample {highcharts} highcharts/plotoptions/series-showinlegend/ One series in the legend, one hidden
29276 * @product highcharts
29277 */
29278 showInLegend: false,
29279
29280 /**
29281 * If a point is sliced, moved out from the center, how many pixels
29282 * should it be moved?.
29283 *
29284 * @type {Number}
29285 * @sample {highcharts} highcharts/plotoptions/pie-slicedoffset-20/ 20px offset
29286 * @default 10
29287 * @product highcharts
29288 */
29289 slicedOffset: 10,
29290
29291 /**
29292 * The start angle of the pie slices in degrees where 0 is top and 90
29293 * right.
29294 *
29295 * @type {Number}
29296 * @sample {highcharts} highcharts/plotoptions/pie-startangle-90/ Start from right
29297 * @default 0
29298 * @since 2.3.4
29299 * @product highcharts
29300 * @apioption plotOptions.pie.startAngle
29301 */
29302
29303 /**
29304 * Sticky tracking of mouse events. When true, the `mouseOut` event
29305 * on a series isn't triggered until the mouse moves over another series,
29306 * or out of the plot area. When false, the `mouseOut` event on a
29307 * series is triggered when the mouse leaves the area around the series'
29308 * graph or markers. This also implies the tooltip. When `stickyTracking`
29309 * is false and `tooltip.shared` is false, the tooltip will be hidden
29310 * when moving the mouse between series.
29311 *
29312 * @product highcharts
29313 */
29314 stickyTracking: false,
29315
29316 tooltip: {
29317 followPointer: true
29318 }
29319
29320
29321 }, /** @lends seriesTypes.pie.prototype */ {
29322 isCartesian: false,
29323 requireSorting: false,
29324 directTouch: true,
29325 noSharedTooltip: true,
29326 trackerGroups: ['group', 'dataLabelsGroup'],
29327 axisTypes: [],
29328 pointAttribs: seriesTypes.column.prototype.pointAttribs,
29329 /**
29330 * Animate the pies in
29331 */
29332 animate: function(init) {
29333 var series = this,
29334 points = series.points,
29335 startAngleRad = series.startAngleRad;
29336
29337 if (!init) {
29338 each(points, function(point) {
29339 var graphic = point.graphic,
29340 args = point.shapeArgs;
29341
29342 if (graphic) {
29343 // start values
29344 graphic.attr({
29345 r: point.startR || (series.center[3] / 2), // animate from inner radius (#779)
29346 start: startAngleRad,
29347 end: startAngleRad
29348 });
29349
29350 // animate
29351 graphic.animate({
29352 r: args.r,
29353 start: args.start,
29354 end: args.end
29355 }, series.options.animation);
29356 }
29357 });
29358
29359 // delete this function to allow it only once
29360 series.animate = null;
29361 }
29362 },
29363
29364 /**
29365 * Recompute total chart sum and update percentages of points.
29366 */
29367 updateTotals: function() {
29368 var i,
29369 total = 0,
29370 points = this.points,
29371 len = points.length,
29372 point,
29373 ignoreHiddenPoint = this.options.ignoreHiddenPoint;
29374
29375 // Get the total sum
29376 for (i = 0; i < len; i++) {
29377 point = points[i];
29378 total += (ignoreHiddenPoint && !point.visible) ?
29379 0 :
29380 point.isNull ? 0 : point.y;
29381 }
29382 this.total = total;
29383
29384 // Set each point's properties
29385 for (i = 0; i < len; i++) {
29386 point = points[i];
29387 point.percentage = (total > 0 && (point.visible || !ignoreHiddenPoint)) ? point.y / total * 100 : 0;
29388 point.total = total;
29389 }
29390 },
29391
29392 /**
29393 * Extend the generatePoints method by adding total and percentage properties to each point
29394 */
29395 generatePoints: function() {
29396 Series.prototype.generatePoints.call(this);
29397 this.updateTotals();
29398 },
29399
29400 /**
29401 * Do translation for pie slices
29402 */
29403 translate: function(positions) {
29404 this.generatePoints();
29405
29406 var series = this,
29407 cumulative = 0,
29408 precision = 1000, // issue #172
29409 options = series.options,
29410 slicedOffset = options.slicedOffset,
29411 connectorOffset = slicedOffset + (options.borderWidth || 0),
29412 finalConnectorOffset,
29413 start,
29414 end,
29415 angle,
29416 radians = getStartAndEndRadians(options.startAngle, options.endAngle),
29417 startAngleRad = series.startAngleRad = radians.start,
29418 endAngleRad = series.endAngleRad = radians.end,
29419 circ = endAngleRad - startAngleRad, // 2 * Math.PI,
29420 points = series.points,
29421 radiusX, // the x component of the radius vector for a given point
29422 radiusY,
29423 labelDistance = options.dataLabels.distance,
29424 ignoreHiddenPoint = options.ignoreHiddenPoint,
29425 i,
29426 len = points.length,
29427 point;
29428
29429 // Get positions - either an integer or a percentage string must be given.
29430 // If positions are passed as a parameter, we're in a recursive loop for adjusting
29431 // space for data labels.
29432 if (!positions) {
29433 series.center = positions = series.getCenter();
29434 }
29435
29436 // Utility for getting the x value from a given y, used for anticollision
29437 // logic in data labels.
29438 // Added point for using specific points' label distance.
29439 series.getX = function(y, left, point) {
29440 angle = Math.asin(Math.min((y - positions[1]) / (positions[2] / 2 + point.labelDistance), 1));
29441 return positions[0] +
29442 (left ? -1 : 1) *
29443 (Math.cos(angle) * (positions[2] / 2 + point.labelDistance));
29444 };
29445
29446 // Calculate the geometry for each point
29447 for (i = 0; i < len; i++) {
29448
29449 point = points[i];
29450
29451 // Used for distance calculation for specific point.
29452 point.labelDistance = pick(
29453 point.options.dataLabels && point.options.dataLabels.distance,
29454 labelDistance
29455 );
29456
29457 // Saved for later dataLabels distance calculation.
29458 series.maxLabelDistance = Math.max(series.maxLabelDistance || 0, point.labelDistance);
29459
29460 // set start and end angle
29461 start = startAngleRad + (cumulative * circ);
29462 if (!ignoreHiddenPoint || point.visible) {
29463 cumulative += point.percentage / 100;
29464 }
29465 end = startAngleRad + (cumulative * circ);
29466
29467 // set the shape
29468 point.shapeType = 'arc';
29469 point.shapeArgs = {
29470 x: positions[0],
29471 y: positions[1],
29472 r: positions[2] / 2,
29473 innerR: positions[3] / 2,
29474 start: Math.round(start * precision) / precision,
29475 end: Math.round(end * precision) / precision
29476 };
29477
29478 // The angle must stay within -90 and 270 (#2645)
29479 angle = (end + start) / 2;
29480 if (angle > 1.5 * Math.PI) {
29481 angle -= 2 * Math.PI;
29482 } else if (angle < -Math.PI / 2) {
29483 angle += 2 * Math.PI;
29484 }
29485
29486 // Center for the sliced out slice
29487 point.slicedTranslation = {
29488 translateX: Math.round(Math.cos(angle) * slicedOffset),
29489 translateY: Math.round(Math.sin(angle) * slicedOffset)
29490 };
29491
29492 // set the anchor point for tooltips
29493 radiusX = Math.cos(angle) * positions[2] / 2;
29494 radiusY = Math.sin(angle) * positions[2] / 2;
29495 point.tooltipPos = [
29496 positions[0] + radiusX * 0.7,
29497 positions[1] + radiusY * 0.7
29498 ];
29499
29500 point.half = angle < -Math.PI / 2 || angle > Math.PI / 2 ? 1 : 0;
29501 point.angle = angle;
29502
29503 // Set the anchor point for data labels. Use point.labelDistance
29504 // instead of labelDistance // #1174
29505 // finalConnectorOffset - not override connectorOffset value.
29506 finalConnectorOffset = Math.min(connectorOffset, point.labelDistance / 5); // #1678
29507 point.labelPos = [
29508 positions[0] + radiusX + Math.cos(angle) * point.labelDistance, // first break of connector
29509 positions[1] + radiusY + Math.sin(angle) * point.labelDistance, // a/a
29510 positions[0] + radiusX + Math.cos(angle) * finalConnectorOffset, // second break, right outside pie
29511 positions[1] + radiusY + Math.sin(angle) * finalConnectorOffset, // a/a
29512 positions[0] + radiusX, // landing point for connector
29513 positions[1] + radiusY, // a/a
29514 point.labelDistance < 0 ? // alignment
29515 'center' :
29516 point.half ? 'right' : 'left', // alignment
29517 angle // center angle
29518 ];
29519
29520 }
29521 },
29522
29523 drawGraph: null,
29524
29525 /**
29526 * Draw the data points
29527 */
29528 drawPoints: function() {
29529 var series = this,
29530 chart = series.chart,
29531 renderer = chart.renderer,
29532 groupTranslation,
29533 graphic,
29534 pointAttr,
29535 shapeArgs;
29536
29537
29538
29539 // draw the slices
29540 each(series.points, function(point) {
29541 graphic = point.graphic;
29542 if (!point.isNull) {
29543 shapeArgs = point.shapeArgs;
29544
29545
29546 // If the point is sliced, use special translation, else use
29547 // plot area traslation
29548 groupTranslation = point.getTranslate();
29549
29550
29551
29552 // Draw the slice
29553 if (graphic) {
29554 graphic
29555 .setRadialReference(series.center)
29556
29557 .animate(extend(shapeArgs, groupTranslation));
29558 } else {
29559
29560 point.graphic = graphic = renderer[point.shapeType](shapeArgs)
29561 .setRadialReference(series.center)
29562 .attr(groupTranslation)
29563 .add(series.group);
29564
29565 if (!point.visible) {
29566 graphic.attr({
29567 visibility: 'hidden'
29568 });
29569 }
29570
29571
29572 }
29573
29574 graphic.addClass(point.getClassName());
29575
29576 } else if (graphic) {
29577 point.graphic = graphic.destroy();
29578 }
29579 });
29580
29581 },
29582
29583
29584 searchPoint: noop,
29585
29586 /**
29587 * Utility for sorting data labels
29588 */
29589 sortByAngle: function(points, sign) {
29590 points.sort(function(a, b) {
29591 return a.angle !== undefined && (b.angle - a.angle) * sign;
29592 });
29593 },
29594
29595 /**
29596 * Use a simple symbol from LegendSymbolMixin
29597 */
29598 drawLegendSymbol: LegendSymbolMixin.drawRectangle,
29599
29600 /**
29601 * Use the getCenter method from drawLegendSymbol
29602 */
29603 getCenter: CenteredSeriesMixin.getCenter,
29604
29605 /**
29606 * Pies don't have point marker symbols
29607 */
29608 getSymbol: noop
29609
29610
29611 /**
29612 * @constructor seriesTypes.pie.prototype.pointClass
29613 * @extends {Point}
29614 */
29615 }, /** @lends seriesTypes.pie.prototype.pointClass.prototype */ {
29616 /**
29617 * Initiate the pie slice
29618 */
29619 init: function() {
29620
29621 Point.prototype.init.apply(this, arguments);
29622
29623 var point = this,
29624 toggleSlice;
29625
29626 point.name = pick(point.name, 'Slice');
29627
29628 // add event listener for select
29629 toggleSlice = function(e) {
29630 point.slice(e.type === 'select');
29631 };
29632 addEvent(point, 'select', toggleSlice);
29633 addEvent(point, 'unselect', toggleSlice);
29634
29635 return point;
29636 },
29637
29638 /**
29639 * Negative points are not valid (#1530, #3623, #5322)
29640 */
29641 isValid: function() {
29642 return H.isNumber(this.y, true) && this.y >= 0;
29643 },
29644
29645 /**
29646 * Toggle the visibility of the pie slice
29647 * @param {Boolean} vis Whether to show the slice or not. If undefined, the
29648 * visibility is toggled
29649 */
29650 setVisible: function(vis, redraw) {
29651 var point = this,
29652 series = point.series,
29653 chart = series.chart,
29654 ignoreHiddenPoint = series.options.ignoreHiddenPoint;
29655
29656 redraw = pick(redraw, ignoreHiddenPoint);
29657
29658 if (vis !== point.visible) {
29659
29660 // If called without an argument, toggle visibility
29661 point.visible = point.options.visible = vis = vis === undefined ? !point.visible : vis;
29662 series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
29663
29664 // Show and hide associated elements. This is performed regardless of redraw or not,
29665 // because chart.redraw only handles full series.
29666 each(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function(key) {
29667 if (point[key]) {
29668 point[key][vis ? 'show' : 'hide'](true);
29669 }
29670 });
29671
29672 if (point.legendItem) {
29673 chart.legend.colorizeItem(point, vis);
29674 }
29675
29676 // #4170, hide halo after hiding point
29677 if (!vis && point.state === 'hover') {
29678 point.setState('');
29679 }
29680
29681 // Handle ignore hidden slices
29682 if (ignoreHiddenPoint) {
29683 series.isDirty = true;
29684 }
29685
29686 if (redraw) {
29687 chart.redraw();
29688 }
29689 }
29690 },
29691
29692 /**
29693 * Set or toggle whether the slice is cut out from the pie
29694 * @param {Boolean} sliced When undefined, the slice state is toggled
29695 * @param {Boolean} redraw Whether to redraw the chart. True by default.
29696 */
29697 slice: function(sliced, redraw, animation) {
29698 var point = this,
29699 series = point.series,
29700 chart = series.chart;
29701
29702 setAnimation(animation, chart);
29703
29704 // redraw is true by default
29705 redraw = pick(redraw, true);
29706
29707 // if called without an argument, toggle
29708 point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced;
29709 series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
29710
29711 point.graphic.animate(this.getTranslate());
29712
29713
29714 },
29715
29716 getTranslate: function() {
29717 return this.sliced ? this.slicedTranslation : {
29718 translateX: 0,
29719 translateY: 0
29720 };
29721 },
29722
29723 haloPath: function(size) {
29724 var shapeArgs = this.shapeArgs;
29725
29726 return this.sliced || !this.visible ? [] :
29727 this.series.chart.renderer.symbols.arc(
29728 shapeArgs.x,
29729 shapeArgs.y,
29730 shapeArgs.r + size,
29731 shapeArgs.r + size, {
29732 innerR: this.shapeArgs.r,
29733 start: shapeArgs.start,
29734 end: shapeArgs.end
29735 }
29736 );
29737 }
29738 });
29739
29740 /**
29741 * A `pie` series. If the [type](#series.pie.type) option is not specified,
29742 * it is inherited from [chart.type](#chart.type).
29743 *
29744 * For options that apply to multiple series, it is recommended to add
29745 * them to the [plotOptions.series](#plotOptions.series) options structure.
29746 * To apply to all series of this specific type, apply it to [plotOptions.
29747 * pie](#plotOptions.pie).
29748 *
29749 * @type {Object}
29750 * @extends series,plotOptions.pie
29751 * @excluding dataParser,dataURL,stack,xAxis,yAxis
29752 * @product highcharts
29753 * @apioption series.pie
29754 */
29755
29756 /**
29757 * An array of data points for the series. For the `pie` series type,
29758 * points can be given in the following ways:
29759 *
29760 * 1. An array of numerical values. In this case, the numerical values
29761 * will be interpreted as `y` options. Example:
29762 *
29763 * ```js
29764 * data: [0, 5, 3, 5]
29765 * ```
29766 *
29767 * 2. An array of objects with named values. The objects are point
29768 * configuration objects as seen below. If the total number of data
29769 * points exceeds the series' [turboThreshold](#series.pie.turboThreshold),
29770 * this option is not available.
29771 *
29772 * ```js
29773 * data: [{
29774 * y: 1,
29775 * name: "Point2",
29776 * color: "#00FF00"
29777 * }, {
29778 * y: 7,
29779 * name: "Point1",
29780 * color: "#FF00FF"
29781 * }]</pre>
29782 *
29783 * @type {Array<Object|Number>}
29784 * @extends series.line.data
29785 * @excluding marker,x
29786 * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
29787 * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
29788 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
29789 * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
29790 * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
29791 * @product highcharts
29792 * @apioption series.pie.data
29793 */
29794
29795 /**
29796 * The sequential index of the data point in the legend.
29797 *
29798 * @type {Number}
29799 * @product highcharts
29800 * @apioption series.pie.data.legendIndex
29801 */
29802
29803 /**
29804 * Whether to display a slice offset from the center.
29805 *
29806 * @type {Boolean}
29807 * @sample {highcharts} highcharts/point/sliced/ One sliced point
29808 * @product highcharts
29809 * @apioption series.pie.data.sliced
29810 */
29811
29812 /**
29813 * Fires when the checkbox next to the point name in the legend is clicked.
29814 * One parameter, event, is passed to the function. The state of the
29815 * checkbox is found by event.checked. The checked item is found by
29816 * event.item. Return false to prevent the default action which is to
29817 * toggle the select state of the series.
29818 *
29819 * @type {Function}
29820 * @context Point
29821 * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/
29822 * Alert checkbox status
29823 * @since 1.2.0
29824 * @product highcharts
29825 * @apioption plotOptions.pie.events.checkboxClick
29826 */
29827
29828 /**
29829 * Not applicable to pies, as the legend item is per point. See point.
29830 * events.
29831 *
29832 * @type {Function}
29833 * @since 1.2.0
29834 * @product highcharts
29835 * @apioption plotOptions.pie.events.legendItemClick
29836 */
29837
29838 /**
29839 * Fires when the legend item belonging to the pie point (slice) is
29840 * clicked. The `this` keyword refers to the point itself. One parameter,
29841 * `event`, is passed to the function, containing common event information. The
29842 * default action is to toggle the visibility of the point. This can be
29843 * prevented by calling `event.preventDefault()`.
29844 *
29845 * @type {Function}
29846 * @sample {highcharts} highcharts/plotoptions/pie-point-events-legenditemclick/
29847 * Confirm toggle visibility
29848 * @since 1.2.0
29849 * @product highcharts
29850 * @apioption plotOptions.pie.point.events.legendItemClick
29851 */
29852
29853 }(Highcharts));
29854 (function(H) {
29855 /**
29856 * (c) 2010-2017 Torstein Honsi
29857 *
29858 * License: www.highcharts.com/license
29859 */
29860 var addEvent = H.addEvent,
29861 arrayMax = H.arrayMax,
29862 defined = H.defined,
29863 each = H.each,
29864 extend = H.extend,
29865 format = H.format,
29866 map = H.map,
29867 merge = H.merge,
29868 noop = H.noop,
29869 pick = H.pick,
29870 relativeLength = H.relativeLength,
29871 Series = H.Series,
29872 seriesTypes = H.seriesTypes,
29873 stableSort = H.stableSort;
29874
29875 /* eslint max-len: ["warn", 80, 4] */
29876 /**
29877 * General distribution algorithm for distributing labels of differing size
29878 * along a confined length in two dimensions. The algorithm takes an array of
29879 * objects containing a size, a target and a rank. It will place the labels as
29880 * close as possible to their targets, skipping the lowest ranked labels if
29881 * necessary.
29882 */
29883 H.distribute = function(boxes, len) {
29884
29885 var i,
29886 overlapping = true,
29887 origBoxes = boxes, // Original array will be altered with added .pos
29888 restBoxes = [], // The outranked overshoot
29889 box,
29890 target,
29891 total = 0;
29892
29893 function sortByTarget(a, b) {
29894 return a.target - b.target;
29895 }
29896
29897 // If the total size exceeds the len, remove those boxes with the lowest
29898 // rank
29899 i = boxes.length;
29900 while (i--) {
29901 total += boxes[i].size;
29902 }
29903
29904 // Sort by rank, then slice away overshoot
29905 if (total > len) {
29906 stableSort(boxes, function(a, b) {
29907 return (b.rank || 0) - (a.rank || 0);
29908 });
29909 i = 0;
29910 total = 0;
29911 while (total <= len) {
29912 total += boxes[i].size;
29913 i++;
29914 }
29915 restBoxes = boxes.splice(i - 1, boxes.length);
29916 }
29917
29918 // Order by target
29919 stableSort(boxes, sortByTarget);
29920
29921
29922 // So far we have been mutating the original array. Now
29923 // create a copy with target arrays
29924 boxes = map(boxes, function(box) {
29925 return {
29926 size: box.size,
29927 targets: [box.target],
29928 align: pick(box.align, 0.5)
29929 };
29930 });
29931
29932 while (overlapping) {
29933 // Initial positions: target centered in box
29934 i = boxes.length;
29935 while (i--) {
29936 box = boxes[i];
29937 // Composite box, average of targets
29938 target = (
29939 Math.min.apply(0, box.targets) +
29940 Math.max.apply(0, box.targets)
29941 ) / 2;
29942 box.pos = Math.min(
29943 Math.max(0, target - box.size * box.align),
29944 len - box.size
29945 );
29946 }
29947
29948 // Detect overlap and join boxes
29949 i = boxes.length;
29950 overlapping = false;
29951 while (i--) {
29952 // Overlap
29953 if (i > 0 && boxes[i - 1].pos + boxes[i - 1].size > boxes[i].pos) {
29954 // Add this size to the previous box
29955 boxes[i - 1].size += boxes[i].size;
29956 boxes[i - 1].targets = boxes[i - 1]
29957 .targets
29958 .concat(boxes[i].targets);
29959 boxes[i - 1].align = 0.5;
29960
29961 // Overlapping right, push left
29962 if (boxes[i - 1].pos + boxes[i - 1].size > len) {
29963 boxes[i - 1].pos = len - boxes[i - 1].size;
29964 }
29965 boxes.splice(i, 1); // Remove this item
29966 overlapping = true;
29967 }
29968 }
29969 }
29970
29971 // Now the composite boxes are placed, we need to put the original boxes
29972 // within them
29973 i = 0;
29974 each(boxes, function(box) {
29975 var posInCompositeBox = 0;
29976 each(box.targets, function() {
29977 origBoxes[i].pos = box.pos + posInCompositeBox;
29978 posInCompositeBox += origBoxes[i].size;
29979 i++;
29980 });
29981 });
29982
29983 // Add the rest (hidden) boxes and sort by target
29984 origBoxes.push.apply(origBoxes, restBoxes);
29985 stableSort(origBoxes, sortByTarget);
29986 };
29987
29988
29989 /**
29990 * Draw the data labels
29991 */
29992 Series.prototype.drawDataLabels = function() {
29993 var series = this,
29994 seriesOptions = series.options,
29995 options = seriesOptions.dataLabels,
29996 points = series.points,
29997 pointOptions,
29998 generalOptions,
29999 hasRendered = series.hasRendered || 0,
30000 str,
30001 dataLabelsGroup,
30002 defer = pick(options.defer, !!seriesOptions.animation),
30003 renderer = series.chart.renderer;
30004
30005 /*
30006 * Handle the dataLabels.filter option.
30007 */
30008 function applyFilter(point, options) {
30009 var filter = options.filter,
30010 op,
30011 prop,
30012 val;
30013 if (filter) {
30014 op = filter.operator;
30015 prop = point[filter.property];
30016 val = filter.value;
30017 if (
30018 (op === '>' && prop > val) ||
30019 (op === '<' && prop < val) ||
30020 (op === '>=' && prop >= val) ||
30021 (op === '<=' && prop <= val) ||
30022 (op === '==' && prop == val) || // eslint-disable-line eqeqeq
30023 (op === '===' && prop === val)
30024 ) {
30025 return true;
30026 }
30027 return false;
30028 }
30029 return true;
30030 }
30031
30032 if (options.enabled || series._hasPointLabels) {
30033
30034 // Process default alignment of data labels for columns
30035 if (series.dlProcessOptions) {
30036 series.dlProcessOptions(options);
30037 }
30038
30039 // Create a separate group for the data labels to avoid rotation
30040 dataLabelsGroup = series.plotGroup(
30041 'dataLabelsGroup',
30042 'data-labels',
30043 defer && !hasRendered ? 'hidden' : 'visible', // #5133
30044 options.zIndex || 6
30045 );
30046
30047 if (defer) {
30048 dataLabelsGroup.attr({
30049 opacity: +hasRendered
30050 }); // #3300
30051 if (!hasRendered) {
30052 addEvent(series, 'afterAnimate', function() {
30053 if (series.visible) { // #2597, #3023, #3024
30054 dataLabelsGroup.show(true);
30055 }
30056 dataLabelsGroup[
30057 seriesOptions.animation ? 'animate' : 'attr'
30058 ]({
30059 opacity: 1
30060 }, {
30061 duration: 200
30062 });
30063 });
30064 }
30065 }
30066
30067 // Make the labels for each point
30068 generalOptions = options;
30069 each(points, function(point) {
30070 var enabled,
30071 dataLabel = point.dataLabel,
30072 labelConfig,
30073 attr,
30074 rotation,
30075 connector = point.connector,
30076 isNew = !dataLabel,
30077 style,
30078 formatString;
30079
30080 // Determine if each data label is enabled
30081 // @note dataLabelAttribs (like pointAttribs) would eradicate
30082 // the need for dlOptions, and simplify the section below.
30083 pointOptions = point.dlOptions || // dlOptions is used in treemaps
30084 (point.options && point.options.dataLabels);
30085 enabled = pick(
30086 pointOptions && pointOptions.enabled,
30087 generalOptions.enabled
30088 ) && !point.isNull; // #2282, #4641, #7112
30089
30090 if (enabled) {
30091 enabled = applyFilter(point, pointOptions || options) === true;
30092 }
30093
30094 if (enabled) {
30095 // Create individual options structure that can be extended
30096 // without affecting others
30097 options = merge(generalOptions, pointOptions);
30098 labelConfig = point.getLabelConfig();
30099 formatString = (
30100 options[point.formatPrefix + 'Format'] ||
30101 options.format
30102 );
30103
30104 str = defined(formatString) ?
30105 format(formatString, labelConfig) :
30106 (
30107 options[point.formatPrefix + 'Formatter'] ||
30108 options.formatter
30109 ).call(labelConfig, options);
30110
30111 style = options.style;
30112 rotation = options.rotation;
30113
30114
30115 attr = {
30116
30117 r: options.borderRadius || 0,
30118 rotation: rotation,
30119 padding: options.padding,
30120 zIndex: 1
30121 };
30122
30123 // Remove unused attributes (#947)
30124 H.objectEach(attr, function(val, name) {
30125 if (val === undefined) {
30126 delete attr[name];
30127 }
30128 });
30129 }
30130 // If the point is outside the plot area, destroy it. #678, #820
30131 if (dataLabel && (!enabled || !defined(str))) {
30132 point.dataLabel = dataLabel = dataLabel.destroy();
30133 if (connector) {
30134 point.connector = connector.destroy();
30135 }
30136 // Individual labels are disabled if the are explicitly disabled
30137 // in the point options, or if they fall outside the plot area.
30138 } else if (enabled && defined(str)) {
30139 // create new label
30140 if (!dataLabel) {
30141 dataLabel = point.dataLabel = renderer[
30142 rotation ? 'text' : 'label' // labels don't rotate
30143 ](
30144 str,
30145 0, -9999,
30146 options.shape,
30147 null,
30148 null,
30149 options.useHTML,
30150 null,
30151 'data-label'
30152 );
30153 dataLabel.addClass(
30154 'highcharts-data-label-color-' + point.colorIndex +
30155 ' ' + (options.className || '') +
30156 (options.useHTML ? 'highcharts-tracker' : '') // #3398
30157 );
30158 } else {
30159 attr.text = str;
30160 }
30161 dataLabel.attr(attr);
30162
30163
30164 if (!dataLabel.added) {
30165 dataLabel.add(dataLabelsGroup);
30166 }
30167 // Now the data label is created and placed at 0,0, so we need
30168 // to align it
30169 series.alignDataLabel(point, dataLabel, options, null, isNew);
30170 }
30171 });
30172 }
30173 };
30174
30175 /**
30176 * Align each individual data label
30177 */
30178 Series.prototype.alignDataLabel = function(
30179 point,
30180 dataLabel,
30181 options,
30182 alignTo,
30183 isNew
30184 ) {
30185 var chart = this.chart,
30186 inverted = chart.inverted,
30187 plotX = pick(point.dlBox && point.dlBox.centerX, point.plotX, -9999),
30188 plotY = pick(point.plotY, -9999),
30189 bBox = dataLabel.getBBox(),
30190 fontSize,
30191 baseline,
30192 rotation = options.rotation,
30193 normRotation,
30194 negRotation,
30195 align = options.align,
30196 rotCorr, // rotation correction
30197 // Math.round for rounding errors (#2683), alignTo to allow column
30198 // labels (#2700)
30199 visible =
30200 this.visible &&
30201 (
30202 point.series.forceDL ||
30203 chart.isInsidePlot(plotX, Math.round(plotY), inverted) ||
30204 (
30205 alignTo && chart.isInsidePlot(
30206 plotX,
30207 inverted ?
30208 alignTo.x + 1 :
30209 alignTo.y + alignTo.height - 1,
30210 inverted
30211 )
30212 )
30213 ),
30214 alignAttr, // the final position;
30215 justify = pick(options.overflow, 'justify') === 'justify';
30216
30217 if (visible) {
30218
30219
30220
30221 baseline = chart.renderer.fontMetrics(fontSize, dataLabel).b;
30222
30223 // The alignment box is a singular point
30224 alignTo = extend({
30225 x: inverted ? this.yAxis.len - plotY : plotX,
30226 y: Math.round(inverted ? this.xAxis.len - plotX : plotY),
30227 width: 0,
30228 height: 0
30229 }, alignTo);
30230
30231 // Add the text size for alignment calculation
30232 extend(options, {
30233 width: bBox.width,
30234 height: bBox.height
30235 });
30236
30237 // Allow a hook for changing alignment in the last moment, then do the
30238 // alignment
30239 if (rotation) {
30240 justify = false; // Not supported for rotated text
30241 rotCorr = chart.renderer.rotCorr(baseline, rotation); // #3723
30242 alignAttr = {
30243 x: alignTo.x + options.x + alignTo.width / 2 + rotCorr.x,
30244 y: (
30245 alignTo.y +
30246 options.y + {
30247 top: 0,
30248 middle: 0.5,
30249 bottom: 1
30250 }[options.verticalAlign] *
30251 alignTo.height
30252 )
30253 };
30254 dataLabel[isNew ? 'attr' : 'animate'](alignAttr)
30255 .attr({ // #3003
30256 align: align
30257 });
30258
30259 // Compensate for the rotated label sticking out on the sides
30260 normRotation = (rotation + 720) % 360;
30261 negRotation = normRotation > 180 && normRotation < 360;
30262
30263 if (align === 'left') {
30264 alignAttr.y -= negRotation ? bBox.height : 0;
30265 } else if (align === 'center') {
30266 alignAttr.x -= bBox.width / 2;
30267 alignAttr.y -= bBox.height / 2;
30268 } else if (align === 'right') {
30269 alignAttr.x -= bBox.width;
30270 alignAttr.y -= negRotation ? 0 : bBox.height;
30271 }
30272
30273
30274 } else {
30275 dataLabel.align(options, null, alignTo);
30276 alignAttr = dataLabel.alignAttr;
30277 }
30278
30279 // Handle justify or crop
30280 if (justify) {
30281 point.isLabelJustified = this.justifyDataLabel(
30282 dataLabel,
30283 options,
30284 alignAttr,
30285 bBox,
30286 alignTo,
30287 isNew
30288 );
30289
30290 // Now check that the data label is within the plot area
30291 } else if (pick(options.crop, true)) {
30292 visible =
30293 chart.isInsidePlot(
30294 alignAttr.x,
30295 alignAttr.y
30296 ) &&
30297 chart.isInsidePlot(
30298 alignAttr.x + bBox.width,
30299 alignAttr.y + bBox.height
30300 );
30301 }
30302
30303 // When we're using a shape, make it possible with a connector or an
30304 // arrow pointing to thie point
30305 if (options.shape && !rotation) {
30306 dataLabel[isNew ? 'attr' : 'animate']({
30307 anchorX: inverted ? chart.plotWidth - point.plotY : point.plotX,
30308 anchorY: inverted ? chart.plotHeight - point.plotX : point.plotY
30309 });
30310 }
30311 }
30312
30313 // Show or hide based on the final aligned position
30314 if (!visible) {
30315 dataLabel.attr({
30316 y: -9999
30317 });
30318 dataLabel.placed = false; // don't animate back in
30319 }
30320
30321 };
30322
30323 /**
30324 * If data labels fall partly outside the plot area, align them back in, in a
30325 * way that doesn't hide the point.
30326 */
30327 Series.prototype.justifyDataLabel = function(
30328 dataLabel,
30329 options,
30330 alignAttr,
30331 bBox,
30332 alignTo,
30333 isNew
30334 ) {
30335 var chart = this.chart,
30336 align = options.align,
30337 verticalAlign = options.verticalAlign,
30338 off,
30339 justified,
30340 padding = dataLabel.box ? 0 : (dataLabel.padding || 0);
30341
30342 // Off left
30343 off = alignAttr.x + padding;
30344 if (off < 0) {
30345 if (align === 'right') {
30346 options.align = 'left';
30347 } else {
30348 options.x = -off;
30349 }
30350 justified = true;
30351 }
30352
30353 // Off right
30354 off = alignAttr.x + bBox.width - padding;
30355 if (off > chart.plotWidth) {
30356 if (align === 'left') {
30357 options.align = 'right';
30358 } else {
30359 options.x = chart.plotWidth - off;
30360 }
30361 justified = true;
30362 }
30363
30364 // Off top
30365 off = alignAttr.y + padding;
30366 if (off < 0) {
30367 if (verticalAlign === 'bottom') {
30368 options.verticalAlign = 'top';
30369 } else {
30370 options.y = -off;
30371 }
30372 justified = true;
30373 }
30374
30375 // Off bottom
30376 off = alignAttr.y + bBox.height - padding;
30377 if (off > chart.plotHeight) {
30378 if (verticalAlign === 'top') {
30379 options.verticalAlign = 'bottom';
30380 } else {
30381 options.y = chart.plotHeight - off;
30382 }
30383 justified = true;
30384 }
30385
30386 if (justified) {
30387 dataLabel.placed = !isNew;
30388 dataLabel.align(options, null, alignTo);
30389 }
30390
30391 return justified;
30392 };
30393
30394 /**
30395 * Override the base drawDataLabels method by pie specific functionality
30396 */
30397 if (seriesTypes.pie) {
30398 seriesTypes.pie.prototype.drawDataLabels = function() {
30399 var series = this,
30400 data = series.data,
30401 point,
30402 chart = series.chart,
30403 options = series.options.dataLabels,
30404 connectorPadding = pick(options.connectorPadding, 10),
30405 connectorWidth = pick(options.connectorWidth, 1),
30406 plotWidth = chart.plotWidth,
30407 plotHeight = chart.plotHeight,
30408 connector,
30409 seriesCenter = series.center,
30410 radius = seriesCenter[2] / 2,
30411 centerY = seriesCenter[1],
30412 dataLabel,
30413 dataLabelWidth,
30414 labelPos,
30415 labelHeight,
30416 // divide the points into right and left halves for anti collision
30417 halves = [
30418 [], // right
30419 [] // left
30420 ],
30421 x,
30422 y,
30423 visibility,
30424 j,
30425 overflow = [0, 0, 0, 0]; // top, right, bottom, left
30426
30427 // get out if not enabled
30428 if (!series.visible || (!options.enabled && !series._hasPointLabels)) {
30429 return;
30430 }
30431
30432 // Reset all labels that have been shortened
30433 each(data, function(point) {
30434 if (point.dataLabel && point.visible && point.dataLabel.shortened) {
30435 point.dataLabel
30436 .attr({
30437 width: 'auto'
30438 }).css({
30439 width: 'auto',
30440 textOverflow: 'clip'
30441 });
30442 point.dataLabel.shortened = false;
30443 }
30444 });
30445
30446
30447 // run parent method
30448 Series.prototype.drawDataLabels.apply(series);
30449
30450 each(data, function(point) {
30451 if (point.dataLabel && point.visible) { // #407, #2510
30452
30453 // Arrange points for detection collision
30454 halves[point.half].push(point);
30455
30456 // Reset positions (#4905)
30457 point.dataLabel._pos = null;
30458 }
30459 });
30460
30461 /* Loop over the points in each half, starting from the top and bottom
30462 * of the pie to detect overlapping labels.
30463 */
30464 each(halves, function(points, i) {
30465
30466 var top,
30467 bottom,
30468 length = points.length,
30469 positions = [],
30470 naturalY,
30471 sideOverflow,
30472 positionsIndex, // Point index in positions array.
30473 size;
30474
30475 if (!length) {
30476 return;
30477 }
30478
30479 // Sort by angle
30480 series.sortByAngle(points, i - 0.5);
30481 // Only do anti-collision when we have dataLabels outside the pie
30482 // and have connectors. (#856)
30483 if (series.maxLabelDistance > 0) {
30484 top = Math.max(
30485 0,
30486 centerY - radius - series.maxLabelDistance
30487 );
30488 bottom = Math.min(
30489 centerY + radius + series.maxLabelDistance,
30490 chart.plotHeight
30491 );
30492 each(points, function(point) {
30493 // check if specific points' label is outside the pie
30494 if (point.labelDistance > 0 && point.dataLabel) {
30495 // point.top depends on point.labelDistance value
30496 // Used for calculation of y value in getX method
30497 point.top = Math.max(
30498 0,
30499 centerY - radius - point.labelDistance
30500 );
30501 point.bottom = Math.min(
30502 centerY + radius + point.labelDistance,
30503 chart.plotHeight
30504 );
30505 size = point.dataLabel.getBBox().height || 21;
30506
30507 // point.positionsIndex is needed for getting index of
30508 // parameter related to specific point inside positions
30509 // array - not every point is in positions array.
30510 point.positionsIndex = positions.push({
30511 target: point.labelPos[1] - point.top + size / 2,
30512 size: size,
30513 rank: point.y
30514 }) - 1;
30515 }
30516 });
30517 H.distribute(positions, bottom + size - top);
30518 }
30519
30520 // Now the used slots are sorted, fill them up sequentially
30521 for (j = 0; j < length; j++) {
30522
30523 point = points[j];
30524 positionsIndex = point.positionsIndex;
30525 labelPos = point.labelPos;
30526 dataLabel = point.dataLabel;
30527 visibility = point.visible === false ? 'hidden' : 'inherit';
30528 naturalY = labelPos[1];
30529 y = naturalY;
30530
30531 if (positions && defined(positions[positionsIndex])) {
30532 if (positions[positionsIndex].pos === undefined) {
30533 visibility = 'hidden';
30534 } else {
30535 labelHeight = positions[positionsIndex].size;
30536 y = point.top + positions[positionsIndex].pos;
30537 }
30538 }
30539
30540 // It is needed to delete point.positionIndex for
30541 // dynamically added points etc.
30542
30543 delete point.positionIndex;
30544
30545 // get the x - use the natural x position for labels near the
30546 // top and bottom, to prevent the top and botton slice
30547 // connectors from touching each other on either side
30548 if (options.justify) {
30549 x = seriesCenter[0] +
30550 (i ? -1 : 1) * (radius + point.labelDistance);
30551 } else {
30552 x = series.getX(
30553 y < point.top + 2 || y > point.bottom - 2 ?
30554 naturalY :
30555 y,
30556 i,
30557 point
30558 );
30559 }
30560
30561
30562 // Record the placement and visibility
30563 dataLabel._attr = {
30564 visibility: visibility,
30565 align: labelPos[6]
30566 };
30567 dataLabel._pos = {
30568 x: (
30569 x +
30570 options.x +
30571 ({
30572 left: connectorPadding,
30573 right: -connectorPadding
30574 }[labelPos[6]] || 0)
30575 ),
30576
30577 // 10 is for the baseline (label vs text)
30578 y: y + options.y - 10
30579 };
30580 labelPos.x = x;
30581 labelPos.y = y;
30582
30583
30584 // Detect overflowing data labels
30585 if (pick(options.crop, true)) {
30586 dataLabelWidth = dataLabel.getBBox().width;
30587
30588 sideOverflow = null;
30589 // Overflow left
30590 if (x - dataLabelWidth < connectorPadding) {
30591 sideOverflow = Math.round(
30592 dataLabelWidth - x + connectorPadding
30593 );
30594 overflow[3] = Math.max(sideOverflow, overflow[3]);
30595
30596 // Overflow right
30597 } else if (
30598 x + dataLabelWidth >
30599 plotWidth - connectorPadding
30600 ) {
30601 sideOverflow = Math.round(
30602 x + dataLabelWidth - plotWidth + connectorPadding
30603 );
30604 overflow[1] = Math.max(sideOverflow, overflow[1]);
30605 }
30606
30607 // Overflow top
30608 if (y - labelHeight / 2 < 0) {
30609 overflow[0] = Math.max(
30610 Math.round(-y + labelHeight / 2),
30611 overflow[0]
30612 );
30613
30614 // Overflow left
30615 } else if (y + labelHeight / 2 > plotHeight) {
30616 overflow[2] = Math.max(
30617 Math.round(y + labelHeight / 2 - plotHeight),
30618 overflow[2]
30619 );
30620 }
30621 dataLabel.sideOverflow = sideOverflow;
30622 }
30623 } // for each point
30624 }); // for each half
30625
30626 // Do not apply the final placement and draw the connectors until we
30627 // have verified that labels are not spilling over.
30628 if (
30629 arrayMax(overflow) === 0 ||
30630 this.verifyDataLabelOverflow(overflow)
30631 ) {
30632
30633 // Place the labels in the final position
30634 this.placeDataLabels();
30635
30636 // Draw the connectors
30637 if (connectorWidth) {
30638 each(this.points, function(point) {
30639 var isNew;
30640
30641 connector = point.connector;
30642 dataLabel = point.dataLabel;
30643
30644 if (
30645 dataLabel &&
30646 dataLabel._pos &&
30647 point.visible &&
30648 point.labelDistance > 0
30649 ) {
30650 visibility = dataLabel._attr.visibility;
30651
30652 isNew = !connector;
30653
30654 if (isNew) {
30655 point.connector = connector = chart.renderer.path()
30656 .addClass('highcharts-data-label-connector ' +
30657 ' highcharts-color-' + point.colorIndex)
30658 .add(series.dataLabelsGroup);
30659
30660
30661 }
30662 connector[isNew ? 'attr' : 'animate']({
30663 d: series.connectorPath(point.labelPos)
30664 });
30665 connector.attr('visibility', visibility);
30666
30667 } else if (connector) {
30668 point.connector = connector.destroy();
30669 }
30670 });
30671 }
30672 }
30673 };
30674
30675 /**
30676 * Extendable method for getting the path of the connector between the data
30677 * label and the pie slice.
30678 */
30679 seriesTypes.pie.prototype.connectorPath = function(labelPos) {
30680 var x = labelPos.x,
30681 y = labelPos.y;
30682 return pick(this.options.dataLabels.softConnector, true) ? [
30683 'M',
30684 // end of the string at the label
30685 x + (labelPos[6] === 'left' ? 5 : -5), y,
30686 'C',
30687 x, y, // first break, next to the label
30688 2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],
30689 labelPos[2], labelPos[3], // second break
30690 'L',
30691 labelPos[4], labelPos[5] // base
30692 ] : [
30693 'M',
30694 // end of the string at the label
30695 x + (labelPos[6] === 'left' ? 5 : -5), y,
30696 'L',
30697 labelPos[2], labelPos[3], // second break
30698 'L',
30699 labelPos[4], labelPos[5] // base
30700 ];
30701 };
30702
30703 /**
30704 * Perform the final placement of the data labels after we have verified
30705 * that they fall within the plot area.
30706 */
30707 seriesTypes.pie.prototype.placeDataLabels = function() {
30708 each(this.points, function(point) {
30709 var dataLabel = point.dataLabel,
30710 _pos;
30711 if (dataLabel && point.visible) {
30712 _pos = dataLabel._pos;
30713 if (_pos) {
30714
30715 // Shorten data labels with ellipsis if they still overflow
30716 // after the pie has reached minSize (#223).
30717 if (dataLabel.sideOverflow) {
30718 dataLabel._attr.width =
30719 dataLabel.getBBox().width - dataLabel.sideOverflow;
30720 dataLabel.css({
30721 width: dataLabel._attr.width + 'px',
30722 textOverflow: 'ellipsis'
30723 });
30724 dataLabel.shortened = true;
30725 }
30726
30727 dataLabel.attr(dataLabel._attr);
30728 dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos);
30729 dataLabel.moved = true;
30730 } else if (dataLabel) {
30731 dataLabel.attr({
30732 y: -9999
30733 });
30734 }
30735 }
30736 }, this);
30737 };
30738
30739 seriesTypes.pie.prototype.alignDataLabel = noop;
30740
30741 /**
30742 * Verify whether the data labels are allowed to draw, or we should run more
30743 * translation and data label positioning to keep them inside the plot area.
30744 * Returns true when data labels are ready to draw.
30745 */
30746 seriesTypes.pie.prototype.verifyDataLabelOverflow = function(overflow) {
30747
30748 var center = this.center,
30749 options = this.options,
30750 centerOption = options.center,
30751 minSize = options.minSize || 80,
30752 newSize = minSize,
30753 // If a size is set, return true and don't try to shrink the pie
30754 // to fit the labels.
30755 ret = options.size !== null;
30756
30757 if (!ret) {
30758 // Handle horizontal size and center
30759 if (centerOption[0] !== null) { // Fixed center
30760 newSize = Math.max(center[2] -
30761 Math.max(overflow[1], overflow[3]), minSize);
30762
30763 } else { // Auto center
30764 newSize = Math.max(
30765 // horizontal overflow
30766 center[2] - overflow[1] - overflow[3],
30767 minSize
30768 );
30769 // horizontal center
30770 center[0] += (overflow[3] - overflow[1]) / 2;
30771 }
30772
30773 // Handle vertical size and center
30774 if (centerOption[1] !== null) { // Fixed center
30775 newSize = Math.max(Math.min(newSize, center[2] -
30776 Math.max(overflow[0], overflow[2])), minSize);
30777
30778 } else { // Auto center
30779 newSize = Math.max(
30780 Math.min(
30781 newSize,
30782 // vertical overflow
30783 center[2] - overflow[0] - overflow[2]
30784 ),
30785 minSize
30786 );
30787 // vertical center
30788 center[1] += (overflow[0] - overflow[2]) / 2;
30789 }
30790
30791 // If the size must be decreased, we need to run translate and
30792 // drawDataLabels again
30793 if (newSize < center[2]) {
30794 center[2] = newSize;
30795 center[3] = Math.min( // #3632
30796 relativeLength(options.innerSize || 0, newSize),
30797 newSize
30798 );
30799 this.translate(center);
30800
30801 if (this.drawDataLabels) {
30802 this.drawDataLabels();
30803 }
30804 // Else, return true to indicate that the pie and its labels is
30805 // within the plot area
30806 } else {
30807 ret = true;
30808 }
30809 }
30810 return ret;
30811 };
30812 }
30813
30814 if (seriesTypes.column) {
30815
30816 /**
30817 * Override the basic data label alignment by adjusting for the position of
30818 * the column
30819 */
30820 seriesTypes.column.prototype.alignDataLabel = function(
30821 point,
30822 dataLabel,
30823 options,
30824 alignTo,
30825 isNew
30826 ) {
30827 var inverted = this.chart.inverted,
30828 series = point.series,
30829 // data label box for alignment
30830 dlBox = point.dlBox || point.shapeArgs,
30831 below = pick(
30832 point.below, // range series
30833 point.plotY > pick(this.translatedThreshold, series.yAxis.len)
30834 ),
30835 // draw it inside the box?
30836 inside = pick(options.inside, !!this.options.stacking),
30837 overshoot;
30838
30839 // Align to the column itself, or the top of it
30840 if (dlBox) { // Area range uses this method but not alignTo
30841 alignTo = merge(dlBox);
30842
30843 if (alignTo.y < 0) {
30844 alignTo.height += alignTo.y;
30845 alignTo.y = 0;
30846 }
30847 overshoot = alignTo.y + alignTo.height - series.yAxis.len;
30848 if (overshoot > 0) {
30849 alignTo.height -= overshoot;
30850 }
30851
30852 if (inverted) {
30853 alignTo = {
30854 x: series.yAxis.len - alignTo.y - alignTo.height,
30855 y: series.xAxis.len - alignTo.x - alignTo.width,
30856 width: alignTo.height,
30857 height: alignTo.width
30858 };
30859 }
30860
30861 // Compute the alignment box
30862 if (!inside) {
30863 if (inverted) {
30864 alignTo.x += below ? 0 : alignTo.width;
30865 alignTo.width = 0;
30866 } else {
30867 alignTo.y += below ? alignTo.height : 0;
30868 alignTo.height = 0;
30869 }
30870 }
30871 }
30872
30873
30874 // When alignment is undefined (typically columns and bars), display the
30875 // individual point below or above the point depending on the threshold
30876 options.align = pick(
30877 options.align, !inverted || inside ? 'center' : below ? 'right' : 'left'
30878 );
30879 options.verticalAlign = pick(
30880 options.verticalAlign,
30881 inverted || inside ? 'middle' : below ? 'top' : 'bottom'
30882 );
30883
30884 // Call the parent method
30885 Series.prototype.alignDataLabel.call(
30886 this,
30887 point,
30888 dataLabel,
30889 options,
30890 alignTo,
30891 isNew
30892 );
30893
30894 // If label was justified and we have contrast, set it:
30895 if (point.isLabelJustified && point.contrastColor) {
30896 point.dataLabel.css({
30897 color: point.contrastColor
30898 });
30899 }
30900 };
30901 }
30902
30903 }(Highcharts));
30904 (function(H) {
30905 /**
30906 * (c) 2009-2017 Torstein Honsi
30907 *
30908 * License: www.highcharts.com/license
30909 */
30910 /**
30911 * Highcharts module to hide overlapping data labels. This module is included in
30912 * Highcharts.
30913 */
30914 var Chart = H.Chart,
30915 each = H.each,
30916 objectEach = H.objectEach,
30917 pick = H.pick,
30918 addEvent = H.addEvent;
30919
30920 // Collect potensial overlapping data labels. Stack labels probably don't need
30921 // to be considered because they are usually accompanied by data labels that lie
30922 // inside the columns.
30923 addEvent(Chart.prototype, 'render', function collectAndHide() {
30924 var labels = [];
30925
30926 // Consider external label collectors
30927 each(this.labelCollectors || [], function(collector) {
30928 labels = labels.concat(collector());
30929 });
30930
30931 each(this.yAxis || [], function(yAxis) {
30932 if (
30933 yAxis.options.stackLabels &&
30934 !yAxis.options.stackLabels.allowOverlap
30935 ) {
30936 objectEach(yAxis.stacks, function(stack) {
30937 objectEach(stack, function(stackItem) {
30938 labels.push(stackItem.label);
30939 });
30940 });
30941 }
30942 });
30943
30944 each(this.series || [], function(series) {
30945 var dlOptions = series.options.dataLabels,
30946 // Range series have two collections
30947 collections = series.dataLabelCollections || ['dataLabel'];
30948
30949 if (
30950 (dlOptions.enabled || series._hasPointLabels) &&
30951 !dlOptions.allowOverlap &&
30952 series.visible
30953 ) { // #3866
30954 each(collections, function(coll) {
30955 each(series.points, function(point) {
30956 if (point[coll]) {
30957 point[coll].labelrank = pick(
30958 point.labelrank,
30959 point.shapeArgs && point.shapeArgs.height
30960 ); // #4118
30961 labels.push(point[coll]);
30962 }
30963 });
30964 });
30965 }
30966 });
30967 this.hideOverlappingLabels(labels);
30968 });
30969
30970 /**
30971 * Hide overlapping labels. Labels are moved and faded in and out on zoom to
30972 * provide a smooth visual imression.
30973 */
30974 Chart.prototype.hideOverlappingLabels = function(labels) {
30975
30976 var len = labels.length,
30977 label,
30978 i,
30979 j,
30980 label1,
30981 label2,
30982 isIntersecting,
30983 pos1,
30984 pos2,
30985 parent1,
30986 parent2,
30987 padding,
30988 bBox,
30989 intersectRect = function(x1, y1, w1, h1, x2, y2, w2, h2) {
30990 return !(
30991 x2 > x1 + w1 ||
30992 x2 + w2 < x1 ||
30993 y2 > y1 + h1 ||
30994 y2 + h2 < y1
30995 );
30996 };
30997
30998 for (i = 0; i < len; i++) {
30999 label = labels[i];
31000 if (label) {
31001
31002 // Mark with initial opacity
31003 label.oldOpacity = label.opacity;
31004 label.newOpacity = 1;
31005
31006 // Get width and height if pure text nodes (stack labels)
31007 if (!label.width) {
31008 bBox = label.getBBox();
31009 label.width = bBox.width;
31010 label.height = bBox.height;
31011 }
31012 }
31013 }
31014
31015 // Prevent a situation in a gradually rising slope, that each label will
31016 // hide the previous one because the previous one always has lower rank.
31017 labels.sort(function(a, b) {
31018 return (b.labelrank || 0) - (a.labelrank || 0);
31019 });
31020
31021 // Detect overlapping labels
31022 for (i = 0; i < len; i++) {
31023 label1 = labels[i];
31024
31025 for (j = i + 1; j < len; ++j) {
31026 label2 = labels[j];
31027 if (
31028 label1 && label2 &&
31029 label1 !== label2 && // #6465, polar chart with connectEnds
31030 label1.placed && label2.placed &&
31031 label1.newOpacity !== 0 && label2.newOpacity !== 0
31032 ) {
31033 pos1 = label1.alignAttr;
31034 pos2 = label2.alignAttr;
31035 // Different panes have different positions
31036 parent1 = label1.parentGroup;
31037 parent2 = label2.parentGroup;
31038 // Substract the padding if no background or border (#4333)
31039 padding = 2 * (label1.box ? 0 : (label1.padding || 0));
31040 isIntersecting = intersectRect(
31041 pos1.x + parent1.translateX,
31042 pos1.y + parent1.translateY,
31043 label1.width - padding,
31044 label1.height - padding,
31045 pos2.x + parent2.translateX,
31046 pos2.y + parent2.translateY,
31047 label2.width - padding,
31048 label2.height - padding
31049 );
31050
31051 if (isIntersecting) {
31052 (label1.labelrank < label2.labelrank ? label1 : label2)
31053 .newOpacity = 0;
31054 }
31055 }
31056 }
31057 }
31058
31059 // Hide or show
31060 each(labels, function(label) {
31061 var complete,
31062 newOpacity;
31063
31064 if (label) {
31065 newOpacity = label.newOpacity;
31066
31067 if (label.oldOpacity !== newOpacity && label.placed) {
31068
31069 // Make sure the label is completely hidden to avoid catching
31070 // clicks (#4362)
31071 if (newOpacity) {
31072 label.show(true);
31073 } else {
31074 complete = function() {
31075 label.hide();
31076 };
31077 }
31078
31079 // Animate or set the opacity
31080 label.alignAttr.opacity = newOpacity;
31081 label[label.isOld ? 'animate' : 'attr'](
31082 label.alignAttr,
31083 null,
31084 complete
31085 );
31086
31087 }
31088 label.isOld = true;
31089 }
31090 });
31091 };
31092
31093 }(Highcharts));
31094 (function(H) {
31095 /**
31096 * (c) 2010-2017 Torstein Honsi
31097 *
31098 * License: www.highcharts.com/license
31099 */
31100 var addEvent = H.addEvent,
31101 Chart = H.Chart,
31102 createElement = H.createElement,
31103 css = H.css,
31104 defaultOptions = H.defaultOptions,
31105 defaultPlotOptions = H.defaultPlotOptions,
31106 each = H.each,
31107 extend = H.extend,
31108 fireEvent = H.fireEvent,
31109 hasTouch = H.hasTouch,
31110 inArray = H.inArray,
31111 isObject = H.isObject,
31112 Legend = H.Legend,
31113 merge = H.merge,
31114 pick = H.pick,
31115 Point = H.Point,
31116 Series = H.Series,
31117 seriesTypes = H.seriesTypes,
31118 svg = H.svg,
31119 TrackerMixin;
31120
31121 /**
31122 * TrackerMixin for points and graphs.
31123 */
31124 TrackerMixin = H.TrackerMixin = {
31125
31126 /**
31127 * Draw the tracker for a point.
31128 */
31129 drawTrackerPoint: function() {
31130 var series = this,
31131 chart = series.chart,
31132 pointer = chart.pointer,
31133 onMouseOver = function(e) {
31134 var point = pointer.getPointFromEvent(e);
31135 // undefined on graph in scatterchart
31136 if (point !== undefined) {
31137 pointer.isDirectTouch = true;
31138 point.onMouseOver(e);
31139 }
31140 };
31141
31142 // Add reference to the point
31143 each(series.points, function(point) {
31144 if (point.graphic) {
31145 point.graphic.element.point = point;
31146 }
31147 if (point.dataLabel) {
31148 if (point.dataLabel.div) {
31149 point.dataLabel.div.point = point;
31150 } else {
31151 point.dataLabel.element.point = point;
31152 }
31153 }
31154 });
31155
31156 // Add the event listeners, we need to do this only once
31157 if (!series._hasTracking) {
31158 each(series.trackerGroups, function(key) {
31159 if (series[key]) { // we don't always have dataLabelsGroup
31160 series[key]
31161 .addClass('highcharts-tracker')
31162 .on('mouseover', onMouseOver)
31163 .on('mouseout', function(e) {
31164 pointer.onTrackerMouseOut(e);
31165 });
31166 if (hasTouch) {
31167 series[key].on('touchstart', onMouseOver);
31168 }
31169
31170
31171 }
31172 });
31173 series._hasTracking = true;
31174 }
31175 },
31176
31177 /**
31178 * Draw the tracker object that sits above all data labels and markers to
31179 * track mouse events on the graph or points. For the line type charts
31180 * the tracker uses the same graphPath, but with a greater stroke width
31181 * for better control.
31182 */
31183 drawTrackerGraph: function() {
31184 var series = this,
31185 options = series.options,
31186 trackByArea = options.trackByArea,
31187 trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath),
31188 trackerPathLength = trackerPath.length,
31189 chart = series.chart,
31190 pointer = chart.pointer,
31191 renderer = chart.renderer,
31192 snap = chart.options.tooltip.snap,
31193 tracker = series.tracker,
31194 i,
31195 onMouseOver = function() {
31196 if (chart.hoverSeries !== series) {
31197 series.onMouseOver();
31198 }
31199 },
31200 /*
31201 * Empirical lowest possible opacities for TRACKER_FILL for an element to stay invisible but clickable
31202 * IE6: 0.002
31203 * IE7: 0.002
31204 * IE8: 0.002
31205 * IE9: 0.00000000001 (unlimited)
31206 * IE10: 0.0001 (exporting only)
31207 * FF: 0.00000000001 (unlimited)
31208 * Chrome: 0.000001
31209 * Safari: 0.000001
31210 * Opera: 0.00000000001 (unlimited)
31211 */
31212 TRACKER_FILL = 'rgba(192,192,192,' + (svg ? 0.0001 : 0.002) + ')';
31213
31214 // Extend end points. A better way would be to use round linecaps,
31215 // but those are not clickable in VML.
31216 if (trackerPathLength && !trackByArea) {
31217 i = trackerPathLength + 1;
31218 while (i--) {
31219 if (trackerPath[i] === 'M') { // extend left side
31220 trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], 'L');
31221 }
31222 if ((i && trackerPath[i] === 'M') || i === trackerPathLength) { // extend right side
31223 trackerPath.splice(i, 0, 'L', trackerPath[i - 2] + snap, trackerPath[i - 1]);
31224 }
31225 }
31226 }
31227
31228 // draw the tracker
31229 if (tracker) {
31230 tracker.attr({
31231 d: trackerPath
31232 });
31233 } else if (series.graph) { // create
31234
31235 series.tracker = renderer.path(trackerPath)
31236 .attr({
31237 'stroke-linejoin': 'round', // #1225
31238 visibility: series.visible ? 'visible' : 'hidden',
31239 stroke: TRACKER_FILL,
31240 fill: trackByArea ? TRACKER_FILL : 'none',
31241 'stroke-width': series.graph.strokeWidth() + (trackByArea ? 0 : 2 * snap),
31242 zIndex: 2
31243 })
31244 .add(series.group);
31245
31246 // The tracker is added to the series group, which is clipped, but is covered
31247 // by the marker group. So the marker group also needs to capture events.
31248 each([series.tracker, series.markerGroup], function(tracker) {
31249 tracker.addClass('highcharts-tracker')
31250 .on('mouseover', onMouseOver)
31251 .on('mouseout', function(e) {
31252 pointer.onTrackerMouseOut(e);
31253 });
31254
31255
31256
31257 if (hasTouch) {
31258 tracker.on('touchstart', onMouseOver);
31259 }
31260 });
31261 }
31262 }
31263 };
31264 /* End TrackerMixin */
31265
31266
31267 /**
31268 * Add tracking event listener to the series group, so the point graphics
31269 * themselves act as trackers
31270 */
31271
31272 if (seriesTypes.column) {
31273 seriesTypes.column.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
31274 }
31275
31276 if (seriesTypes.pie) {
31277 seriesTypes.pie.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
31278 }
31279
31280 if (seriesTypes.scatter) {
31281 seriesTypes.scatter.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
31282 }
31283
31284 /*
31285 * Extend Legend for item events
31286 */
31287 extend(Legend.prototype, {
31288
31289 setItemEvents: function(item, legendItem, useHTML) {
31290 var legend = this,
31291 boxWrapper = legend.chart.renderer.boxWrapper,
31292 activeClass = 'highcharts-legend-' + (item.series ? 'point' : 'series') + '-active';
31293
31294 // Set the events on the item group, or in case of useHTML, the item itself (#1249)
31295 (useHTML ? legendItem : item.legendGroup).on('mouseover', function() {
31296 item.setState('hover');
31297
31298 // A CSS class to dim or hide other than the hovered series
31299 boxWrapper.addClass(activeClass);
31300
31301
31302 })
31303 .on('mouseout', function() {
31304
31305
31306 // A CSS class to dim or hide other than the hovered series
31307 boxWrapper.removeClass(activeClass);
31308
31309 item.setState();
31310 })
31311 .on('click', function(event) {
31312 var strLegendItemClick = 'legendItemClick',
31313 fnLegendItemClick = function() {
31314 if (item.setVisible) {
31315 item.setVisible();
31316 }
31317 };
31318
31319 // Pass over the click/touch event. #4.
31320 event = {
31321 browserEvent: event
31322 };
31323
31324 // click the name or symbol
31325 if (item.firePointEvent) { // point
31326 item.firePointEvent(strLegendItemClick, event, fnLegendItemClick);
31327 } else {
31328 fireEvent(item, strLegendItemClick, event, fnLegendItemClick);
31329 }
31330 });
31331 },
31332
31333 createCheckboxForItem: function(item) {
31334 var legend = this;
31335
31336 item.checkbox = createElement('input', {
31337 type: 'checkbox',
31338 checked: item.selected,
31339 defaultChecked: item.selected // required by IE7
31340 }, legend.options.itemCheckboxStyle, legend.chart.container);
31341
31342 addEvent(item.checkbox, 'click', function(event) {
31343 var target = event.target;
31344 fireEvent(
31345 item.series || item,
31346 'checkboxClick', { // #3712
31347 checked: target.checked,
31348 item: item
31349 },
31350 function() {
31351 item.select();
31352 }
31353 );
31354 });
31355 }
31356 });
31357
31358
31359
31360
31361
31362 /*
31363 * Extend the Chart object with interaction
31364 */
31365
31366 extend(Chart.prototype, /** @lends Chart.prototype */ {
31367 /**
31368 * Display the zoom button.
31369 *
31370 * @private
31371 */
31372 showResetZoom: function() {
31373 var chart = this,
31374 lang = defaultOptions.lang,
31375 btnOptions = chart.options.chart.resetZoomButton,
31376 theme = btnOptions.theme,
31377 states = theme.states,
31378 alignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox';
31379
31380 function zoomOut() {
31381 chart.zoomOut();
31382 }
31383
31384 this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, zoomOut, theme, states && states.hover)
31385 .attr({
31386 align: btnOptions.position.align,
31387 title: lang.resetZoomTitle
31388 })
31389 .addClass('highcharts-reset-zoom')
31390 .add()
31391 .align(btnOptions.position, false, alignTo);
31392
31393 },
31394
31395 /**
31396 * Zoom out to 1:1.
31397 *
31398 * @private
31399 */
31400 zoomOut: function() {
31401 var chart = this;
31402 fireEvent(chart, 'selection', {
31403 resetSelection: true
31404 }, function() {
31405 chart.zoom();
31406 });
31407 },
31408
31409 /**
31410 * Zoom into a given portion of the chart given by axis coordinates.
31411 * @param {Object} event
31412 *
31413 * @private
31414 */
31415 zoom: function(event) {
31416 var chart = this,
31417 hasZoomed,
31418 pointer = chart.pointer,
31419 displayButton = false,
31420 resetZoomButton;
31421
31422 // If zoom is called with no arguments, reset the axes
31423 if (!event || event.resetSelection) {
31424 each(chart.axes, function(axis) {
31425 hasZoomed = axis.zoom();
31426 });
31427 pointer.initiated = false; // #6804
31428
31429 } else { // else, zoom in on all axes
31430 each(event.xAxis.concat(event.yAxis), function(axisData) {
31431 var axis = axisData.axis,
31432 isXAxis = axis.isXAxis;
31433
31434 // don't zoom more than minRange
31435 if (pointer[isXAxis ? 'zoomX' : 'zoomY']) {
31436 hasZoomed = axis.zoom(axisData.min, axisData.max);
31437 if (axis.displayBtn) {
31438 displayButton = true;
31439 }
31440 }
31441 });
31442 }
31443
31444 // Show or hide the Reset zoom button
31445 resetZoomButton = chart.resetZoomButton;
31446 if (displayButton && !resetZoomButton) {
31447 chart.showResetZoom();
31448 } else if (!displayButton && isObject(resetZoomButton)) {
31449 chart.resetZoomButton = resetZoomButton.destroy();
31450 }
31451
31452
31453 // Redraw
31454 if (hasZoomed) {
31455 chart.redraw(
31456 pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation
31457 );
31458 }
31459 },
31460
31461 /**
31462 * Pan the chart by dragging the mouse across the pane. This function is
31463 * called on mouse move, and the distance to pan is computed from chartX
31464 * compared to the first chartX position in the dragging operation.
31465 *
31466 * @private
31467 */
31468 pan: function(e, panning) {
31469
31470 var chart = this,
31471 hoverPoints = chart.hoverPoints,
31472 doRedraw;
31473
31474 // remove active points for shared tooltip
31475 if (hoverPoints) {
31476 each(hoverPoints, function(point) {
31477 point.setState();
31478 });
31479 }
31480
31481 each(panning === 'xy' ? [1, 0] : [1], function(isX) { // xy is used in maps
31482 var axis = chart[isX ? 'xAxis' : 'yAxis'][0],
31483 horiz = axis.horiz,
31484 mousePos = e[horiz ? 'chartX' : 'chartY'],
31485 mouseDown = horiz ? 'mouseDownX' : 'mouseDownY',
31486 startPos = chart[mouseDown],
31487 halfPointRange = (axis.pointRange || 0) / 2,
31488 extremes = axis.getExtremes(),
31489 panMin = axis.toValue(startPos - mousePos, true) +
31490 halfPointRange,
31491 panMax = axis.toValue(startPos + axis.len - mousePos, true) -
31492 halfPointRange,
31493 flipped = panMax < panMin,
31494 newMin = flipped ? panMax : panMin,
31495 newMax = flipped ? panMin : panMax,
31496 paddedMin = Math.min(
31497 extremes.dataMin,
31498 axis.toValue(
31499 axis.toPixels(extremes.min) - axis.minPixelPadding
31500 )
31501 ),
31502 paddedMax = Math.max(
31503 extremes.dataMax,
31504 axis.toValue(
31505 axis.toPixels(extremes.max) + axis.minPixelPadding
31506 )
31507 ),
31508 spill;
31509
31510 // If the new range spills over, either to the min or max, adjust
31511 // the new range.
31512 spill = paddedMin - newMin;
31513 if (spill > 0) {
31514 newMax += spill;
31515 newMin = paddedMin;
31516 }
31517 spill = newMax - paddedMax;
31518 if (spill > 0) {
31519 newMax = paddedMax;
31520 newMin -= spill;
31521 }
31522
31523 // Set new extremes if they are actually new
31524 if (axis.series.length && newMin !== extremes.min && newMax !== extremes.max) {
31525 axis.setExtremes(
31526 newMin,
31527 newMax,
31528 false,
31529 false, {
31530 trigger: 'pan'
31531 }
31532 );
31533 doRedraw = true;
31534 }
31535
31536 chart[mouseDown] = mousePos; // set new reference for next run
31537 });
31538
31539 if (doRedraw) {
31540 chart.redraw(false);
31541 }
31542 css(chart.container, {
31543 cursor: 'move'
31544 });
31545 }
31546 });
31547
31548 /*
31549 * Extend the Point object with interaction
31550 */
31551 extend(Point.prototype, /** @lends Highcharts.Point.prototype */ {
31552 /**
31553 * Toggle the selection status of a point.
31554 * @param {Boolean} [selected]
31555 * When `true`, the point is selected. When `false`, the point is
31556 * unselected. When `null` or `undefined`, the selection state is
31557 * toggled.
31558 * @param {Boolean} [accumulate=false]
31559 * When `true`, the selection is added to other selected points.
31560 * When `false`, other selected points are deselected. Internally in
31561 * Highcharts, when {@link http://api.highcharts.com/highcharts/plotOptions.series.allowPointSelect|allowPointSelect}
31562 * is `true`, selected points are accumulated on Control, Shift or
31563 * Cmd clicking the point.
31564 *
31565 * @see Highcharts.Chart#getSelectedPoints
31566 *
31567 * @sample highcharts/members/point-select/
31568 * Select a point from a button
31569 * @sample highcharts/chart/events-selection-points/
31570 * Select a range of points through a drag selection
31571 * @sample maps/series/data-id/
31572 * Select a point in Highmaps
31573 */
31574 select: function(selected, accumulate) {
31575 var point = this,
31576 series = point.series,
31577 chart = series.chart;
31578
31579 selected = pick(selected, !point.selected);
31580
31581 // fire the event with the default handler
31582 point.firePointEvent(selected ? 'select' : 'unselect', {
31583 accumulate: accumulate
31584 }, function() {
31585
31586 /**
31587 * Whether the point is selected or not.
31588 * @see Point#select
31589 * @see Chart#getSelectedPoints
31590 * @memberof Point
31591 * @name selected
31592 * @type {Boolean}
31593 */
31594 point.selected = point.options.selected = selected;
31595 series.options.data[inArray(point, series.data)] = point.options;
31596
31597 point.setState(selected && 'select');
31598
31599 // unselect all other points unless Ctrl or Cmd + click
31600 if (!accumulate) {
31601 each(chart.getSelectedPoints(), function(loopPoint) {
31602 if (loopPoint.selected && loopPoint !== point) {
31603 loopPoint.selected = loopPoint.options.selected = false;
31604 series.options.data[inArray(loopPoint, series.data)] = loopPoint.options;
31605 loopPoint.setState('');
31606 loopPoint.firePointEvent('unselect');
31607 }
31608 });
31609 }
31610 });
31611 },
31612
31613 /**
31614 * Runs on mouse over the point. Called internally from mouse and touch
31615 * events.
31616 *
31617 * @param {Object} e The event arguments
31618 */
31619 onMouseOver: function(e) {
31620 var point = this,
31621 series = point.series,
31622 chart = series.chart,
31623 pointer = chart.pointer;
31624 e = e ?
31625 pointer.normalize(e) :
31626 // In cases where onMouseOver is called directly without an event
31627 pointer.getChartCoordinatesFromPoint(point, chart.inverted);
31628 pointer.runPointActions(e, point);
31629 },
31630
31631 /**
31632 * Runs on mouse out from the point. Called internally from mouse and touch
31633 * events.
31634 */
31635 onMouseOut: function() {
31636 var point = this,
31637 chart = point.series.chart;
31638 point.firePointEvent('mouseOut');
31639 each(chart.hoverPoints || [], function(p) {
31640 p.setState();
31641 });
31642 chart.hoverPoints = chart.hoverPoint = null;
31643 },
31644
31645 /**
31646 * Import events from the series' and point's options. Only do it on
31647 * demand, to save processing time on hovering.
31648 *
31649 * @private
31650 */
31651 importEvents: function() {
31652 if (!this.hasImportedEvents) {
31653 var point = this,
31654 options = merge(point.series.options.point, point.options),
31655 events = options.events;
31656
31657 point.events = events;
31658
31659 H.objectEach(events, function(event, eventType) {
31660 addEvent(point, eventType, event);
31661 });
31662 this.hasImportedEvents = true;
31663
31664 }
31665 },
31666
31667 /**
31668 * Set the point's state.
31669 * @param {String} [state]
31670 * The new state, can be one of `''` (an empty string), `hover` or
31671 * `select`.
31672 */
31673 setState: function(state, move) {
31674 var point = this,
31675 plotX = Math.floor(point.plotX), // #4586
31676 plotY = point.plotY,
31677 series = point.series,
31678 stateOptions = series.options.states[state] || {},
31679 markerOptions = defaultPlotOptions[series.type].marker &&
31680 series.options.marker,
31681 normalDisabled = markerOptions && markerOptions.enabled === false,
31682 markerStateOptions = (markerOptions && markerOptions.states &&
31683 markerOptions.states[state]) || {},
31684 stateDisabled = markerStateOptions.enabled === false,
31685 stateMarkerGraphic = series.stateMarkerGraphic,
31686 pointMarker = point.marker || {},
31687 chart = series.chart,
31688 halo = series.halo,
31689 haloOptions,
31690 markerAttribs,
31691 hasMarkers = markerOptions && series.markerAttribs,
31692 newSymbol;
31693
31694 state = state || ''; // empty string
31695
31696 if (
31697 // already has this state
31698 (state === point.state && !move) ||
31699
31700 // selected points don't respond to hover
31701 (point.selected && state !== 'select') ||
31702
31703 // series' state options is disabled
31704 (stateOptions.enabled === false) ||
31705
31706 // general point marker's state options is disabled
31707 (state && (
31708 stateDisabled ||
31709 (normalDisabled && markerStateOptions.enabled === false)
31710 )) ||
31711
31712 // individual point marker's state options is disabled
31713 (
31714 state &&
31715 pointMarker.states &&
31716 pointMarker.states[state] &&
31717 pointMarker.states[state].enabled === false
31718 ) // #1610
31719
31720 ) {
31721 return;
31722 }
31723
31724 if (hasMarkers) {
31725 markerAttribs = series.markerAttribs(point, state);
31726 }
31727
31728 // Apply hover styles to the existing point
31729 if (point.graphic) {
31730
31731 if (point.state) {
31732 point.graphic.removeClass('highcharts-point-' + point.state);
31733 }
31734 if (state) {
31735 point.graphic.addClass('highcharts-point-' + state);
31736 }
31737
31738
31739
31740 if (markerAttribs) {
31741 point.graphic.animate(
31742 markerAttribs,
31743 pick(
31744 chart.options.chart.animation, // Turn off globally
31745 markerStateOptions.animation,
31746 markerOptions.animation
31747 )
31748 );
31749 }
31750
31751 // Zooming in from a range with no markers to a range with markers
31752 if (stateMarkerGraphic) {
31753 stateMarkerGraphic.hide();
31754 }
31755 } else {
31756 // if a graphic is not applied to each point in the normal state, create a shared
31757 // graphic for the hover state
31758 if (state && markerStateOptions) {
31759 newSymbol = pointMarker.symbol || series.symbol;
31760
31761 // If the point has another symbol than the previous one, throw away the
31762 // state marker graphic and force a new one (#1459)
31763 if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) {
31764 stateMarkerGraphic = stateMarkerGraphic.destroy();
31765 }
31766
31767 // Add a new state marker graphic
31768 if (!stateMarkerGraphic) {
31769 if (newSymbol) {
31770 series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
31771 newSymbol,
31772 markerAttribs.x,
31773 markerAttribs.y,
31774 markerAttribs.width,
31775 markerAttribs.height
31776 )
31777 .add(series.markerGroup);
31778 stateMarkerGraphic.currentSymbol = newSymbol;
31779 }
31780
31781 // Move the existing graphic
31782 } else {
31783 stateMarkerGraphic[move ? 'animate' : 'attr']({ // #1054
31784 x: markerAttribs.x,
31785 y: markerAttribs.y
31786 });
31787 }
31788
31789 }
31790
31791 if (stateMarkerGraphic) {
31792 stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY, chart.inverted) ? 'show' : 'hide'](); // #2450
31793 stateMarkerGraphic.element.point = point; // #4310
31794 }
31795 }
31796
31797 // Show me your halo
31798 haloOptions = stateOptions.halo;
31799 if (haloOptions && haloOptions.size) {
31800 if (!halo) {
31801 series.halo = halo = chart.renderer.path()
31802 // #5818, #5903, #6705
31803 .add((point.graphic || stateMarkerGraphic).parentGroup);
31804 }
31805 halo[move ? 'animate' : 'attr']({
31806 d: point.haloPath(haloOptions.size)
31807 });
31808 halo.attr({
31809 'class': 'highcharts-halo highcharts-color-' +
31810 pick(point.colorIndex, series.colorIndex)
31811 });
31812 halo.point = point; // #6055
31813
31814
31815
31816 } else if (halo && halo.point && halo.point.haloPath) {
31817 // Animate back to 0 on the current halo point (#6055)
31818 halo.animate({
31819 d: halo.point.haloPath(0)
31820 });
31821 }
31822
31823 point.state = state;
31824 },
31825
31826 /**
31827 * Get the path definition for the halo, which is usually a shadow-like
31828 * circle around the currently hovered point.
31829 * @param {Number} size
31830 * The radius of the circular halo.
31831 * @return {Array} The path definition
31832 */
31833 haloPath: function(size) {
31834 var series = this.series,
31835 chart = series.chart;
31836
31837 return chart.renderer.symbols.circle(
31838 Math.floor(this.plotX) - size,
31839 this.plotY - size,
31840 size * 2,
31841 size * 2
31842 );
31843 }
31844 });
31845
31846 /*
31847 * Extend the Series object with interaction
31848 */
31849
31850 extend(Series.prototype, /** @lends Highcharts.Series.prototype */ {
31851 /**
31852 * Runs on mouse over the series graphical items.
31853 */
31854 onMouseOver: function() {
31855 var series = this,
31856 chart = series.chart,
31857 hoverSeries = chart.hoverSeries;
31858
31859 // set normal state to previous series
31860 if (hoverSeries && hoverSeries !== series) {
31861 hoverSeries.onMouseOut();
31862 }
31863
31864 // trigger the event, but to save processing time,
31865 // only if defined
31866 if (series.options.events.mouseOver) {
31867 fireEvent(series, 'mouseOver');
31868 }
31869
31870 // hover this
31871 series.setState('hover');
31872 chart.hoverSeries = series;
31873 },
31874
31875 /**
31876 * Runs on mouse out of the series graphical items.
31877 */
31878 onMouseOut: function() {
31879 // trigger the event only if listeners exist
31880 var series = this,
31881 options = series.options,
31882 chart = series.chart,
31883 tooltip = chart.tooltip,
31884 hoverPoint = chart.hoverPoint;
31885
31886 chart.hoverSeries = null; // #182, set to null before the mouseOut event fires
31887
31888 // trigger mouse out on the point, which must be in this series
31889 if (hoverPoint) {
31890 hoverPoint.onMouseOut();
31891 }
31892
31893 // fire the mouse out event
31894 if (series && options.events.mouseOut) {
31895 fireEvent(series, 'mouseOut');
31896 }
31897
31898
31899 // hide the tooltip
31900 if (tooltip && !series.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) {
31901 tooltip.hide();
31902 }
31903
31904 // set normal state
31905 series.setState();
31906 },
31907
31908 /**
31909 * Set the state of the series. Called internally on mouse interaction and
31910 * select operations, but it can also be called directly to visually
31911 * highlight a series.
31912 *
31913 * @param {String} [state]
31914 * Can be either `hover`, `select` or undefined to set to normal
31915 * state.
31916 */
31917 setState: function(state) {
31918 var series = this,
31919 options = series.options,
31920 graph = series.graph,
31921 stateOptions = options.states,
31922 lineWidth = options.lineWidth,
31923 attribs,
31924 i = 0;
31925
31926 state = state || '';
31927
31928 if (series.state !== state) {
31929
31930 // Toggle class names
31931 each([
31932 series.group,
31933 series.markerGroup,
31934 series.dataLabelsGroup
31935 ], function(group) {
31936 if (group) {
31937 // Old state
31938 if (series.state) {
31939 group.removeClass('highcharts-series-' + series.state);
31940 }
31941 // New state
31942 if (state) {
31943 group.addClass('highcharts-series-' + state);
31944 }
31945 }
31946 });
31947
31948 series.state = state;
31949
31950
31951 }
31952 },
31953
31954 /**
31955 * Show or hide the series.
31956 *
31957 * @param {Boolean} [visible]
31958 * True to show the series, false to hide. If undefined, the
31959 * visibility is toggled.
31960 * @param {Boolean} [redraw=true]
31961 * Whether to redraw the chart after the series is altered. If doing
31962 * more operations on the chart, it is a good idea to set redraw to
31963 * false and call {@link Chart#redraw|chart.redraw()} after.
31964 */
31965 setVisible: function(vis, redraw) {
31966 var series = this,
31967 chart = series.chart,
31968 legendItem = series.legendItem,
31969 showOrHide,
31970 ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
31971 oldVisibility = series.visible;
31972
31973 // if called without an argument, toggle visibility
31974 series.visible = vis = series.options.visible = series.userOptions.visible = vis === undefined ? !oldVisibility : vis; // #5618
31975 showOrHide = vis ? 'show' : 'hide';
31976
31977 // show or hide elements
31978 each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker', 'tt'], function(key) {
31979 if (series[key]) {
31980 series[key][showOrHide]();
31981 }
31982 });
31983
31984
31985 // hide tooltip (#1361)
31986 if (chart.hoverSeries === series || (chart.hoverPoint && chart.hoverPoint.series) === series) {
31987 series.onMouseOut();
31988 }
31989
31990
31991 if (legendItem) {
31992 chart.legend.colorizeItem(series, vis);
31993 }
31994
31995
31996 // rescale or adapt to resized chart
31997 series.isDirty = true;
31998 // in a stack, all other series are affected
31999 if (series.options.stacking) {
32000 each(chart.series, function(otherSeries) {
32001 if (otherSeries.options.stacking && otherSeries.visible) {
32002 otherSeries.isDirty = true;
32003 }
32004 });
32005 }
32006
32007 // show or hide linked series
32008 each(series.linkedSeries, function(otherSeries) {
32009 otherSeries.setVisible(vis, false);
32010 });
32011
32012 if (ignoreHiddenSeries) {
32013 chart.isDirtyBox = true;
32014 }
32015 if (redraw !== false) {
32016 chart.redraw();
32017 }
32018
32019 fireEvent(series, showOrHide);
32020 },
32021
32022 /**
32023 * Show the series if hidden.
32024 *
32025 * @sample highcharts/members/series-hide/
32026 * Toggle visibility from a button
32027 */
32028 show: function() {
32029 this.setVisible(true);
32030 },
32031
32032 /**
32033 * Hide the series if visible. If the {@link
32034 * https://api.highcharts.com/highcharts/chart.ignoreHiddenSeries|
32035 * chart.ignoreHiddenSeries} option is true, the chart is redrawn without
32036 * this series.
32037 *
32038 * @sample highcharts/members/series-hide/
32039 * Toggle visibility from a button
32040 */
32041 hide: function() {
32042 this.setVisible(false);
32043 },
32044
32045
32046 /**
32047 * Select or unselect the series. This means its {@link
32048 * Highcharts.Series.selected|selected} property is set, the checkbox in the
32049 * legend is toggled and when selected, the series is returned by the
32050 * {@link Highcharts.Chart#getSelectedSeries} function.
32051 *
32052 * @param {Boolean} [selected]
32053 * True to select the series, false to unselect. If undefined, the
32054 * selection state is toggled.
32055 *
32056 * @sample highcharts/members/series-select/
32057 * Select a series from a button
32058 */
32059 select: function(selected) {
32060 var series = this;
32061
32062 series.selected = selected = (selected === undefined) ?
32063 !series.selected :
32064 selected;
32065
32066 if (series.checkbox) {
32067 series.checkbox.checked = selected;
32068 }
32069
32070 fireEvent(series, selected ? 'select' : 'unselect');
32071 },
32072
32073 drawTracker: TrackerMixin.drawTrackerGraph
32074 });
32075
32076 }(Highcharts));
32077 (function(H) {
32078 /**
32079 * (c) 2010-2017 Torstein Honsi
32080 *
32081 * License: www.highcharts.com/license
32082 */
32083 var Chart = H.Chart,
32084 each = H.each,
32085 inArray = H.inArray,
32086 isArray = H.isArray,
32087 isObject = H.isObject,
32088 pick = H.pick,
32089 splat = H.splat;
32090
32091
32092 /**
32093 * Allows setting a set of rules to apply for different screen or chart
32094 * sizes. Each rule specifies additional chart options.
32095 *
32096 * @sample {highstock} stock/demo/responsive/ Stock chart
32097 * @sample highcharts/responsive/axis/ Axis
32098 * @sample highcharts/responsive/legend/ Legend
32099 * @sample highcharts/responsive/classname/ Class name
32100 * @since 5.0.0
32101 * @apioption responsive
32102 */
32103
32104 /**
32105 * A set of rules for responsive settings. The rules are executed from
32106 * the top down.
32107 *
32108 * @type {Array<Object>}
32109 * @sample {highcharts} highcharts/responsive/axis/ Axis changes
32110 * @sample {highstock} highcharts/responsive/axis/ Axis changes
32111 * @sample {highmaps} highcharts/responsive/axis/ Axis changes
32112 * @since 5.0.0
32113 * @apioption responsive.rules
32114 */
32115
32116 /**
32117 * A full set of chart options to apply as overrides to the general
32118 * chart options. The chart options are applied when the given rule
32119 * is active.
32120 *
32121 * A special case is configuration objects that take arrays, for example
32122 * [xAxis](#xAxis), [yAxis](#yAxis) or [series](#series). For these
32123 * collections, an `id` option is used to map the new option set to
32124 * an existing object. If an existing object of the same id is not found,
32125 * the item of the same indexupdated. So for example, setting `chartOptions`
32126 * with two series items without an `id`, will cause the existing chart's
32127 * two series to be updated with respective options.
32128 *
32129 * @type {Object}
32130 * @sample {highstock} stock/demo/responsive/ Stock chart
32131 * @sample highcharts/responsive/axis/ Axis
32132 * @sample highcharts/responsive/legend/ Legend
32133 * @sample highcharts/responsive/classname/ Class name
32134 * @since 5.0.0
32135 * @apioption responsive.rules.chartOptions
32136 */
32137
32138 /**
32139 * Under which conditions the rule applies.
32140 *
32141 * @type {Object}
32142 * @since 5.0.0
32143 * @apioption responsive.rules.condition
32144 */
32145
32146 /**
32147 * A callback function to gain complete control on when the responsive
32148 * rule applies. Return `true` if it applies. This opens for checking
32149 * against other metrics than the chart size, or example the document
32150 * size or other elements.
32151 *
32152 * @type {Function}
32153 * @context Chart
32154 * @since 5.0.0
32155 * @apioption responsive.rules.condition.callback
32156 */
32157
32158 /**
32159 * The responsive rule applies if the chart height is less than this.
32160 *
32161 * @type {Number}
32162 * @since 5.0.0
32163 * @apioption responsive.rules.condition.maxHeight
32164 */
32165
32166 /**
32167 * The responsive rule applies if the chart width is less than this.
32168 *
32169 * @type {Number}
32170 * @sample highcharts/responsive/axis/ Max width is 500
32171 * @since 5.0.0
32172 * @apioption responsive.rules.condition.maxWidth
32173 */
32174
32175 /**
32176 * The responsive rule applies if the chart height is greater than this.
32177 *
32178 * @type {Number}
32179 * @default 0
32180 * @since 5.0.0
32181 * @apioption responsive.rules.condition.minHeight
32182 */
32183
32184 /**
32185 * The responsive rule applies if the chart width is greater than this.
32186 *
32187 * @type {Number}
32188 * @default 0
32189 * @since 5.0.0
32190 * @apioption responsive.rules.condition.minWidth
32191 */
32192
32193 /**
32194 * Update the chart based on the current chart/document size and options for
32195 * responsiveness.
32196 */
32197 Chart.prototype.setResponsive = function(redraw) {
32198 var options = this.options.responsive,
32199 ruleIds = [],
32200 currentResponsive = this.currentResponsive,
32201 currentRuleIds;
32202
32203 if (options && options.rules) {
32204 each(options.rules, function(rule) {
32205 if (rule._id === undefined) {
32206 rule._id = H.uniqueKey();
32207 }
32208
32209 this.matchResponsiveRule(rule, ruleIds, redraw);
32210 }, this);
32211 }
32212
32213 // Merge matching rules
32214 var mergedOptions = H.merge.apply(0, H.map(ruleIds, function(ruleId) {
32215 return H.find(options.rules, function(rule) {
32216 return rule._id === ruleId;
32217 }).chartOptions;
32218 }));
32219
32220 // Stringified key for the rules that currently apply.
32221 ruleIds = ruleIds.toString() || undefined;
32222 currentRuleIds = currentResponsive && currentResponsive.ruleIds;
32223
32224
32225 // Changes in what rules apply
32226 if (ruleIds !== currentRuleIds) {
32227
32228 // Undo previous rules. Before we apply a new set of rules, we need to
32229 // roll back completely to base options (#6291).
32230 if (currentResponsive) {
32231 this.update(currentResponsive.undoOptions, redraw);
32232 }
32233
32234 if (ruleIds) {
32235 // Get undo-options for matching rules
32236 this.currentResponsive = {
32237 ruleIds: ruleIds,
32238 mergedOptions: mergedOptions,
32239 undoOptions: this.currentOptions(mergedOptions)
32240 };
32241
32242 this.update(mergedOptions, redraw);
32243
32244 } else {
32245 this.currentResponsive = undefined;
32246 }
32247 }
32248 };
32249
32250 /**
32251 * Handle a single responsiveness rule
32252 */
32253 Chart.prototype.matchResponsiveRule = function(rule, matches) {
32254 var condition = rule.condition,
32255 fn = condition.callback || function() {
32256 return this.chartWidth <= pick(condition.maxWidth, Number.MAX_VALUE) &&
32257 this.chartHeight <= pick(condition.maxHeight, Number.MAX_VALUE) &&
32258 this.chartWidth >= pick(condition.minWidth, 0) &&
32259 this.chartHeight >= pick(condition.minHeight, 0);
32260 };
32261
32262 if (fn.call(this)) {
32263 matches.push(rule._id);
32264 }
32265
32266 };
32267
32268 /**
32269 * Get the current values for a given set of options. Used before we update
32270 * the chart with a new responsiveness rule.
32271 * TODO: Restore axis options (by id?)
32272 */
32273 Chart.prototype.currentOptions = function(options) {
32274
32275 var ret = {};
32276
32277 /**
32278 * Recurse over a set of options and its current values,
32279 * and store the current values in the ret object.
32280 */
32281 function getCurrent(options, curr, ret, depth) {
32282 var i;
32283 H.objectEach(options, function(val, key) {
32284 if (!depth && inArray(key, ['series', 'xAxis', 'yAxis']) > -1) {
32285 val = splat(val);
32286
32287 ret[key] = [];
32288
32289 // Iterate over collections like series, xAxis or yAxis and map
32290 // the items by index.
32291 for (i = 0; i < val.length; i++) {
32292 if (curr[key][i]) { // Item exists in current data (#6347)
32293 ret[key][i] = {};
32294 getCurrent(
32295 val[i],
32296 curr[key][i],
32297 ret[key][i],
32298 depth + 1
32299 );
32300 }
32301 }
32302 } else if (isObject(val)) {
32303 ret[key] = isArray(val) ? [] : {};
32304 getCurrent(val, curr[key] || {}, ret[key], depth + 1);
32305 } else {
32306 ret[key] = curr[key] || null;
32307 }
32308 });
32309 }
32310
32311 getCurrent(options, this.options, ret, 0);
32312 return ret;
32313 };
32314
32315 }(Highcharts));
32316 return Highcharts
32317}));