UNPKG

1.42 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 // Let the shadow follow the main element
2952 if (
2953 this.shadows &&
2954 /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/
2955 .test(key)
2956 ) {
2957 this.updateShadows(key, val, setter);
2958 }
2959
2960 }
2961 }, this);
2962
2963 this.afterSetters();
2964 }
2965
2966 // In accordance with animate, run a complete callback
2967 if (complete) {
2968 complete();
2969 }
2970
2971 return ret;
2972 },
2973
2974 /**
2975 * This method is executed in the end of `attr()`, after setting all
2976 * attributes in the hash. In can be used to efficiently consolidate
2977 * multiple attributes in one SVG property -- e.g., translate, rotate and
2978 * scale are merged in one "transform" attribute in the SVG node.
2979 *
2980 * @private
2981 */
2982 afterSetters: function() {
2983 // Update transform. Do this outside the loop to prevent redundant
2984 // updating for batch setting of attributes.
2985 if (this.doTransform) {
2986 this.updateTransform();
2987 this.doTransform = false;
2988 }
2989 },
2990
2991
2992 /**
2993 * Update the shadow elements with new attributes.
2994 *
2995 * @private
2996 * @param {String} key - The attribute name.
2997 * @param {String|Number} value - The value of the attribute.
2998 * @param {Function} setter - The setter function, inherited from the
2999 * parent wrapper
3000 *
3001 */
3002 updateShadows: function(key, value, setter) {
3003 var shadows = this.shadows,
3004 i = shadows.length;
3005
3006 while (i--) {
3007 setter.call(
3008 shadows[i],
3009 key === 'height' ?
3010 Math.max(value - (shadows[i].cutHeight || 0), 0) :
3011 key === 'd' ? this.d : value,
3012 key,
3013 shadows[i]
3014 );
3015 }
3016 },
3017
3018
3019 /**
3020 * Add a class name to an element.
3021 *
3022 * @param {string} className - The new class name to add.
3023 * @param {boolean} [replace=false] - When true, the existing class name(s)
3024 * will be overwritten with the new one. When false, the new one is
3025 * added.
3026 * @returns {SVGElement} Return the SVG element for chainability.
3027 */
3028 addClass: function(className, replace) {
3029 var currentClassName = this.attr('class') || '';
3030 if (currentClassName.indexOf(className) === -1) {
3031 if (!replace) {
3032 className =
3033 (currentClassName + (currentClassName ? ' ' : '') +
3034 className).replace(' ', ' ');
3035 }
3036 this.attr('class', className);
3037 }
3038
3039 return this;
3040 },
3041
3042 /**
3043 * Check if an element has the given class name.
3044 * @param {string} className
3045 * The class name to check for.
3046 * @return {Boolean}
3047 * Whether the class name is found.
3048 */
3049 hasClass: function(className) {
3050 return inArray(
3051 className,
3052 (this.attr('class') || '').split(' ')
3053 ) !== -1;
3054 },
3055
3056 /**
3057 * Remove a class name from the element.
3058 * @param {String|RegExp} className The class name to remove.
3059 * @return {SVGElement} Returns the SVG element for chainability.
3060 */
3061 removeClass: function(className) {
3062 return this.attr(
3063 'class',
3064 (this.attr('class') || '').replace(className, '')
3065 );
3066 },
3067
3068 /**
3069 * If one of the symbol size affecting parameters are changed,
3070 * check all the others only once for each call to an element's
3071 * .attr() method
3072 * @param {Object} hash - The attributes to set.
3073 * @private
3074 */
3075 symbolAttr: function(hash) {
3076 var wrapper = this;
3077
3078 each([
3079 'x',
3080 'y',
3081 'r',
3082 'start',
3083 'end',
3084 'width',
3085 'height',
3086 'innerR',
3087 'anchorX',
3088 'anchorY'
3089 ], function(key) {
3090 wrapper[key] = pick(hash[key], wrapper[key]);
3091 });
3092
3093 wrapper.attr({
3094 d: wrapper.renderer.symbols[wrapper.symbolName](
3095 wrapper.x,
3096 wrapper.y,
3097 wrapper.width,
3098 wrapper.height,
3099 wrapper
3100 )
3101 });
3102 },
3103
3104 /**
3105 * Apply a clipping rectangle to this element.
3106 *
3107 * @param {ClipRect} [clipRect] - The clipping rectangle. If skipped, the
3108 * current clip is removed.
3109 * @returns {SVGElement} Returns the SVG element to allow chaining.
3110 */
3111 clip: function(clipRect) {
3112 return this.attr(
3113 'clip-path',
3114 clipRect ?
3115 'url(' + this.renderer.url + '#' + clipRect.id + ')' :
3116 'none'
3117 );
3118 },
3119
3120 /**
3121 * Calculate the coordinates needed for drawing a rectangle crisply and
3122 * return the calculated attributes.
3123 *
3124 * @param {Object} rect - A rectangle.
3125 * @param {number} rect.x - The x position.
3126 * @param {number} rect.y - The y position.
3127 * @param {number} rect.width - The width.
3128 * @param {number} rect.height - The height.
3129 * @param {number} [strokeWidth] - The stroke width to consider when
3130 * computing crisp positioning. It can also be set directly on the rect
3131 * parameter.
3132 *
3133 * @returns {{x: Number, y: Number, width: Number, height: Number}} The
3134 * modified rectangle arguments.
3135 */
3136 crisp: function(rect, strokeWidth) {
3137
3138 var wrapper = this,
3139 attribs = {},
3140 normalizer;
3141
3142 strokeWidth = strokeWidth || rect.strokeWidth || 0;
3143 // Math.round because strokeWidth can sometimes have roundoff errors
3144 normalizer = Math.round(strokeWidth) % 2 / 2;
3145
3146 // normalize for crisp edges
3147 rect.x = Math.floor(rect.x || wrapper.x || 0) + normalizer;
3148 rect.y = Math.floor(rect.y || wrapper.y || 0) + normalizer;
3149 rect.width = Math.floor(
3150 (rect.width || wrapper.width || 0) - 2 * normalizer
3151 );
3152 rect.height = Math.floor(
3153 (rect.height || wrapper.height || 0) - 2 * normalizer
3154 );
3155 if (defined(rect.strokeWidth)) {
3156 rect.strokeWidth = strokeWidth;
3157 }
3158
3159 objectEach(rect, function(val, key) {
3160 if (wrapper[key] !== val) { // only set attribute if changed
3161 wrapper[key] = attribs[key] = val;
3162 }
3163 });
3164
3165 return attribs;
3166 },
3167
3168 /**
3169 * Set styles for the element. In addition to CSS styles supported by
3170 * native SVG and HTML elements, there are also some custom made for
3171 * Highcharts, like `width`, `ellipsis` and `textOverflow` for SVG text
3172 * elements.
3173 * @param {CSSObject} styles The new CSS styles.
3174 * @returns {SVGElement} Return the SVG element for chaining.
3175 *
3176 * @sample highcharts/members/renderer-text-on-chart/
3177 * Styled text
3178 */
3179 css: function(styles) {
3180 var oldStyles = this.styles,
3181 newStyles = {},
3182 elem = this.element,
3183 textWidth,
3184 serializedCss = '',
3185 hyphenate,
3186 hasNew = !oldStyles,
3187 // These CSS properties are interpreted internally by the SVG
3188 // renderer, but are not supported by SVG and should not be added to
3189 // the DOM. In styled mode, no CSS should find its way to the DOM
3190 // whatsoever (#6173, #6474).
3191 svgPseudoProps = ['textOutline', 'textOverflow', 'width'];
3192
3193 // convert legacy
3194 if (styles && styles.color) {
3195 styles.fill = styles.color;
3196 }
3197
3198 // Filter out existing styles to increase performance (#2640)
3199 if (oldStyles) {
3200 objectEach(styles, function(style, n) {
3201 if (style !== oldStyles[n]) {
3202 newStyles[n] = style;
3203 hasNew = true;
3204 }
3205 });
3206 }
3207 if (hasNew) {
3208
3209 // Merge the new styles with the old ones
3210 if (oldStyles) {
3211 styles = extend(
3212 oldStyles,
3213 newStyles
3214 );
3215 }
3216
3217 // Get the text width from style
3218 textWidth = this.textWidth = (
3219 styles &&
3220 styles.width &&
3221 styles.width !== 'auto' &&
3222 elem.nodeName.toLowerCase() === 'text' &&
3223 pInt(styles.width)
3224 );
3225
3226 // store object
3227 this.styles = styles;
3228
3229 if (textWidth && (!svg && this.renderer.forExport)) {
3230 delete styles.width;
3231 }
3232
3233 // serialize and set style attribute
3234 if (isMS && !svg) {
3235 css(this.element, styles);
3236 } else {
3237 hyphenate = function(a, b) {
3238 return '-' + b.toLowerCase();
3239 };
3240 objectEach(styles, function(style, n) {
3241 if (inArray(n, svgPseudoProps) === -1) {
3242 serializedCss +=
3243 n.replace(/([A-Z])/g, hyphenate) + ':' +
3244 style + ';';
3245 }
3246 });
3247 if (serializedCss) {
3248 attr(elem, 'style', serializedCss); // #1881
3249 }
3250 }
3251
3252
3253 if (this.added) {
3254
3255 // Rebuild text after added. Cache mechanisms in the buildText
3256 // will prevent building if there are no significant changes.
3257 if (this.element.nodeName === 'text') {
3258 this.renderer.buildText(this);
3259 }
3260
3261 // Apply text outline after added
3262 if (styles && styles.textOutline) {
3263 this.applyTextOutline(styles.textOutline);
3264 }
3265 }
3266 }
3267
3268 return this;
3269 },
3270
3271
3272 /**
3273 * Get the current stroke width. In classic mode, the setter registers it
3274 * directly on the element.
3275 * @returns {number} The stroke width in pixels.
3276 * @ignore
3277 */
3278 strokeWidth: function() {
3279 return this['stroke-width'] || 0;
3280 },
3281
3282
3283 /**
3284 * Add an event listener. This is a simple setter that replaces all other
3285 * events of the same type, opposed to the {@link Highcharts#addEvent}
3286 * function.
3287 * @param {string} eventType - The event type. If the type is `click`,
3288 * Highcharts will internally translate it to a `touchstart` event on
3289 * touch devices, to prevent the browser from waiting for a click event
3290 * from firing.
3291 * @param {Function} handler - The handler callback.
3292 * @returns {SVGElement} The SVGElement for chaining.
3293 *
3294 * @sample highcharts/members/element-on/
3295 * A clickable rectangle
3296 */
3297 on: function(eventType, handler) {
3298 var svgElement = this,
3299 element = svgElement.element;
3300
3301 // touch
3302 if (hasTouch && eventType === 'click') {
3303 element.ontouchstart = function(e) {
3304 svgElement.touchEventFired = Date.now(); // #2269
3305 e.preventDefault();
3306 handler.call(element, e);
3307 };
3308 element.onclick = function(e) {
3309 if (win.navigator.userAgent.indexOf('Android') === -1 ||
3310 Date.now() - (svgElement.touchEventFired || 0) > 1100) {
3311 handler.call(element, e);
3312 }
3313 };
3314 } else {
3315 // simplest possible event model for internal use
3316 element['on' + eventType] = handler;
3317 }
3318 return this;
3319 },
3320
3321 /**
3322 * Set the coordinates needed to draw a consistent radial gradient across
3323 * a shape regardless of positioning inside the chart. Used on pie slices
3324 * to make all the slices have the same radial reference point.
3325 *
3326 * @param {Array} coordinates The center reference. The format is
3327 * `[centerX, centerY, diameter]` in pixels.
3328 * @returns {SVGElement} Returns the SVGElement for chaining.
3329 */
3330 setRadialReference: function(coordinates) {
3331 var existingGradient = this.renderer.gradients[this.element.gradient];
3332
3333 this.element.radialReference = coordinates;
3334
3335 // On redrawing objects with an existing gradient, the gradient needs
3336 // to be repositioned (#3801)
3337 if (existingGradient && existingGradient.radAttr) {
3338 existingGradient.animate(
3339 this.renderer.getRadialAttr(
3340 coordinates,
3341 existingGradient.radAttr
3342 )
3343 );
3344 }
3345
3346 return this;
3347 },
3348
3349 /**
3350 * Move an object and its children by x and y values.
3351 *
3352 * @param {number} x - The x value.
3353 * @param {number} y - The y value.
3354 */
3355 translate: function(x, y) {
3356 return this.attr({
3357 translateX: x,
3358 translateY: y
3359 });
3360 },
3361
3362 /**
3363 * Invert a group, rotate and flip. This is used internally on inverted
3364 * charts, where the points and graphs are drawn as if not inverted, then
3365 * the series group elements are inverted.
3366 *
3367 * @param {boolean} inverted
3368 * Whether to invert or not. An inverted shape can be un-inverted by
3369 * setting it to false.
3370 * @return {SVGElement}
3371 * Return the SVGElement for chaining.
3372 */
3373 invert: function(inverted) {
3374 var wrapper = this;
3375 wrapper.inverted = inverted;
3376 wrapper.updateTransform();
3377 return wrapper;
3378 },
3379
3380 /**
3381 * Update the transform attribute based on internal properties. Deals with
3382 * the custom `translateX`, `translateY`, `rotation`, `scaleX` and `scaleY`
3383 * attributes and updates the SVG `transform` attribute.
3384 * @private
3385 *
3386 */
3387 updateTransform: function() {
3388 var wrapper = this,
3389 translateX = wrapper.translateX || 0,
3390 translateY = wrapper.translateY || 0,
3391 scaleX = wrapper.scaleX,
3392 scaleY = wrapper.scaleY,
3393 inverted = wrapper.inverted,
3394 rotation = wrapper.rotation,
3395 matrix = wrapper.matrix,
3396 element = wrapper.element,
3397 transform;
3398
3399 // Flipping affects translate as adjustment for flipping around the
3400 // group's axis
3401 if (inverted) {
3402 translateX += wrapper.width;
3403 translateY += wrapper.height;
3404 }
3405
3406 // Apply translate. Nearly all transformed elements have translation,
3407 // so instead of checking for translate = 0, do it always (#1767,
3408 // #1846).
3409 transform = ['translate(' + translateX + ',' + translateY + ')'];
3410
3411 // apply matrix
3412 if (defined(matrix)) {
3413 transform.push(
3414 'matrix(' + matrix.join(',') + ')'
3415 );
3416 }
3417
3418 // apply rotation
3419 if (inverted) {
3420 transform.push('rotate(90) scale(-1,1)');
3421 } else if (rotation) { // text rotation
3422 transform.push(
3423 'rotate(' + rotation + ' ' +
3424 pick(this.rotationOriginX, element.getAttribute('x'), 0) +
3425 ' ' +
3426 pick(this.rotationOriginY, element.getAttribute('y') || 0) + ')'
3427 );
3428 }
3429
3430 // apply scale
3431 if (defined(scaleX) || defined(scaleY)) {
3432 transform.push(
3433 'scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')'
3434 );
3435 }
3436
3437 if (transform.length) {
3438 element.setAttribute('transform', transform.join(' '));
3439 }
3440 },
3441
3442 /**
3443 * Bring the element to the front. Alternatively, a new zIndex can be set.
3444 *
3445 * @returns {SVGElement} Returns the SVGElement for chaining.
3446 *
3447 * @sample highcharts/members/element-tofront/
3448 * Click an element to bring it to front
3449 */
3450 toFront: function() {
3451 var element = this.element;
3452 element.parentNode.appendChild(element);
3453 return this;
3454 },
3455
3456
3457 /**
3458 * Align the element relative to the chart or another box.
3459 *
3460 * @param {Object} [alignOptions] The alignment options. The function can be
3461 * called without this parameter in order to re-align an element after the
3462 * box has been updated.
3463 * @param {string} [alignOptions.align=left] Horizontal alignment. Can be
3464 * one of `left`, `center` and `right`.
3465 * @param {string} [alignOptions.verticalAlign=top] Vertical alignment. Can
3466 * be one of `top`, `middle` and `bottom`.
3467 * @param {number} [alignOptions.x=0] Horizontal pixel offset from
3468 * alignment.
3469 * @param {number} [alignOptions.y=0] Vertical pixel offset from alignment.
3470 * @param {Boolean} [alignByTranslate=false] Use the `transform` attribute
3471 * with translateX and translateY custom attributes to align this elements
3472 * rather than `x` and `y` attributes.
3473 * @param {String|Object} box The box to align to, needs a width and height.
3474 * When the box is a string, it refers to an object in the Renderer. For
3475 * example, when box is `spacingBox`, it refers to `Renderer.spacingBox`
3476 * which holds `width`, `height`, `x` and `y` properties.
3477 * @returns {SVGElement} Returns the SVGElement for chaining.
3478 */
3479 align: function(alignOptions, alignByTranslate, box) {
3480 var align,
3481 vAlign,
3482 x,
3483 y,
3484 attribs = {},
3485 alignTo,
3486 renderer = this.renderer,
3487 alignedObjects = renderer.alignedObjects,
3488 alignFactor,
3489 vAlignFactor;
3490
3491 // First call on instanciate
3492 if (alignOptions) {
3493 this.alignOptions = alignOptions;
3494 this.alignByTranslate = alignByTranslate;
3495 if (!box || isString(box)) {
3496 this.alignTo = alignTo = box || 'renderer';
3497 // prevent duplicates, like legendGroup after resize
3498 erase(alignedObjects, this);
3499 alignedObjects.push(this);
3500 box = null; // reassign it below
3501 }
3502
3503 // When called on resize, no arguments are supplied
3504 } else {
3505 alignOptions = this.alignOptions;
3506 alignByTranslate = this.alignByTranslate;
3507 alignTo = this.alignTo;
3508 }
3509
3510 box = pick(box, renderer[alignTo], renderer);
3511
3512 // Assign variables
3513 align = alignOptions.align;
3514 vAlign = alignOptions.verticalAlign;
3515 x = (box.x || 0) + (alignOptions.x || 0); // default: left align
3516 y = (box.y || 0) + (alignOptions.y || 0); // default: top align
3517
3518 // Align
3519 if (align === 'right') {
3520 alignFactor = 1;
3521 } else if (align === 'center') {
3522 alignFactor = 2;
3523 }
3524 if (alignFactor) {
3525 x += (box.width - (alignOptions.width || 0)) / alignFactor;
3526 }
3527 attribs[alignByTranslate ? 'translateX' : 'x'] = Math.round(x);
3528
3529
3530 // Vertical align
3531 if (vAlign === 'bottom') {
3532 vAlignFactor = 1;
3533 } else if (vAlign === 'middle') {
3534 vAlignFactor = 2;
3535 }
3536 if (vAlignFactor) {
3537 y += (box.height - (alignOptions.height || 0)) / vAlignFactor;
3538 }
3539 attribs[alignByTranslate ? 'translateY' : 'y'] = Math.round(y);
3540
3541 // Animate only if already placed
3542 this[this.placed ? 'animate' : 'attr'](attribs);
3543 this.placed = true;
3544 this.alignAttr = attribs;
3545
3546 return this;
3547 },
3548
3549 /**
3550 * Get the bounding box (width, height, x and y) for the element. Generally
3551 * used to get rendered text size. Since this is called a lot in charts,
3552 * the results are cached based on text properties, in order to save DOM
3553 * traffic. The returned bounding box includes the rotation, so for example
3554 * a single text line of rotation 90 will report a greater height, and a
3555 * width corresponding to the line-height.
3556 *
3557 * @param {boolean} [reload] Skip the cache and get the updated DOM bouding
3558 * box.
3559 * @param {number} [rot] Override the element's rotation. This is internally
3560 * used on axis labels with a value of 0 to find out what the bounding box
3561 * would be have been if it were not rotated.
3562 * @returns {Object} The bounding box with `x`, `y`, `width` and `height`
3563 * properties.
3564 *
3565 * @sample highcharts/members/renderer-on-chart/
3566 * Draw a rectangle based on a text's bounding box
3567 */
3568 getBBox: function(reload, rot) {
3569 var wrapper = this,
3570 bBox, // = wrapper.bBox,
3571 renderer = wrapper.renderer,
3572 width,
3573 height,
3574 rotation,
3575 rad,
3576 element = wrapper.element,
3577 styles = wrapper.styles,
3578 fontSize,
3579 textStr = wrapper.textStr,
3580 toggleTextShadowShim,
3581 cache = renderer.cache,
3582 cacheKeys = renderer.cacheKeys,
3583 cacheKey;
3584
3585 rotation = pick(rot, wrapper.rotation);
3586 rad = rotation * deg2rad;
3587
3588
3589 fontSize = styles && styles.fontSize;
3590
3591
3592 // Avoid undefined and null (#7316)
3593 if (defined(textStr)) {
3594
3595 cacheKey = textStr.toString();
3596
3597 // Since numbers are monospaced, and numerical labels appear a lot
3598 // in a chart, we assume that a label of n characters has the same
3599 // bounding box as others of the same length. Unless there is inner
3600 // HTML in the label. In that case, leave the numbers as is (#5899).
3601 if (cacheKey.indexOf('<') === -1) {
3602 cacheKey = cacheKey.replace(/[0-9]/g, '0');
3603 }
3604
3605 // Properties that affect bounding box
3606 cacheKey += [
3607 '',
3608 rotation || 0,
3609 fontSize,
3610 styles && styles.width,
3611 styles && styles.textOverflow // #5968
3612 ]
3613 .join(',');
3614
3615 }
3616
3617 if (cacheKey && !reload) {
3618 bBox = cache[cacheKey];
3619 }
3620
3621 // No cache found
3622 if (!bBox) {
3623
3624 // SVG elements
3625 if (element.namespaceURI === wrapper.SVG_NS || renderer.forExport) {
3626 try { // Fails in Firefox if the container has display: none.
3627
3628 // When the text shadow shim is used, we need to hide the
3629 // fake shadows to get the correct bounding box (#3872)
3630 toggleTextShadowShim = this.fakeTS && function(display) {
3631 each(
3632 element.querySelectorAll(
3633 '.highcharts-text-outline'
3634 ),
3635 function(tspan) {
3636 tspan.style.display = display;
3637 }
3638 );
3639 };
3640
3641 // Workaround for #3842, Firefox reporting wrong bounding
3642 // box for shadows
3643 if (toggleTextShadowShim) {
3644 toggleTextShadowShim('none');
3645 }
3646
3647 bBox = element.getBBox ?
3648 // SVG: use extend because IE9 is not allowed to change
3649 // width and height in case of rotation (below)
3650 extend({}, element.getBBox()) : {
3651
3652 // Legacy IE in export mode
3653 width: element.offsetWidth,
3654 height: element.offsetHeight
3655 };
3656
3657 // #3842
3658 if (toggleTextShadowShim) {
3659 toggleTextShadowShim('');
3660 }
3661 } catch (e) {}
3662
3663 // If the bBox is not set, the try-catch block above failed. The
3664 // other condition is for Opera that returns a width of
3665 // -Infinity on hidden elements.
3666 if (!bBox || bBox.width < 0) {
3667 bBox = {
3668 width: 0,
3669 height: 0
3670 };
3671 }
3672
3673
3674 // VML Renderer or useHTML within SVG
3675 } else {
3676
3677 bBox = wrapper.htmlGetBBox();
3678
3679 }
3680
3681 // True SVG elements as well as HTML elements in modern browsers
3682 // using the .useHTML option need to compensated for rotation
3683 if (renderer.isSVG) {
3684 width = bBox.width;
3685 height = bBox.height;
3686
3687 // Workaround for wrong bounding box in IE, Edge and Chrome on
3688 // Windows. With Highcharts' default font, IE and Edge report
3689 // a box height of 16.899 and Chrome rounds it to 17. If this
3690 // stands uncorrected, it results in more padding added below
3691 // the text than above when adding a label border or background.
3692 // Also vertical positioning is affected.
3693 // http://jsfiddle.net/highcharts/em37nvuj/
3694 // (#1101, #1505, #1669, #2568, #6213).
3695 if (
3696 styles &&
3697 styles.fontSize === '11px' &&
3698 Math.round(height) === 17
3699 ) {
3700 bBox.height = height = 14;
3701 }
3702
3703 // Adjust for rotated text
3704 if (rotation) {
3705 bBox.width = Math.abs(height * Math.sin(rad)) +
3706 Math.abs(width * Math.cos(rad));
3707 bBox.height = Math.abs(height * Math.cos(rad)) +
3708 Math.abs(width * Math.sin(rad));
3709 }
3710 }
3711
3712 // Cache it. When loading a chart in a hidden iframe in Firefox and
3713 // IE/Edge, the bounding box height is 0, so don't cache it (#5620).
3714 if (cacheKey && bBox.height > 0) {
3715
3716 // Rotate (#4681)
3717 while (cacheKeys.length > 250) {
3718 delete cache[cacheKeys.shift()];
3719 }
3720
3721 if (!cache[cacheKey]) {
3722 cacheKeys.push(cacheKey);
3723 }
3724 cache[cacheKey] = bBox;
3725 }
3726 }
3727 return bBox;
3728 },
3729
3730 /**
3731 * Show the element after it has been hidden.
3732 *
3733 * @param {boolean} [inherit=false] Set the visibility attribute to
3734 * `inherit` rather than `visible`. The difference is that an element with
3735 * `visibility="visible"` will be visible even if the parent is hidden.
3736 *
3737 * @returns {SVGElement} Returns the SVGElement for chaining.
3738 */
3739 show: function(inherit) {
3740 return this.attr({
3741 visibility: inherit ? 'inherit' : 'visible'
3742 });
3743 },
3744
3745 /**
3746 * Hide the element, equivalent to setting the `visibility` attribute to
3747 * `hidden`.
3748 *
3749 * @returns {SVGElement} Returns the SVGElement for chaining.
3750 */
3751 hide: function() {
3752 return this.attr({
3753 visibility: 'hidden'
3754 });
3755 },
3756
3757 /**
3758 * Fade out an element by animating its opacity down to 0, and hide it on
3759 * complete. Used internally for the tooltip.
3760 *
3761 * @param {number} [duration=150] The fade duration in milliseconds.
3762 */
3763 fadeOut: function(duration) {
3764 var elemWrapper = this;
3765 elemWrapper.animate({
3766 opacity: 0
3767 }, {
3768 duration: duration || 150,
3769 complete: function() {
3770 // #3088, assuming we're only using this for tooltips
3771 elemWrapper.attr({
3772 y: -9999
3773 });
3774 }
3775 });
3776 },
3777
3778 /**
3779 * Add the element to the DOM. All elements must be added this way.
3780 *
3781 * @param {SVGElement|SVGDOMElement} [parent] The parent item to add it to.
3782 * If undefined, the element is added to the {@link
3783 * Highcharts.SVGRenderer.box}.
3784 *
3785 * @returns {SVGElement} Returns the SVGElement for chaining.
3786 *
3787 * @sample highcharts/members/renderer-g - Elements added to a group
3788 */
3789 add: function(parent) {
3790
3791 var renderer = this.renderer,
3792 element = this.element,
3793 inserted;
3794
3795 if (parent) {
3796 this.parentGroup = parent;
3797 }
3798
3799 // mark as inverted
3800 this.parentInverted = parent && parent.inverted;
3801
3802 // build formatted text
3803 if (this.textStr !== undefined) {
3804 renderer.buildText(this);
3805 }
3806
3807 // Mark as added
3808 this.added = true;
3809
3810 // If we're adding to renderer root, or other elements in the group
3811 // have a z index, we need to handle it
3812 if (!parent || parent.handleZ || this.zIndex) {
3813 inserted = this.zIndexSetter();
3814 }
3815
3816 // If zIndex is not handled, append at the end
3817 if (!inserted) {
3818 (parent ? parent.element : renderer.box).appendChild(element);
3819 }
3820
3821 // fire an event for internal hooks
3822 if (this.onAdd) {
3823 this.onAdd();
3824 }
3825
3826 return this;
3827 },
3828
3829 /**
3830 * Removes an element from the DOM.
3831 *
3832 * @private
3833 * @param {SVGDOMElement|HTMLDOMElement} element The DOM node to remove.
3834 */
3835 safeRemoveChild: function(element) {
3836 var parentNode = element.parentNode;
3837 if (parentNode) {
3838 parentNode.removeChild(element);
3839 }
3840 },
3841
3842 /**
3843 * Destroy the element and element wrapper and clear up the DOM and event
3844 * hooks.
3845 *
3846 *
3847 */
3848 destroy: function() {
3849 var wrapper = this,
3850 element = wrapper.element || {},
3851 parentToClean =
3852 wrapper.renderer.isSVG &&
3853 element.nodeName === 'SPAN' &&
3854 wrapper.parentGroup,
3855 grandParent,
3856 ownerSVGElement = element.ownerSVGElement,
3857 i;
3858
3859 // remove events
3860 element.onclick = element.onmouseout = element.onmouseover =
3861 element.onmousemove = element.point = null;
3862 stop(wrapper); // stop running animations
3863
3864 if (wrapper.clipPath && ownerSVGElement) {
3865 // Look for existing references to this clipPath and remove them
3866 // before destroying the element (#6196).
3867 each(
3868 // The upper case version is for Edge
3869 ownerSVGElement.querySelectorAll('[clip-path],[CLIP-PATH]'),
3870 function(el) {
3871 // Include the closing paranthesis in the test to rule out
3872 // id's from 10 and above (#6550)
3873 if (el
3874 .getAttribute('clip-path')
3875 .match(RegExp(
3876 // Edge puts quotes inside the url, others not
3877 '[\("]#' + wrapper.clipPath.element.id + '[\)"]'
3878 ))
3879 ) {
3880 el.removeAttribute('clip-path');
3881 }
3882 }
3883 );
3884 wrapper.clipPath = wrapper.clipPath.destroy();
3885 }
3886
3887 // Destroy stops in case this is a gradient object
3888 if (wrapper.stops) {
3889 for (i = 0; i < wrapper.stops.length; i++) {
3890 wrapper.stops[i] = wrapper.stops[i].destroy();
3891 }
3892 wrapper.stops = null;
3893 }
3894
3895 // remove element
3896 wrapper.safeRemoveChild(element);
3897
3898
3899 wrapper.destroyShadows();
3900
3901
3902 // In case of useHTML, clean up empty containers emulating SVG groups
3903 // (#1960, #2393, #2697).
3904 while (
3905 parentToClean &&
3906 parentToClean.div &&
3907 parentToClean.div.childNodes.length === 0
3908 ) {
3909 grandParent = parentToClean.parentGroup;
3910 wrapper.safeRemoveChild(parentToClean.div);
3911 delete parentToClean.div;
3912 parentToClean = grandParent;
3913 }
3914
3915 // remove from alignObjects
3916 if (wrapper.alignTo) {
3917 erase(wrapper.renderer.alignedObjects, wrapper);
3918 }
3919
3920 objectEach(wrapper, function(val, key) {
3921 delete wrapper[key];
3922 });
3923
3924 return null;
3925 },
3926
3927
3928 /**
3929 * @typedef {Object} ShadowOptions
3930 * @property {string} [color=#000000] The shadow color.
3931 * @property {number} [offsetX=1] The horizontal offset from the element.
3932 * @property {number} [offsetY=1] The vertical offset from the element.
3933 * @property {number} [opacity=0.15] The shadow opacity.
3934 * @property {number} [width=3] The shadow width or distance from the
3935 * element.
3936 */
3937 /**
3938 * Add a shadow to the element. Must be called after the element is added to
3939 * the DOM. In styled mode, this method is not used, instead use `defs` and
3940 * filters.
3941 *
3942 * @param {boolean|ShadowOptions} shadowOptions The shadow options. If
3943 * `true`, the default options are applied. If `false`, the current
3944 * shadow will be removed.
3945 * @param {SVGElement} [group] The SVG group element where the shadows will
3946 * be applied. The default is to add it to the same parent as the current
3947 * element. Internally, this is ised for pie slices, where all the
3948 * shadows are added to an element behind all the slices.
3949 * @param {boolean} [cutOff] Used internally for column shadows.
3950 *
3951 * @returns {SVGElement} Returns the SVGElement for chaining.
3952 *
3953 * @example
3954 * renderer.rect(10, 100, 100, 100)
3955 * .attr({ fill: 'red' })
3956 * .shadow(true);
3957 */
3958 shadow: function(shadowOptions, group, cutOff) {
3959 var shadows = [],
3960 i,
3961 shadow,
3962 element = this.element,
3963 strokeWidth,
3964 shadowWidth,
3965 shadowElementOpacity,
3966
3967 // compensate for inverted plot area
3968 transform;
3969
3970 if (!shadowOptions) {
3971 this.destroyShadows();
3972
3973 } else if (!this.shadows) {
3974 shadowWidth = pick(shadowOptions.width, 3);
3975 shadowElementOpacity = (shadowOptions.opacity || 0.15) /
3976 shadowWidth;
3977 transform = this.parentInverted ?
3978 '(-1,-1)' :
3979 '(' + pick(shadowOptions.offsetX, 1) + ', ' +
3980 pick(shadowOptions.offsetY, 1) + ')';
3981 for (i = 1; i <= shadowWidth; i++) {
3982 shadow = element.cloneNode(0);
3983 strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
3984 attr(shadow, {
3985 'isShadow': 'true',
3986 'stroke': shadowOptions.color || '#000000',
3987 'stroke-opacity': shadowElementOpacity * i,
3988 'stroke-width': strokeWidth,
3989 'transform': 'translate' + transform,
3990 'fill': 'none'
3991 });
3992 if (cutOff) {
3993 attr(
3994 shadow,
3995 'height',
3996 Math.max(attr(shadow, 'height') - strokeWidth, 0)
3997 );
3998 shadow.cutHeight = strokeWidth;
3999 }
4000
4001 if (group) {
4002 group.element.appendChild(shadow);
4003 } else if (element.parentNode) {
4004 element.parentNode.insertBefore(shadow, element);
4005 }
4006
4007 shadows.push(shadow);
4008 }
4009
4010 this.shadows = shadows;
4011 }
4012 return this;
4013
4014 },
4015
4016 /**
4017 * Destroy shadows on the element.
4018 * @private
4019 */
4020 destroyShadows: function() {
4021 each(this.shadows || [], function(shadow) {
4022 this.safeRemoveChild(shadow);
4023 }, this);
4024 this.shadows = undefined;
4025 },
4026
4027
4028
4029 xGetter: function(key) {
4030 if (this.element.nodeName === 'circle') {
4031 if (key === 'x') {
4032 key = 'cx';
4033 } else if (key === 'y') {
4034 key = 'cy';
4035 }
4036 }
4037 return this._defaultGetter(key);
4038 },
4039
4040 /**
4041 * Get the current value of an attribute or pseudo attribute, used mainly
4042 * for animation. Called internally from the {@link
4043 * Highcharts.SVGRenderer#attr}
4044 * function.
4045 *
4046 * @private
4047 */
4048 _defaultGetter: function(key) {
4049 var ret = pick(
4050 this[key],
4051 this.element ? this.element.getAttribute(key) : null,
4052 0
4053 );
4054
4055 if (/^[\-0-9\.]+$/.test(ret)) { // is numerical
4056 ret = parseFloat(ret);
4057 }
4058 return ret;
4059 },
4060
4061
4062 dSetter: function(value, key, element) {
4063 if (value && value.join) { // join path
4064 value = value.join(' ');
4065 }
4066 if (/(NaN| {2}|^$)/.test(value)) {
4067 value = 'M 0 0';
4068 }
4069
4070 // Check for cache before resetting. Resetting causes disturbance in the
4071 // DOM, causing flickering in some cases in Edge/IE (#6747). Also
4072 // possible performance gain.
4073 if (this[key] !== value) {
4074 element.setAttribute(key, value);
4075 this[key] = value;
4076 }
4077
4078 },
4079
4080 dashstyleSetter: function(value) {
4081 var i,
4082 strokeWidth = this['stroke-width'];
4083
4084 // If "inherit", like maps in IE, assume 1 (#4981). With HC5 and the new
4085 // strokeWidth function, we should be able to use that instead.
4086 if (strokeWidth === 'inherit') {
4087 strokeWidth = 1;
4088 }
4089 value = value && value.toLowerCase();
4090 if (value) {
4091 value = value
4092 .replace('shortdashdotdot', '3,1,1,1,1,1,')
4093 .replace('shortdashdot', '3,1,1,1')
4094 .replace('shortdot', '1,1,')
4095 .replace('shortdash', '3,1,')
4096 .replace('longdash', '8,3,')
4097 .replace(/dot/g, '1,3,')
4098 .replace('dash', '4,3,')
4099 .replace(/,$/, '')
4100 .split(','); // ending comma
4101
4102 i = value.length;
4103 while (i--) {
4104 value[i] = pInt(value[i]) * strokeWidth;
4105 }
4106 value = value.join(',')
4107 .replace(/NaN/g, 'none'); // #3226
4108 this.element.setAttribute('stroke-dasharray', value);
4109 }
4110 },
4111
4112 alignSetter: function(value) {
4113 var convert = {
4114 left: 'start',
4115 center: 'middle',
4116 right: 'end'
4117 };
4118 this.element.setAttribute('text-anchor', convert[value]);
4119 },
4120 opacitySetter: function(value, key, element) {
4121 this[key] = value;
4122 element.setAttribute(key, value);
4123 },
4124 titleSetter: function(value) {
4125 var titleNode = this.element.getElementsByTagName('title')[0];
4126 if (!titleNode) {
4127 titleNode = doc.createElementNS(this.SVG_NS, 'title');
4128 this.element.appendChild(titleNode);
4129 }
4130
4131 // Remove text content if it exists
4132 if (titleNode.firstChild) {
4133 titleNode.removeChild(titleNode.firstChild);
4134 }
4135
4136 titleNode.appendChild(
4137 doc.createTextNode(
4138 // #3276, #3895
4139 (String(pick(value), '')).replace(/<[^>]*>/g, '')
4140 )
4141 );
4142 },
4143 textSetter: function(value) {
4144 if (value !== this.textStr) {
4145 // Delete bBox memo when the text changes
4146 delete this.bBox;
4147
4148 this.textStr = value;
4149 if (this.added) {
4150 this.renderer.buildText(this);
4151 }
4152 }
4153 },
4154 fillSetter: function(value, key, element) {
4155 if (typeof value === 'string') {
4156 element.setAttribute(key, value);
4157 } else if (value) {
4158 this.colorGradient(value, key, element);
4159 }
4160 },
4161 visibilitySetter: function(value, key, element) {
4162 // IE9-11 doesn't handle visibilty:inherit well, so we remove the
4163 // attribute instead (#2881, #3909)
4164 if (value === 'inherit') {
4165 element.removeAttribute(key);
4166 } else if (this[key] !== value) { // #6747
4167 element.setAttribute(key, value);
4168 }
4169 this[key] = value;
4170 },
4171 zIndexSetter: function(value, key) {
4172 var renderer = this.renderer,
4173 parentGroup = this.parentGroup,
4174 parentWrapper = parentGroup || renderer,
4175 parentNode = parentWrapper.element || renderer.box,
4176 childNodes,
4177 otherElement,
4178 otherZIndex,
4179 element = this.element,
4180 inserted,
4181 undefinedOtherZIndex,
4182 svgParent = parentNode === renderer.box,
4183 run = this.added,
4184 i;
4185
4186 if (defined(value)) {
4187 // So we can read it for other elements in the group
4188 element.zIndex = value;
4189
4190 value = +value;
4191 if (this[key] === value) { // Only update when needed (#3865)
4192 run = false;
4193 }
4194 this[key] = value;
4195 }
4196
4197 // Insert according to this and other elements' zIndex. Before .add() is
4198 // called, nothing is done. Then on add, or by later calls to
4199 // zIndexSetter, the node is placed on the right place in the DOM.
4200 if (run) {
4201 value = this.zIndex;
4202
4203 if (value && parentGroup) {
4204 parentGroup.handleZ = true;
4205 }
4206
4207 childNodes = parentNode.childNodes;
4208 for (i = childNodes.length - 1; i >= 0 && !inserted; i--) {
4209 otherElement = childNodes[i];
4210 otherZIndex = otherElement.zIndex;
4211 undefinedOtherZIndex = !defined(otherZIndex);
4212
4213 if (otherElement !== element) {
4214 if (
4215 // Negative zIndex versus no zIndex:
4216 // On all levels except the highest. If the parent is <svg>,
4217 // then we don't want to put items before <desc> or <defs>
4218 (value < 0 && undefinedOtherZIndex && !svgParent && !i)
4219 ) {
4220 parentNode.insertBefore(element, childNodes[i]);
4221 inserted = true;
4222 } else if (
4223 // Insert after the first element with a lower zIndex
4224 pInt(otherZIndex) <= value ||
4225 // If negative zIndex, add this before first undefined zIndex element
4226 (undefinedOtherZIndex && (!defined(value) || value >= 0))
4227 ) {
4228 parentNode.insertBefore(
4229 element,
4230 childNodes[i + 1] || null // null for oldIE export
4231 );
4232 inserted = true;
4233 }
4234 }
4235 }
4236
4237 if (!inserted) {
4238 parentNode.insertBefore(
4239 element,
4240 childNodes[svgParent ? 3 : 0] || null // null for oldIE
4241 );
4242 inserted = true;
4243 }
4244 }
4245 return inserted;
4246 },
4247 _defaultSetter: function(value, key, element) {
4248 element.setAttribute(key, value);
4249 }
4250 });
4251
4252 // Some shared setters and getters
4253 SVGElement.prototype.yGetter =
4254 SVGElement.prototype.xGetter;
4255 SVGElement.prototype.translateXSetter =
4256 SVGElement.prototype.translateYSetter =
4257 SVGElement.prototype.rotationSetter =
4258 SVGElement.prototype.verticalAlignSetter =
4259 SVGElement.prototype.rotationOriginXSetter =
4260 SVGElement.prototype.rotationOriginYSetter =
4261 SVGElement.prototype.scaleXSetter =
4262 SVGElement.prototype.scaleYSetter =
4263 SVGElement.prototype.matrixSetter = function(value, key) {
4264 this[key] = value;
4265 this.doTransform = true;
4266 };
4267
4268
4269 // WebKit and Batik have problems with a stroke-width of zero, so in this case
4270 // we remove the stroke attribute altogether. #1270, #1369, #3065, #3072.
4271 SVGElement.prototype['stroke-widthSetter'] =
4272 SVGElement.prototype.strokeSetter = function(value, key, element) {
4273 this[key] = value;
4274 // Only apply the stroke attribute if the stroke width is defined and larger
4275 // than 0
4276 if (this.stroke && this['stroke-width']) {
4277 // Use prototype as instance may be overridden
4278 SVGElement.prototype.fillSetter.call(
4279 this,
4280 this.stroke,
4281 'stroke',
4282 element
4283 );
4284
4285 element.setAttribute('stroke-width', this['stroke-width']);
4286 this.hasStroke = true;
4287 } else if (key === 'stroke-width' && value === 0 && this.hasStroke) {
4288 element.removeAttribute('stroke');
4289 this.hasStroke = false;
4290 }
4291 };
4292
4293
4294 /**
4295 * Allows direct access to the Highcharts rendering layer in order to draw
4296 * primitive shapes like circles, rectangles, paths or text directly on a chart,
4297 * or independent from any chart. The SVGRenderer represents a wrapper object
4298 * for SVG in modern browsers. Through the VMLRenderer, part of the `oldie.js`
4299 * module, it also brings vector graphics to IE <= 8.
4300 *
4301 * An existing chart's renderer can be accessed through {@link Chart.renderer}.
4302 * The renderer can also be used completely decoupled from a chart.
4303 *
4304 * @param {HTMLDOMElement} container - Where to put the SVG in the web page.
4305 * @param {number} width - The width of the SVG.
4306 * @param {number} height - The height of the SVG.
4307 * @param {boolean} [forExport=false] - Whether the rendered content is intended
4308 * for export.
4309 * @param {boolean} [allowHTML=true] - Whether the renderer is allowed to
4310 * include HTML text, which will be projected on top of the SVG.
4311 *
4312 * @example
4313 * // Use directly without a chart object.
4314 * var renderer = new Highcharts.Renderer(parentNode, 600, 400);
4315 *
4316 * @sample highcharts/members/renderer-on-chart
4317 * Annotating a chart programmatically.
4318 * @sample highcharts/members/renderer-basic
4319 * Independent SVG drawing.
4320 *
4321 * @class Highcharts.SVGRenderer
4322 */
4323 SVGRenderer = H.SVGRenderer = function() {
4324 this.init.apply(this, arguments);
4325 };
4326 extend(SVGRenderer.prototype, /** @lends Highcharts.SVGRenderer.prototype */ {
4327 /**
4328 * A pointer to the renderer's associated Element class. The VMLRenderer
4329 * will have a pointer to VMLElement here.
4330 * @type {SVGElement}
4331 */
4332 Element: SVGElement,
4333 SVG_NS: SVG_NS,
4334 /**
4335 * Initialize the SVGRenderer. Overridable initiator function that takes
4336 * the same parameters as the constructor.
4337 */
4338 init: function(container, width, height, style, forExport, allowHTML) {
4339 var renderer = this,
4340 boxWrapper,
4341 element,
4342 desc;
4343
4344 boxWrapper = renderer.createElement('svg')
4345 .attr({
4346 'version': '1.1',
4347 'class': 'highcharts-root'
4348 })
4349
4350 .css(this.getStyle(style));
4351 element = boxWrapper.element;
4352 container.appendChild(element);
4353
4354 // Always use ltr on the container, otherwise text-anchor will be
4355 // flipped and text appear outside labels, buttons, tooltip etc (#3482)
4356 attr(container, 'dir', 'ltr');
4357
4358 // For browsers other than IE, add the namespace attribute (#1978)
4359 if (container.innerHTML.indexOf('xmlns') === -1) {
4360 attr(element, 'xmlns', this.SVG_NS);
4361 }
4362
4363 // object properties
4364 renderer.isSVG = true;
4365
4366 /**
4367 * The root `svg` node of the renderer.
4368 * @name box
4369 * @memberOf SVGRenderer
4370 * @type {SVGDOMElement}
4371 */
4372 this.box = element;
4373 /**
4374 * The wrapper for the root `svg` node of the renderer.
4375 *
4376 * @name boxWrapper
4377 * @memberOf SVGRenderer
4378 * @type {SVGElement}
4379 */
4380 this.boxWrapper = boxWrapper;
4381 renderer.alignedObjects = [];
4382
4383 /**
4384 * Page url used for internal references.
4385 * @type {string}
4386 */
4387 // #24, #672, #1070
4388 this.url = (
4389 (isFirefox || isWebKit) &&
4390 doc.getElementsByTagName('base').length
4391 ) ?
4392 win.location.href
4393 .replace(/#.*?$/, '') // remove the hash
4394 .replace(/<[^>]*>/g, '') // wing cut HTML
4395 // escape parantheses and quotes
4396 .replace(/([\('\)])/g, '\\$1')
4397 // replace spaces (needed for Safari only)
4398 .replace(/ /g, '%20') :
4399 '';
4400
4401 // Add description
4402 desc = this.createElement('desc').add();
4403 desc.element.appendChild(
4404 doc.createTextNode('Created with Highcharts 6.0.3')
4405 );
4406
4407 /**
4408 * A pointer to the `defs` node of the root SVG.
4409 * @type {SVGElement}
4410 * @name defs
4411 * @memberOf SVGRenderer
4412 */
4413 renderer.defs = this.createElement('defs').add();
4414 renderer.allowHTML = allowHTML;
4415 renderer.forExport = forExport;
4416 renderer.gradients = {}; // Object where gradient SvgElements are stored
4417 renderer.cache = {}; // Cache for numerical bounding boxes
4418 renderer.cacheKeys = [];
4419 renderer.imgCount = 0;
4420
4421 renderer.setSize(width, height, false);
4422
4423
4424
4425 // Issue 110 workaround:
4426 // In Firefox, if a div is positioned by percentage, its pixel position
4427 // may land between pixels. The container itself doesn't display this,
4428 // but an SVG element inside this container will be drawn at subpixel
4429 // precision. In order to draw sharp lines, this must be compensated
4430 // for. This doesn't seem to work inside iframes though (like in
4431 // jsFiddle).
4432 var subPixelFix, rect;
4433 if (isFirefox && container.getBoundingClientRect) {
4434 subPixelFix = function() {
4435 css(container, {
4436 left: 0,
4437 top: 0
4438 });
4439 rect = container.getBoundingClientRect();
4440 css(container, {
4441 left: (Math.ceil(rect.left) - rect.left) + 'px',
4442 top: (Math.ceil(rect.top) - rect.top) + 'px'
4443 });
4444 };
4445
4446 // run the fix now
4447 subPixelFix();
4448
4449 // run it on resize
4450 renderer.unSubPixelFix = addEvent(win, 'resize', subPixelFix);
4451 }
4452 },
4453
4454
4455
4456 /**
4457 * Get the global style setting for the renderer.
4458 * @private
4459 * @param {CSSObject} style - Style settings.
4460 * @return {CSSObject} The style settings mixed with defaults.
4461 */
4462 getStyle: function(style) {
4463 this.style = extend({
4464
4465 fontFamily: '"Lucida Grande", "Lucida Sans Unicode", ' +
4466 'Arial, Helvetica, sans-serif',
4467 fontSize: '12px'
4468
4469 }, style);
4470 return this.style;
4471 },
4472 /**
4473 * Apply the global style on the renderer, mixed with the default styles.
4474 *
4475 * @param {CSSObject} style - CSS to apply.
4476 */
4477 setStyle: function(style) {
4478 this.boxWrapper.css(this.getStyle(style));
4479 },
4480
4481
4482 /**
4483 * Detect whether the renderer is hidden. This happens when one of the
4484 * parent elements has `display: none`. Used internally to detect when we
4485 * needto render preliminarily in another div to get the text bounding boxes
4486 * right.
4487 *
4488 * @returns {boolean} True if it is hidden.
4489 */
4490 isHidden: function() { // #608
4491 return !this.boxWrapper.getBBox().width;
4492 },
4493
4494 /**
4495 * Destroys the renderer and its allocated members.
4496 */
4497 destroy: function() {
4498 var renderer = this,
4499 rendererDefs = renderer.defs;
4500 renderer.box = null;
4501 renderer.boxWrapper = renderer.boxWrapper.destroy();
4502
4503 // Call destroy on all gradient elements
4504 destroyObjectProperties(renderer.gradients || {});
4505 renderer.gradients = null;
4506
4507 // Defs are null in VMLRenderer
4508 // Otherwise, destroy them here.
4509 if (rendererDefs) {
4510 renderer.defs = rendererDefs.destroy();
4511 }
4512
4513 // Remove sub pixel fix handler (#982)
4514 if (renderer.unSubPixelFix) {
4515 renderer.unSubPixelFix();
4516 }
4517
4518 renderer.alignedObjects = null;
4519
4520 return null;
4521 },
4522
4523 /**
4524 * Create a wrapper for an SVG element. Serves as a factory for
4525 * {@link SVGElement}, but this function is itself mostly called from
4526 * primitive factories like {@link SVGRenderer#path}, {@link
4527 * SVGRenderer#rect} or {@link SVGRenderer#text}.
4528 *
4529 * @param {string} nodeName - The node name, for example `rect`, `g` etc.
4530 * @returns {SVGElement} The generated SVGElement.
4531 */
4532 createElement: function(nodeName) {
4533 var wrapper = new this.Element();
4534 wrapper.init(this, nodeName);
4535 return wrapper;
4536 },
4537
4538 /**
4539 * Dummy function for plugins, called every time the renderer is updated.
4540 * Prior to Highcharts 5, this was used for the canvg renderer.
4541 * @function
4542 */
4543 draw: noop,
4544
4545 /**
4546 * Get converted radial gradient attributes according to the radial
4547 * reference. Used internally from the {@link SVGElement#colorGradient}
4548 * function.
4549 *
4550 * @private
4551 */
4552 getRadialAttr: function(radialReference, gradAttr) {
4553 return {
4554 cx: (radialReference[0] - radialReference[2] / 2) +
4555 gradAttr.cx * radialReference[2],
4556 cy: (radialReference[1] - radialReference[2] / 2) +
4557 gradAttr.cy * radialReference[2],
4558 r: gradAttr.r * radialReference[2]
4559 };
4560 },
4561
4562 getSpanWidth: function(wrapper, tspan) {
4563 var renderer = this,
4564 bBox = wrapper.getBBox(true),
4565 actualWidth = bBox.width;
4566
4567 // Old IE cannot measure the actualWidth for SVG elements (#2314)
4568 if (!svg && renderer.forExport) {
4569 actualWidth = renderer.measureSpanWidth(
4570 tspan.firstChild.data,
4571 wrapper.styles
4572 );
4573 }
4574 return actualWidth;
4575 },
4576
4577 applyEllipsis: function(wrapper, tspan, text, width) {
4578 var renderer = this,
4579 rotation = wrapper.rotation,
4580 str = text,
4581 currentIndex,
4582 minIndex = 0,
4583 maxIndex = text.length,
4584 updateTSpan = function(s) {
4585 tspan.removeChild(tspan.firstChild);
4586 if (s) {
4587 tspan.appendChild(doc.createTextNode(s));
4588 }
4589 },
4590 actualWidth,
4591 wasTooLong;
4592 wrapper.rotation = 0; // discard rotation when computing box
4593 actualWidth = renderer.getSpanWidth(wrapper, tspan);
4594 wasTooLong = actualWidth > width;
4595 if (wasTooLong) {
4596 while (minIndex <= maxIndex) {
4597 currentIndex = Math.ceil((minIndex + maxIndex) / 2);
4598 str = text.substring(0, currentIndex) + '\u2026';
4599 updateTSpan(str);
4600 actualWidth = renderer.getSpanWidth(wrapper, tspan);
4601 if (minIndex === maxIndex) {
4602 // Complete
4603 minIndex = maxIndex + 1;
4604 } else if (actualWidth > width) {
4605 // Too large. Set max index to current.
4606 maxIndex = currentIndex - 1;
4607 } else {
4608 // Within width. Set min index to current.
4609 minIndex = currentIndex;
4610 }
4611 }
4612 // If max index was 0 it means just ellipsis was also to large.
4613 if (maxIndex === 0) {
4614 // Remove ellipses.
4615 updateTSpan('');
4616 }
4617 }
4618 wrapper.rotation = rotation; // Apply rotation again.
4619 return wasTooLong;
4620 },
4621
4622 /**
4623 * A collection of characters mapped to HTML entities. When `useHTML` on an
4624 * element is true, these entities will be rendered correctly by HTML. In
4625 * the SVG pseudo-HTML, they need to be unescaped back to simple characters,
4626 * so for example `&lt;` will render as `<`.
4627 *
4628 * @example
4629 * // Add support for unescaping quotes
4630 * Highcharts.SVGRenderer.prototype.escapes['"'] = '&quot;';
4631 *
4632 * @type {Object}
4633 */
4634 escapes: {
4635 '&': '&amp;',
4636 '<': '&lt;',
4637 '>': '&gt;',
4638 "'": '&#39;', // eslint-disable-line quotes
4639 '"': '&quot'
4640 },
4641
4642 /**
4643 * Parse a simple HTML string into SVG tspans. Called internally when text
4644 * is set on an SVGElement. The function supports a subset of HTML tags,
4645 * CSS text features like `width`, `text-overflow`, `white-space`, and
4646 * also attributes like `href` and `style`.
4647 * @private
4648 * @param {SVGElement} wrapper The parent SVGElement.
4649 */
4650 buildText: function(wrapper) {
4651 var textNode = wrapper.element,
4652 renderer = this,
4653 forExport = renderer.forExport,
4654 textStr = pick(wrapper.textStr, '').toString(),
4655 hasMarkup = textStr.indexOf('<') !== -1,
4656 lines,
4657 childNodes = textNode.childNodes,
4658 clsRegex,
4659 styleRegex,
4660 hrefRegex,
4661 wasTooLong,
4662 parentX = attr(textNode, 'x'),
4663 textStyles = wrapper.styles,
4664 width = wrapper.textWidth,
4665 textLineHeight = textStyles && textStyles.lineHeight,
4666 textOutline = textStyles && textStyles.textOutline,
4667 ellipsis = textStyles && textStyles.textOverflow === 'ellipsis',
4668 noWrap = textStyles && textStyles.whiteSpace === 'nowrap',
4669 fontSize = textStyles && textStyles.fontSize,
4670 textCache,
4671 isSubsequentLine,
4672 i = childNodes.length,
4673 tempParent = width && !wrapper.added && this.box,
4674 getLineHeight = function(tspan) {
4675 var fontSizeStyle;
4676
4677 fontSizeStyle = /(px|em)$/.test(tspan && tspan.style.fontSize) ?
4678 tspan.style.fontSize :
4679 (fontSize || renderer.style.fontSize || 12);
4680
4681
4682 return textLineHeight ?
4683 pInt(textLineHeight) :
4684 renderer.fontMetrics(
4685 fontSizeStyle,
4686 // Get the computed size from parent if not explicit
4687 tspan.getAttribute('style') ? tspan : textNode
4688 ).h;
4689 },
4690 unescapeEntities = function(inputStr) {
4691 objectEach(renderer.escapes, function(value, key) {
4692 inputStr = inputStr.replace(
4693 new RegExp(value, 'g'),
4694 key
4695 );
4696 });
4697 return inputStr;
4698 };
4699
4700 // The buildText code is quite heavy, so if we're not changing something
4701 // that affects the text, skip it (#6113).
4702 textCache = [
4703 textStr,
4704 ellipsis,
4705 noWrap,
4706 textLineHeight,
4707 textOutline,
4708 fontSize,
4709 width
4710 ].join(',');
4711 if (textCache === wrapper.textCache) {
4712 return;
4713 }
4714 wrapper.textCache = textCache;
4715
4716 // Remove old text
4717 while (i--) {
4718 textNode.removeChild(childNodes[i]);
4719 }
4720
4721 // Skip tspans, add text directly to text node. The forceTSpan is a hook
4722 // used in text outline hack.
4723 if (!hasMarkup &&
4724 !textOutline &&
4725 !ellipsis &&
4726 !width &&
4727 textStr.indexOf(' ') === -1
4728 ) {
4729 textNode.appendChild(doc.createTextNode(unescapeEntities(textStr)));
4730
4731 // Complex strings, add more logic
4732 } else {
4733
4734 clsRegex = /<.*class="([^"]+)".*>/;
4735 styleRegex = /<.*style="([^"]+)".*>/;
4736 hrefRegex = /<.*href="([^"]+)".*>/;
4737
4738 if (tempParent) {
4739 // attach it to the DOM to read offset width
4740 tempParent.appendChild(textNode);
4741 }
4742
4743 if (hasMarkup) {
4744 lines = textStr
4745
4746 .replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
4747 .replace(/<(i|em)>/g, '<span style="font-style:italic">')
4748
4749 .replace(/<a/g, '<span')
4750 .replace(/<\/(b|strong|i|em|a)>/g, '</span>')
4751 .split(/<br.*?>/g);
4752
4753 } else {
4754 lines = [textStr];
4755 }
4756
4757
4758 // Trim empty lines (#5261)
4759 lines = grep(lines, function(line) {
4760 return line !== '';
4761 });
4762
4763
4764 // build the lines
4765 each(lines, function buildTextLines(line, lineNo) {
4766 var spans,
4767 spanNo = 0;
4768 line = line
4769 // Trim to prevent useless/costly process on the spaces
4770 // (#5258)
4771 .replace(/^\s+|\s+$/g, '')
4772 .replace(/<span/g, '|||<span')
4773 .replace(/<\/span>/g, '</span>|||');
4774 spans = line.split('|||');
4775
4776 each(spans, function buildTextSpans(span) {
4777 if (span !== '' || spans.length === 1) {
4778 var attributes = {},
4779 tspan = doc.createElementNS(
4780 renderer.SVG_NS,
4781 'tspan'
4782 ),
4783 spanCls,
4784 spanStyle; // #390
4785 if (clsRegex.test(span)) {
4786 spanCls = span.match(clsRegex)[1];
4787 attr(tspan, 'class', spanCls);
4788 }
4789 if (styleRegex.test(span)) {
4790 spanStyle = span.match(styleRegex)[1].replace(
4791 /(;| |^)color([ :])/,
4792 '$1fill$2'
4793 );
4794 attr(tspan, 'style', spanStyle);
4795 }
4796
4797 // Not for export - #1529
4798 if (hrefRegex.test(span) && !forExport) {
4799 attr(
4800 tspan,
4801 'onclick',
4802 'location.href=\"' +
4803 span.match(hrefRegex)[1] + '\"'
4804 );
4805 attr(tspan, 'class', 'highcharts-anchor');
4806
4807 css(tspan, {
4808 cursor: 'pointer'
4809 });
4810
4811 }
4812
4813 // Strip away unsupported HTML tags (#7126)
4814 span = unescapeEntities(
4815 span.replace(/<[a-zA-Z\/](.|\n)*?>/g, '') || ' '
4816 );
4817
4818 // Nested tags aren't supported, and cause crash in
4819 // Safari (#1596)
4820 if (span !== ' ') {
4821
4822 // add the text node
4823 tspan.appendChild(doc.createTextNode(span));
4824
4825 // First span in a line, align it to the left
4826 if (!spanNo) {
4827 if (lineNo && parentX !== null) {
4828 attributes.x = parentX;
4829 }
4830 } else {
4831 attributes.dx = 0; // #16
4832 }
4833
4834 // add attributes
4835 attr(tspan, attributes);
4836
4837 // Append it
4838 textNode.appendChild(tspan);
4839
4840 // first span on subsequent line, add the line
4841 // height
4842 if (!spanNo && isSubsequentLine) {
4843
4844 // allow getting the right offset height in
4845 // exporting in IE
4846 if (!svg && forExport) {
4847 css(tspan, {
4848 display: 'block'
4849 });
4850 }
4851
4852 // Set the line height based on the font size of
4853 // either the text element or the tspan element
4854 attr(
4855 tspan,
4856 'dy',
4857 getLineHeight(tspan)
4858 );
4859 }
4860
4861 /*
4862 if (width) {
4863 renderer.breakText(wrapper, width);
4864 }
4865 */
4866
4867 // Check width and apply soft breaks or ellipsis
4868 if (width) {
4869 var words = span.replace(
4870 /([^\^])-/g,
4871 '$1- '
4872 ).split(' '), // #1273
4873 hasWhiteSpace = (
4874 spans.length > 1 ||
4875 lineNo ||
4876 (words.length > 1 && !noWrap)
4877 ),
4878 tooLong,
4879 rest = [],
4880 actualWidth,
4881 dy = getLineHeight(tspan),
4882 rotation = wrapper.rotation;
4883
4884 if (ellipsis) {
4885 wasTooLong = renderer.applyEllipsis(
4886 wrapper,
4887 tspan,
4888 span,
4889 width
4890 );
4891 }
4892
4893 while (!ellipsis &&
4894 hasWhiteSpace &&
4895 (words.length || rest.length)
4896 ) {
4897 // discard rotation when computing box
4898 wrapper.rotation = 0;
4899 actualWidth = renderer.getSpanWidth(
4900 wrapper,
4901 tspan
4902 );
4903 tooLong = actualWidth > width;
4904
4905 // For ellipsis, do a binary search for the
4906 // correct string length
4907 if (wasTooLong === undefined) {
4908 wasTooLong = tooLong; // First time
4909 }
4910
4911 // Looping down, this is the first word
4912 // sequence that is not too long, so we can
4913 // move on to build the next line.
4914 if (!tooLong || words.length === 1) {
4915 words = rest;
4916 rest = [];
4917
4918 if (words.length && !noWrap) {
4919 tspan = doc.createElementNS(
4920 SVG_NS,
4921 'tspan'
4922 );
4923 attr(tspan, {
4924 dy: dy,
4925 x: parentX
4926 });
4927 if (spanStyle) { // #390
4928 attr(tspan, 'style', spanStyle);
4929 }
4930 textNode.appendChild(tspan);
4931 }
4932
4933 // a single word is pressing it out
4934 if (actualWidth > width) {
4935 width = actualWidth;
4936 }
4937 } else { // append to existing line tspan
4938 tspan.removeChild(tspan.firstChild);
4939 rest.unshift(words.pop());
4940 }
4941 if (words.length) {
4942 tspan.appendChild(
4943 doc.createTextNode(
4944 words.join(' ')
4945 .replace(/- /g, '-')
4946 )
4947 );
4948 }
4949 }
4950 wrapper.rotation = rotation;
4951 }
4952
4953 spanNo++;
4954 }
4955 }
4956 });
4957 // To avoid beginning lines that doesn't add to the textNode
4958 // (#6144)
4959 isSubsequentLine = (
4960 isSubsequentLine ||
4961 textNode.childNodes.length
4962 );
4963 });
4964
4965 if (wasTooLong) {
4966 wrapper.attr('title', wrapper.textStr);
4967 }
4968 if (tempParent) {
4969 tempParent.removeChild(textNode);
4970 }
4971
4972 // Apply the text outline
4973 if (textOutline && wrapper.applyTextOutline) {
4974 wrapper.applyTextOutline(textOutline);
4975 }
4976 }
4977 },
4978
4979
4980
4981 /*
4982 breakText: function (wrapper, width) {
4983 var bBox = wrapper.getBBox(),
4984 node = wrapper.element,
4985 textLength = node.textContent.length,
4986 // try this position first, based on average character width
4987 pos = Math.round(width * textLength / bBox.width),
4988 increment = 0,
4989 finalPos;
4990
4991 if (bBox.width > width) {
4992 while (finalPos === undefined) {
4993 textLength = node.getSubStringLength(0, pos);
4994
4995 if (textLength <= width) {
4996 if (increment === -1) {
4997 finalPos = pos;
4998 } else {
4999 increment = 1;
5000 }
5001 } else {
5002 if (increment === 1) {
5003 finalPos = pos - 1;
5004 } else {
5005 increment = -1;
5006 }
5007 }
5008 pos += increment;
5009 }
5010 }
5011 console.log(
5012 'width',
5013 width,
5014 'stringWidth',
5015 node.getSubStringLength(0, finalPos)
5016 )
5017 },
5018 */
5019
5020 /**
5021 * Returns white for dark colors and black for bright colors.
5022 *
5023 * @param {ColorString} rgba - The color to get the contrast for.
5024 * @returns {string} The contrast color, either `#000000` or `#FFFFFF`.
5025 */
5026 getContrast: function(rgba) {
5027 rgba = color(rgba).rgba;
5028
5029 // The threshold may be discussed. Here's a proposal for adding
5030 // different weight to the color channels (#6216)
5031 /*
5032 rgba[0] *= 1; // red
5033 rgba[1] *= 1.2; // green
5034 rgba[2] *= 0.7; // blue
5035 */
5036
5037 return rgba[0] + rgba[1] + rgba[2] > 2 * 255 ? '#000000' : '#FFFFFF';
5038 },
5039
5040 /**
5041 * Create a button with preset states.
5042 * @param {string} text - The text or HTML to draw.
5043 * @param {number} x - The x position of the button's left side.
5044 * @param {number} y - The y position of the button's top side.
5045 * @param {Function} callback - The function to execute on button click or
5046 * touch.
5047 * @param {SVGAttributes} [normalState] - SVG attributes for the normal
5048 * state.
5049 * @param {SVGAttributes} [hoverState] - SVG attributes for the hover state.
5050 * @param {SVGAttributes} [pressedState] - SVG attributes for the pressed
5051 * state.
5052 * @param {SVGAttributes} [disabledState] - SVG attributes for the disabled
5053 * state.
5054 * @param {Symbol} [shape=rect] - The shape type.
5055 * @returns {SVGRenderer} The button element.
5056 */
5057 button: function(
5058 text,
5059 x,
5060 y,
5061 callback,
5062 normalState,
5063 hoverState,
5064 pressedState,
5065 disabledState,
5066 shape
5067 ) {
5068 var label = this.label(
5069 text,
5070 x,
5071 y,
5072 shape,
5073 null,
5074 null,
5075 null,
5076 null,
5077 'button'
5078 ),
5079 curState = 0;
5080
5081 // Default, non-stylable attributes
5082 label.attr(merge({
5083 'padding': 8,
5084 'r': 2
5085 }, normalState));
5086
5087
5088 // Presentational
5089 var normalStyle,
5090 hoverStyle,
5091 pressedStyle,
5092 disabledStyle;
5093
5094 // Normal state - prepare the attributes
5095 normalState = merge({
5096 fill: '#f7f7f7',
5097 stroke: '#cccccc',
5098 'stroke-width': 1,
5099 style: {
5100 color: '#333333',
5101 cursor: 'pointer',
5102 fontWeight: 'normal'
5103 }
5104 }, normalState);
5105 normalStyle = normalState.style;
5106 delete normalState.style;
5107
5108 // Hover state
5109 hoverState = merge(normalState, {
5110 fill: '#e6e6e6'
5111 }, hoverState);
5112 hoverStyle = hoverState.style;
5113 delete hoverState.style;
5114
5115 // Pressed state
5116 pressedState = merge(normalState, {
5117 fill: '#e6ebf5',
5118 style: {
5119 color: '#000000',
5120 fontWeight: 'bold'
5121 }
5122 }, pressedState);
5123 pressedStyle = pressedState.style;
5124 delete pressedState.style;
5125
5126 // Disabled state
5127 disabledState = merge(normalState, {
5128 style: {
5129 color: '#cccccc'
5130 }
5131 }, disabledState);
5132 disabledStyle = disabledState.style;
5133 delete disabledState.style;
5134
5135
5136 // Add the events. IE9 and IE10 need mouseover and mouseout to funciton
5137 // (#667).
5138 addEvent(label.element, isMS ? 'mouseover' : 'mouseenter', function() {
5139 if (curState !== 3) {
5140 label.setState(1);
5141 }
5142 });
5143 addEvent(label.element, isMS ? 'mouseout' : 'mouseleave', function() {
5144 if (curState !== 3) {
5145 label.setState(curState);
5146 }
5147 });
5148
5149 label.setState = function(state) {
5150 // Hover state is temporary, don't record it
5151 if (state !== 1) {
5152 label.state = curState = state;
5153 }
5154 // Update visuals
5155 label.removeClass(
5156 /highcharts-button-(normal|hover|pressed|disabled)/
5157 )
5158 .addClass(
5159 'highcharts-button-' + ['normal', 'hover', 'pressed', 'disabled'][state || 0]
5160 );
5161
5162
5163 label.attr([
5164 normalState,
5165 hoverState,
5166 pressedState,
5167 disabledState
5168 ][state || 0])
5169 .css([
5170 normalStyle,
5171 hoverStyle,
5172 pressedStyle,
5173 disabledStyle
5174 ][state || 0]);
5175
5176 };
5177
5178
5179
5180 // Presentational attributes
5181 label
5182 .attr(normalState)
5183 .css(extend({
5184 cursor: 'default'
5185 }, normalStyle));
5186
5187
5188 return label
5189 .on('click', function(e) {
5190 if (curState !== 3) {
5191 callback.call(label, e);
5192 }
5193 });
5194 },
5195
5196 /**
5197 * Make a straight line crisper by not spilling out to neighbour pixels.
5198 *
5199 * @param {Array} points - The original points on the format `['M', 0, 0,
5200 * 'L', 100, 0]`.
5201 * @param {number} width - The width of the line.
5202 * @returns {Array} The original points array, but modified to render
5203 * crisply.
5204 */
5205 crispLine: function(points, width) {
5206 // normalize to a crisp line
5207 if (points[1] === points[4]) {
5208 // Substract due to #1129. Now bottom and left axis gridlines behave
5209 // the same.
5210 points[1] = points[4] = Math.round(points[1]) - (width % 2 / 2);
5211 }
5212 if (points[2] === points[5]) {
5213 points[2] = points[5] = Math.round(points[2]) + (width % 2 / 2);
5214 }
5215 return points;
5216 },
5217
5218
5219 /**
5220 * Draw a path, wraps the SVG `path` element.
5221 *
5222 * @param {Array} [path] An SVG path definition in array form.
5223 *
5224 * @example
5225 * var path = renderer.path(['M', 10, 10, 'L', 30, 30, 'z'])
5226 * .attr({ stroke: '#ff00ff' })
5227 * .add();
5228 * @returns {SVGElement} The generated wrapper element.
5229 *
5230 * @sample highcharts/members/renderer-path-on-chart/
5231 * Draw a path in a chart
5232 * @sample highcharts/members/renderer-path/
5233 * Draw a path independent from a chart
5234 *
5235 */
5236 /**
5237 * Draw a path, wraps the SVG `path` element.
5238 *
5239 * @param {SVGAttributes} [attribs] The initial attributes.
5240 * @returns {SVGElement} The generated wrapper element.
5241 */
5242 path: function(path) {
5243 var attribs = {
5244
5245 fill: 'none'
5246
5247 };
5248 if (isArray(path)) {
5249 attribs.d = path;
5250 } else if (isObject(path)) { // attributes
5251 extend(attribs, path);
5252 }
5253 return this.createElement('path').attr(attribs);
5254 },
5255
5256 /**
5257 * Draw a circle, wraps the SVG `circle` element.
5258 *
5259 * @param {number} [x] The center x position.
5260 * @param {number} [y] The center y position.
5261 * @param {number} [r] The radius.
5262 * @returns {SVGElement} The generated wrapper element.
5263 *
5264 * @sample highcharts/members/renderer-circle/ Drawing a circle
5265 */
5266 /**
5267 * Draw a circle, wraps the SVG `circle` element.
5268 *
5269 * @param {SVGAttributes} [attribs] The initial attributes.
5270 * @returns {SVGElement} The generated wrapper element.
5271 */
5272 circle: function(x, y, r) {
5273 var attribs = isObject(x) ? x : {
5274 x: x,
5275 y: y,
5276 r: r
5277 },
5278 wrapper = this.createElement('circle');
5279
5280 // Setting x or y translates to cx and cy
5281 wrapper.xSetter = wrapper.ySetter = function(value, key, element) {
5282 element.setAttribute('c' + key, value);
5283 };
5284
5285 return wrapper.attr(attribs);
5286 },
5287
5288 /**
5289 * Draw and return an arc.
5290 * @param {number} [x=0] Center X position.
5291 * @param {number} [y=0] Center Y position.
5292 * @param {number} [r=0] The outer radius of the arc.
5293 * @param {number} [innerR=0] Inner radius like used in donut charts.
5294 * @param {number} [start=0] The starting angle of the arc in radians, where
5295 * 0 is to the right and `-Math.PI/2` is up.
5296 * @param {number} [end=0] The ending angle of the arc in radians, where 0
5297 * is to the right and `-Math.PI/2` is up.
5298 * @returns {SVGElement} The generated wrapper element.
5299 *
5300 * @sample highcharts/members/renderer-arc/
5301 * Drawing an arc
5302 */
5303 /**
5304 * Draw and return an arc. Overloaded function that takes arguments object.
5305 * @param {SVGAttributes} attribs Initial SVG attributes.
5306 * @returns {SVGElement} The generated wrapper element.
5307 */
5308 arc: function(x, y, r, innerR, start, end) {
5309 var arc,
5310 options;
5311
5312 if (isObject(x)) {
5313 options = x;
5314 y = options.y;
5315 r = options.r;
5316 innerR = options.innerR;
5317 start = options.start;
5318 end = options.end;
5319 x = options.x;
5320 } else {
5321 options = {
5322 innerR: innerR,
5323 start: start,
5324 end: end
5325 };
5326 }
5327
5328 // Arcs are defined as symbols for the ability to set
5329 // attributes in attr and animate
5330 arc = this.symbol('arc', x, y, r, r, options);
5331 arc.r = r; // #959
5332 return arc;
5333 },
5334
5335 /**
5336 * Draw and return a rectangle.
5337 * @param {number} [x] Left position.
5338 * @param {number} [y] Top position.
5339 * @param {number} [width] Width of the rectangle.
5340 * @param {number} [height] Height of the rectangle.
5341 * @param {number} [r] Border corner radius.
5342 * @param {number} [strokeWidth] A stroke width can be supplied to allow
5343 * crisp drawing.
5344 * @returns {SVGElement} The generated wrapper element.
5345 */
5346 /**
5347 * Draw and return a rectangle.
5348 * @param {SVGAttributes} [attributes]
5349 * General SVG attributes for the rectangle.
5350 * @return {SVGElement}
5351 * The generated wrapper element.
5352 *
5353 * @sample highcharts/members/renderer-rect-on-chart/
5354 * Draw a rectangle in a chart
5355 * @sample highcharts/members/renderer-rect/
5356 * Draw a rectangle independent from a chart
5357 */
5358 rect: function(x, y, width, height, r, strokeWidth) {
5359
5360 r = isObject(x) ? x.r : r;
5361
5362 var wrapper = this.createElement('rect'),
5363 attribs = isObject(x) ? x : x === undefined ? {} : {
5364 x: x,
5365 y: y,
5366 width: Math.max(width, 0),
5367 height: Math.max(height, 0)
5368 };
5369
5370
5371 if (strokeWidth !== undefined) {
5372 attribs.strokeWidth = strokeWidth;
5373 attribs = wrapper.crisp(attribs);
5374 }
5375 attribs.fill = 'none';
5376
5377
5378 if (r) {
5379 attribs.r = r;
5380 }
5381
5382 wrapper.rSetter = function(value, key, element) {
5383 attr(element, {
5384 rx: value,
5385 ry: value
5386 });
5387 };
5388
5389 return wrapper.attr(attribs);
5390 },
5391
5392 /**
5393 * Resize the {@link SVGRenderer#box} and re-align all aligned child
5394 * elements.
5395 * @param {number} width
5396 * The new pixel width.
5397 * @param {number} height
5398 * The new pixel height.
5399 * @param {Boolean|AnimationOptions} [animate=true]
5400 * Whether and how to animate.
5401 */
5402 setSize: function(width, height, animate) {
5403 var renderer = this,
5404 alignedObjects = renderer.alignedObjects,
5405 i = alignedObjects.length;
5406
5407 renderer.width = width;
5408 renderer.height = height;
5409
5410 renderer.boxWrapper.animate({
5411 width: width,
5412 height: height
5413 }, {
5414 step: function() {
5415 this.attr({
5416 viewBox: '0 0 ' + this.attr('width') + ' ' +
5417 this.attr('height')
5418 });
5419 },
5420 duration: pick(animate, true) ? undefined : 0
5421 });
5422
5423 while (i--) {
5424 alignedObjects[i].align();
5425 }
5426 },
5427
5428 /**
5429 * Create and return an svg group element. Child
5430 * {@link Highcharts.SVGElement} objects are added to the group by using the
5431 * group as the first parameter
5432 * in {@link Highcharts.SVGElement#add|add()}.
5433 *
5434 * @param {string} [name] The group will be given a class name of
5435 * `highcharts-{name}`. This can be used for styling and scripting.
5436 * @returns {SVGElement} The generated wrapper element.
5437 *
5438 * @sample highcharts/members/renderer-g/
5439 * Show and hide grouped objects
5440 */
5441 g: function(name) {
5442 var elem = this.createElement('g');
5443 return name ? elem.attr({
5444 'class': 'highcharts-' + name
5445 }) : elem;
5446 },
5447
5448 /**
5449 * Display an image.
5450 * @param {string} src The image source.
5451 * @param {number} [x] The X position.
5452 * @param {number} [y] The Y position.
5453 * @param {number} [width] The image width. If omitted, it defaults to the
5454 * image file width.
5455 * @param {number} [height] The image height. If omitted it defaults to the
5456 * image file height.
5457 * @returns {SVGElement} The generated wrapper element.
5458 *
5459 * @sample highcharts/members/renderer-image-on-chart/
5460 * Add an image in a chart
5461 * @sample highcharts/members/renderer-image/
5462 * Add an image independent of a chart
5463 */
5464 image: function(src, x, y, width, height) {
5465 var attribs = {
5466 preserveAspectRatio: 'none'
5467 },
5468 elemWrapper;
5469
5470 // optional properties
5471 if (arguments.length > 1) {
5472 extend(attribs, {
5473 x: x,
5474 y: y,
5475 width: width,
5476 height: height
5477 });
5478 }
5479
5480 elemWrapper = this.createElement('image').attr(attribs);
5481
5482 // set the href in the xlink namespace
5483 if (elemWrapper.element.setAttributeNS) {
5484 elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
5485 'href', src);
5486 } else {
5487 // could be exporting in IE
5488 // using href throws "not supported" in ie7 and under, requries
5489 // regex shim to fix later
5490 elemWrapper.element.setAttribute('hc-svg-href', src);
5491 }
5492 return elemWrapper;
5493 },
5494
5495 /**
5496 * Draw a symbol out of pre-defined shape paths from
5497 * {@link SVGRenderer#symbols}.
5498 * It is used in Highcharts for point makers, which cake a `symbol` option,
5499 * and label and button backgrounds like in the tooltip and stock flags.
5500 *
5501 * @param {Symbol} symbol - The symbol name.
5502 * @param {number} x - The X coordinate for the top left position.
5503 * @param {number} y - The Y coordinate for the top left position.
5504 * @param {number} width - The pixel width.
5505 * @param {number} height - The pixel height.
5506 * @param {Object} [options] - Additional options, depending on the actual
5507 * symbol drawn.
5508 * @param {number} [options.anchorX] - The anchor X position for the
5509 * `callout` symbol. This is where the chevron points to.
5510 * @param {number} [options.anchorY] - The anchor Y position for the
5511 * `callout` symbol. This is where the chevron points to.
5512 * @param {number} [options.end] - The end angle of an `arc` symbol.
5513 * @param {boolean} [options.open] - Whether to draw `arc` symbol open or
5514 * closed.
5515 * @param {number} [options.r] - The radius of an `arc` symbol, or the
5516 * border radius for the `callout` symbol.
5517 * @param {number} [options.start] - The start angle of an `arc` symbol.
5518 */
5519 symbol: function(symbol, x, y, width, height, options) {
5520
5521 var ren = this,
5522 obj,
5523 imageRegex = /^url\((.*?)\)$/,
5524 isImage = imageRegex.test(symbol),
5525 sym = !isImage && (this.symbols[symbol] ? symbol : 'circle'),
5526
5527
5528 // get the symbol definition function
5529 symbolFn = sym && this.symbols[sym],
5530
5531 // check if there's a path defined for this symbol
5532 path = defined(x) && symbolFn && symbolFn.call(
5533 this.symbols,
5534 Math.round(x),
5535 Math.round(y),
5536 width,
5537 height,
5538 options
5539 ),
5540 imageSrc,
5541 centerImage;
5542
5543 if (symbolFn) {
5544 obj = this.path(path);
5545
5546
5547 obj.attr('fill', 'none');
5548
5549
5550 // expando properties for use in animate and attr
5551 extend(obj, {
5552 symbolName: sym,
5553 x: x,
5554 y: y,
5555 width: width,
5556 height: height
5557 });
5558 if (options) {
5559 extend(obj, options);
5560 }
5561
5562
5563 // Image symbols
5564 } else if (isImage) {
5565
5566
5567 imageSrc = symbol.match(imageRegex)[1];
5568
5569 // Create the image synchronously, add attribs async
5570 obj = this.image(imageSrc);
5571
5572 // The image width is not always the same as the symbol width. The
5573 // image may be centered within the symbol, as is the case when
5574 // image shapes are used as label backgrounds, for example in flags.
5575 obj.imgwidth = pick(
5576 symbolSizes[imageSrc] && symbolSizes[imageSrc].width,
5577 options && options.width
5578 );
5579 obj.imgheight = pick(
5580 symbolSizes[imageSrc] && symbolSizes[imageSrc].height,
5581 options && options.height
5582 );
5583 /**
5584 * Set the size and position
5585 */
5586 centerImage = function() {
5587 obj.attr({
5588 width: obj.width,
5589 height: obj.height
5590 });
5591 };
5592
5593 /**
5594 * Width and height setters that take both the image's physical size
5595 * and the label size into consideration, and translates the image
5596 * to center within the label.
5597 */
5598 each(['width', 'height'], function(key) {
5599 obj[key + 'Setter'] = function(value, key) {
5600 var attribs = {},
5601 imgSize = this['img' + key],
5602 trans = key === 'width' ? 'translateX' : 'translateY';
5603 this[key] = value;
5604 if (defined(imgSize)) {
5605 if (this.element) {
5606 this.element.setAttribute(key, imgSize);
5607 }
5608 if (!this.alignByTranslate) {
5609 attribs[trans] = ((this[key] || 0) - imgSize) / 2;
5610 this.attr(attribs);
5611 }
5612 }
5613 };
5614 });
5615
5616
5617 if (defined(x)) {
5618 obj.attr({
5619 x: x,
5620 y: y
5621 });
5622 }
5623 obj.isImg = true;
5624
5625 if (defined(obj.imgwidth) && defined(obj.imgheight)) {
5626 centerImage();
5627 } else {
5628 // Initialize image to be 0 size so export will still function
5629 // if there's no cached sizes.
5630 obj.attr({
5631 width: 0,
5632 height: 0
5633 });
5634
5635 // Create a dummy JavaScript image to get the width and height.
5636 createElement('img', {
5637 onload: function() {
5638
5639 var chart = charts[ren.chartIndex];
5640
5641 // Special case for SVGs on IE11, the width is not
5642 // accessible until the image is part of the DOM
5643 // (#2854).
5644 if (this.width === 0) {
5645 css(this, {
5646 position: 'absolute',
5647 top: '-999em'
5648 });
5649 doc.body.appendChild(this);
5650 }
5651
5652 // Center the image
5653 symbolSizes[imageSrc] = { // Cache for next
5654 width: this.width,
5655 height: this.height
5656 };
5657 obj.imgwidth = this.width;
5658 obj.imgheight = this.height;
5659
5660 if (obj.element) {
5661 centerImage();
5662 }
5663
5664 // Clean up after #2854 workaround.
5665 if (this.parentNode) {
5666 this.parentNode.removeChild(this);
5667 }
5668
5669 // Fire the load event when all external images are
5670 // loaded
5671 ren.imgCount--;
5672 if (!ren.imgCount && chart && chart.onload) {
5673 chart.onload();
5674 }
5675 },
5676 src: imageSrc
5677 });
5678 this.imgCount++;
5679 }
5680 }
5681
5682 return obj;
5683 },
5684
5685 /**
5686 * @typedef {string} Symbol
5687 *
5688 * Can be one of `arc`, `callout`, `circle`, `diamond`, `square`,
5689 * `triangle`, `triangle-down`. Symbols are used internally for point
5690 * markers, button and label borders and backgrounds, or custom shapes.
5691 * Extendable by adding to {@link SVGRenderer#symbols}.
5692 */
5693 /**
5694 * An extendable collection of functions for defining symbol paths.
5695 */
5696 symbols: {
5697 'circle': function(x, y, w, h) {
5698 // Return a full arc
5699 return this.arc(x + w / 2, y + h / 2, w / 2, h / 2, {
5700 start: 0,
5701 end: Math.PI * 2,
5702 open: false
5703 });
5704 },
5705
5706 'square': function(x, y, w, h) {
5707 return [
5708 'M', x, y,
5709 'L', x + w, y,
5710 x + w, y + h,
5711 x, y + h,
5712 'Z'
5713 ];
5714 },
5715
5716 'triangle': function(x, y, w, h) {
5717 return [
5718 'M', x + w / 2, y,
5719 'L', x + w, y + h,
5720 x, y + h,
5721 'Z'
5722 ];
5723 },
5724
5725 'triangle-down': function(x, y, w, h) {
5726 return [
5727 'M', x, y,
5728 'L', x + w, y,
5729 x + w / 2, y + h,
5730 'Z'
5731 ];
5732 },
5733 'diamond': function(x, y, w, h) {
5734 return [
5735 'M', x + w / 2, y,
5736 'L', x + w, y + h / 2,
5737 x + w / 2, y + h,
5738 x, y + h / 2,
5739 'Z'
5740 ];
5741 },
5742 'arc': function(x, y, w, h, options) {
5743 var start = options.start,
5744 rx = options.r || w,
5745 ry = options.r || h || w,
5746 proximity = 0.001,
5747 fullCircle =
5748 Math.abs(options.end - options.start - 2 * Math.PI) <
5749 proximity,
5750 // Substract a small number to prevent cos and sin of start and
5751 // end from becoming equal on 360 arcs (related: #1561)
5752 end = options.end - proximity,
5753 innerRadius = options.innerR,
5754 open = pick(options.open, fullCircle),
5755 cosStart = Math.cos(start),
5756 sinStart = Math.sin(start),
5757 cosEnd = Math.cos(end),
5758 sinEnd = Math.sin(end),
5759 // Proximity takes care of rounding errors around PI (#6971)
5760 longArc = options.end - start - Math.PI < proximity ? 0 : 1,
5761 arc;
5762
5763 arc = [
5764 'M',
5765 x + rx * cosStart,
5766 y + ry * sinStart,
5767 'A', // arcTo
5768 rx, // x radius
5769 ry, // y radius
5770 0, // slanting
5771 longArc, // long or short arc
5772 1, // clockwise
5773 x + rx * cosEnd,
5774 y + ry * sinEnd
5775 ];
5776
5777 if (defined(innerRadius)) {
5778 arc.push(
5779 open ? 'M' : 'L',
5780 x + innerRadius * cosEnd,
5781 y + innerRadius * sinEnd,
5782 'A', // arcTo
5783 innerRadius, // x radius
5784 innerRadius, // y radius
5785 0, // slanting
5786 longArc, // long or short arc
5787 0, // clockwise
5788 x + innerRadius * cosStart,
5789 y + innerRadius * sinStart
5790 );
5791 }
5792
5793 arc.push(open ? '' : 'Z'); // close
5794 return arc;
5795 },
5796
5797 /**
5798 * Callout shape used for default tooltips, also used for rounded
5799 * rectangles in VML
5800 */
5801 callout: function(x, y, w, h, options) {
5802 var arrowLength = 6,
5803 halfDistance = 6,
5804 r = Math.min((options && options.r) || 0, w, h),
5805 safeDistance = r + halfDistance,
5806 anchorX = options && options.anchorX,
5807 anchorY = options && options.anchorY,
5808 path;
5809
5810 path = [
5811 'M', x + r, y,
5812 'L', x + w - r, y, // top side
5813 'C', x + w, y, x + w, y, x + w, y + r, // top-right corner
5814 'L', x + w, y + h - r, // right side
5815 'C', x + w, y + h, x + w, y + h, x + w - r, y + h, // bottom-rgt
5816 'L', x + r, y + h, // bottom side
5817 'C', x, y + h, x, y + h, x, y + h - r, // bottom-left corner
5818 'L', x, y + r, // left side
5819 'C', x, y, x, y, x + r, y // top-left corner
5820 ];
5821
5822 // Anchor on right side
5823 if (anchorX && anchorX > w) {
5824
5825 // Chevron
5826 if (
5827 anchorY > y + safeDistance &&
5828 anchorY < y + h - safeDistance
5829 ) {
5830 path.splice(13, 3,
5831 'L', x + w, anchorY - halfDistance,
5832 x + w + arrowLength, anchorY,
5833 x + w, anchorY + halfDistance,
5834 x + w, y + h - r
5835 );
5836
5837 // Simple connector
5838 } else {
5839 path.splice(13, 3,
5840 'L', x + w, h / 2,
5841 anchorX, anchorY,
5842 x + w, h / 2,
5843 x + w, y + h - r
5844 );
5845 }
5846
5847 // Anchor on left side
5848 } else if (anchorX && anchorX < 0) {
5849
5850 // Chevron
5851 if (
5852 anchorY > y + safeDistance &&
5853 anchorY < y + h - safeDistance
5854 ) {
5855 path.splice(33, 3,
5856 'L', x, anchorY + halfDistance,
5857 x - arrowLength, anchorY,
5858 x, anchorY - halfDistance,
5859 x, y + r
5860 );
5861
5862 // Simple connector
5863 } else {
5864 path.splice(33, 3,
5865 'L', x, h / 2,
5866 anchorX, anchorY,
5867 x, h / 2,
5868 x, y + r
5869 );
5870 }
5871
5872 } else if ( // replace bottom
5873 anchorY &&
5874 anchorY > h &&
5875 anchorX > x + safeDistance &&
5876 anchorX < x + w - safeDistance
5877 ) {
5878 path.splice(23, 3,
5879 'L', anchorX + halfDistance, y + h,
5880 anchorX, y + h + arrowLength,
5881 anchorX - halfDistance, y + h,
5882 x + r, y + h
5883 );
5884
5885 } else if ( // replace top
5886 anchorY &&
5887 anchorY < 0 &&
5888 anchorX > x + safeDistance &&
5889 anchorX < x + w - safeDistance
5890 ) {
5891 path.splice(3, 3,
5892 'L', anchorX - halfDistance, y,
5893 anchorX, y - arrowLength,
5894 anchorX + halfDistance, y,
5895 w - r, y
5896 );
5897 }
5898
5899 return path;
5900 }
5901 },
5902
5903 /**
5904 * @typedef {SVGElement} ClipRect - A clipping rectangle that can be applied
5905 * to one or more {@link SVGElement} instances. It is instanciated with the
5906 * {@link SVGRenderer#clipRect} function and applied with the {@link
5907 * SVGElement#clip} function.
5908 *
5909 * @example
5910 * var circle = renderer.circle(100, 100, 100)
5911 * .attr({ fill: 'red' })
5912 * .add();
5913 * var clipRect = renderer.clipRect(100, 100, 100, 100);
5914 *
5915 * // Leave only the lower right quarter visible
5916 * circle.clip(clipRect);
5917 */
5918 /**
5919 * Define a clipping rectangle. The clipping rectangle is later applied
5920 * to {@link SVGElement} objects through the {@link SVGElement#clip}
5921 * function.
5922 *
5923 * @param {String} id
5924 * @param {number} x
5925 * @param {number} y
5926 * @param {number} width
5927 * @param {number} height
5928 * @returns {ClipRect} A clipping rectangle.
5929 *
5930 * @example
5931 * var circle = renderer.circle(100, 100, 100)
5932 * .attr({ fill: 'red' })
5933 * .add();
5934 * var clipRect = renderer.clipRect(100, 100, 100, 100);
5935 *
5936 * // Leave only the lower right quarter visible
5937 * circle.clip(clipRect);
5938 */
5939 clipRect: function(x, y, width, height) {
5940 var wrapper,
5941 id = H.uniqueKey(),
5942
5943 clipPath = this.createElement('clipPath').attr({
5944 id: id
5945 }).add(this.defs);
5946
5947 wrapper = this.rect(x, y, width, height, 0).add(clipPath);
5948 wrapper.id = id;
5949 wrapper.clipPath = clipPath;
5950 wrapper.count = 0;
5951
5952 return wrapper;
5953 },
5954
5955
5956
5957
5958
5959 /**
5960 * Draw text. The text can contain a subset of HTML, like spans and anchors
5961 * and some basic text styling of these. For more advanced features like
5962 * border and background, use {@link Highcharts.SVGRenderer#label} instead.
5963 * To update the text after render, run `text.attr({ text: 'New text' })`.
5964 * @param {String} str
5965 * The text of (subset) HTML to draw.
5966 * @param {number} x
5967 * The x position of the text's lower left corner.
5968 * @param {number} y
5969 * The y position of the text's lower left corner.
5970 * @param {Boolean} [useHTML=false]
5971 * Use HTML to render the text.
5972 *
5973 * @return {SVGElement} The text object.
5974 *
5975 * @sample highcharts/members/renderer-text-on-chart/
5976 * Annotate the chart freely
5977 * @sample highcharts/members/renderer-on-chart/
5978 * Annotate with a border and in response to the data
5979 * @sample highcharts/members/renderer-text/
5980 * Formatted text
5981 */
5982 text: function(str, x, y, useHTML) {
5983
5984 // declare variables
5985 var renderer = this,
5986 wrapper,
5987 attribs = {};
5988
5989 if (useHTML && (renderer.allowHTML || !renderer.forExport)) {
5990 return renderer.html(str, x, y);
5991 }
5992
5993 attribs.x = Math.round(x || 0); // X always needed for line-wrap logic
5994 if (y) {
5995 attribs.y = Math.round(y);
5996 }
5997 if (str || str === 0) {
5998 attribs.text = str;
5999 }
6000
6001 wrapper = renderer.createElement('text')
6002 .attr(attribs);
6003
6004 if (!useHTML) {
6005 wrapper.xSetter = function(value, key, element) {
6006 var tspans = element.getElementsByTagName('tspan'),
6007 tspan,
6008 parentVal = element.getAttribute(key),
6009 i;
6010 for (i = 0; i < tspans.length; i++) {
6011 tspan = tspans[i];
6012 // If the x values are equal, the tspan represents a
6013 // linebreak
6014 if (tspan.getAttribute(key) === parentVal) {
6015 tspan.setAttribute(key, value);
6016 }
6017 }
6018 element.setAttribute(key, value);
6019 };
6020 }
6021
6022 return wrapper;
6023 },
6024
6025 /**
6026 * Utility to return the baseline offset and total line height from the font
6027 * size.
6028 *
6029 * @param {?string} fontSize The current font size to inspect. If not given,
6030 * the font size will be found from the DOM element.
6031 * @param {SVGElement|SVGDOMElement} [elem] The element to inspect for a
6032 * current font size.
6033 * @returns {Object} An object containing `h`: the line height, `b`: the
6034 * baseline relative to the top of the box, and `f`: the font size.
6035 */
6036 fontMetrics: function(fontSize, elem) {
6037 var lineHeight,
6038 baseline;
6039
6040
6041 fontSize = fontSize ||
6042 // When the elem is a DOM element (#5932)
6043 (elem && elem.style && elem.style.fontSize) ||
6044 // Fall back on the renderer style default
6045 (this.style && this.style.fontSize);
6046
6047
6048
6049 // Handle different units
6050 if (/px/.test(fontSize)) {
6051 fontSize = pInt(fontSize);
6052 } else if (/em/.test(fontSize)) {
6053 // The em unit depends on parent items
6054 fontSize = parseFloat(fontSize) *
6055 (elem ? this.fontMetrics(null, elem.parentNode).f : 16);
6056 } else {
6057 fontSize = 12;
6058 }
6059
6060 // Empirical values found by comparing font size and bounding box
6061 // height. Applies to the default font family.
6062 // http://jsfiddle.net/highcharts/7xvn7/
6063 lineHeight = fontSize < 24 ? fontSize + 3 : Math.round(fontSize * 1.2);
6064 baseline = Math.round(lineHeight * 0.8);
6065
6066 return {
6067 h: lineHeight,
6068 b: baseline,
6069 f: fontSize
6070 };
6071 },
6072
6073 /**
6074 * Correct X and Y positioning of a label for rotation (#1764).
6075 *
6076 * @private
6077 */
6078 rotCorr: function(baseline, rotation, alterY) {
6079 var y = baseline;
6080 if (rotation && alterY) {
6081 y = Math.max(y * Math.cos(rotation * deg2rad), 4);
6082 }
6083 return {
6084 x: (-baseline / 3) * Math.sin(rotation * deg2rad),
6085 y: y
6086 };
6087 },
6088
6089 /**
6090 * Draw a label, which is an extended text element with support for border
6091 * and background. Highcharts creates a `g` element with a text and a `path`
6092 * or `rect` inside, to make it behave somewhat like a HTML div. Border and
6093 * background are set through `stroke`, `stroke-width` and `fill` attributes
6094 * using the {@link Highcharts.SVGElement#attr|attr} method. To update the
6095 * text after render, run `label.attr({ text: 'New text' })`.
6096 *
6097 * @param {string} str
6098 * The initial text string or (subset) HTML to render.
6099 * @param {number} x
6100 * The x position of the label's left side.
6101 * @param {number} y
6102 * The y position of the label's top side or baseline, depending on
6103 * the `baseline` parameter.
6104 * @param {String} shape
6105 * The shape of the label's border/background, if any. Defaults to
6106 * `rect`. Other possible values are `callout` or other shapes
6107 * defined in {@link Highcharts.SVGRenderer#symbols}.
6108 * @param {number} anchorX
6109 * In case the `shape` has a pointer, like a flag, this is the
6110 * coordinates it should be pinned to.
6111 * @param {number} anchorY
6112 * In case the `shape` has a pointer, like a flag, this is the
6113 * coordinates it should be pinned to.
6114 * @param {Boolean} baseline
6115 * Whether to position the label relative to the text baseline,
6116 * like {@link Highcharts.SVGRenderer#text|renderer.text}, or to the
6117 * upper border of the rectangle.
6118 * @param {String} className
6119 * Class name for the group.
6120 *
6121 * @return {SVGElement}
6122 * The generated label.
6123 *
6124 * @sample highcharts/members/renderer-label-on-chart/
6125 * A label on the chart
6126 */
6127 label: function(
6128 str,
6129 x,
6130 y,
6131 shape,
6132 anchorX,
6133 anchorY,
6134 useHTML,
6135 baseline,
6136 className
6137 ) {
6138
6139 var renderer = this,
6140 wrapper = renderer.g(className !== 'button' && 'label'),
6141 text = wrapper.text = renderer.text('', 0, 0, useHTML)
6142 .attr({
6143 zIndex: 1
6144 }),
6145 box,
6146 bBox,
6147 alignFactor = 0,
6148 padding = 3,
6149 paddingLeft = 0,
6150 width,
6151 height,
6152 wrapperX,
6153 wrapperY,
6154 textAlign,
6155 deferredAttr = {},
6156 strokeWidth,
6157 baselineOffset,
6158 hasBGImage = /^url\((.*?)\)$/.test(shape),
6159 needsBox = hasBGImage,
6160 getCrispAdjust,
6161 updateBoxSize,
6162 updateTextPadding,
6163 boxAttr;
6164
6165 if (className) {
6166 wrapper.addClass('highcharts-' + className);
6167 }
6168
6169
6170 needsBox = hasBGImage;
6171 getCrispAdjust = function() {
6172 return (strokeWidth || 0) % 2 / 2;
6173 };
6174
6175
6176
6177 /**
6178 * This function runs after the label is added to the DOM (when the
6179 * bounding box is available), and after the text of the label is
6180 * updated to detect the new bounding box and reflect it in the border
6181 * box.
6182 */
6183 updateBoxSize = function() {
6184 var style = text.element.style,
6185 crispAdjust,
6186 attribs = {};
6187
6188 bBox = (
6189 (width === undefined || height === undefined || textAlign) &&
6190 defined(text.textStr) &&
6191 text.getBBox()
6192 ); // #3295 && 3514 box failure when string equals 0
6193 wrapper.width = (
6194 (width || bBox.width || 0) +
6195 2 * padding +
6196 paddingLeft
6197 );
6198 wrapper.height = (height || bBox.height || 0) + 2 * padding;
6199
6200 // Update the label-scoped y offset
6201 baselineOffset = padding +
6202 renderer.fontMetrics(style && style.fontSize, text).b;
6203
6204
6205 if (needsBox) {
6206
6207 // Create the border box if it is not already present
6208 if (!box) {
6209 // Symbol definition exists (#5324)
6210 wrapper.box = box = renderer.symbols[shape] || hasBGImage ?
6211 renderer.symbol(shape) :
6212 renderer.rect();
6213
6214 box.addClass( // Don't use label className for buttons
6215 (className === 'button' ? '' : 'highcharts-label-box') +
6216 (className ? ' highcharts-' + className + '-box' : '')
6217 );
6218
6219 box.add(wrapper);
6220
6221 crispAdjust = getCrispAdjust();
6222 attribs.x = crispAdjust;
6223 attribs.y = (baseline ? -baselineOffset : 0) + crispAdjust;
6224 }
6225
6226 // Apply the box attributes
6227 attribs.width = Math.round(wrapper.width);
6228 attribs.height = Math.round(wrapper.height);
6229
6230 box.attr(extend(attribs, deferredAttr));
6231 deferredAttr = {};
6232 }
6233 };
6234
6235 /**
6236 * This function runs after setting text or padding, but only if padding
6237 * is changed
6238 */
6239 updateTextPadding = function() {
6240 var textX = paddingLeft + padding,
6241 textY;
6242
6243 // determin y based on the baseline
6244 textY = baseline ? 0 : baselineOffset;
6245
6246 // compensate for alignment
6247 if (
6248 defined(width) &&
6249 bBox &&
6250 (textAlign === 'center' || textAlign === 'right')
6251 ) {
6252 textX += {
6253 center: 0.5,
6254 right: 1
6255 }[textAlign] *
6256 (width - bBox.width);
6257 }
6258
6259 // update if anything changed
6260 if (textX !== text.x || textY !== text.y) {
6261 text.attr('x', textX);
6262 if (textY !== undefined) {
6263 text.attr('y', textY);
6264 }
6265 }
6266
6267 // record current values
6268 text.x = textX;
6269 text.y = textY;
6270 };
6271
6272 /**
6273 * Set a box attribute, or defer it if the box is not yet created
6274 * @param {Object} key
6275 * @param {Object} value
6276 */
6277 boxAttr = function(key, value) {
6278 if (box) {
6279 box.attr(key, value);
6280 } else {
6281 deferredAttr[key] = value;
6282 }
6283 };
6284
6285 /**
6286 * After the text element is added, get the desired size of the border
6287 * box and add it before the text in the DOM.
6288 */
6289 wrapper.onAdd = function() {
6290 text.add(wrapper);
6291 wrapper.attr({
6292 // Alignment is available now (#3295, 0 not rendered if given
6293 // as a value)
6294 text: (str || str === 0) ? str : '',
6295 x: x,
6296 y: y
6297 });
6298
6299 if (box && defined(anchorX)) {
6300 wrapper.attr({
6301 anchorX: anchorX,
6302 anchorY: anchorY
6303 });
6304 }
6305 };
6306
6307 /*
6308 * Add specific attribute setters.
6309 */
6310
6311 // only change local variables
6312 wrapper.widthSetter = function(value) {
6313 width = H.isNumber(value) ? value : null; // width:auto => null
6314 };
6315 wrapper.heightSetter = function(value) {
6316 height = value;
6317 };
6318 wrapper['text-alignSetter'] = function(value) {
6319 textAlign = value;
6320 };
6321 wrapper.paddingSetter = function(value) {
6322 if (defined(value) && value !== padding) {
6323 padding = wrapper.padding = value;
6324 updateTextPadding();
6325 }
6326 };
6327 wrapper.paddingLeftSetter = function(value) {
6328 if (defined(value) && value !== paddingLeft) {
6329 paddingLeft = value;
6330 updateTextPadding();
6331 }
6332 };
6333
6334
6335 // change local variable and prevent setting attribute on the group
6336 wrapper.alignSetter = function(value) {
6337 value = {
6338 left: 0,
6339 center: 0.5,
6340 right: 1
6341 }[value];
6342 if (value !== alignFactor) {
6343 alignFactor = value;
6344 // Bounding box exists, means we're dynamically changing
6345 if (bBox) {
6346 wrapper.attr({
6347 x: wrapperX
6348 }); // #5134
6349 }
6350 }
6351 };
6352
6353 // apply these to the box and the text alike
6354 wrapper.textSetter = function(value) {
6355 if (value !== undefined) {
6356 text.textSetter(value);
6357 }
6358 updateBoxSize();
6359 updateTextPadding();
6360 };
6361
6362 // apply these to the box but not to the text
6363 wrapper['stroke-widthSetter'] = function(value, key) {
6364 if (value) {
6365 needsBox = true;
6366 }
6367 strokeWidth = this['stroke-width'] = value;
6368 boxAttr(key, value);
6369 };
6370
6371 wrapper.strokeSetter =
6372 wrapper.fillSetter =
6373 wrapper.rSetter = function(value, key) {
6374 if (key !== 'r') {
6375 if (key === 'fill' && value) {
6376 needsBox = true;
6377 }
6378 // for animation getter (#6776)
6379 wrapper[key] = value;
6380 }
6381 boxAttr(key, value);
6382 };
6383
6384 wrapper.anchorXSetter = function(value, key) {
6385 anchorX = wrapper.anchorX = value;
6386 boxAttr(key, Math.round(value) - getCrispAdjust() - wrapperX);
6387 };
6388 wrapper.anchorYSetter = function(value, key) {
6389 anchorY = wrapper.anchorY = value;
6390 boxAttr(key, value - wrapperY);
6391 };
6392
6393 // rename attributes
6394 wrapper.xSetter = function(value) {
6395 wrapper.x = value; // for animation getter
6396 if (alignFactor) {
6397 value -= alignFactor * ((width || bBox.width) + 2 * padding);
6398 }
6399 wrapperX = Math.round(value);
6400 wrapper.attr('translateX', wrapperX);
6401 };
6402 wrapper.ySetter = function(value) {
6403 wrapperY = wrapper.y = Math.round(value);
6404 wrapper.attr('translateY', wrapperY);
6405 };
6406
6407 // Redirect certain methods to either the box or the text
6408 var baseCss = wrapper.css;
6409 return extend(wrapper, {
6410 /**
6411 * Pick up some properties and apply them to the text instead of the
6412 * wrapper.
6413 * @ignore
6414 */
6415 css: function(styles) {
6416 if (styles) {
6417 var textStyles = {};
6418 // Create a copy to avoid altering the original object
6419 // (#537)
6420 styles = merge(styles);
6421 each(wrapper.textProps, function(prop) {
6422 if (styles[prop] !== undefined) {
6423 textStyles[prop] = styles[prop];
6424 delete styles[prop];
6425 }
6426 });
6427 text.css(textStyles);
6428 }
6429 return baseCss.call(wrapper, styles);
6430 },
6431 /**
6432 * Return the bounding box of the box, not the group.
6433 * @ignore
6434 */
6435 getBBox: function() {
6436 return {
6437 width: bBox.width + 2 * padding,
6438 height: bBox.height + 2 * padding,
6439 x: bBox.x - padding,
6440 y: bBox.y - padding
6441 };
6442 },
6443
6444 /**
6445 * Apply the shadow to the box.
6446 * @ignore
6447 */
6448 shadow: function(b) {
6449 if (b) {
6450 updateBoxSize();
6451 if (box) {
6452 box.shadow(b);
6453 }
6454 }
6455 return wrapper;
6456 },
6457
6458 /**
6459 * Destroy and release memory.
6460 * @ignore
6461 */
6462 destroy: function() {
6463
6464 // Added by button implementation
6465 removeEvent(wrapper.element, 'mouseenter');
6466 removeEvent(wrapper.element, 'mouseleave');
6467
6468 if (text) {
6469 text = text.destroy();
6470 }
6471 if (box) {
6472 box = box.destroy();
6473 }
6474 // Call base implementation to destroy the rest
6475 SVGElement.prototype.destroy.call(wrapper);
6476
6477 // Release local pointers (#1298)
6478 wrapper =
6479 renderer =
6480 updateBoxSize =
6481 updateTextPadding =
6482 boxAttr = null;
6483 }
6484 });
6485 }
6486 }); // end SVGRenderer
6487
6488
6489 // general renderer
6490 H.Renderer = SVGRenderer;
6491
6492 }(Highcharts));
6493 (function(H) {
6494 /**
6495 * (c) 2010-2017 Torstein Honsi
6496 *
6497 * License: www.highcharts.com/license
6498 */
6499 /* eslint max-len: ["warn", 80, 4] */
6500 var attr = H.attr,
6501 createElement = H.createElement,
6502 css = H.css,
6503 defined = H.defined,
6504 each = H.each,
6505 extend = H.extend,
6506 isFirefox = H.isFirefox,
6507 isMS = H.isMS,
6508 isWebKit = H.isWebKit,
6509 pick = H.pick,
6510 pInt = H.pInt,
6511 SVGElement = H.SVGElement,
6512 SVGRenderer = H.SVGRenderer,
6513 win = H.win,
6514 wrap = H.wrap;
6515
6516 // Extend SvgElement for useHTML option
6517 extend(SVGElement.prototype, /** @lends SVGElement.prototype */ {
6518 /**
6519 * Apply CSS to HTML elements. This is used in text within SVG rendering and
6520 * by the VML renderer
6521 */
6522 htmlCss: function(styles) {
6523 var wrapper = this,
6524 element = wrapper.element,
6525 textWidth = styles && element.tagName === 'SPAN' && styles.width;
6526
6527 if (textWidth) {
6528 delete styles.width;
6529 wrapper.textWidth = textWidth;
6530 wrapper.updateTransform();
6531 }
6532 if (styles && styles.textOverflow === 'ellipsis') {
6533 styles.whiteSpace = 'nowrap';
6534 styles.overflow = 'hidden';
6535 }
6536 wrapper.styles = extend(wrapper.styles, styles);
6537 css(wrapper.element, styles);
6538
6539 return wrapper;
6540 },
6541
6542 /**
6543 * VML and useHTML method for calculating the bounding box based on offsets
6544 * @param {Boolean} refresh Whether to force a fresh value from the DOM or
6545 * to use the cached value.
6546 *
6547 * @return {Object} A hash containing values for x, y, width and height
6548 */
6549
6550 htmlGetBBox: function() {
6551 var wrapper = this,
6552 element = wrapper.element;
6553
6554 return {
6555 x: element.offsetLeft,
6556 y: element.offsetTop,
6557 width: element.offsetWidth,
6558 height: element.offsetHeight
6559 };
6560 },
6561
6562 /**
6563 * VML override private method to update elements based on internal
6564 * properties based on SVG transform
6565 */
6566 htmlUpdateTransform: function() {
6567 // aligning non added elements is expensive
6568 if (!this.added) {
6569 this.alignOnAdd = true;
6570 return;
6571 }
6572
6573 var wrapper = this,
6574 renderer = wrapper.renderer,
6575 elem = wrapper.element,
6576 translateX = wrapper.translateX || 0,
6577 translateY = wrapper.translateY || 0,
6578 x = wrapper.x || 0,
6579 y = wrapper.y || 0,
6580 align = wrapper.textAlign || 'left',
6581 alignCorrection = {
6582 left: 0,
6583 center: 0.5,
6584 right: 1
6585 }[align],
6586 styles = wrapper.styles;
6587
6588 // apply translate
6589 css(elem, {
6590 marginLeft: translateX,
6591 marginTop: translateY
6592 });
6593
6594
6595 if (wrapper.shadows) { // used in labels/tooltip
6596 each(wrapper.shadows, function(shadow) {
6597 css(shadow, {
6598 marginLeft: translateX + 1,
6599 marginTop: translateY + 1
6600 });
6601 });
6602 }
6603
6604
6605 // apply inversion
6606 if (wrapper.inverted) { // wrapper is a group
6607 each(elem.childNodes, function(child) {
6608 renderer.invertChild(child, elem);
6609 });
6610 }
6611
6612 if (elem.tagName === 'SPAN') {
6613
6614 var rotation = wrapper.rotation,
6615 baseline,
6616 textWidth = pInt(wrapper.textWidth),
6617 whiteSpace = styles && styles.whiteSpace,
6618 currentTextTransform = [
6619 rotation,
6620 align,
6621 elem.innerHTML,
6622 wrapper.textWidth,
6623 wrapper.textAlign
6624 ].join(',');
6625
6626 // Do the calculations and DOM access only if properties changed
6627 if (currentTextTransform !== wrapper.cTT) {
6628
6629
6630 baseline = renderer.fontMetrics(elem.style.fontSize).b;
6631
6632 // Renderer specific handling of span rotation
6633 if (defined(rotation)) {
6634 wrapper.setSpanRotation(
6635 rotation,
6636 alignCorrection,
6637 baseline
6638 );
6639 }
6640
6641 // Reset multiline/ellipsis in order to read width (#4928,
6642 // #5417)
6643 css(elem, {
6644 width: '',
6645 whiteSpace: whiteSpace || 'nowrap'
6646 });
6647
6648 // Update textWidth
6649 if (
6650 elem.offsetWidth > textWidth &&
6651 /[ \-]/.test(elem.textContent || elem.innerText)
6652 ) { // #983, #1254
6653 css(elem, {
6654 width: textWidth + 'px',
6655 display: 'block',
6656 whiteSpace: whiteSpace || 'normal' // #3331
6657 });
6658 }
6659
6660
6661 wrapper.getSpanCorrection(
6662 elem.offsetWidth,
6663 baseline,
6664 alignCorrection,
6665 rotation,
6666 align
6667 );
6668 }
6669
6670 // apply position with correction
6671 css(elem, {
6672 left: (x + (wrapper.xCorr || 0)) + 'px',
6673 top: (y + (wrapper.yCorr || 0)) + 'px'
6674 });
6675
6676 // Force reflow in webkit to apply the left and top on useHTML
6677 // element (#1249)
6678 if (isWebKit) {
6679 // Assigned to baseline for lint purpose
6680 baseline = elem.offsetHeight;
6681 }
6682
6683 // record current text transform
6684 wrapper.cTT = currentTextTransform;
6685 }
6686 },
6687
6688 /**
6689 * Set the rotation of an individual HTML span
6690 */
6691 setSpanRotation: function(rotation, alignCorrection, baseline) {
6692 var rotationStyle = {},
6693 cssTransformKey = this.renderer.getTransformKey();
6694
6695 rotationStyle[cssTransformKey] = rotationStyle.transform =
6696 'rotate(' + rotation + 'deg)';
6697 rotationStyle[cssTransformKey + (isFirefox ? 'Origin' : '-origin')] =
6698 rotationStyle.transformOrigin =
6699 (alignCorrection * 100) + '% ' + baseline + 'px';
6700 css(this.element, rotationStyle);
6701 },
6702
6703 /**
6704 * Get the correction in X and Y positioning as the element is rotated.
6705 */
6706 getSpanCorrection: function(width, baseline, alignCorrection) {
6707 this.xCorr = -width * alignCorrection;
6708 this.yCorr = -baseline;
6709 }
6710 });
6711
6712 // Extend SvgRenderer for useHTML option.
6713 extend(SVGRenderer.prototype, /** @lends SVGRenderer.prototype */ {
6714
6715 getTransformKey: function() {
6716 return isMS && !/Edge/.test(win.navigator.userAgent) ?
6717 '-ms-transform' :
6718 isWebKit ?
6719 '-webkit-transform' :
6720 isFirefox ?
6721 'MozTransform' :
6722 win.opera ?
6723 '-o-transform' :
6724 '';
6725 },
6726
6727 /**
6728 * Create HTML text node. This is used by the VML renderer as well as the
6729 * SVG renderer through the useHTML option.
6730 *
6731 * @param {String} str
6732 * @param {Number} x
6733 * @param {Number} y
6734 */
6735 html: function(str, x, y) {
6736 var wrapper = this.createElement('span'),
6737 element = wrapper.element,
6738 renderer = wrapper.renderer,
6739 isSVG = renderer.isSVG,
6740 addSetters = function(element, style) {
6741 // These properties are set as attributes on the SVG group, and
6742 // as identical CSS properties on the div. (#3542)
6743 each(['opacity', 'visibility'], function(prop) {
6744 wrap(element, prop + 'Setter', function(
6745 proceed,
6746 value,
6747 key,
6748 elem
6749 ) {
6750 proceed.call(this, value, key, elem);
6751 style[key] = value;
6752 });
6753 });
6754 };
6755
6756 // Text setter
6757 wrapper.textSetter = function(value) {
6758 if (value !== element.innerHTML) {
6759 delete this.bBox;
6760 }
6761 this.textStr = value;
6762 element.innerHTML = pick(value, '');
6763 wrapper.htmlUpdateTransform();
6764 };
6765
6766 // Add setters for the element itself (#4938)
6767 if (isSVG) { // #4938, only for HTML within SVG
6768 addSetters(wrapper, wrapper.element.style);
6769 }
6770
6771 // Various setters which rely on update transform
6772 wrapper.xSetter =
6773 wrapper.ySetter =
6774 wrapper.alignSetter =
6775 wrapper.rotationSetter =
6776 function(value, key) {
6777 if (key === 'align') {
6778 // Do not overwrite the SVGElement.align method. Same as VML.
6779 key = 'textAlign';
6780 }
6781 wrapper[key] = value;
6782 wrapper.htmlUpdateTransform();
6783 };
6784
6785 // Set the default attributes
6786 wrapper
6787 .attr({
6788 text: str,
6789 x: Math.round(x),
6790 y: Math.round(y)
6791 })
6792 .css({
6793
6794 fontFamily: this.style.fontFamily,
6795 fontSize: this.style.fontSize,
6796
6797 position: 'absolute'
6798 });
6799
6800 // Keep the whiteSpace style outside the wrapper.styles collection
6801 element.style.whiteSpace = 'nowrap';
6802
6803 // Use the HTML specific .css method
6804 wrapper.css = wrapper.htmlCss;
6805
6806 // This is specific for HTML within SVG
6807 if (isSVG) {
6808 wrapper.add = function(svgGroupWrapper) {
6809
6810 var htmlGroup,
6811 container = renderer.box.parentNode,
6812 parentGroup,
6813 parents = [];
6814
6815 this.parentGroup = svgGroupWrapper;
6816
6817 // Create a mock group to hold the HTML elements
6818 if (svgGroupWrapper) {
6819 htmlGroup = svgGroupWrapper.div;
6820 if (!htmlGroup) {
6821
6822 // Read the parent chain into an array and read from top
6823 // down
6824 parentGroup = svgGroupWrapper;
6825 while (parentGroup) {
6826
6827 parents.push(parentGroup);
6828
6829 // Move up to the next parent group
6830 parentGroup = parentGroup.parentGroup;
6831 }
6832
6833 // Ensure dynamically updating position when any parent
6834 // is translated
6835 each(parents.reverse(), function(parentGroup) {
6836 var htmlGroupStyle,
6837 cls = attr(parentGroup.element, 'class');
6838
6839 // Common translate setter for X and Y on the HTML
6840 // group. Using CSS transform instead of left and
6841 // right prevents flickering in IE and Edge when
6842 // moving tooltip (#6957).
6843 function translateSetter(value, key) {
6844 parentGroup[key] = value;
6845
6846 // In IE and Edge, use translate because items
6847 // would flicker below a HTML tooltip (#6957)
6848 if (isMS) {
6849 htmlGroupStyle[renderer.getTransformKey()] =
6850 'translate(' + (
6851 parentGroup.x ||
6852 parentGroup.translateX
6853 ) + 'px,' + (
6854 parentGroup.y ||
6855 parentGroup.translateY
6856 ) + 'px)';
6857
6858 // Otherwise, use left and top. Using translate
6859 // doesn't work well with offline export (#7254,
6860 // #7280)
6861 } else {
6862 if (key === 'translateX') {
6863 htmlGroupStyle.left = value + 'px';
6864 } else {
6865 htmlGroupStyle.top = value + 'px';
6866 }
6867 }
6868
6869 parentGroup.doTransform = true;
6870 }
6871
6872 if (cls) {
6873 cls = {
6874 className: cls
6875 };
6876 } // else null
6877
6878 // Create a HTML div and append it to the parent div
6879 // to emulate the SVG group structure
6880 htmlGroup =
6881 parentGroup.div =
6882 parentGroup.div || createElement('div', cls, {
6883 position: 'absolute',
6884 left: (parentGroup.translateX || 0) + 'px',
6885 top: (parentGroup.translateY || 0) + 'px',
6886 display: parentGroup.display,
6887 opacity: parentGroup.opacity, // #5075
6888 pointerEvents: (
6889 parentGroup.styles &&
6890 parentGroup.styles.pointerEvents
6891 ) // #5595
6892
6893 // the top group is appended to container
6894 }, htmlGroup || container);
6895
6896 // Shortcut
6897 htmlGroupStyle = htmlGroup.style;
6898
6899 // Set listeners to update the HTML div's position
6900 // whenever the SVG group position is changed.
6901 extend(parentGroup, {
6902 classSetter: function(value) {
6903 this.element.setAttribute('class', value);
6904 htmlGroup.className = value;
6905 },
6906 on: function() {
6907 if (parents[0].div) { // #6418
6908 wrapper.on.apply({
6909 element: parents[0].div
6910 },
6911 arguments
6912 );
6913 }
6914 return parentGroup;
6915 },
6916 translateXSetter: translateSetter,
6917 translateYSetter: translateSetter
6918 });
6919 addSetters(parentGroup, htmlGroupStyle);
6920 });
6921
6922 }
6923 } else {
6924 htmlGroup = container;
6925 }
6926
6927 htmlGroup.appendChild(element);
6928
6929 // Shared with VML:
6930 wrapper.added = true;
6931 if (wrapper.alignOnAdd) {
6932 wrapper.htmlUpdateTransform();
6933 }
6934
6935 return wrapper;
6936 };
6937 }
6938 return wrapper;
6939 }
6940 });
6941
6942 }(Highcharts));
6943 (function(H) {
6944 /**
6945 * (c) 2010-2017 Torstein Honsi
6946 *
6947 * License: www.highcharts.com/license
6948 */
6949 var color = H.color,
6950 getTZOffset = H.getTZOffset,
6951 isTouchDevice = H.isTouchDevice,
6952 merge = H.merge,
6953 pick = H.pick,
6954 svg = H.svg,
6955 win = H.win;
6956
6957 /* ****************************************************************************
6958 * Handle the options *
6959 *****************************************************************************/
6960 /**
6961 * @optionparent
6962 */
6963 H.defaultOptions = {
6964
6965
6966 /**
6967 * An array containing the default colors for the chart's series. When
6968 * all colors are used, new colors are pulled from the start again.
6969 *
6970 * Default colors can also be set on a series or series.type basis,
6971 * see [column.colors](#plotOptions.column.colors), [pie.colors](#plotOptions.
6972 * pie.colors).
6973 *
6974 * In styled mode, the colors option doesn't exist. Instead, colors
6975 * are defined in CSS and applied either through series or point class
6976 * names, or through the [chart.colorCount](#chart.colorCount) option.
6977 *
6978 *
6979 * ### Legacy
6980 *
6981 * In Highcharts 3.x, the default colors were:
6982 *
6983 * <pre>colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce',
6984 * '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']</pre>
6985 *
6986 * In Highcharts 2.x, the default colors were:
6987 *
6988 * <pre>colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE',
6989 * '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92']</pre>
6990 *
6991 * @type {Array<Color>}
6992 * @sample {highcharts} highcharts/chart/colors/ Assign a global color theme
6993 * @default ["#7cb5ec", "#434348", "#90ed7d", "#f7a35c", "#8085e9",
6994 * "#f15c80", "#e4d354", "#2b908f", "#f45b5b", "#91e8e1"]
6995 */
6996 colors: '#7cb5ec #434348 #90ed7d #f7a35c #8085e9 #f15c80 #e4d354 #2b908f #f45b5b #91e8e1'.split(' '),
6997
6998
6999
7000 /**
7001 * Styled mode only. Configuration object for adding SVG definitions for
7002 * reusable elements. See [gradients, shadows and patterns](http://www.
7003 * highcharts.com/docs/chart-design-and-style/gradients-shadows-and-
7004 * patterns) for more information and code examples.
7005 *
7006 * @type {Object}
7007 * @since 5.0.0
7008 * @apioption defs
7009 */
7010
7011 /**
7012 * @ignore
7013 */
7014 symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
7015 lang: {
7016
7017 /**
7018 * The loading text that appears when the chart is set into the loading
7019 * state following a call to `chart.showLoading`.
7020 *
7021 * @type {String}
7022 * @default Loading...
7023 */
7024 loading: 'Loading...',
7025
7026 /**
7027 * An array containing the months names. Corresponds to the `%B` format
7028 * in `Highcharts.dateFormat()`.
7029 *
7030 * @type {Array<String>}
7031 * @default [ "January" , "February" , "March" , "April" , "May" ,
7032 * "June" , "July" , "August" , "September" , "October" ,
7033 * "November" , "December"]
7034 */
7035 months: [
7036 'January', 'February', 'March', 'April', 'May', 'June', 'July',
7037 'August', 'September', 'October', 'November', 'December'
7038 ],
7039
7040 /**
7041 * An array containing the months names in abbreviated form. Corresponds
7042 * to the `%b` format in `Highcharts.dateFormat()`.
7043 *
7044 * @type {Array<String>}
7045 * @default [ "Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" ,
7046 * "Jul" , "Aug" , "Sep" , "Oct" , "Nov" , "Dec"]
7047 */
7048 shortMonths: [
7049 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
7050 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
7051 ],
7052
7053 /**
7054 * An array containing the weekday names.
7055 *
7056 * @type {Array<String>}
7057 * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
7058 * "Friday", "Saturday"]
7059 */
7060 weekdays: [
7061 'Sunday', 'Monday', 'Tuesday', 'Wednesday',
7062 'Thursday', 'Friday', 'Saturday'
7063 ],
7064
7065 /**
7066 * Short week days, starting Sunday. If not specified, Highcharts uses
7067 * the first three letters of the `lang.weekdays` option.
7068 *
7069 * @type {Array<String>}
7070 * @sample highcharts/lang/shortweekdays/
7071 * Finnish two-letter abbreviations
7072 * @since 4.2.4
7073 * @apioption lang.shortWeekdays
7074 */
7075
7076 /**
7077 * What to show in a date field for invalid dates. Defaults to an empty
7078 * string.
7079 *
7080 * @type {String}
7081 * @since 4.1.8
7082 * @product highcharts highstock
7083 * @apioption lang.invalidDate
7084 */
7085
7086 /**
7087 * The default decimal point used in the `Highcharts.numberFormat`
7088 * method unless otherwise specified in the function arguments.
7089 *
7090 * @type {String}
7091 * @default .
7092 * @since 1.2.2
7093 */
7094 decimalPoint: '.',
7095
7096 /**
7097 * [Metric prefixes](http://en.wikipedia.org/wiki/Metric_prefix) used
7098 * to shorten high numbers in axis labels. Replacing any of the positions
7099 * with `null` causes the full number to be written. Setting `numericSymbols`
7100 * to `null` disables shortening altogether.
7101 *
7102 * @type {Array<String>}
7103 * @sample {highcharts} highcharts/lang/numericsymbols/
7104 * Replacing the symbols with text
7105 * @sample {highstock} highcharts/lang/numericsymbols/
7106 * Replacing the symbols with text
7107 * @default [ "k" , "M" , "G" , "T" , "P" , "E"]
7108 * @since 2.3.0
7109 */
7110 numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'],
7111
7112 /**
7113 * The magnitude of [numericSymbols](#lang.numericSymbol) replacements.
7114 * Use 10000 for Japanese, Korean and various Chinese locales, which
7115 * use symbols for 10^4, 10^8 and 10^12.
7116 *
7117 * @type {Number}
7118 * @sample highcharts/lang/numericsymbolmagnitude/
7119 * 10000 magnitude for Japanese
7120 * @default 1000
7121 * @since 5.0.3
7122 * @apioption lang.numericSymbolMagnitude
7123 */
7124
7125 /**
7126 * The text for the label appearing when a chart is zoomed.
7127 *
7128 * @type {String}
7129 * @default Reset zoom
7130 * @since 1.2.4
7131 */
7132 resetZoom: 'Reset zoom',
7133
7134 /**
7135 * The tooltip title for the label appearing when a chart is zoomed.
7136 *
7137 * @type {String}
7138 * @default Reset zoom level 1:1
7139 * @since 1.2.4
7140 */
7141 resetZoomTitle: 'Reset zoom level 1:1',
7142
7143 /**
7144 * The default thousands separator used in the `Highcharts.numberFormat`
7145 * method unless otherwise specified in the function arguments. Since
7146 * Highcharts 4.1 it defaults to a single space character, which is
7147 * compatible with ISO and works across Anglo-American and continental
7148 * European languages.
7149 *
7150 * The default is a single space.
7151 *
7152 * @type {String}
7153 * @default
7154 * @since 1.2.2
7155 */
7156 thousandsSep: ' '
7157 },
7158
7159 /**
7160 * Global options that don't apply to each chart. These options, like
7161 * the `lang` options, must be set using the `Highcharts.setOptions`
7162 * method.
7163 *
7164 * <pre>Highcharts.setOptions({
7165 * global: {
7166 * useUTC: false
7167 * }
7168 * });</pre>
7169 *
7170 */
7171 global: {
7172
7173 /**
7174 * Whether to use UTC time for axis scaling, tickmark placement and
7175 * time display in `Highcharts.dateFormat`. Advantages of using UTC
7176 * is that the time displays equally regardless of the user agent's
7177 * time zone settings. Local time can be used when the data is loaded
7178 * in real time or when correct Daylight Saving Time transitions are
7179 * required.
7180 *
7181 * @type {Boolean}
7182 * @sample {highcharts} highcharts/global/useutc-true/ True by default
7183 * @sample {highcharts} highcharts/global/useutc-false/ False
7184 * @default true
7185 */
7186 useUTC: true
7187
7188 /**
7189 * A custom `Date` class for advanced date handling. For example,
7190 * [JDate](https://githubcom/tahajahangir/jdate) can be hooked in to
7191 * handle Jalali dates.
7192 *
7193 * @type {Object}
7194 * @since 4.0.4
7195 * @product highcharts highstock
7196 * @apioption global.Date
7197 */
7198
7199 /**
7200 * _Canvg rendering for Android 2.x is removed as of Highcharts 5.0\.
7201 * Use the [libURL](#exporting.libURL) option to configure exporting._
7202 *
7203 * The URL to the additional file to lazy load for Android 2.x devices.
7204 * These devices don't support SVG, so we download a helper file that
7205 * contains [canvg](http://code.google.com/p/canvg/), its dependency
7206 * rbcolor, and our own CanVG Renderer class. To avoid hotlinking to
7207 * our site, you can install canvas-tools.js on your own server and
7208 * change this option accordingly.
7209 *
7210 * @type {String}
7211 * @deprecated
7212 * @default http://code.highcharts.com/{version}/modules/canvas-tools.js
7213 * @product highcharts highmaps
7214 * @apioption global.canvasToolsURL
7215 */
7216
7217 /**
7218 * A callback to return the time zone offset for a given datetime. It
7219 * takes the timestamp in terms of milliseconds since January 1 1970,
7220 * and returns the timezone offset in minutes. This provides a hook
7221 * for drawing time based charts in specific time zones using their
7222 * local DST crossover dates, with the help of external libraries.
7223 *
7224 * @type {Function}
7225 * @see [global.timezoneOffset](#global.timezoneOffset)
7226 * @sample {highcharts} highcharts/global/gettimezoneoffset/
7227 * Use moment.js to draw Oslo time regardless of browser locale
7228 * @sample {highstock} highcharts/global/gettimezoneoffset/
7229 * Use moment.js to draw Oslo time regardless of browser locale
7230 * @since 4.1.0
7231 * @product highcharts highstock
7232 * @apioption global.getTimezoneOffset
7233 */
7234
7235 /**
7236 * Requires [moment.js](http://momentjs.com/). If the timezone option
7237 * is specified, it creates a default
7238 * [getTimezoneOffset](#global.getTimezoneOffset) function that looks
7239 * up the specified timezone in moment.js. If moment.js is not included,
7240 * this throws a Highcharts error in the console, but does not crash the
7241 * chart.
7242 *
7243 * @type {String}
7244 * @see [getTimezoneOffset](#global.getTimezoneOffset)
7245 * @sample {highcharts} highcharts/global/timezone/ Europe/Oslo
7246 * @sample {highstock} highcharts/global/timezone/ Europe/Oslo
7247 * @default undefined
7248 * @since 5.0.7
7249 * @product highcharts highstock
7250 * @apioption global.timezone
7251 */
7252
7253 /**
7254 * The timezone offset in minutes. Positive values are west, negative
7255 * values are east of UTC, as in the ECMAScript [getTimezoneOffset](https://developer.
7256 * mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset)
7257 * method. Use this to display UTC based data in a predefined time zone.
7258 *
7259 * @type {Number}
7260 * @see [global.getTimezoneOffset](#global.getTimezoneOffset)
7261 * @sample {highcharts} highcharts/global/timezoneoffset/
7262 * Timezone offset
7263 * @sample {highstock} highcharts/global/timezoneoffset/
7264 * Timezone offset
7265 * @default 0
7266 * @since 3.0.8
7267 * @product highcharts highstock
7268 * @apioption global.timezoneOffset
7269 */
7270 },
7271 chart: {
7272
7273 /**
7274 * When using multiple axis, the ticks of two or more opposite axes
7275 * will automatically be aligned by adding ticks to the axis or axes
7276 * with the least ticks, as if `tickAmount` were specified.
7277 *
7278 * This can be prevented by setting `alignTicks` to false. If the grid
7279 * lines look messy, it's a good idea to hide them for the secondary
7280 * axis by setting `gridLineWidth` to 0.
7281 *
7282 * @type {Boolean}
7283 * @sample {highcharts} highcharts/chart/alignticks-true/ True by default
7284 * @sample {highcharts} highcharts/chart/alignticks-false/ False
7285 * @sample {highstock} stock/chart/alignticks-true/
7286 * True by default
7287 * @sample {highstock} stock/chart/alignticks-false/
7288 * False
7289 * @default true
7290 * @product highcharts highstock
7291 * @apioption chart.alignTicks
7292 */
7293
7294
7295 /**
7296 * Set the overall animation for all chart updating. Animation can be
7297 * disabled throughout the chart by setting it to false here. It can
7298 * be overridden for each individual API method as a function parameter.
7299 * The only animation not affected by this option is the initial series
7300 * animation, see [plotOptions.series.animation](#plotOptions.series.
7301 * animation).
7302 *
7303 * The animation can either be set as a boolean or a configuration
7304 * object. If `true`, it will use the 'swing' jQuery easing and a
7305 * duration of 500 ms. If used as a configuration object, the following
7306 * properties are supported:
7307 *
7308 * <dl>
7309 *
7310 * <dt>duration</dt>
7311 *
7312 * <dd>The duration of the animation in milliseconds.</dd>
7313 *
7314 * <dt>easing</dt>
7315 *
7316 * <dd>A string reference to an easing function set on the `Math` object.
7317 * See [the easing demo](http://jsfiddle.net/gh/get/library/pure/
7318 * highcharts/highcharts/tree/master/samples/highcharts/plotoptions/
7319 * series-animation-easing/).</dd>
7320 *
7321 * </dl>
7322 *
7323 * @type {Boolean|Object}
7324 * @sample {highcharts} highcharts/chart/animation-none/
7325 * Updating with no animation
7326 * @sample {highcharts} highcharts/chart/animation-duration/
7327 * With a longer duration
7328 * @sample {highcharts} highcharts/chart/animation-easing/
7329 * With a jQuery UI easing
7330 * @sample {highmaps} maps/chart/animation-none/
7331 * Updating with no animation
7332 * @sample {highmaps} maps/chart/animation-duration/
7333 * With a longer duration
7334 * @default true
7335 * @apioption chart.animation
7336 */
7337
7338 /**
7339 * A CSS class name to apply to the charts container `div`, allowing
7340 * unique CSS styling for each chart.
7341 *
7342 * @type {String}
7343 * @apioption chart.className
7344 */
7345
7346 /**
7347 * Event listeners for the chart.
7348 *
7349 * @apioption chart.events
7350 */
7351
7352 /**
7353 * Fires when a series is added to the chart after load time, using
7354 * the `addSeries` method. One parameter, `event`, is passed to the
7355 * function, containing common event information.
7356 * Through `event.options` you can access the series options that was
7357 * passed to the `addSeries` method. Returning false prevents the series
7358 * from being added.
7359 *
7360 * @type {Function}
7361 * @context Chart
7362 * @sample {highcharts} highcharts/chart/events-addseries/ Alert on add series
7363 * @sample {highstock} stock/chart/events-addseries/ Alert on add series
7364 * @since 1.2.0
7365 * @apioption chart.events.addSeries
7366 */
7367
7368 /**
7369 * Fires when clicking on the plot background. One parameter, `event`,
7370 * is passed to the function, containing common event information.
7371 *
7372 * Information on the clicked spot can be found through `event.xAxis`
7373 * and `event.yAxis`, which are arrays containing the axes of each dimension
7374 * and each axis' value at the clicked spot. The primary axes are `event.
7375 * xAxis[0]` and `event.yAxis[0]`. Remember the unit of a datetime axis
7376 * is milliseconds since 1970-01-01 00:00:00.
7377 *
7378 * <pre>click: function(e) {
7379 * console.log(
7380 * Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', e.xAxis[0].value),
7381 * e.yAxis[0].value
7382 * )
7383 * }</pre>
7384 *
7385 * @type {Function}
7386 * @context Chart
7387 * @sample {highcharts} highcharts/chart/events-click/
7388 * Alert coordinates on click
7389 * @sample {highcharts} highcharts/chart/events-container/
7390 * Alternatively, attach event to container
7391 * @sample {highstock} stock/chart/events-click/
7392 * Alert coordinates on click
7393 * @sample {highstock} highcharts/chart/events-container/
7394 * Alternatively, attach event to container
7395 * @sample {highmaps} maps/chart/events-click/
7396 * Record coordinates on click
7397 * @sample {highmaps} highcharts/chart/events-container/
7398 * Alternatively, attach event to container
7399 * @since 1.2.0
7400 * @apioption chart.events.click
7401 */
7402
7403
7404 /**
7405 * Fires when the chart is finished loading. Since v4.2.2, it also waits
7406 * for images to be loaded, for example from point markers. One parameter,
7407 * `event`, is passed to the function, containing common event information.
7408 *
7409 * There is also a second parameter to the chart constructor where a
7410 * callback function can be passed to be executed on chart.load.
7411 *
7412 * @type {Function}
7413 * @context Chart
7414 * @sample {highcharts} highcharts/chart/events-load/
7415 * Alert on chart load
7416 * @sample {highstock} stock/chart/events-load/
7417 * Alert on chart load
7418 * @sample {highmaps} maps/chart/events-load/
7419 * Add series on chart load
7420 * @apioption chart.events.load
7421 */
7422
7423 /**
7424 * Fires when the chart is redrawn, either after a call to chart.redraw()
7425 * or after an axis, series or point is modified with the `redraw` option
7426 * set to true. One parameter, `event`, is passed to the function, containing common event information.
7427 *
7428 * @type {Function}
7429 * @context Chart
7430 * @sample {highcharts} highcharts/chart/events-redraw/
7431 * Alert on chart redraw
7432 * @sample {highstock} stock/chart/events-redraw/
7433 * Alert on chart redraw when adding a series or moving the
7434 * zoomed range
7435 * @sample {highmaps} maps/chart/events-redraw/
7436 * Set subtitle on chart redraw
7437 * @since 1.2.0
7438 * @apioption chart.events.redraw
7439 */
7440
7441 /**
7442 * Fires after initial load of the chart (directly after the `load`
7443 * event), and after each redraw (directly after the `redraw` event).
7444 *
7445 * @type {Function}
7446 * @context Chart
7447 * @since 5.0.7
7448 * @apioption chart.events.render
7449 */
7450
7451 /**
7452 * Fires when an area of the chart has been selected. Selection is enabled
7453 * by setting the chart's zoomType. One parameter, `event`, is passed
7454 * to the function, containing common event information. The default action for the selection event is to
7455 * zoom the chart to the selected area. It can be prevented by calling
7456 * `event.preventDefault()`.
7457 *
7458 * Information on the selected area can be found through `event.xAxis`
7459 * and `event.yAxis`, which are arrays containing the axes of each dimension
7460 * and each axis' min and max values. The primary axes are `event.xAxis[0]`
7461 * and `event.yAxis[0]`. Remember the unit of a datetime axis is milliseconds
7462 * since 1970-01-01 00:00:00.
7463 *
7464 * <pre>selection: function(event) {
7465 * // log the min and max of the primary, datetime x-axis
7466 * console.log(
7467 * Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', event.xAxis[0].min),
7468 * Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', event.xAxis[0].max)
7469 * );
7470 * // log the min and max of the y axis
7471 * console.log(event.yAxis[0].min, event.yAxis[0].max);
7472 * }</pre>
7473 *
7474 * @type {Function}
7475 * @sample {highcharts} highcharts/chart/events-selection/
7476 * Report on selection and reset
7477 * @sample {highcharts} highcharts/chart/events-selection-points/
7478 * Select a range of points through a drag selection
7479 * @sample {highstock} stock/chart/events-selection/
7480 * Report on selection and reset
7481 * @sample {highstock} highcharts/chart/events-selection-points/
7482 * Select a range of points through a drag selection (Highcharts)
7483 * @apioption chart.events.selection
7484 */
7485
7486 /**
7487 * The margin between the outer edge of the chart and the plot area.
7488 * The numbers in the array designate top, right, bottom and left
7489 * respectively. Use the options `marginTop`, `marginRight`,
7490 * `marginBottom` and `marginLeft` for shorthand setting of one option.
7491 *
7492 * By default there is no margin. The actual space is dynamically calculated
7493 * from the offset of axis labels, axis title, title, subtitle and legend
7494 * in addition to the `spacingTop`, `spacingRight`, `spacingBottom`
7495 * and `spacingLeft` options.
7496 *
7497 * @type {Array}
7498 * @sample {highcharts} highcharts/chart/margins-zero/
7499 * Zero margins
7500 * @sample {highstock} stock/chart/margin-zero/
7501 * Zero margins
7502 *
7503 * @defaults {all} null
7504 * @apioption chart.margin
7505 */
7506
7507 /**
7508 * The margin between the bottom outer edge of the chart and the plot
7509 * area. Use this to set a fixed pixel value for the margin as opposed
7510 * to the default dynamic margin. See also `spacingBottom`.
7511 *
7512 * @type {Number}
7513 * @sample {highcharts} highcharts/chart/marginbottom/
7514 * 100px bottom margin
7515 * @sample {highstock} stock/chart/marginbottom/
7516 * 100px bottom margin
7517 * @sample {highmaps} maps/chart/margin/
7518 * 100px margins
7519 * @since 2.0
7520 * @apioption chart.marginBottom
7521 */
7522
7523 /**
7524 * The margin between the left outer edge of the chart and the plot
7525 * area. Use this to set a fixed pixel value for the margin as opposed
7526 * to the default dynamic margin. See also `spacingLeft`.
7527 *
7528 * @type {Number}
7529 * @sample {highcharts} highcharts/chart/marginleft/
7530 * 150px left margin
7531 * @sample {highstock} stock/chart/marginleft/
7532 * 150px left margin
7533 * @sample {highmaps} maps/chart/margin/
7534 * 100px margins
7535 * @default null
7536 * @since 2.0
7537 * @apioption chart.marginLeft
7538 */
7539
7540 /**
7541 * The margin between the right outer edge of the chart and the plot
7542 * area. Use this to set a fixed pixel value for the margin as opposed
7543 * to the default dynamic margin. See also `spacingRight`.
7544 *
7545 * @type {Number}
7546 * @sample {highcharts} highcharts/chart/marginright/
7547 * 100px right margin
7548 * @sample {highstock} stock/chart/marginright/
7549 * 100px right margin
7550 * @sample {highmaps} maps/chart/margin/
7551 * 100px margins
7552 * @default null
7553 * @since 2.0
7554 * @apioption chart.marginRight
7555 */
7556
7557 /**
7558 * The margin between the top outer edge of the chart and the plot area.
7559 * Use this to set a fixed pixel value for the margin as opposed to
7560 * the default dynamic margin. See also `spacingTop`.
7561 *
7562 * @type {Number}
7563 * @sample {highcharts} highcharts/chart/margintop/ 100px top margin
7564 * @sample {highstock} stock/chart/margintop/
7565 * 100px top margin
7566 * @sample {highmaps} maps/chart/margin/
7567 * 100px margins
7568 * @default null
7569 * @since 2.0
7570 * @apioption chart.marginTop
7571 */
7572
7573 /**
7574 * Allows setting a key to switch between zooming and panning. Can be
7575 * one of `alt`, `ctrl`, `meta` (the command key on Mac and Windows
7576 * key on Windows) or `shift`. The keys are mapped directly to the key
7577 * properties of the click event argument (`event.altKey`, `event.ctrlKey`,
7578 * `event.metaKey` and `event.shiftKey`).
7579 *
7580 * @validvalue [null, "alt", "ctrl", "meta", "shift"]
7581 * @type {String}
7582 * @since 4.0.3
7583 * @product highcharts
7584 * @apioption chart.panKey
7585 */
7586
7587 /**
7588 * Allow panning in a chart. Best used with [panKey](#chart.panKey)
7589 * to combine zooming and panning.
7590 *
7591 * On touch devices, when the [tooltip.followTouchMove](#tooltip.followTouchMove)
7592 * option is `true` (default), panning requires two fingers. To allow
7593 * panning with one finger, set `followTouchMove` to `false`.
7594 *
7595 * @type {Boolean}
7596 * @sample {highcharts} highcharts/chart/pankey/ Zooming and panning
7597 * @default {highcharts} false
7598 * @default {highstock} true
7599 * @since 4.0.3
7600 * @product highcharts highstock
7601 * @apioption chart.panning
7602 */
7603
7604
7605 /**
7606 * Equivalent to [zoomType](#chart.zoomType), but for multitouch gestures
7607 * only. By default, the `pinchType` is the same as the `zoomType` setting.
7608 * However, pinching can be enabled separately in some cases, for example
7609 * in stock charts where a mouse drag pans the chart, while pinching
7610 * is enabled. When [tooltip.followTouchMove](#tooltip.followTouchMove)
7611 * is true, pinchType only applies to two-finger touches.
7612 *
7613 * @validvalue ["x", "y", "xy"]
7614 * @type {String}
7615 * @default {highcharts} null
7616 * @default {highstock} x
7617 * @since 3.0
7618 * @product highcharts highstock
7619 * @apioption chart.pinchType
7620 */
7621
7622 /**
7623 * The corner radius of the outer chart border.
7624 *
7625 * @type {Number}
7626 * @sample {highcharts} highcharts/chart/borderradius/ 20px radius
7627 * @sample {highstock} stock/chart/border/ 10px radius
7628 * @sample {highmaps} maps/chart/border/ Border options
7629 * @default 0
7630 */
7631 borderRadius: 0,
7632
7633
7634 /**
7635 * Alias of `type`.
7636 *
7637 * @validvalue ["line", "spline", "column", "area", "areaspline", "pie"]
7638 * @type {String}
7639 * @deprecated
7640 * @sample {highcharts} highcharts/chart/defaultseriestype/ Bar
7641 * @default line
7642 * @product highcharts
7643 */
7644 defaultSeriesType: 'line',
7645
7646 /**
7647 * If true, the axes will scale to the remaining visible series once
7648 * one series is hidden. If false, hiding and showing a series will
7649 * not affect the axes or the other series. For stacks, once one series
7650 * within the stack is hidden, the rest of the stack will close in
7651 * around it even if the axis is not affected.
7652 *
7653 * @type {Boolean}
7654 * @sample {highcharts} highcharts/chart/ignorehiddenseries-true/
7655 * True by default
7656 * @sample {highcharts} highcharts/chart/ignorehiddenseries-false/
7657 * False
7658 * @sample {highcharts} highcharts/chart/ignorehiddenseries-true-stacked/
7659 * True with stack
7660 * @sample {highstock} stock/chart/ignorehiddenseries-true/
7661 * True by default
7662 * @sample {highstock} stock/chart/ignorehiddenseries-false/
7663 * False
7664 * @default true
7665 * @since 1.2.0
7666 * @product highcharts highstock
7667 */
7668 ignoreHiddenSeries: true,
7669
7670
7671 /**
7672 * Whether to invert the axes so that the x axis is vertical and y axis
7673 * is horizontal. When `true`, the x axis is [reversed](#xAxis.reversed)
7674 * by default.
7675 *
7676 * @productdesc {highcharts}
7677 * If a bar series is present in the chart, it will be inverted
7678 * automatically. Inverting the chart doesn't have an effect if there
7679 * are no cartesian series in the chart, or if the chart is
7680 * [polar](#chart.polar).
7681 *
7682 * @type {Boolean}
7683 * @sample {highcharts} highcharts/chart/inverted/
7684 * Inverted line
7685 * @sample {highstock} stock/navigator/inverted/
7686 * Inverted stock chart
7687 * @default false
7688 * @product highcharts highstock
7689 * @apioption chart.inverted
7690 */
7691
7692 /**
7693 * The distance between the outer edge of the chart and the content,
7694 * like title or legend, or axis title and labels if present. The
7695 * numbers in the array designate top, right, bottom and left respectively.
7696 * Use the options spacingTop, spacingRight, spacingBottom and spacingLeft
7697 * options for shorthand setting of one option.
7698 *
7699 * @type {Array<Number>}
7700 * @see [chart.margin](#chart.margin)
7701 * @default [10, 10, 15, 10]
7702 * @since 3.0.6
7703 */
7704 spacing: [10, 10, 15, 10],
7705
7706 /**
7707 * The button that appears after a selection zoom, allowing the user
7708 * to reset zoom.
7709 *
7710 */
7711 resetZoomButton: {
7712
7713 /**
7714 * A collection of attributes for the button. The object takes SVG
7715 * attributes like `fill`, `stroke`, `stroke-width` or `r`, the border
7716 * radius. The theme also supports `style`, a collection of CSS properties
7717 * for the text. Equivalent attributes for the hover state are given
7718 * in `theme.states.hover`.
7719 *
7720 * @type {Object}
7721 * @sample {highcharts} highcharts/chart/resetzoombutton-theme/
7722 * Theming the button
7723 * @sample {highstock} highcharts/chart/resetzoombutton-theme/
7724 * Theming the button
7725 * @since 2.2
7726 */
7727 theme: {
7728
7729 /**
7730 * The Z index for the reset zoom button.
7731 */
7732 zIndex: 20
7733 },
7734
7735 /**
7736 * The position of the button.
7737 *
7738 * @type {Object}
7739 * @sample {highcharts} highcharts/chart/resetzoombutton-position/
7740 * Above the plot area
7741 * @sample {highstock} highcharts/chart/resetzoombutton-position/
7742 * Above the plot area
7743 * @sample {highmaps} highcharts/chart/resetzoombutton-position/
7744 * Above the plot area
7745 * @since 2.2
7746 */
7747 position: {
7748
7749 /**
7750 * The horizontal alignment of the button.
7751 *
7752 * @type {String}
7753 */
7754 align: 'right',
7755
7756 /**
7757 * The horizontal offset of the button.
7758 *
7759 * @type {Number}
7760 */
7761 x: -10,
7762
7763 /**
7764 * The vertical alignment of the button.
7765 *
7766 * @validvalue ["top", "middle", "bottom"]
7767 * @type {String}
7768 * @default top
7769 * @apioption chart.resetZoomButton.position.verticalAlign
7770 */
7771
7772 /**
7773 * The vertical offset of the button.
7774 *
7775 * @type {Number}
7776 */
7777 y: 10
7778 }
7779
7780 /**
7781 * What frame the button should be placed related to. Can be either
7782 * `plot` or `chart`
7783 *
7784 * @validvalue ["plot", "chart"]
7785 * @type {String}
7786 * @sample {highcharts} highcharts/chart/resetzoombutton-relativeto/
7787 * Relative to the chart
7788 * @sample {highstock} highcharts/chart/resetzoombutton-relativeto/
7789 * Relative to the chart
7790 * @default plot
7791 * @since 2.2
7792 * @apioption chart.resetZoomButton.relativeTo
7793 */
7794 },
7795
7796 /**
7797 * An explicit width for the chart. By default (when `null`) the width
7798 * is calculated from the offset width of the containing element.
7799 *
7800 * @type {Number}
7801 * @sample {highcharts} highcharts/chart/width/ 800px wide
7802 * @sample {highstock} stock/chart/width/ 800px wide
7803 * @sample {highmaps} maps/chart/size/ Chart with explicit size
7804 * @default null
7805 */
7806 width: null,
7807
7808 /**
7809 * An explicit height for the chart. If a _number_, the height is
7810 * given in pixels. If given a _percentage string_ (for example `'56%'`),
7811 * the height is given as the percentage of the actual chart width.
7812 * This allows for preserving the aspect ratio across responsive
7813 * sizes.
7814 *
7815 * By default (when `null`) the height is calculated from the offset
7816 * height of the containing element, or 400 pixels if the containing
7817 * element's height is 0.
7818 *
7819 * @type {Number|String}
7820 * @sample {highcharts} highcharts/chart/height/
7821 * 500px height
7822 * @sample {highstock} stock/chart/height/
7823 * 300px height
7824 * @sample {highmaps} maps/chart/size/
7825 * Chart with explicit size
7826 * @sample highcharts/chart/height-percent/
7827 * Highcharts with percentage height
7828 * @default null
7829 */
7830 height: null,
7831
7832
7833
7834 /**
7835 * The color of the outer chart border.
7836 *
7837 * @type {Color}
7838 * @see In styled mode, the stroke is set with the `.highcharts-background`
7839 * class.
7840 * @sample {highcharts} highcharts/chart/bordercolor/ Brown border
7841 * @sample {highstock} stock/chart/border/ Brown border
7842 * @sample {highmaps} maps/chart/border/ Border options
7843 * @default #335cad
7844 */
7845 borderColor: '#335cad',
7846
7847 /**
7848 * The pixel width of the outer chart border.
7849 *
7850 * @type {Number}
7851 * @see In styled mode, the stroke is set with the `.highcharts-background`
7852 * class.
7853 * @sample {highcharts} highcharts/chart/borderwidth/ 5px border
7854 * @sample {highstock} stock/chart/border/
7855 * 2px border
7856 * @sample {highmaps} maps/chart/border/
7857 * Border options
7858 * @default 0
7859 * @apioption chart.borderWidth
7860 */
7861
7862 /**
7863 * The background color or gradient for the outer chart area.
7864 *
7865 * @type {Color}
7866 * @see In styled mode, the background is set with the `.highcharts-background` class.
7867 * @sample {highcharts} highcharts/chart/backgroundcolor-color/ Color
7868 * @sample {highcharts} highcharts/chart/backgroundcolor-gradient/ Gradient
7869 * @sample {highstock} stock/chart/backgroundcolor-color/
7870 * Color
7871 * @sample {highstock} stock/chart/backgroundcolor-gradient/
7872 * Gradient
7873 * @sample {highmaps} maps/chart/backgroundcolor-color/
7874 * Color
7875 * @sample {highmaps} maps/chart/backgroundcolor-gradient/
7876 * Gradient
7877 * @default #FFFFFF
7878 */
7879 backgroundColor: '#ffffff',
7880
7881 /**
7882 * The background color or gradient for the plot area.
7883 *
7884 * @type {Color}
7885 * @see In styled mode, the plot background is set with the `.highcharts-plot-background` class.
7886 * @sample {highcharts} highcharts/chart/plotbackgroundcolor-color/
7887 * Color
7888 * @sample {highcharts} highcharts/chart/plotbackgroundcolor-gradient/
7889 * Gradient
7890 * @sample {highstock} stock/chart/plotbackgroundcolor-color/
7891 * Color
7892 * @sample {highstock} stock/chart/plotbackgroundcolor-gradient/
7893 * Gradient
7894 * @sample {highmaps} maps/chart/plotbackgroundcolor-color/
7895 * Color
7896 * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
7897 * Gradient
7898 * @default null
7899 * @apioption chart.plotBackgroundColor
7900 */
7901
7902
7903 /**
7904 * The URL for an image to use as the plot background. To set an image
7905 * as the background for the entire chart, set a CSS background image
7906 * to the container element. Note that for the image to be applied to
7907 * exported charts, its URL needs to be accessible by the export server.
7908 *
7909 * @type {String}
7910 * @see In styled mode, a plot background image can be set with the
7911 * `.highcharts-plot-background` class and a [custom pattern](http://www.
7912 * highcharts.com/docs/chart-design-and-style/gradients-shadows-and-
7913 * patterns).
7914 * @sample {highcharts} highcharts/chart/plotbackgroundimage/ Skies
7915 * @sample {highstock} stock/chart/plotbackgroundimage/ Skies
7916 * @default null
7917 * @apioption chart.plotBackgroundImage
7918 */
7919
7920 /**
7921 * The color of the inner chart or plot area border.
7922 *
7923 * @type {Color}
7924 * @see In styled mode, a plot border stroke can be set with the `.
7925 * highcharts-plot-border` class.
7926 * @sample {highcharts} highcharts/chart/plotbordercolor/ Blue border
7927 * @sample {highstock} stock/chart/plotborder/ Blue border
7928 * @sample {highmaps} maps/chart/plotborder/ Plot border options
7929 * @default #cccccc
7930 */
7931 plotBorderColor: '#cccccc'
7932
7933
7934 /**
7935 * The pixel width of the plot area border.
7936 *
7937 * @type {Number}
7938 * @sample {highcharts} highcharts/chart/plotborderwidth/ 1px border
7939 * @sample {highstock} stock/chart/plotborder/
7940 * 2px border
7941 * @sample {highmaps} maps/chart/plotborder/
7942 * Plot border options
7943 * @default 0
7944 * @apioption chart.plotBorderWidth
7945 */
7946
7947 /**
7948 * Whether to apply a drop shadow to the plot area. Requires that
7949 * plotBackgroundColor be set. The shadow can be an object configuration
7950 * containing `color`, `offsetX`, `offsetY`, `opacity` and `width`.
7951 *
7952 * @type {Boolean|Object}
7953 * @sample {highcharts} highcharts/chart/plotshadow/ Plot shadow
7954 * @sample {highstock} stock/chart/plotshadow/
7955 * Plot shadow
7956 * @sample {highmaps} maps/chart/plotborder/
7957 * Plot border options
7958 * @default false
7959 * @apioption chart.plotShadow
7960 */
7961
7962 /**
7963 * When true, cartesian charts like line, spline, area and column are
7964 * transformed into the polar coordinate system. Requires `highcharts-
7965 * more.js`.
7966 *
7967 * @type {Boolean}
7968 * @default false
7969 * @since 2.3.0
7970 * @product highcharts
7971 * @apioption chart.polar
7972 */
7973
7974 /**
7975 * Whether to reflow the chart to fit the width of the container div
7976 * on resizing the window.
7977 *
7978 * @type {Boolean}
7979 * @sample {highcharts} highcharts/chart/reflow-true/ True by default
7980 * @sample {highcharts} highcharts/chart/reflow-false/ False
7981 * @sample {highstock} stock/chart/reflow-true/
7982 * True by default
7983 * @sample {highstock} stock/chart/reflow-false/
7984 * False
7985 * @sample {highmaps} maps/chart/reflow-true/
7986 * True by default
7987 * @sample {highmaps} maps/chart/reflow-false/
7988 * False
7989 * @default true
7990 * @since 2.1
7991 * @apioption chart.reflow
7992 */
7993
7994
7995
7996
7997 /**
7998 * The HTML element where the chart will be rendered. If it is a string,
7999 * the element by that id is used. The HTML element can also be passed
8000 * by direct reference, or as the first argument of the chart constructor,
8001 * in which case the option is not needed.
8002 *
8003 * @type {String|Object}
8004 * @sample {highcharts} highcharts/chart/reflow-true/
8005 * String
8006 * @sample {highcharts} highcharts/chart/renderto-object/
8007 * Object reference
8008 * @sample {highcharts} highcharts/chart/renderto-jquery/
8009 * Object reference through jQuery
8010 * @sample {highstock} stock/chart/renderto-string/
8011 * String
8012 * @sample {highstock} stock/chart/renderto-object/
8013 * Object reference
8014 * @sample {highstock} stock/chart/renderto-jquery/
8015 * Object reference through jQuery
8016 * @apioption chart.renderTo
8017 */
8018
8019 /**
8020 * The background color of the marker square when selecting (zooming
8021 * in on) an area of the chart.
8022 *
8023 * @type {Color}
8024 * @see In styled mode, the selection marker fill is set with the
8025 * `.highcharts-selection-marker` class.
8026 * @default rgba(51,92,173,0.25)
8027 * @since 2.1.7
8028 * @apioption chart.selectionMarkerFill
8029 */
8030
8031 /**
8032 * Whether to apply a drop shadow to the outer chart area. Requires
8033 * that backgroundColor be set. The shadow can be an object configuration
8034 * containing `color`, `offsetX`, `offsetY`, `opacity` and `width`.
8035 *
8036 * @type {Boolean|Object}
8037 * @sample {highcharts} highcharts/chart/shadow/ Shadow
8038 * @sample {highstock} stock/chart/shadow/
8039 * Shadow
8040 * @sample {highmaps} maps/chart/border/
8041 * Chart border and shadow
8042 * @default false
8043 * @apioption chart.shadow
8044 */
8045
8046 /**
8047 * Whether to show the axes initially. This only applies to empty charts
8048 * where series are added dynamically, as axes are automatically added
8049 * to cartesian series.
8050 *
8051 * @type {Boolean}
8052 * @sample {highcharts} highcharts/chart/showaxes-false/ False by default
8053 * @sample {highcharts} highcharts/chart/showaxes-true/ True
8054 * @since 1.2.5
8055 * @product highcharts
8056 * @apioption chart.showAxes
8057 */
8058
8059 /**
8060 * The space between the bottom edge of the chart and the content (plot
8061 * area, axis title and labels, title, subtitle or legend in top position).
8062 *
8063 * @type {Number}
8064 * @sample {highcharts} highcharts/chart/spacingbottom/
8065 * Spacing bottom set to 100
8066 * @sample {highstock} stock/chart/spacingbottom/
8067 * Spacing bottom set to 100
8068 * @sample {highmaps} maps/chart/spacing/
8069 * Spacing 100 all around
8070 * @default 15
8071 * @since 2.1
8072 * @apioption chart.spacingBottom
8073 */
8074
8075 /**
8076 * The space between the left edge of the chart and the content (plot
8077 * area, axis title and labels, title, subtitle or legend in top position).
8078 *
8079 * @type {Number}
8080 * @sample {highcharts} highcharts/chart/spacingleft/
8081 * Spacing left set to 100
8082 * @sample {highstock} stock/chart/spacingleft/
8083 * Spacing left set to 100
8084 * @sample {highmaps} maps/chart/spacing/
8085 * Spacing 100 all around
8086 * @default 10
8087 * @since 2.1
8088 * @apioption chart.spacingLeft
8089 */
8090
8091 /**
8092 * The space between the right edge of the chart and the content (plot
8093 * area, axis title and labels, title, subtitle or legend in top
8094 * position).
8095 *
8096 * @type {Number}
8097 * @sample {highcharts} highcharts/chart/spacingright-100/
8098 * Spacing set to 100
8099 * @sample {highcharts} highcharts/chart/spacingright-legend/
8100 * Legend in right position with default spacing
8101 * @sample {highstock} stock/chart/spacingright/
8102 * Spacing set to 100
8103 * @sample {highmaps} maps/chart/spacing/
8104 * Spacing 100 all around
8105 * @default 10
8106 * @since 2.1
8107 * @apioption chart.spacingRight
8108 */
8109
8110 /**
8111 * The space between the top edge of the chart and the content (plot
8112 * area, axis title and labels, title, subtitle or legend in top
8113 * position).
8114 *
8115 * @type {Number}
8116 * @sample {highcharts} highcharts/chart/spacingtop-100/
8117 * A top spacing of 100
8118 * @sample {highcharts} highcharts/chart/spacingtop-10/
8119 * Floating chart title makes the plot area align to the default
8120 * spacingTop of 10.
8121 * @sample {highstock} stock/chart/spacingtop/
8122 * A top spacing of 100
8123 * @sample {highmaps} maps/chart/spacing/
8124 * Spacing 100 all around
8125 * @default 10
8126 * @since 2.1
8127 * @apioption chart.spacingTop
8128 */
8129
8130 /**
8131 * Additional CSS styles to apply inline to the container `div`. Note
8132 * that since the default font styles are applied in the renderer, it
8133 * is ignorant of the individual chart options and must be set globally.
8134 *
8135 * @type {CSSObject}
8136 * @see In styled mode, general chart styles can be set with the `.highcharts-root` class.
8137 * @sample {highcharts} highcharts/chart/style-serif-font/
8138 * Using a serif type font
8139 * @sample {highcharts} highcharts/css/em/
8140 * Styled mode with relative font sizes
8141 * @sample {highstock} stock/chart/style/
8142 * Using a serif type font
8143 * @sample {highmaps} maps/chart/style-serif-font/
8144 * Using a serif type font
8145 * @default {"fontFamily":"\"Lucida Grande\", \"Lucida Sans Unicode\", Verdana, Arial, Helvetica, sans-serif","fontSize":"12px"}
8146 * @apioption chart.style
8147 */
8148
8149 /**
8150 * The default series type for the chart. Can be any of the chart types
8151 * listed under [plotOptions](#plotOptions).
8152 *
8153 * @validvalue ["line", "spline", "column", "bar", "area", "areaspline", "pie", "arearange", "areasplinerange", "boxplot", "bubble", "columnrange", "errorbar", "funnel", "gauge", "heatmap", "polygon", "pyramid", "scatter", "solidgauge", "treemap", "waterfall"]
8154 * @type {String}
8155 * @sample {highcharts} highcharts/chart/type-bar/ Bar
8156 * @sample {highstock} stock/chart/type/
8157 * Areaspline
8158 * @sample {highmaps} maps/chart/type-mapline/
8159 * Mapline
8160 * @default {highcharts} line
8161 * @default {highstock} line
8162 * @default {highmaps} map
8163 * @since 2.1.0
8164 * @apioption chart.type
8165 */
8166
8167 /**
8168 * Decides in what dimensions the user can zoom by dragging the mouse.
8169 * Can be one of `x`, `y` or `xy`.
8170 *
8171 * @validvalue [null, "x", "y", "xy"]
8172 * @type {String}
8173 * @see [panKey](#chart.panKey)
8174 * @sample {highcharts} highcharts/chart/zoomtype-none/ None by default
8175 * @sample {highcharts} highcharts/chart/zoomtype-x/ X
8176 * @sample {highcharts} highcharts/chart/zoomtype-y/ Y
8177 * @sample {highcharts} highcharts/chart/zoomtype-xy/ Xy
8178 * @sample {highstock} stock/demo/basic-line/ None by default
8179 * @sample {highstock} stock/chart/zoomtype-x/ X
8180 * @sample {highstock} stock/chart/zoomtype-y/ Y
8181 * @sample {highstock} stock/chart/zoomtype-xy/ Xy
8182 * @product highcharts highstock
8183 * @apioption chart.zoomType
8184 */
8185 },
8186
8187 /**
8188 * The chart's main title.
8189 *
8190 * @sample {highmaps} maps/title/title/ Title options demonstrated
8191 */
8192 title: {
8193
8194 /**
8195 * The title of the chart. To disable the title, set the `text` to
8196 * `null`.
8197 *
8198 * @type {String}
8199 * @sample {highcharts} highcharts/title/text/ Custom title
8200 * @sample {highstock} stock/chart/title-text/ Custom title
8201 * @default {highcharts|highmaps} Chart title
8202 * @default {highstock} null
8203 */
8204 text: 'Chart title',
8205
8206 /**
8207 * The horizontal alignment of the title. Can be one of "left", "center"
8208 * and "right".
8209 *
8210 * @validvalue ["left", "center", "right"]
8211 * @type {String}
8212 * @sample {highcharts} highcharts/title/align/ Aligned to the plot area (x = 70px = margin left - spacing left)
8213 * @sample {highstock} stock/chart/title-align/ Aligned to the plot area (x = 50px = margin left - spacing left)
8214 * @default center
8215 * @since 2.0
8216 */
8217 align: 'center',
8218
8219 /**
8220 * The margin between the title and the plot area, or if a subtitle
8221 * is present, the margin between the subtitle and the plot area.
8222 *
8223 * @type {Number}
8224 * @sample {highcharts} highcharts/title/margin-50/ A chart title margin of 50
8225 * @sample {highcharts} highcharts/title/margin-subtitle/ The same margin applied with a subtitle
8226 * @sample {highstock} stock/chart/title-margin/ A chart title margin of 50
8227 * @default 15
8228 * @since 2.1
8229 */
8230 margin: 15,
8231
8232 /**
8233 * Adjustment made to the title width, normally to reserve space for
8234 * the exporting burger menu.
8235 *
8236 * @type {Number}
8237 * @sample {highcharts} highcharts/title/widthadjust/ Wider menu, greater padding
8238 * @sample {highstock} highcharts/title/widthadjust/ Wider menu, greater padding
8239 * @sample {highmaps} highcharts/title/widthadjust/ Wider menu, greater padding
8240 * @default -44
8241 * @since 4.2.5
8242 */
8243 widthAdjust: -44
8244
8245 /**
8246 * When the title is floating, the plot area will not move to make space
8247 * for it.
8248 *
8249 * @type {Boolean}
8250 * @sample {highcharts} highcharts/chart/zoomtype-none/ False by default
8251 * @sample {highcharts} highcharts/title/floating/
8252 * True - title on top of the plot area
8253 * @sample {highstock} stock/chart/title-floating/
8254 * True - title on top of the plot area
8255 * @default false
8256 * @since 2.1
8257 * @apioption title.floating
8258 */
8259
8260 /**
8261 * CSS styles for the title. Use this for font styling, but use `align`,
8262 * `x` and `y` for text alignment.
8263 *
8264 * In styled mode, the title style is given in the `.highcharts-title` class.
8265 *
8266 * @type {CSSObject}
8267 * @sample {highcharts} highcharts/title/style/ Custom color and weight
8268 * @sample {highstock} stock/chart/title-style/ Custom color and weight
8269 * @sample highcharts/css/titles/ Styled mode
8270 * @default {highcharts|highmaps} { "color": "#333333", "fontSize": "18px" }
8271 * @default {highstock} { "color": "#333333", "fontSize": "16px" }
8272 * @apioption title.style
8273 */
8274
8275 /**
8276 * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
8277 * and-string-formatting#html) to render the text.
8278 *
8279 * @type {Boolean}
8280 * @default false
8281 * @apioption title.useHTML
8282 */
8283
8284 /**
8285 * The vertical alignment of the title. Can be one of `"top"`, `"middle"`
8286 * and `"bottom"`. When a value is given, the title behaves as if [floating](#title.
8287 * floating) were `true`.
8288 *
8289 * @validvalue ["top", "middle", "bottom"]
8290 * @type {String}
8291 * @sample {highcharts} highcharts/title/verticalalign/
8292 * Chart title in bottom right corner
8293 * @sample {highstock} stock/chart/title-verticalalign/
8294 * Chart title in bottom right corner
8295 * @since 2.1
8296 * @apioption title.verticalAlign
8297 */
8298
8299 /**
8300 * The x position of the title relative to the alignment within chart.
8301 * spacingLeft and chart.spacingRight.
8302 *
8303 * @type {Number}
8304 * @sample {highcharts} highcharts/title/align/
8305 * Aligned to the plot area (x = 70px = margin left - spacing left)
8306 * @sample {highstock} stock/chart/title-align/
8307 * Aligned to the plot area (x = 50px = margin left - spacing left)
8308 * @default 0
8309 * @since 2.0
8310 * @apioption title.x
8311 */
8312
8313 /**
8314 * The y position of the title relative to the alignment within [chart.
8315 * spacingTop](#chart.spacingTop) and [chart.spacingBottom](#chart.spacingBottom).
8316 * By default it depends on the font size.
8317 *
8318 * @type {Number}
8319 * @sample {highcharts} highcharts/title/y/
8320 * Title inside the plot area
8321 * @sample {highstock} stock/chart/title-verticalalign/
8322 * Chart title in bottom right corner
8323 * @since 2.0
8324 * @apioption title.y
8325 */
8326
8327 },
8328
8329 /**
8330 * The chart's subtitle. This can be used both to display a subtitle below
8331 * the main title, and to display random text anywhere in the chart. The
8332 * subtitle can be updated after chart initialization through the
8333 * `Chart.setTitle` method.
8334 *
8335 * @sample {highmaps} maps/title/subtitle/ Subtitle options demonstrated
8336 */
8337 subtitle: {
8338
8339 /**
8340 * The subtitle of the chart.
8341 *
8342 * @type {String}
8343 * @sample {highcharts} highcharts/subtitle/text/ Custom subtitle
8344 * @sample {highcharts} highcharts/subtitle/text-formatted/ Formatted and linked text.
8345 * @sample {highstock} stock/chart/subtitle-text Custom subtitle
8346 * @sample {highstock} stock/chart/subtitle-text-formatted Formatted and linked text.
8347 */
8348 text: '',
8349
8350 /**
8351 * The horizontal alignment of the subtitle. Can be one of "left",
8352 * "center" and "right".
8353 *
8354 * @validvalue ["left", "center", "right"]
8355 * @type {String}
8356 * @sample {highcharts} highcharts/subtitle/align/ Footnote at right of plot area
8357 * @sample {highstock} stock/chart/subtitle-footnote Footnote at bottom right of plot area
8358 * @default center
8359 * @since 2.0
8360 */
8361 align: 'center',
8362
8363 /**
8364 * Adjustment made to the subtitle width, normally to reserve space
8365 * for the exporting burger menu.
8366 *
8367 * @type {Number}
8368 * @see [title.widthAdjust](#title.widthAdjust)
8369 * @sample {highcharts} highcharts/title/widthadjust/ Wider menu, greater padding
8370 * @sample {highstock} highcharts/title/widthadjust/ Wider menu, greater padding
8371 * @sample {highmaps} highcharts/title/widthadjust/ Wider menu, greater padding
8372 * @default -44
8373 * @since 4.2.5
8374 */
8375 widthAdjust: -44
8376
8377 /**
8378 * When the subtitle is floating, the plot area will not move to make
8379 * space for it.
8380 *
8381 * @type {Boolean}
8382 * @sample {highcharts} highcharts/subtitle/floating/
8383 * Floating title and subtitle
8384 * @sample {highstock} stock/chart/subtitle-footnote
8385 * Footnote floating at bottom right of plot area
8386 * @default false
8387 * @since 2.1
8388 * @apioption subtitle.floating
8389 */
8390
8391 /**
8392 * CSS styles for the title.
8393 *
8394 * In styled mode, the subtitle style is given in the `.highcharts-subtitle` class.
8395 *
8396 * @type {CSSObject}
8397 * @sample {highcharts} highcharts/subtitle/style/
8398 * Custom color and weight
8399 * @sample {highcharts} highcharts/css/titles/
8400 * Styled mode
8401 * @sample {highstock} stock/chart/subtitle-style
8402 * Custom color and weight
8403 * @sample {highstock} highcharts/css/titles/
8404 * Styled mode
8405 * @sample {highmaps} highcharts/css/titles/
8406 * Styled mode
8407 * @default { "color": "#666666" }
8408 * @apioption subtitle.style
8409 */
8410
8411 /**
8412 * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
8413 * and-string-formatting#html) to render the text.
8414 *
8415 * @type {Boolean}
8416 * @default false
8417 * @apioption subtitle.useHTML
8418 */
8419
8420 /**
8421 * The vertical alignment of the title. Can be one of "top", "middle"
8422 * and "bottom". When a value is given, the title behaves as floating.
8423 *
8424 * @validvalue ["top", "middle", "bottom"]
8425 * @type {String}
8426 * @sample {highcharts} highcharts/subtitle/verticalalign/
8427 * Footnote at the bottom right of plot area
8428 * @sample {highstock} stock/chart/subtitle-footnote
8429 * Footnote at the bottom right of plot area
8430 * @default
8431 * @since 2.1
8432 * @apioption subtitle.verticalAlign
8433 */
8434
8435 /**
8436 * The x position of the subtitle relative to the alignment within chart.
8437 * spacingLeft and chart.spacingRight.
8438 *
8439 * @type {Number}
8440 * @sample {highcharts} highcharts/subtitle/align/
8441 * Footnote at right of plot area
8442 * @sample {highstock} stock/chart/subtitle-footnote
8443 * Footnote at the bottom right of plot area
8444 * @default 0
8445 * @since 2.0
8446 * @apioption subtitle.x
8447 */
8448
8449 /**
8450 * The y position of the subtitle relative to the alignment within chart.
8451 * spacingTop and chart.spacingBottom. By default the subtitle is laid
8452 * out below the title unless the title is floating.
8453 *
8454 * @type {Number}
8455 * @sample {highcharts} highcharts/subtitle/verticalalign/
8456 * Footnote at the bottom right of plot area
8457 * @sample {highstock} stock/chart/subtitle-footnote
8458 * Footnote at the bottom right of plot area
8459 * @default {highcharts} null
8460 * @default {highstock} null
8461 * @default {highmaps}
8462 * @since 2.0
8463 * @apioption subtitle.y
8464 */
8465 },
8466
8467 /**
8468 * The plotOptions is a wrapper object for config objects for each series
8469 * type. The config objects for each series can also be overridden for
8470 * each series item as given in the series array.
8471 *
8472 * Configuration options for the series are given in three levels. Options
8473 * for all series in a chart are given in the [plotOptions.series](#plotOptions.
8474 * series) object. Then options for all series of a specific type are
8475 * given in the plotOptions of that type, for example plotOptions.line.
8476 * Next, options for one single series are given in [the series array](#series).
8477 *
8478 */
8479 plotOptions: {},
8480
8481 /**
8482 * HTML labels that can be positioned anywhere in the chart area.
8483 *
8484 */
8485 labels: {
8486
8487 /**
8488 * A HTML label that can be positioned anywhere in the chart area.
8489 *
8490 * @type {Array<Object>}
8491 * @apioption labels.items
8492 */
8493
8494 /**
8495 * Inner HTML or text for the label.
8496 *
8497 * @type {String}
8498 * @apioption labels.items.html
8499 */
8500
8501 /**
8502 * CSS styles for each label. To position the label, use left and top
8503 * like this:
8504 *
8505 * <pre>style: {
8506 * left: '100px',
8507 * top: '100px'
8508 * }</pre>
8509 *
8510 * @type {CSSObject}
8511 * @apioption labels.items.style
8512 */
8513
8514 /**
8515 * Shared CSS styles for all labels.
8516 *
8517 * @type {CSSObject}
8518 * @default { "color": "#333333" }
8519 */
8520 style: {
8521 position: 'absolute',
8522 color: '#333333'
8523 }
8524 },
8525
8526 /**
8527 * The legend is a box containing a symbol and name for each series
8528 * item or point item in the chart. Each series (or points in case
8529 * of pie charts) is represented by a symbol and its name in the legend.
8530 *
8531 * It is possible to override the symbol creator function and
8532 * create [custom legend symbols](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/studies/legend-
8533 * custom-symbol/).
8534 *
8535 * @productdesc {highmaps}
8536 * A Highmaps legend by default contains one legend item per series, but if
8537 * a `colorAxis` is defined, the axis will be displayed in the legend.
8538 * Either as a gradient, or as multiple legend items for `dataClasses`.
8539 */
8540 legend: {
8541
8542 /**
8543 * The background color of the legend.
8544 *
8545 * @type {Color}
8546 * @see In styled mode, the legend background fill can be applied with
8547 * the `.highcharts-legend-box` class.
8548 * @sample {highcharts} highcharts/legend/backgroundcolor/ Yellowish background
8549 * @sample {highstock} stock/legend/align/ Various legend options
8550 * @sample {highmaps} maps/legend/border-background/ Border and background options
8551 * @apioption legend.backgroundColor
8552 */
8553
8554 /**
8555 * The width of the drawn border around the legend.
8556 *
8557 * @type {Number}
8558 * @see In styled mode, the legend border stroke width can be applied
8559 * with the `.highcharts-legend-box` class.
8560 * @sample {highcharts} highcharts/legend/borderwidth/ 2px border width
8561 * @sample {highstock} stock/legend/align/ Various legend options
8562 * @sample {highmaps} maps/legend/border-background/ Border and background options
8563 * @default 0
8564 * @apioption legend.borderWidth
8565 */
8566
8567 /**
8568 * Enable or disable the legend.
8569 *
8570 * @type {Boolean}
8571 * @sample {highcharts} highcharts/legend/enabled-false/ Legend disabled
8572 * @sample {highstock} stock/legend/align/ Various legend options
8573 * @sample {highmaps} maps/legend/enabled-false/ Legend disabled
8574 * @default {highstock} false
8575 * @default {highmaps} true
8576 */
8577 enabled: true,
8578
8579 /**
8580 * The horizontal alignment of the legend box within the chart area.
8581 * Valid values are `left`, `center` and `right`.
8582 *
8583 * In the case that the legend is aligned in a corner position, the
8584 * `layout` option will determine whether to place it above/below
8585 * or on the side of the plot area.
8586 *
8587 * @validvalue ["left", "center", "right"]
8588 * @type {String}
8589 * @sample {highcharts} highcharts/legend/align/
8590 * Legend at the right of the chart
8591 * @sample {highstock} stock/legend/align/
8592 * Various legend options
8593 * @sample {highmaps} maps/legend/alignment/
8594 * Legend alignment
8595 * @since 2.0
8596 */
8597 align: 'center',
8598
8599 /**
8600 * When the legend is floating, the plot area ignores it and is allowed
8601 * to be placed below it.
8602 *
8603 * @type {Boolean}
8604 * @sample {highcharts} highcharts/legend/floating-false/ False by default
8605 * @sample {highcharts} highcharts/legend/floating-true/ True
8606 * @sample {highmaps} maps/legend/alignment/ Floating legend
8607 * @default false
8608 * @since 2.1
8609 * @apioption legend.floating
8610 */
8611
8612 /**
8613 * The layout of the legend items. Can be one of "horizontal" or "vertical".
8614 *
8615 * @validvalue ["horizontal", "vertical"]
8616 * @type {String}
8617 * @sample {highcharts} highcharts/legend/layout-horizontal/ Horizontal by default
8618 * @sample {highcharts} highcharts/legend/layout-vertical/ Vertical
8619 * @sample {highstock} stock/legend/layout-horizontal/ Horizontal by default
8620 * @sample {highmaps} maps/legend/padding-itemmargin/ Vertical with data classes
8621 * @sample {highmaps} maps/legend/layout-vertical/ Vertical with color axis gradient
8622 * @default horizontal
8623 */
8624 layout: 'horizontal',
8625
8626 /**
8627 * In a legend with horizontal layout, the itemDistance defines the
8628 * pixel distance between each item.
8629 *
8630 * @type {Number}
8631 * @sample {highcharts} highcharts/legend/layout-horizontal/ 50px item distance
8632 * @sample {highstock} highcharts/legend/layout-horizontal/ 50px item distance
8633 * @default {highcharts} 20
8634 * @default {highstock} 20
8635 * @default {highmaps} 8
8636 * @since 3.0.3
8637 * @apioption legend.itemDistance
8638 */
8639
8640 /**
8641 * The pixel bottom margin for each legend item.
8642 *
8643 * @type {Number}
8644 * @sample {highcharts} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
8645 * @sample {highstock} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
8646 * @sample {highmaps} maps/legend/padding-itemmargin/ Padding and item margins demonstrated
8647 * @default 0
8648 * @since 2.2.0
8649 * @apioption legend.itemMarginBottom
8650 */
8651
8652 /**
8653 * The pixel top margin for each legend item.
8654 *
8655 * @type {Number}
8656 * @sample {highcharts} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
8657 * @sample {highstock} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
8658 * @sample {highmaps} maps/legend/padding-itemmargin/ Padding and item margins demonstrated
8659 * @default 0
8660 * @since 2.2.0
8661 * @apioption legend.itemMarginTop
8662 */
8663
8664 /**
8665 * The width for each legend item. This is useful in a horizontal layout
8666 * with many items when you want the items to align vertically. .
8667 *
8668 * @type {Number}
8669 * @sample {highcharts} highcharts/legend/itemwidth-default/ Null by default
8670 * @sample {highcharts} highcharts/legend/itemwidth-80/ 80 for aligned legend items
8671 * @default null
8672 * @since 2.0
8673 * @apioption legend.itemWidth
8674 */
8675
8676 /**
8677 * A [format string](http://www.highcharts.com/docs/chart-concepts/labels-
8678 * and-string-formatting) for each legend label. Available variables
8679 * relates to properties on the series, or the point in case of pies.
8680 *
8681 * @type {String}
8682 * @default {name}
8683 * @since 1.3
8684 * @apioption legend.labelFormat
8685 */
8686
8687 /**
8688 * Callback function to format each of the series' labels. The `this`
8689 * keyword refers to the series object, or the point object in case
8690 * of pie charts. By default the series or point name is printed.
8691 *
8692 * @productdesc {highmaps}
8693 * In Highmaps the context can also be a data class in case
8694 * of a `colorAxis`.
8695 *
8696 * @type {Function}
8697 * @sample {highcharts} highcharts/legend/labelformatter/ Add text
8698 * @sample {highmaps} maps/legend/labelformatter/ Data classes with label formatter
8699 * @context {Series|Point}
8700 */
8701 labelFormatter: function() {
8702 return this.name;
8703 },
8704
8705 /**
8706 * Line height for the legend items. Deprecated as of 2.1\. Instead,
8707 * the line height for each item can be set using itemStyle.lineHeight,
8708 * and the padding between items using itemMarginTop and itemMarginBottom.
8709 *
8710 * @type {Number}
8711 * @sample {highcharts} highcharts/legend/lineheight/ Setting padding
8712 * @default 16
8713 * @since 2.0
8714 * @product highcharts
8715 * @apioption legend.lineHeight
8716 */
8717
8718 /**
8719 * If the plot area sized is calculated automatically and the legend
8720 * is not floating, the legend margin is the space between the legend
8721 * and the axis labels or plot area.
8722 *
8723 * @type {Number}
8724 * @sample {highcharts} highcharts/legend/margin-default/ 12 pixels by default
8725 * @sample {highcharts} highcharts/legend/margin-30/ 30 pixels
8726 * @default 12
8727 * @since 2.1
8728 * @apioption legend.margin
8729 */
8730
8731 /**
8732 * Maximum pixel height for the legend. When the maximum height is extended,
8733 * navigation will show.
8734 *
8735 * @type {Number}
8736 * @default undefined
8737 * @since 2.3.0
8738 * @apioption legend.maxHeight
8739 */
8740
8741 /**
8742 * The color of the drawn border around the legend.
8743 *
8744 * @type {Color}
8745 * @see In styled mode, the legend border stroke can be applied with
8746 * the `.highcharts-legend-box` class.
8747 * @sample {highcharts} highcharts/legend/bordercolor/ Brown border
8748 * @sample {highstock} stock/legend/align/ Various legend options
8749 * @sample {highmaps} maps/legend/border-background/ Border and background options
8750 * @default #999999
8751 */
8752 borderColor: '#999999',
8753
8754 /**
8755 * The border corner radius of the legend.
8756 *
8757 * @type {Number}
8758 * @sample {highcharts} highcharts/legend/borderradius-default/ Square by default
8759 * @sample {highcharts} highcharts/legend/borderradius-round/ 5px rounded
8760 * @sample {highmaps} maps/legend/border-background/ Border and background options
8761 * @default 0
8762 */
8763 borderRadius: 0,
8764
8765 /**
8766 * Options for the paging or navigation appearing when the legend
8767 * is overflown. Navigation works well on screen, but not in static
8768 * exported images. One way of working around that is to [increase
8769 * the chart height in export](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/legend/navigation-
8770 * enabled-false/).
8771 *
8772 */
8773 navigation: {
8774
8775
8776 /**
8777 * The color for the active up or down arrow in the legend page navigation.
8778 *
8779 * @type {Color}
8780 * @see In styled mode, the active arrow be styled with the `.highcharts-legend-nav-active` class.
8781 * @sample {highcharts} highcharts/legend/navigation/ Legend page navigation demonstrated
8782 * @sample {highstock} highcharts/legend/navigation/ Legend page navigation demonstrated
8783 * @default #003399
8784 * @since 2.2.4
8785 */
8786 activeColor: '#003399',
8787
8788 /**
8789 * The color of the inactive up or down arrow in the legend page
8790 * navigation. .
8791 *
8792 * @type {Color}
8793 * @see In styled mode, the inactive arrow be styled with the
8794 * `.highcharts-legend-nav-inactive` class.
8795 * @sample {highcharts} highcharts/legend/navigation/
8796 * Legend page navigation demonstrated
8797 * @sample {highstock} highcharts/legend/navigation/
8798 * Legend page navigation demonstrated
8799 * @default {highcharts} #cccccc
8800 * @default {highstock} #cccccc
8801 * @default {highmaps} ##cccccc
8802 * @since 2.2.4
8803 */
8804 inactiveColor: '#cccccc'
8805
8806
8807 /**
8808 * How to animate the pages when navigating up or down. A value of `true`
8809 * applies the default navigation given in the chart.animation option.
8810 * Additional options can be given as an object containing values for
8811 * easing and duration.
8812 *
8813 * @type {Boolean|Object}
8814 * @sample {highcharts} highcharts/legend/navigation/
8815 * Legend page navigation demonstrated
8816 * @sample {highstock} highcharts/legend/navigation/
8817 * Legend page navigation demonstrated
8818 * @default true
8819 * @since 2.2.4
8820 * @apioption legend.navigation.animation
8821 */
8822
8823 /**
8824 * The pixel size of the up and down arrows in the legend paging
8825 * navigation.
8826 *
8827 * @type {Number}
8828 * @sample {highcharts} highcharts/legend/navigation/
8829 * Legend page navigation demonstrated
8830 * @sample {highstock} highcharts/legend/navigation/
8831 * Legend page navigation demonstrated
8832 * @default 12
8833 * @since 2.2.4
8834 * @apioption legend.navigation.arrowSize
8835 */
8836
8837 /**
8838 * Whether to enable the legend navigation. In most cases, disabling
8839 * the navigation results in an unwanted overflow.
8840 *
8841 * See also the [adapt chart to legend](http://www.highcharts.com/plugin-
8842 * registry/single/8/Adapt-Chart-To-Legend) plugin for a solution to
8843 * extend the chart height to make room for the legend, optionally in
8844 * exported charts only.
8845 *
8846 * @type {Boolean}
8847 * @default true
8848 * @since 4.2.4
8849 * @apioption legend.navigation.enabled
8850 */
8851
8852 /**
8853 * Text styles for the legend page navigation.
8854 *
8855 * @type {CSSObject}
8856 * @see In styled mode, the navigation items are styled with the
8857 * `.highcharts-legend-navigation` class.
8858 * @sample {highcharts} highcharts/legend/navigation/
8859 * Legend page navigation demonstrated
8860 * @sample {highstock} highcharts/legend/navigation/
8861 * Legend page navigation demonstrated
8862 * @since 2.2.4
8863 * @apioption legend.navigation.style
8864 */
8865 },
8866
8867 /**
8868 * The inner padding of the legend box.
8869 *
8870 * @type {Number}
8871 * @sample {highcharts} highcharts/legend/padding-itemmargin/
8872 * Padding and item margins demonstrated
8873 * @sample {highstock} highcharts/legend/padding-itemmargin/
8874 * Padding and item margins demonstrated
8875 * @sample {highmaps} maps/legend/padding-itemmargin/
8876 * Padding and item margins demonstrated
8877 * @default 8
8878 * @since 2.2.0
8879 * @apioption legend.padding
8880 */
8881
8882 /**
8883 * Whether to reverse the order of the legend items compared to the
8884 * order of the series or points as defined in the configuration object.
8885 *
8886 * @type {Boolean}
8887 * @see [yAxis.reversedStacks](#yAxis.reversedStacks),
8888 * [series.legendIndex](#series.legendIndex)
8889 * @sample {highcharts} highcharts/legend/reversed/
8890 * Stacked bar with reversed legend
8891 * @default false
8892 * @since 1.2.5
8893 * @apioption legend.reversed
8894 */
8895
8896 /**
8897 * Whether to show the symbol on the right side of the text rather than
8898 * the left side. This is common in Arabic and Hebraic.
8899 *
8900 * @type {Boolean}
8901 * @sample {highcharts} highcharts/legend/rtl/ Symbol to the right
8902 * @default false
8903 * @since 2.2
8904 * @apioption legend.rtl
8905 */
8906
8907 /**
8908 * CSS styles for the legend area. In the 1.x versions the position
8909 * of the legend area was determined by CSS. In 2.x, the position is
8910 * determined by properties like `align`, `verticalAlign`, `x` and `y`,
8911 * but the styles are still parsed for backwards compatibility.
8912 *
8913 * @type {CSSObject}
8914 * @deprecated
8915 * @product highcharts highstock
8916 * @apioption legend.style
8917 */
8918
8919
8920
8921 /**
8922 * CSS styles for each legend item. Only a subset of CSS is supported,
8923 * notably those options related to text. The default `textOverflow`
8924 * property makes long texts truncate. Set it to `null` to wrap text
8925 * instead. A `width` property can be added to control the text width.
8926 *
8927 * @type {CSSObject}
8928 * @see In styled mode, the legend items can be styled with the `.
8929 * highcharts-legend-item` class.
8930 * @sample {highcharts} highcharts/legend/itemstyle/ Bold black text
8931 * @sample {highmaps} maps/legend/itemstyle/ Item text styles
8932 * @default { "color": "#333333", "cursor": "pointer", "fontSize": "12px", "fontWeight": "bold", "textOverflow": "ellipsis" }
8933 */
8934 itemStyle: {
8935 color: '#333333',
8936 fontSize: '12px',
8937 fontWeight: 'bold',
8938 textOverflow: 'ellipsis'
8939 },
8940
8941 /**
8942 * CSS styles for each legend item in hover mode. Only a subset of
8943 * CSS is supported, notably those options related to text. Properties
8944 * are inherited from `style` unless overridden here.
8945 *
8946 * @type {CSSObject}
8947 * @see In styled mode, the hovered legend items can be styled with
8948 * the `.highcharts-legend-item:hover` pesudo-class.
8949 * @sample {highcharts} highcharts/legend/itemhoverstyle/ Red on hover
8950 * @sample {highmaps} maps/legend/itemstyle/ Item text styles
8951 * @default { "color": "#000000" }
8952 */
8953 itemHoverStyle: {
8954 color: '#000000'
8955 },
8956
8957 /**
8958 * CSS styles for each legend item when the corresponding series or
8959 * point is hidden. Only a subset of CSS is supported, notably those
8960 * options related to text. Properties are inherited from `style`
8961 * unless overridden here.
8962 *
8963 * @type {CSSObject}
8964 * @see In styled mode, the hidden legend items can be styled with
8965 * the `.highcharts-legend-item-hidden` class.
8966 * @sample {highcharts} highcharts/legend/itemhiddenstyle/ Darker gray color
8967 * @default { "color": "#cccccc" }
8968 */
8969 itemHiddenStyle: {
8970 color: '#cccccc'
8971 },
8972
8973 /**
8974 * Whether to apply a drop shadow to the legend. A `backgroundColor`
8975 * also needs to be applied for this to take effect. The shadow can be
8976 * an object configuration containing `color`, `offsetX`, `offsetY`,
8977 * `opacity` and `width`.
8978 *
8979 * @type {Boolean|Object}
8980 * @sample {highcharts} highcharts/legend/shadow/
8981 * White background and drop shadow
8982 * @sample {highstock} stock/legend/align/
8983 * Various legend options
8984 * @sample {highmaps} maps/legend/border-background/
8985 * Border and background options
8986 * @default false
8987 */
8988 shadow: false,
8989
8990
8991 /**
8992 * Default styling for the checkbox next to a legend item when
8993 * `showCheckbox` is true.
8994 */
8995 itemCheckboxStyle: {
8996 position: 'absolute',
8997 width: '13px', // for IE precision
8998 height: '13px'
8999 },
9000 // itemWidth: undefined,
9001
9002 /**
9003 * When this is true, the legend symbol width will be the same as
9004 * the symbol height, which in turn defaults to the font size of the
9005 * legend items.
9006 *
9007 * @type {Boolean}
9008 * @default true
9009 * @since 5.0.0
9010 */
9011 squareSymbol: true,
9012
9013 /**
9014 * The pixel height of the symbol for series types that use a rectangle
9015 * in the legend. Defaults to the font size of legend items.
9016 *
9017 * @productdesc {highmaps}
9018 * In Highmaps, when the symbol is the gradient of a vertical color
9019 * axis, the height defaults to 200.
9020 *
9021 * @type {Number}
9022 * @sample {highmaps} maps/legend/layout-vertical-sized/
9023 * Sized vertical gradient
9024 * @sample {highmaps} maps/legend/padding-itemmargin/
9025 * No distance between data classes
9026 * @since 3.0.8
9027 * @apioption legend.symbolHeight
9028 */
9029
9030 /**
9031 * The border radius of the symbol for series types that use a rectangle
9032 * in the legend. Defaults to half the `symbolHeight`.
9033 *
9034 * @type {Number}
9035 * @sample {highcharts} highcharts/legend/symbolradius/ Round symbols
9036 * @sample {highstock} highcharts/legend/symbolradius/ Round symbols
9037 * @sample {highmaps} highcharts/legend/symbolradius/ Round symbols
9038 * @since 3.0.8
9039 * @apioption legend.symbolRadius
9040 */
9041
9042 /**
9043 * The pixel width of the legend item symbol. When the `squareSymbol`
9044 * option is set, this defaults to the `symbolHeight`, otherwise 16.
9045 *
9046 * @productdesc {highmaps}
9047 * In Highmaps, when the symbol is the gradient of a horizontal color
9048 * axis, the width defaults to 200.
9049 *
9050 * @type {Number}
9051 * @sample {highcharts} highcharts/legend/symbolwidth/
9052 * Greater symbol width and padding
9053 * @sample {highmaps} maps/legend/padding-itemmargin/
9054 * Padding and item margins demonstrated
9055 * @sample {highmaps} maps/legend/layout-vertical-sized/
9056 * Sized vertical gradient
9057 * @apioption legend.symbolWidth
9058 */
9059
9060 /**
9061 * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
9062 * and-string-formatting#html) to render the legend item texts. Prior
9063 * to 4.1.7, when using HTML, [legend.navigation](#legend.navigation)
9064 * was disabled.
9065 *
9066 * @type {Boolean}
9067 * @default false
9068 * @apioption legend.useHTML
9069 */
9070
9071 /**
9072 * The width of the legend box.
9073 *
9074 * @type {Number}
9075 * @sample {highcharts} highcharts/legend/width/ Aligned to the plot area
9076 * @default null
9077 * @since 2.0
9078 * @apioption legend.width
9079 */
9080
9081 /**
9082 * The pixel padding between the legend item symbol and the legend
9083 * item text.
9084 *
9085 * @type {Number}
9086 * @sample {highcharts} highcharts/legend/symbolpadding/ Greater symbol width and padding
9087 * @default 5
9088 */
9089 symbolPadding: 5,
9090
9091 /**
9092 * The vertical alignment of the legend box. Can be one of `top`,
9093 * `middle` or `bottom`. Vertical position can be further determined
9094 * by the `y` option.
9095 *
9096 * In the case that the legend is aligned in a corner position, the
9097 * `layout` option will determine whether to place it above/below
9098 * or on the side of the plot area.
9099 *
9100 * @validvalue ["top", "middle", "bottom"]
9101 * @type {String}
9102 * @sample {highcharts} highcharts/legend/verticalalign/ Legend 100px from the top of the chart
9103 * @sample {highstock} stock/legend/align/ Various legend options
9104 * @sample {highmaps} maps/legend/alignment/ Legend alignment
9105 * @default bottom
9106 * @since 2.0
9107 */
9108 verticalAlign: 'bottom',
9109 // width: undefined,
9110
9111 /**
9112 * The x offset of the legend relative to its horizontal alignment
9113 * `align` within chart.spacingLeft and chart.spacingRight. Negative
9114 * x moves it to the left, positive x moves it to the right.
9115 *
9116 * @type {Number}
9117 * @sample {highcharts} highcharts/legend/width/ Aligned to the plot area
9118 * @default 0
9119 * @since 2.0
9120 */
9121 x: 0,
9122
9123 /**
9124 * The vertical offset of the legend relative to it's vertical alignment
9125 * `verticalAlign` within chart.spacingTop and chart.spacingBottom.
9126 * Negative y moves it up, positive y moves it down.
9127 *
9128 * @type {Number}
9129 * @sample {highcharts} highcharts/legend/verticalalign/ Legend 100px from the top of the chart
9130 * @sample {highstock} stock/legend/align/ Various legend options
9131 * @sample {highmaps} maps/legend/alignment/ Legend alignment
9132 * @default 0
9133 * @since 2.0
9134 */
9135 y: 0,
9136
9137 /**
9138 * A title to be added on top of the legend.
9139 *
9140 * @sample {highcharts} highcharts/legend/title/ Legend title
9141 * @sample {highmaps} maps/legend/alignment/ Legend with title
9142 * @since 3.0
9143 */
9144 title: {
9145 /**
9146 * A text or HTML string for the title.
9147 *
9148 * @type {String}
9149 * @default null
9150 * @since 3.0
9151 * @apioption legend.title.text
9152 */
9153
9154
9155
9156 /**
9157 * Generic CSS styles for the legend title.
9158 *
9159 * @type {CSSObject}
9160 * @see In styled mode, the legend title is styled with the
9161 * `.highcharts-legend-title` class.
9162 * @default {"fontWeight":"bold"}
9163 * @since 3.0
9164 */
9165 style: {
9166 fontWeight: 'bold'
9167 }
9168
9169 }
9170 },
9171
9172
9173 /**
9174 * The loading options control the appearance of the loading screen
9175 * that covers the plot area on chart operations. This screen only
9176 * appears after an explicit call to `chart.showLoading()`. It is a
9177 * utility for developers to communicate to the end user that something
9178 * is going on, for example while retrieving new data via an XHR connection.
9179 * The "Loading..." text itself is not part of this configuration
9180 * object, but part of the `lang` object.
9181 *
9182 */
9183 loading: {
9184
9185 /**
9186 * The duration in milliseconds of the fade out effect.
9187 *
9188 * @type {Number}
9189 * @sample highcharts/loading/hideduration/ Fade in and out over a second
9190 * @default 100
9191 * @since 1.2.0
9192 * @apioption loading.hideDuration
9193 */
9194
9195 /**
9196 * The duration in milliseconds of the fade in effect.
9197 *
9198 * @type {Number}
9199 * @sample highcharts/loading/hideduration/ Fade in and out over a second
9200 * @default 100
9201 * @since 1.2.0
9202 * @apioption loading.showDuration
9203 */
9204
9205
9206 /**
9207 * CSS styles for the loading label `span`.
9208 *
9209 * @type {CSSObject}
9210 * @see In styled mode, the loading label is styled with the
9211 * `.highcharts-legend-loading-inner` class.
9212 * @sample {highcharts|highmaps} highcharts/loading/labelstyle/ Vertically centered
9213 * @sample {highstock} stock/loading/general/ Label styles
9214 * @default { "fontWeight": "bold", "position": "relative", "top": "45%" }
9215 * @since 1.2.0
9216 */
9217 labelStyle: {
9218 fontWeight: 'bold',
9219 position: 'relative',
9220 top: '45%'
9221 },
9222
9223 /**
9224 * CSS styles for the loading screen that covers the plot area.
9225 *
9226 * @type {CSSObject}
9227 * @see In styled mode, the loading label is styled with the `.highcharts-legend-loading` class.
9228 * @sample {highcharts|highmaps} highcharts/loading/style/ Gray plot area, white text
9229 * @sample {highstock} stock/loading/general/ Gray plot area, white text
9230 * @default { "position": "absolute", "backgroundColor": "#ffffff", "opacity": 0.5, "textAlign": "center" }
9231 * @since 1.2.0
9232 */
9233 style: {
9234 position: 'absolute',
9235 backgroundColor: '#ffffff',
9236 opacity: 0.5,
9237 textAlign: 'center'
9238 }
9239
9240 },
9241
9242
9243 /**
9244 * Options for the tooltip that appears when the user hovers over a
9245 * series or point.
9246 *
9247 */
9248 tooltip: {
9249
9250 /**
9251 * Enable or disable the tooltip.
9252 *
9253 * @type {Boolean}
9254 * @sample {highcharts} highcharts/tooltip/enabled/ Disabled
9255 * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ Disable tooltip and show values on chart instead
9256 * @default true
9257 */
9258 enabled: true,
9259
9260 /**
9261 * Enable or disable animation of the tooltip. In slow legacy IE browsers
9262 * the animation is disabled by default.
9263 *
9264 * @type {Boolean}
9265 * @default true
9266 * @since 2.3.0
9267 */
9268 animation: svg,
9269
9270 /**
9271 * The radius of the rounded border corners.
9272 *
9273 * @type {Number}
9274 * @sample {highcharts} highcharts/tooltip/bordercolor-default/ 5px by default
9275 * @sample {highcharts} highcharts/tooltip/borderradius-0/ Square borders
9276 * @sample {highmaps} maps/tooltip/background-border/ Background and border demo
9277 * @default 3
9278 */
9279 borderRadius: 3,
9280
9281 /**
9282 * For series on a datetime axes, the date format in the tooltip's
9283 * header will by default be guessed based on the closest data points.
9284 * This member gives the default string representations used for
9285 * each unit. For an overview of the replacement codes, see [dateFormat](#Highcharts.
9286 * dateFormat).
9287 *
9288 * Defaults to:
9289 *
9290 * <pre>{
9291 * millisecond:"%A, %b %e, %H:%M:%S.%L",
9292 * second:"%A, %b %e, %H:%M:%S",
9293 * minute:"%A, %b %e, %H:%M",
9294 * hour:"%A, %b %e, %H:%M",
9295 * day:"%A, %b %e, %Y",
9296 * week:"Week from %A, %b %e, %Y",
9297 * month:"%B %Y",
9298 * year:"%Y"
9299 * }</pre>
9300 *
9301 * @type {Object}
9302 * @see [xAxis.dateTimeLabelFormats](#xAxis.dateTimeLabelFormats)
9303 * @product highcharts highstock
9304 */
9305 dateTimeLabelFormats: {
9306 millisecond: '%A, %b %e, %H:%M:%S.%L',
9307 second: '%A, %b %e, %H:%M:%S',
9308 minute: '%A, %b %e, %H:%M',
9309 hour: '%A, %b %e, %H:%M',
9310 day: '%A, %b %e, %Y',
9311 week: 'Week from %A, %b %e, %Y',
9312 month: '%B %Y',
9313 year: '%Y'
9314 },
9315
9316 /**
9317 * A string to append to the tooltip format.
9318 *
9319 * @type {String}
9320 * @sample {highcharts} highcharts/tooltip/footerformat/ A table for value alignment
9321 * @sample {highmaps} maps/tooltip/format/ Format demo
9322 * @default false
9323 * @since 2.2
9324 */
9325 footerFormat: '',
9326
9327 /**
9328 * Padding inside the tooltip, in pixels.
9329 *
9330 * @type {Number}
9331 * @default 8
9332 * @since 5.0.0
9333 */
9334 padding: 8,
9335
9336 /**
9337 * Proximity snap for graphs or single points. It defaults to 10 for
9338 * mouse-powered devices and 25 for touch devices.
9339 *
9340 * Note that in most cases the whole plot area captures the mouse
9341 * movement, and in these cases `tooltip.snap` doesn't make sense.
9342 * This applies when [stickyTracking](#plotOptions.series.stickyTracking)
9343 * is `true` (default) and when the tooltip is [shared](#tooltip.shared)
9344 * or [split](#tooltip.split).
9345 *
9346 * @type {Number}
9347 * @sample {highcharts} highcharts/tooltip/bordercolor-default/ 10 px by default
9348 * @sample {highcharts} highcharts/tooltip/snap-50/ 50 px on graph
9349 * @default 10/25
9350 * @since 1.2.0
9351 * @product highcharts highstock
9352 */
9353 snap: isTouchDevice ? 25 : 10,
9354
9355
9356 /**
9357 * The background color or gradient for the tooltip.
9358 *
9359 * In styled mode, the stroke width is set in the `.highcharts-tooltip-box` class.
9360 *
9361 * @type {Color}
9362 * @sample {highcharts} highcharts/tooltip/backgroundcolor-solid/ Yellowish background
9363 * @sample {highcharts} highcharts/tooltip/backgroundcolor-gradient/ Gradient
9364 * @sample {highcharts} highcharts/css/tooltip-border-background/ Tooltip in styled mode
9365 * @sample {highstock} stock/tooltip/general/ Custom tooltip
9366 * @sample {highstock} highcharts/css/tooltip-border-background/ Tooltip in styled mode
9367 * @sample {highmaps} maps/tooltip/background-border/ Background and border demo
9368 * @sample {highmaps} highcharts/css/tooltip-border-background/ Tooltip in styled mode
9369 * @default rgba(247,247,247,0.85)
9370 */
9371 backgroundColor: color('#f7f7f7').setOpacity(0.85).get(),
9372
9373 /**
9374 * The pixel width of the tooltip border.
9375 *
9376 * In styled mode, the stroke width is set in the `.highcharts-tooltip-box` class.
9377 *
9378 * @type {Number}
9379 * @sample {highcharts} highcharts/tooltip/bordercolor-default/ 2px by default
9380 * @sample {highcharts} highcharts/tooltip/borderwidth/ No border (shadow only)
9381 * @sample {highcharts} highcharts/css/tooltip-border-background/ Tooltip in styled mode
9382 * @sample {highstock} stock/tooltip/general/ Custom tooltip
9383 * @sample {highstock} highcharts/css/tooltip-border-background/ Tooltip in styled mode
9384 * @sample {highmaps} maps/tooltip/background-border/ Background and border demo
9385 * @sample {highmaps} highcharts/css/tooltip-border-background/ Tooltip in styled mode
9386 * @default 1
9387 */
9388 borderWidth: 1,
9389
9390 /**
9391 * The HTML of the tooltip header line. Variables are enclosed by
9392 * curly brackets. Available variables are `point.key`, `series.name`,
9393 * `series.color` and other members from the `point` and `series`
9394 * objects. The `point.key` variable contains the category name, x
9395 * value or datetime string depending on the type of axis. For datetime
9396 * axes, the `point.key` date format can be set using tooltip.xDateFormat.
9397 *
9398 * @type {String}
9399 * @sample {highcharts} highcharts/tooltip/footerformat/
9400 * A HTML table in the tooltip
9401 * @sample {highstock} highcharts/tooltip/footerformat/
9402 * A HTML table in the tooltip
9403 * @sample {highmaps} maps/tooltip/format/ Format demo
9404 */
9405 headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>',
9406
9407 /**
9408 * The HTML of the point's line in the tooltip. Variables are enclosed
9409 * by curly brackets. Available variables are point.x, point.y, series.
9410 * name and series.color and other properties on the same form. Furthermore,
9411 * point.y can be extended by the `tooltip.valuePrefix` and `tooltip.
9412 * valueSuffix` variables. This can also be overridden for each series,
9413 * which makes it a good hook for displaying units.
9414 *
9415 * In styled mode, the dot is colored by a class name rather
9416 * than the point color.
9417 *
9418 * @type {String}
9419 * @sample {highcharts} highcharts/tooltip/pointformat/ A different point format with value suffix
9420 * @sample {highmaps} maps/tooltip/format/ Format demo
9421 * @default <span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.y}</b><br/>
9422 * @since 2.2
9423 */
9424 pointFormat: '<span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.y}</b><br/>',
9425
9426 /**
9427 * Whether to apply a drop shadow to the tooltip.
9428 *
9429 * @type {Boolean}
9430 * @sample {highcharts} highcharts/tooltip/bordercolor-default/ True by default
9431 * @sample {highcharts} highcharts/tooltip/shadow/ False
9432 * @sample {highmaps} maps/tooltip/positioner/ Fixed tooltip position, border and shadow disabled
9433 * @default true
9434 */
9435 shadow: true,
9436
9437 /**
9438 * CSS styles for the tooltip. The tooltip can also be styled through
9439 * the CSS class `.highcharts-tooltip`.
9440 *
9441 * @type {CSSObject}
9442 * @sample {highcharts} highcharts/tooltip/style/ Greater padding, bold text
9443 * @default { "color": "#333333", "cursor": "default", "fontSize": "12px", "pointerEvents": "none", "whiteSpace": "nowrap" }
9444 */
9445 style: {
9446 color: '#333333',
9447 cursor: 'default',
9448 fontSize: '12px',
9449 pointerEvents: 'none', // #1686 http://caniuse.com/#feat=pointer-events
9450 whiteSpace: 'nowrap'
9451 }
9452
9453
9454
9455 /**
9456 * The color of the tooltip border. When `null`, the border takes the
9457 * color of the corresponding series or point.
9458 *
9459 * @type {Color}
9460 * @sample {highcharts} highcharts/tooltip/bordercolor-default/
9461 * Follow series by default
9462 * @sample {highcharts} highcharts/tooltip/bordercolor-black/
9463 * Black border
9464 * @sample {highstock} stock/tooltip/general/
9465 * Styled tooltip
9466 * @sample {highmaps} maps/tooltip/background-border/
9467 * Background and border demo
9468 * @default null
9469 * @apioption tooltip.borderColor
9470 */
9471
9472 /**
9473 * Since 4.1, the crosshair definitions are moved to the Axis object
9474 * in order for a better separation from the tooltip. See [xAxis.crosshair](#xAxis.
9475 * crosshair)<a>.</a>
9476 *
9477 * @type {Mixed}
9478 * @deprecated
9479 * @sample {highcharts} highcharts/tooltip/crosshairs-x/
9480 * Enable a crosshair for the x value
9481 * @default true
9482 * @apioption tooltip.crosshairs
9483 */
9484
9485 /**
9486 * Whether the tooltip should follow the mouse as it moves across columns,
9487 * pie slices and other point types with an extent. By default it behaves
9488 * this way for scatter, bubble and pie series by override in the `plotOptions`
9489 * for those series types.
9490 *
9491 * For touch moves to behave the same way, [followTouchMove](#tooltip.
9492 * followTouchMove) must be `true` also.
9493 *
9494 * @type {Boolean}
9495 * @default {highcharts} false
9496 * @default {highstock} false
9497 * @default {highmaps} true
9498 * @since 3.0
9499 * @apioption tooltip.followPointer
9500 */
9501
9502 /**
9503 * Whether the tooltip should follow the finger as it moves on a touch
9504 * device. If this is `true` and [chart.panning](#chart.panning) is
9505 * set,`followTouchMove` will take over one-finger touches, so the user
9506 * needs to use two fingers for zooming and panning.
9507 *
9508 * @type {Boolean}
9509 * @default {highcharts} true
9510 * @default {highstock} true
9511 * @default {highmaps} false
9512 * @since 3.0.1
9513 * @apioption tooltip.followTouchMove
9514 */
9515
9516 /**
9517 * Callback function to format the text of the tooltip from scratch. Return
9518 * `false` to disable tooltip for a specific point on series.
9519 *
9520 * A subset of HTML is supported. Unless `useHTML` is true, the HTML of the
9521 * tooltip is parsed and converted to SVG, therefore this isn't a complete HTML
9522 * renderer. The following tags are supported: `<b>`, `<strong>`, `<i>`, `<em>`,
9523 * `<br/>`, `<span>`. Spans can be styled with a `style` attribute,
9524 * but only text-related CSS that is shared with SVG is handled.
9525 *
9526 * Since version 2.1 the tooltip can be shared between multiple series
9527 * through the `shared` option. The available data in the formatter
9528 * differ a bit depending on whether the tooltip is shared or not. In
9529 * a shared tooltip, all properties except `x`, which is common for
9530 * all points, are kept in an array, `this.points`.
9531 *
9532 * Available data are:
9533 *
9534 * <dl>
9535 *
9536 * <dt>this.percentage (not shared) / this.points[i].percentage (shared)</dt>
9537 *
9538 * <dd>Stacked series and pies only. The point's percentage of the total.
9539 * </dd>
9540 *
9541 * <dt>this.point (not shared) / this.points[i].point (shared)</dt>
9542 *
9543 * <dd>The point object. The point name, if defined, is available through
9544 * `this.point.name`.</dd>
9545 *
9546 * <dt>this.points</dt>
9547 *
9548 * <dd>In a shared tooltip, this is an array containing all other properties
9549 * for each point.</dd>
9550 *
9551 * <dt>this.series (not shared) / this.points[i].series (shared)</dt>
9552 *
9553 * <dd>The series object. The series name is available through
9554 * `this.series.name`.</dd>
9555 *
9556 * <dt>this.total (not shared) / this.points[i].total (shared)</dt>
9557 *
9558 * <dd>Stacked series only. The total value at this point's x value.
9559 * </dd>
9560 *
9561 * <dt>this.x</dt>
9562 *
9563 * <dd>The x value. This property is the same regardless of the tooltip
9564 * being shared or not.</dd>
9565 *
9566 * <dt>this.y (not shared) / this.points[i].y (shared)</dt>
9567 *
9568 * <dd>The y value.</dd>
9569 *
9570 * </dl>
9571 *
9572 * @type {Function}
9573 * @sample {highcharts} highcharts/tooltip/formatter-simple/
9574 * Simple string formatting
9575 * @sample {highcharts} highcharts/tooltip/formatter-shared/
9576 * Formatting with shared tooltip
9577 * @sample {highstock} stock/tooltip/formatter/
9578 * Formatting with shared tooltip
9579 * @sample {highmaps} maps/tooltip/formatter/
9580 * String formatting
9581 * @apioption tooltip.formatter
9582 */
9583
9584 /**
9585 * The number of milliseconds to wait until the tooltip is hidden when
9586 * mouse out from a point or chart.
9587 *
9588 * @type {Number}
9589 * @default 500
9590 * @since 3.0
9591 * @apioption tooltip.hideDelay
9592 */
9593
9594 /**
9595 * A callback function for formatting the HTML output for a single point
9596 * in the tooltip. Like the `pointFormat` string, but with more flexibility.
9597 *
9598 * @type {Function}
9599 * @context Point
9600 * @since 4.1.0
9601 * @apioption tooltip.pointFormatter
9602 */
9603
9604 /**
9605 * A callback function to place the tooltip in a default position. The
9606 * callback receives three parameters: `labelWidth`, `labelHeight` and
9607 * `point`, where point contains values for `plotX` and `plotY` telling
9608 * where the reference point is in the plot area. Add `chart.plotLeft`
9609 * and `chart.plotTop` to get the full coordinates.
9610 *
9611 * The return should be an object containing x and y values, for example
9612 * `{ x: 100, y: 100 }`.
9613 *
9614 * @type {Function}
9615 * @sample {highcharts} highcharts/tooltip/positioner/ A fixed tooltip position
9616 * @sample {highstock} stock/tooltip/positioner/ A fixed tooltip position on top of the chart
9617 * @sample {highmaps} maps/tooltip/positioner/ A fixed tooltip position
9618 * @since 2.2.4
9619 * @apioption tooltip.positioner
9620 */
9621
9622 /**
9623 * The name of a symbol to use for the border around the tooltip.
9624 *
9625 * @type {String}
9626 * @default callout
9627 * @validvalue ["callout", "square"]
9628 * @since 4.0
9629 * @apioption tooltip.shape
9630 */
9631
9632 /**
9633 * When the tooltip is shared, the entire plot area will capture mouse
9634 * movement or touch events. Tooltip texts for series types with ordered
9635 * data (not pie, scatter, flags etc) will be shown in a single bubble.
9636 * This is recommended for single series charts and for tablet/mobile
9637 * optimized charts.
9638 *
9639 * See also [tooltip.split](#tooltip.split), that is better suited for
9640 * charts with many series, especially line-type series.
9641 *
9642 * @type {Boolean}
9643 * @sample {highcharts} highcharts/tooltip/shared-false/ False by default
9644 * @sample {highcharts} highcharts/tooltip/shared-true/ True
9645 * @sample {highcharts} highcharts/tooltip/shared-x-crosshair/ True with x axis crosshair
9646 * @sample {highcharts} highcharts/tooltip/shared-true-mixed-types/ True with mixed series types
9647 * @default false
9648 * @since 2.1
9649 * @product highcharts highstock
9650 * @apioption tooltip.shared
9651 */
9652
9653 /**
9654 * Split the tooltip into one label per series, with the header close
9655 * to the axis. This is recommended over [shared](#tooltip.shared) tooltips
9656 * for charts with multiple line series, generally making them easier
9657 * to read.
9658 *
9659 * @productdesc {highstock} In Highstock, tooltips are split by default
9660 * since v6.0.0. Stock charts typically contain multi-dimension points
9661 * and multiple panes, making split tooltips the preferred layout over
9662 * the previous `shared` tooltip.
9663 *
9664 * @type {Boolean}
9665 * @sample {highcharts} highcharts/tooltip/split/ Split tooltip
9666 * @sample {highstock} highcharts/tooltip/split/ Split tooltip
9667 * @sample {highmaps} highcharts/tooltip/split/ Split tooltip
9668 * @default {highcharts} false
9669 * @default {highstock} true
9670 * @product highcharts highstock
9671 * @since 5.0.0
9672 * @apioption tooltip.split
9673 */
9674
9675 /**
9676 * Use HTML to render the contents of the tooltip instead of SVG. Using
9677 * HTML allows advanced formatting like tables and images in the tooltip.
9678 * It is also recommended for rtl languages as it works around rtl
9679 * bugs in early Firefox.
9680 *
9681 * @type {Boolean}
9682 * @sample {highcharts} highcharts/tooltip/footerformat/ A table for value alignment
9683 * @sample {highcharts} highcharts/tooltip/fullhtml/ Full HTML tooltip
9684 * @sample {highstock} highcharts/tooltip/footerformat/ A table for value alignment
9685 * @sample {highstock} highcharts/tooltip/fullhtml/ Full HTML tooltip
9686 * @sample {highmaps} maps/tooltip/usehtml/ Pure HTML tooltip
9687 * @default false
9688 * @since 2.2
9689 * @apioption tooltip.useHTML
9690 */
9691
9692 /**
9693 * How many decimals to show in each series' y value. This is overridable
9694 * in each series' tooltip options object. The default is to preserve
9695 * all decimals.
9696 *
9697 * @type {Number}
9698 * @sample {highcharts} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9699 * @sample {highstock} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9700 * @sample {highmaps} maps/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9701 * @since 2.2
9702 * @apioption tooltip.valueDecimals
9703 */
9704
9705 /**
9706 * A string to prepend to each series' y value. Overridable in each
9707 * series' tooltip options object.
9708 *
9709 * @type {String}
9710 * @sample {highcharts} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9711 * @sample {highstock} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9712 * @sample {highmaps} maps/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9713 * @since 2.2
9714 * @apioption tooltip.valuePrefix
9715 */
9716
9717 /**
9718 * A string to append to each series' y value. Overridable in each series'
9719 * tooltip options object.
9720 *
9721 * @type {String}
9722 * @sample {highcharts} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9723 * @sample {highstock} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9724 * @sample {highmaps} maps/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
9725 * @since 2.2
9726 * @apioption tooltip.valueSuffix
9727 */
9728
9729 /**
9730 * The format for the date in the tooltip header if the X axis is a
9731 * datetime axis. The default is a best guess based on the smallest
9732 * distance between points in the chart.
9733 *
9734 * @type {String}
9735 * @sample {highcharts} highcharts/tooltip/xdateformat/ A different format
9736 * @product highcharts highstock
9737 * @apioption tooltip.xDateFormat
9738 */
9739 },
9740
9741
9742 /**
9743 * Highchart by default puts a credits label in the lower right corner
9744 * of the chart. This can be changed using these options.
9745 */
9746 credits: {
9747
9748 /**
9749 * Whether to show the credits text.
9750 *
9751 * @type {Boolean}
9752 * @sample {highcharts} highcharts/credits/enabled-false/ Credits disabled
9753 * @sample {highstock} stock/credits/enabled/ Credits disabled
9754 * @sample {highmaps} maps/credits/enabled-false/ Credits disabled
9755 * @default true
9756 */
9757 enabled: true,
9758
9759 /**
9760 * The URL for the credits label.
9761 *
9762 * @type {String}
9763 * @sample {highcharts} highcharts/credits/href/ Custom URL and text
9764 * @sample {highmaps} maps/credits/customized/ Custom URL and text
9765 * @default {highcharts} http://www.highcharts.com
9766 * @default {highstock} "http://www.highcharts.com"
9767 * @default {highmaps} http://www.highcharts.com
9768 */
9769 href: 'http://www.highcharts.com',
9770
9771 /**
9772 * Position configuration for the credits label.
9773 *
9774 * @type {Object}
9775 * @sample {highcharts} highcharts/credits/position-left/ Left aligned
9776 * @sample {highcharts} highcharts/credits/position-left/ Left aligned
9777 * @sample {highmaps} maps/credits/customized/ Left aligned
9778 * @sample {highmaps} maps/credits/customized/ Left aligned
9779 * @since 2.1
9780 */
9781 position: {
9782
9783 /**
9784 * Horizontal alignment of the credits.
9785 *
9786 * @validvalue ["left", "center", "right"]
9787 * @type {String}
9788 * @default right
9789 */
9790 align: 'right',
9791
9792 /**
9793 * Horizontal pixel offset of the credits.
9794 *
9795 * @type {Number}
9796 * @default -10
9797 */
9798 x: -10,
9799
9800 /**
9801 * Vertical alignment of the credits.
9802 *
9803 * @validvalue ["top", "middle", "bottom"]
9804 * @type {String}
9805 * @default bottom
9806 */
9807 verticalAlign: 'bottom',
9808
9809 /**
9810 * Vertical pixel offset of the credits.
9811 *
9812 * @type {Number}
9813 * @default -5
9814 */
9815 y: -5
9816 },
9817
9818
9819 /**
9820 * CSS styles for the credits label.
9821 *
9822 * @type {CSSObject}
9823 * @see In styled mode, credits styles can be set with the
9824 * `.highcharts-credits` class.
9825 * @default { "cursor": "pointer", "color": "#999999", "fontSize": "10px" }
9826 */
9827 style: {
9828 cursor: 'pointer',
9829 color: '#999999',
9830 fontSize: '9px'
9831 },
9832
9833
9834 /**
9835 * The text for the credits label.
9836 *
9837 * @productdesc {highmaps}
9838 * If a map is loaded as GeoJSON, the text defaults to `Highcharts @
9839 * {map-credits}`. Otherwise, it defaults to `Highcharts.com`.
9840 *
9841 * @type {String}
9842 * @sample {highcharts} highcharts/credits/href/ Custom URL and text
9843 * @sample {highmaps} maps/credits/customized/ Custom URL and text
9844 * @default {highcharts|highstock} Highcharts.com
9845 */
9846 text: 'Highcharts.com'
9847 }
9848 };
9849
9850
9851
9852 /**
9853 * Sets the getTimezoneOffset function. If the timezone option is set, a default
9854 * getTimezoneOffset function with that timezone is returned. If not, the
9855 * specified getTimezoneOffset function is returned. If neither are specified,
9856 * undefined is returned.
9857 * @return {function} a getTimezoneOffset function or undefined
9858 */
9859 function getTimezoneOffsetOption() {
9860 var globalOptions = H.defaultOptions.global,
9861 moment = win.moment;
9862
9863 if (globalOptions.timezone) {
9864 if (!moment) {
9865 // getTimezoneOffset-function stays undefined because it depends on
9866 // Moment.js
9867 H.error(25);
9868
9869 } else {
9870 return function(timestamp) {
9871 return -moment.tz(
9872 timestamp,
9873 globalOptions.timezone
9874 ).utcOffset();
9875 };
9876 }
9877 }
9878
9879 // If not timezone is set, look for the getTimezoneOffset callback
9880 return globalOptions.useUTC && globalOptions.getTimezoneOffset;
9881 }
9882
9883 /**
9884 * Set the time methods globally based on the useUTC option. Time method can be
9885 * either local time or UTC (default). It is called internally on initiating
9886 * Highcharts and after running `Highcharts.setOptions`.
9887 *
9888 * @private
9889 */
9890 function setTimeMethods() {
9891 var globalOptions = H.defaultOptions.global,
9892 Date,
9893 useUTC = globalOptions.useUTC,
9894 GET = useUTC ? 'getUTC' : 'get',
9895 SET = useUTC ? 'setUTC' : 'set',
9896 setters = ['Minutes', 'Hours', 'Day', 'Date', 'Month', 'FullYear'],
9897 getters = setters.concat(['Milliseconds', 'Seconds']),
9898 n;
9899
9900 H.Date = Date = globalOptions.Date || win.Date; // Allow using a different Date class
9901 Date.hcTimezoneOffset = useUTC && globalOptions.timezoneOffset;
9902 Date.hcGetTimezoneOffset = getTimezoneOffsetOption();
9903 Date.hcMakeTime = function(year, month, date, hours, minutes, seconds) {
9904 var d;
9905 if (useUTC) {
9906 d = Date.UTC.apply(0, arguments);
9907 d += getTZOffset(d);
9908 } else {
9909 d = new Date(
9910 year,
9911 month,
9912 pick(date, 1),
9913 pick(hours, 0),
9914 pick(minutes, 0),
9915 pick(seconds, 0)
9916 ).getTime();
9917 }
9918 return d;
9919 };
9920
9921 // Dynamically set setters and getters. Use for loop, H.each is not yet
9922 // overridden in oldIE.
9923 for (n = 0; n < setters.length; n++) {
9924 Date['hcGet' + setters[n]] = GET + setters[n];
9925 }
9926 for (n = 0; n < getters.length; n++) {
9927 Date['hcSet' + getters[n]] = SET + getters[n];
9928 }
9929 }
9930
9931 /**
9932 * Merge the default options with custom options and return the new options
9933 * structure. Commonly used for defining reusable templates.
9934 *
9935 * @function #setOptions
9936 * @memberOf Highcharts
9937 * @sample highcharts/global/useutc-false Setting a global option
9938 * @sample highcharts/members/setoptions Applying a global theme
9939 * @param {Object} options The new custom chart options.
9940 * @returns {Object} Updated options.
9941 */
9942 H.setOptions = function(options) {
9943
9944 // Copy in the default options
9945 H.defaultOptions = merge(true, H.defaultOptions, options);
9946
9947 // Apply UTC
9948 setTimeMethods();
9949
9950 return H.defaultOptions;
9951 };
9952
9953 /**
9954 * Get the updated default options. Until 3.0.7, merely exposing defaultOptions for outside modules
9955 * wasn't enough because the setOptions method created a new object.
9956 */
9957 H.getOptions = function() {
9958 return H.defaultOptions;
9959 };
9960
9961
9962 // Series defaults
9963 H.defaultPlotOptions = H.defaultOptions.plotOptions;
9964
9965 // set the default time methods
9966 setTimeMethods();
9967
9968 }(Highcharts));
9969 (function(H) {
9970 /**
9971 * (c) 2010-2017 Torstein Honsi
9972 *
9973 * License: www.highcharts.com/license
9974 */
9975 var correctFloat = H.correctFloat,
9976 defined = H.defined,
9977 destroyObjectProperties = H.destroyObjectProperties,
9978 isNumber = H.isNumber,
9979 merge = H.merge,
9980 pick = H.pick,
9981 deg2rad = H.deg2rad;
9982
9983 /**
9984 * The Tick class
9985 */
9986 H.Tick = function(axis, pos, type, noLabel) {
9987 this.axis = axis;
9988 this.pos = pos;
9989 this.type = type || '';
9990 this.isNew = true;
9991 this.isNewLabel = true;
9992
9993 if (!type && !noLabel) {
9994 this.addLabel();
9995 }
9996 };
9997
9998 H.Tick.prototype = {
9999 /**
10000 * Write the tick label
10001 */
10002 addLabel: function() {
10003 var tick = this,
10004 axis = tick.axis,
10005 options = axis.options,
10006 chart = axis.chart,
10007 categories = axis.categories,
10008 names = axis.names,
10009 pos = tick.pos,
10010 labelOptions = options.labels,
10011 str,
10012 tickPositions = axis.tickPositions,
10013 isFirst = pos === tickPositions[0],
10014 isLast = pos === tickPositions[tickPositions.length - 1],
10015 value = categories ?
10016 pick(categories[pos], names[pos], pos) :
10017 pos,
10018 label = tick.label,
10019 tickPositionInfo = tickPositions.info,
10020 dateTimeLabelFormat;
10021
10022 // Set the datetime label format. If a higher rank is set for this
10023 // position, use that. If not, use the general format.
10024 if (axis.isDatetimeAxis && tickPositionInfo) {
10025 dateTimeLabelFormat =
10026 options.dateTimeLabelFormats[
10027 tickPositionInfo.higherRanks[pos] ||
10028 tickPositionInfo.unitName
10029 ];
10030 }
10031 // set properties for access in render method
10032 tick.isFirst = isFirst;
10033 tick.isLast = isLast;
10034
10035 // get the string
10036 str = axis.labelFormatter.call({
10037 axis: axis,
10038 chart: chart,
10039 isFirst: isFirst,
10040 isLast: isLast,
10041 dateTimeLabelFormat: dateTimeLabelFormat,
10042 value: axis.isLog ? correctFloat(axis.lin2log(value)) : value,
10043 pos: pos
10044 });
10045
10046 // first call
10047 if (!defined(label)) {
10048
10049 tick.label = label =
10050 defined(str) && labelOptions.enabled ?
10051 chart.renderer.text(
10052 str,
10053 0,
10054 0,
10055 labelOptions.useHTML
10056 )
10057
10058 // without position absolute, IE export sometimes is
10059 // wrong.
10060 .css(merge(labelOptions.style))
10061
10062 .add(axis.labelGroup) :
10063 null;
10064
10065 // Un-rotated length
10066 tick.labelLength = label && label.getBBox().width;
10067 // Base value to detect change for new calls to getBBox
10068 tick.rotation = 0;
10069
10070 // update
10071 } else if (label) {
10072 label.attr({
10073 text: str
10074 });
10075 }
10076 },
10077
10078 /**
10079 * Get the offset height or width of the label
10080 */
10081 getLabelSize: function() {
10082 return this.label ?
10083 this.label.getBBox()[this.axis.horiz ? 'height' : 'width'] :
10084 0;
10085 },
10086
10087 /**
10088 * Handle the label overflow by adjusting the labels to the left and right
10089 * edge, or hide them if they collide into the neighbour label.
10090 */
10091 handleOverflow: function(xy) {
10092 var axis = this.axis,
10093 pxPos = xy.x,
10094 chartWidth = axis.chart.chartWidth,
10095 spacing = axis.chart.spacing,
10096 leftBound = pick(axis.labelLeft, Math.min(axis.pos, spacing[3])),
10097 rightBound = pick(
10098 axis.labelRight,
10099 Math.max(axis.pos + axis.len, chartWidth - spacing[1])
10100 ),
10101 label = this.label,
10102 rotation = this.rotation,
10103 factor = {
10104 left: 0,
10105 center: 0.5,
10106 right: 1
10107 }[axis.labelAlign],
10108 labelWidth = label.getBBox().width,
10109 slotWidth = axis.getSlotWidth(),
10110 modifiedSlotWidth = slotWidth,
10111 xCorrection = factor,
10112 goRight = 1,
10113 leftPos,
10114 rightPos,
10115 textWidth,
10116 css = {};
10117
10118 // Check if the label overshoots the chart spacing box. If it does, move
10119 // it. If it now overshoots the slotWidth, add ellipsis.
10120 if (!rotation) {
10121 leftPos = pxPos - factor * labelWidth;
10122 rightPos = pxPos + (1 - factor) * labelWidth;
10123
10124 if (leftPos < leftBound) {
10125 modifiedSlotWidth = xy.x + modifiedSlotWidth * (1 - factor) - leftBound;
10126 } else if (rightPos > rightBound) {
10127 modifiedSlotWidth =
10128 rightBound - xy.x + modifiedSlotWidth * factor;
10129 goRight = -1;
10130 }
10131
10132 modifiedSlotWidth = Math.min(slotWidth, modifiedSlotWidth); // #4177
10133 if (modifiedSlotWidth < slotWidth && axis.labelAlign === 'center') {
10134 xy.x += (
10135 goRight *
10136 (
10137 slotWidth -
10138 modifiedSlotWidth -
10139 xCorrection * (
10140 slotWidth - Math.min(labelWidth, modifiedSlotWidth)
10141 )
10142 )
10143 );
10144 }
10145 // If the label width exceeds the available space, set a text width
10146 // to be picked up below. Also, if a width has been set before, we
10147 // need to set a new one because the reported labelWidth will be
10148 // limited by the box (#3938).
10149 if (
10150 labelWidth > modifiedSlotWidth ||
10151 (axis.autoRotation && (label.styles || {}).width)
10152 ) {
10153 textWidth = modifiedSlotWidth;
10154 }
10155
10156 // Add ellipsis to prevent rotated labels to be clipped against the edge
10157 // of the chart
10158 } else if (rotation < 0 && pxPos - factor * labelWidth < leftBound) {
10159 textWidth = Math.round(
10160 pxPos / Math.cos(rotation * deg2rad) - leftBound
10161 );
10162 } else if (rotation > 0 && pxPos + factor * labelWidth > rightBound) {
10163 textWidth = Math.round(
10164 (chartWidth - pxPos) / Math.cos(rotation * deg2rad)
10165 );
10166 }
10167
10168 if (textWidth) {
10169 css.width = textWidth;
10170 if (!(axis.options.labels.style || {}).textOverflow) {
10171 css.textOverflow = 'ellipsis';
10172 }
10173 label.css(css);
10174 }
10175 },
10176
10177 /**
10178 * Get the x and y position for ticks and labels
10179 */
10180 getPosition: function(horiz, pos, tickmarkOffset, old) {
10181 var axis = this.axis,
10182 chart = axis.chart,
10183 cHeight = (old && chart.oldChartHeight) || chart.chartHeight;
10184
10185 return {
10186 x: horiz ?
10187 (
10188 axis.translate(pos + tickmarkOffset, null, null, old) +
10189 axis.transB
10190 ) :
10191 (
10192 axis.left +
10193 axis.offset +
10194 (
10195 axis.opposite ?
10196 (
10197 (
10198 (old && chart.oldChartWidth) ||
10199 chart.chartWidth
10200 ) -
10201 axis.right -
10202 axis.left
10203 ) :
10204 0
10205 )
10206 ),
10207
10208 y: horiz ?
10209 (
10210 cHeight -
10211 axis.bottom +
10212 axis.offset -
10213 (axis.opposite ? axis.height : 0)
10214 ) :
10215 (
10216 cHeight -
10217 axis.translate(pos + tickmarkOffset, null, null, old) -
10218 axis.transB
10219 )
10220 };
10221
10222 },
10223
10224 /**
10225 * Get the x, y position of the tick label
10226 */
10227 getLabelPosition: function(
10228 x,
10229 y,
10230 label,
10231 horiz,
10232 labelOptions,
10233 tickmarkOffset,
10234 index,
10235 step
10236 ) {
10237 var axis = this.axis,
10238 transA = axis.transA,
10239 reversed = axis.reversed,
10240 staggerLines = axis.staggerLines,
10241 rotCorr = axis.tickRotCorr || {
10242 x: 0,
10243 y: 0
10244 },
10245 yOffset = labelOptions.y,
10246 line;
10247
10248 if (!defined(yOffset)) {
10249 if (axis.side === 0) {
10250 yOffset = label.rotation ? -8 : -label.getBBox().height;
10251 } else if (axis.side === 2) {
10252 yOffset = rotCorr.y + 8;
10253 } else {
10254 // #3140, #3140
10255 yOffset = Math.cos(label.rotation * deg2rad) *
10256 (rotCorr.y - label.getBBox(false, 0).height / 2);
10257 }
10258 }
10259
10260 x = x + labelOptions.x + rotCorr.x - (tickmarkOffset && horiz ?
10261 tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
10262 y = y + yOffset - (tickmarkOffset && !horiz ?
10263 tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
10264
10265 // Correct for staggered labels
10266 if (staggerLines) {
10267 line = (index / (step || 1) % staggerLines);
10268 if (axis.opposite) {
10269 line = staggerLines - line - 1;
10270 }
10271 y += line * (axis.labelOffset / staggerLines);
10272 }
10273
10274 return {
10275 x: x,
10276 y: Math.round(y)
10277 };
10278 },
10279
10280 /**
10281 * Extendible method to return the path of the marker
10282 */
10283 getMarkPath: function(x, y, tickLength, tickWidth, horiz, renderer) {
10284 return renderer.crispLine([
10285 'M',
10286 x,
10287 y,
10288 'L',
10289 x + (horiz ? 0 : -tickLength),
10290 y + (horiz ? tickLength : 0)
10291 ], tickWidth);
10292 },
10293
10294 /**
10295 * Renders the gridLine.
10296 * @param {Boolean} old Whether or not the tick is old
10297 * @param {number} opacity The opacity of the grid line
10298 * @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1
10299 * @return {undefined}
10300 */
10301 renderGridLine: function(old, opacity, reverseCrisp) {
10302 var tick = this,
10303 axis = tick.axis,
10304 options = axis.options,
10305 gridLine = tick.gridLine,
10306 gridLinePath,
10307 attribs = {},
10308 pos = tick.pos,
10309 type = tick.type,
10310 tickmarkOffset = axis.tickmarkOffset,
10311 renderer = axis.chart.renderer;
10312
10313
10314 var gridPrefix = type ? type + 'Grid' : 'grid',
10315 gridLineWidth = options[gridPrefix + 'LineWidth'],
10316 gridLineColor = options[gridPrefix + 'LineColor'],
10317 dashStyle = options[gridPrefix + 'LineDashStyle'];
10318
10319
10320 if (!gridLine) {
10321
10322 attribs.stroke = gridLineColor;
10323 attribs['stroke-width'] = gridLineWidth;
10324 if (dashStyle) {
10325 attribs.dashstyle = dashStyle;
10326 }
10327
10328 if (!type) {
10329 attribs.zIndex = 1;
10330 }
10331 if (old) {
10332 attribs.opacity = 0;
10333 }
10334 tick.gridLine = gridLine = renderer.path()
10335 .attr(attribs)
10336 .addClass(
10337 'highcharts-' + (type ? type + '-' : '') + 'grid-line'
10338 )
10339 .add(axis.gridGroup);
10340 }
10341
10342 // If the parameter 'old' is set, the current call will be followed
10343 // by another call, therefore do not do any animations this time
10344 if (!old && gridLine) {
10345 gridLinePath = axis.getPlotLinePath(
10346 pos + tickmarkOffset,
10347 gridLine.strokeWidth() * reverseCrisp,
10348 old, true
10349 );
10350 if (gridLinePath) {
10351 gridLine[tick.isNew ? 'attr' : 'animate']({
10352 d: gridLinePath,
10353 opacity: opacity
10354 });
10355 }
10356 }
10357 },
10358
10359 /**
10360 * Renders the tick mark.
10361 * @param {Object} xy The position vector of the mark
10362 * @param {number} xy.x The x position of the mark
10363 * @param {number} xy.y The y position of the mark
10364 * @param {number} opacity The opacity of the mark
10365 * @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1
10366 * @return {undefined}
10367 */
10368 renderMark: function(xy, opacity, reverseCrisp) {
10369 var tick = this,
10370 axis = tick.axis,
10371 options = axis.options,
10372 renderer = axis.chart.renderer,
10373 type = tick.type,
10374 tickPrefix = type ? type + 'Tick' : 'tick',
10375 tickSize = axis.tickSize(tickPrefix),
10376 mark = tick.mark,
10377 isNewMark = !mark,
10378 x = xy.x,
10379 y = xy.y;
10380
10381
10382 var tickWidth = pick(
10383 options[tickPrefix + 'Width'], !type && axis.isXAxis ? 1 : 0
10384 ), // X axis defaults to 1
10385 tickColor = options[tickPrefix + 'Color'];
10386
10387
10388 if (tickSize) {
10389
10390 // negate the length
10391 if (axis.opposite) {
10392 tickSize[0] = -tickSize[0];
10393 }
10394
10395 // First time, create it
10396 if (isNewMark) {
10397 tick.mark = mark = renderer.path()
10398 .addClass('highcharts-' + (type ? type + '-' : '') + 'tick')
10399 .add(axis.axisGroup);
10400
10401
10402 mark.attr({
10403 stroke: tickColor,
10404 'stroke-width': tickWidth
10405 });
10406
10407 }
10408 mark[isNewMark ? 'attr' : 'animate']({
10409 d: tick.getMarkPath(
10410 x,
10411 y,
10412 tickSize[0],
10413 mark.strokeWidth() * reverseCrisp,
10414 axis.horiz,
10415 renderer),
10416 opacity: opacity
10417 });
10418
10419 }
10420 },
10421
10422 /**
10423 * Renders the tick label.
10424 * Note: The label should already be created in init(), so it should only
10425 * have to be moved into place.
10426 * @param {Object} xy The position vector of the label
10427 * @param {number} xy.x The x position of the label
10428 * @param {number} xy.y The y position of the label
10429 * @param {Boolean} old Whether or not the tick is old
10430 * @param {number} opacity The opacity of the label
10431 * @param {number} index The index of the tick
10432 * @return {undefined}
10433 */
10434 renderLabel: function(xy, old, opacity, index) {
10435 var tick = this,
10436 axis = tick.axis,
10437 horiz = axis.horiz,
10438 options = axis.options,
10439 label = tick.label,
10440 labelOptions = options.labels,
10441 step = labelOptions.step,
10442 tickmarkOffset = axis.tickmarkOffset,
10443 show = true,
10444 x = xy.x,
10445 y = xy.y;
10446 if (label && isNumber(x)) {
10447 label.xy = xy = tick.getLabelPosition(
10448 x,
10449 y,
10450 label,
10451 horiz,
10452 labelOptions,
10453 tickmarkOffset,
10454 index,
10455 step
10456 );
10457
10458 // Apply show first and show last. If the tick is both first and
10459 // last, it is a single centered tick, in which case we show the
10460 // label anyway (#2100).
10461 if (
10462 (
10463 tick.isFirst &&
10464 !tick.isLast &&
10465 !pick(options.showFirstLabel, 1)
10466 ) ||
10467 (
10468 tick.isLast &&
10469 !tick.isFirst &&
10470 !pick(options.showLastLabel, 1)
10471 )
10472 ) {
10473 show = false;
10474
10475 // Handle label overflow and show or hide accordingly
10476 } else if (horiz && !axis.isRadial && !labelOptions.step &&
10477 !labelOptions.rotation && !old && opacity !== 0) {
10478 tick.handleOverflow(xy);
10479 }
10480
10481 // apply step
10482 if (step && index % step) {
10483 // show those indices dividable by step
10484 show = false;
10485 }
10486
10487 // Set the new position, and show or hide
10488 if (show && isNumber(xy.y)) {
10489 xy.opacity = opacity;
10490 label[tick.isNewLabel ? 'attr' : 'animate'](xy);
10491 tick.isNewLabel = false;
10492 } else {
10493 label.attr('y', -9999); // #1338
10494 tick.isNewLabel = true;
10495 }
10496 }
10497 },
10498
10499 /**
10500 * Put everything in place
10501 *
10502 * @param index {Number}
10503 * @param old {Boolean} Use old coordinates to prepare an animation into new
10504 * position
10505 */
10506 render: function(index, old, opacity) {
10507 var tick = this,
10508 axis = tick.axis,
10509 horiz = axis.horiz,
10510 pos = tick.pos,
10511 tickmarkOffset = axis.tickmarkOffset,
10512 xy = tick.getPosition(horiz, pos, tickmarkOffset, old),
10513 x = xy.x,
10514 y = xy.y,
10515 reverseCrisp = ((horiz && x === axis.pos + axis.len) ||
10516 (!horiz && y === axis.pos)) ? -1 : 1; // #1480, #1687
10517
10518 opacity = pick(opacity, 1);
10519 this.isActive = true;
10520
10521 // Create the grid line
10522 this.renderGridLine(old, opacity, reverseCrisp);
10523
10524 // create the tick mark
10525 this.renderMark(xy, opacity, reverseCrisp);
10526
10527 // the label is created on init - now move it into place
10528 this.renderLabel(xy, old, opacity, index);
10529
10530 tick.isNew = false;
10531 },
10532
10533 /**
10534 * Destructor for the tick prototype
10535 */
10536 destroy: function() {
10537 destroyObjectProperties(this, this.axis);
10538 }
10539 };
10540
10541 }(Highcharts));
10542 var Axis = (function(H) {
10543 /**
10544 * (c) 2010-2017 Torstein Honsi
10545 *
10546 * License: www.highcharts.com/license
10547 */
10548
10549 var addEvent = H.addEvent,
10550 animObject = H.animObject,
10551 arrayMax = H.arrayMax,
10552 arrayMin = H.arrayMin,
10553 color = H.color,
10554 correctFloat = H.correctFloat,
10555 defaultOptions = H.defaultOptions,
10556 defined = H.defined,
10557 deg2rad = H.deg2rad,
10558 destroyObjectProperties = H.destroyObjectProperties,
10559 each = H.each,
10560 extend = H.extend,
10561 fireEvent = H.fireEvent,
10562 format = H.format,
10563 getMagnitude = H.getMagnitude,
10564 grep = H.grep,
10565 inArray = H.inArray,
10566 isArray = H.isArray,
10567 isNumber = H.isNumber,
10568 isString = H.isString,
10569 merge = H.merge,
10570 normalizeTickInterval = H.normalizeTickInterval,
10571 objectEach = H.objectEach,
10572 pick = H.pick,
10573 removeEvent = H.removeEvent,
10574 splat = H.splat,
10575 syncTimeout = H.syncTimeout,
10576 Tick = H.Tick;
10577
10578 /**
10579 * Create a new axis object. Called internally when instanciating a new chart or
10580 * adding axes by {@link Highcharts.Chart#addAxis}.
10581 *
10582 * A chart can have from 0 axes (pie chart) to multiples. In a normal, single
10583 * series cartesian chart, there is one X axis and one Y axis.
10584 *
10585 * The X axis or axes are referenced by {@link Highcharts.Chart.xAxis}, which is
10586 * an array of Axis objects. If there is only one axis, it can be referenced
10587 * through `chart.xAxis[0]`, and multiple axes have increasing indices. The same
10588 * pattern goes for Y axes.
10589 *
10590 * If you need to get the axes from a series object, use the `series.xAxis` and
10591 * `series.yAxis` properties. These are not arrays, as one series can only be
10592 * associated to one X and one Y axis.
10593 *
10594 * A third way to reference the axis programmatically is by `id`. Add an `id` in
10595 * the axis configuration options, and get the axis by
10596 * {@link Highcharts.Chart#get}.
10597 *
10598 * Configuration options for the axes are given in options.xAxis and
10599 * options.yAxis.
10600 *
10601 * @class Highcharts.Axis
10602 * @memberOf Highcharts
10603 * @param {Highcharts.Chart} chart - The Chart instance to apply the axis on.
10604 * @param {Object} options - Axis options
10605 */
10606 var Axis = function() {
10607 this.init.apply(this, arguments);
10608 };
10609
10610 H.extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {
10611
10612 /**
10613 * The X axis or category axis. Normally this is the horizontal axis,
10614 * though if the chart is inverted this is the vertical axis. In case of
10615 * multiple axes, the xAxis node is an array of configuration objects.
10616 *
10617 * See [the Axis object](#Axis) for programmatic access to the axis.
10618 *
10619 * @productdesc {highmaps}
10620 * In Highmaps, the axis is hidden, but it is used behind the scenes to
10621 * control features like zooming and panning. Zooming is in effect the same
10622 * as setting the extremes of one of the exes.
10623 *
10624 * @optionparent xAxis
10625 */
10626 defaultOptions: {
10627 /**
10628 * Whether to allow decimals in this axis' ticks. When counting
10629 * integers, like persons or hits on a web page, decimals should
10630 * be avoided in the labels.
10631 *
10632 * @type {Boolean}
10633 * @see [minTickInterval](#xAxis.minTickInterval)
10634 * @sample {highcharts|highstock}
10635 * highcharts/yaxis/allowdecimals-true/
10636 * True by default
10637 * @sample {highcharts|highstock}
10638 * highcharts/yaxis/allowdecimals-false/
10639 * False
10640 * @default true
10641 * @since 2.0
10642 * @apioption xAxis.allowDecimals
10643 */
10644 // allowDecimals: null,
10645
10646
10647 /**
10648 * When using an alternate grid color, a band is painted across the
10649 * plot area between every other grid line.
10650 *
10651 * @type {Color}
10652 * @sample {highcharts} highcharts/yaxis/alternategridcolor/
10653 * Alternate grid color on the Y axis
10654 * @sample {highstock} stock/xaxis/alternategridcolor/
10655 * Alternate grid color on the Y axis
10656 * @default null
10657 * @apioption xAxis.alternateGridColor
10658 */
10659 // alternateGridColor: null,
10660
10661 /**
10662 * An array defining breaks in the axis, the sections defined will be
10663 * left out and all the points shifted closer to each other.
10664 *
10665 * @productdesc {highcharts}
10666 * Requires that the broken-axis.js module is loaded.
10667 *
10668 * @type {Array}
10669 * @sample {highcharts}
10670 * highcharts/axisbreak/break-simple/
10671 * Simple break
10672 * @sample {highcharts|highstock}
10673 * highcharts/axisbreak/break-visualized/
10674 * Advanced with callback
10675 * @sample {highstock}
10676 * stock/demo/intraday-breaks/
10677 * Break on nights and weekends
10678 * @since 4.1.0
10679 * @product highcharts highstock
10680 * @apioption xAxis.breaks
10681 */
10682
10683 /**
10684 * A number indicating how much space should be left between the start
10685 * and the end of the break. The break size is given in axis units,
10686 * so for instance on a `datetime` axis, a break size of 3600000 would
10687 * indicate the equivalent of an hour.
10688 *
10689 * @type {Number}
10690 * @default 0
10691 * @since 4.1.0
10692 * @product highcharts highstock
10693 * @apioption xAxis.breaks.breakSize
10694 */
10695
10696 /**
10697 * The point where the break starts.
10698 *
10699 * @type {Number}
10700 * @since 4.1.0
10701 * @product highcharts highstock
10702 * @apioption xAxis.breaks.from
10703 */
10704
10705 /**
10706 * Defines an interval after which the break appears again. By default
10707 * the breaks do not repeat.
10708 *
10709 * @type {Number}
10710 * @default 0
10711 * @since 4.1.0
10712 * @product highcharts highstock
10713 * @apioption xAxis.breaks.repeat
10714 */
10715
10716 /**
10717 * The point where the break ends.
10718 *
10719 * @type {Number}
10720 * @since 4.1.0
10721 * @product highcharts highstock
10722 * @apioption xAxis.breaks.to
10723 */
10724
10725 /**
10726 * If categories are present for the xAxis, names are used instead of
10727 * numbers for that axis. Since Highcharts 3.0, categories can also
10728 * be extracted by giving each point a [name](#series.data) and setting
10729 * axis [type](#xAxis.type) to `category`. However, if you have multiple
10730 * series, best practice remains defining the `categories` array.
10731 *
10732 * Example:
10733 *
10734 * <pre>categories: ['Apples', 'Bananas', 'Oranges']</pre>
10735 *
10736 * @type {Array<String>}
10737 * @sample {highcharts} highcharts/chart/reflow-true/
10738 * With
10739 * @sample {highcharts} highcharts/xaxis/categories/
10740 * Without
10741 * @product highcharts
10742 * @default null
10743 * @apioption xAxis.categories
10744 */
10745 // categories: [],
10746
10747 /**
10748 * The highest allowed value for automatically computed axis extremes.
10749 *
10750 * @type {Number}
10751 * @see [floor](#xAxis.floor)
10752 * @sample {highcharts|highstock} highcharts/yaxis/floor-ceiling/
10753 * Floor and ceiling
10754 * @since 4.0
10755 * @product highcharts highstock
10756 * @apioption xAxis.ceiling
10757 */
10758
10759 /**
10760 * A class name that opens for styling the axis by CSS, especially in
10761 * Highcharts styled mode. The class name is applied to group elements
10762 * for the grid, axis elements and labels.
10763 *
10764 * @type {String}
10765 * @sample {highcharts|highstock|highmaps}
10766 * highcharts/css/axis/
10767 * Multiple axes with separate styling
10768 * @since 5.0.0
10769 * @apioption xAxis.className
10770 */
10771
10772 /**
10773 * Configure a crosshair that follows either the mouse pointer or the
10774 * hovered point.
10775 *
10776 * In styled mode, the crosshairs are styled in the
10777 * `.highcharts-crosshair`, `.highcharts-crosshair-thin` or
10778 * `.highcharts-xaxis-category` classes.
10779 *
10780 * @productdesc {highstock}
10781 * In Highstock, bu default, the crosshair is enabled on the X axis and
10782 * disabled on the Y axis.
10783 *
10784 * @type {Boolean|Object}
10785 * @sample {highcharts} highcharts/xaxis/crosshair-both/
10786 * Crosshair on both axes
10787 * @sample {highstock} stock/xaxis/crosshairs-xy/
10788 * Crosshair on both axes
10789 * @sample {highmaps} highcharts/xaxis/crosshair-both/
10790 * Crosshair on both axes
10791 * @default false
10792 * @since 4.1
10793 * @apioption xAxis.crosshair
10794 */
10795
10796 /**
10797 * A class name for the crosshair, especially as a hook for styling.
10798 *
10799 * @type {String}
10800 * @since 5.0.0
10801 * @apioption xAxis.crosshair.className
10802 */
10803
10804 /**
10805 * The color of the crosshair. Defaults to `#cccccc` for numeric and
10806 * datetime axes, and `rgba(204,214,235,0.25)` for category axes, where
10807 * the crosshair by default highlights the whole category.
10808 *
10809 * @type {Color}
10810 * @sample {highcharts|highstock|highmaps}
10811 * highcharts/xaxis/crosshair-customized/
10812 * Customized crosshairs
10813 * @default #cccccc
10814 * @since 4.1
10815 * @apioption xAxis.crosshair.color
10816 */
10817
10818 /**
10819 * The dash style for the crosshair. See
10820 * [series.dashStyle](#plotOptions.series.dashStyle)
10821 * for possible values.
10822 *
10823 * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
10824 * "ShortDashDotDot", "Dot", "Dash" ,"LongDash",
10825 * "DashDot", "LongDashDot", "LongDashDotDot"]
10826 * @type {String}
10827 * @sample {highcharts|highmaps} highcharts/xaxis/crosshair-dotted/
10828 * Dotted crosshair
10829 * @sample {highstock} stock/xaxis/crosshair-dashed/
10830 * Dashed X axis crosshair
10831 * @default Solid
10832 * @since 4.1
10833 * @apioption xAxis.crosshair.dashStyle
10834 */
10835
10836 /**
10837 * Whether the crosshair should snap to the point or follow the pointer
10838 * independent of points.
10839 *
10840 * @type {Boolean}
10841 * @sample {highcharts|highstock}
10842 * highcharts/xaxis/crosshair-snap-false/
10843 * True by default
10844 * @sample {highmaps}
10845 * maps/demo/latlon-advanced/
10846 * Snap is false
10847 * @default true
10848 * @since 4.1
10849 * @apioption xAxis.crosshair.snap
10850 */
10851
10852 /**
10853 * The pixel width of the crosshair. Defaults to 1 for numeric or
10854 * datetime axes, and for one category width for category axes.
10855 *
10856 * @type {Number}
10857 * @sample {highcharts} highcharts/xaxis/crosshair-customized/
10858 * Customized crosshairs
10859 * @sample {highstock} highcharts/xaxis/crosshair-customized/
10860 * Customized crosshairs
10861 * @sample {highmaps} highcharts/xaxis/crosshair-customized/
10862 * Customized crosshairs
10863 * @default 1
10864 * @since 4.1
10865 * @apioption xAxis.crosshair.width
10866 */
10867
10868 /**
10869 * The Z index of the crosshair. Higher Z indices allow drawing the
10870 * crosshair on top of the series or behind the grid lines.
10871 *
10872 * @type {Number}
10873 * @default 2
10874 * @since 4.1
10875 * @apioption xAxis.crosshair.zIndex
10876 */
10877
10878 /**
10879 * For a datetime axis, the scale will automatically adjust to the
10880 * appropriate unit. This member gives the default string
10881 * representations used for each unit. For intermediate values,
10882 * different units may be used, for example the `day` unit can be used
10883 * on midnight and `hour` unit be used for intermediate values on the
10884 * same axis. For an overview of the replacement codes, see
10885 * [dateFormat](#Highcharts.dateFormat). Defaults to:
10886 *
10887 * <pre>{
10888 * millisecond: '%H:%M:%S.%L',
10889 * second: '%H:%M:%S',
10890 * minute: '%H:%M',
10891 * hour: '%H:%M',
10892 * day: '%e. %b',
10893 * week: '%e. %b',
10894 * month: '%b \'%y',
10895 * year: '%Y'
10896 * }</pre>
10897 *
10898 * @type {Object}
10899 * @sample {highcharts} highcharts/xaxis/datetimelabelformats/
10900 * Different day format on X axis
10901 * @sample {highstock} stock/xaxis/datetimelabelformats/
10902 * More information in x axis labels
10903 * @product highcharts highstock
10904 */
10905 dateTimeLabelFormats: {
10906 millisecond: '%H:%M:%S.%L',
10907 second: '%H:%M:%S',
10908 minute: '%H:%M',
10909 hour: '%H:%M',
10910 day: '%e. %b',
10911 week: '%e. %b',
10912 month: '%b \'%y',
10913 year: '%Y'
10914 },
10915
10916 /**
10917 * _Requires Accessibility module_
10918 *
10919 * Description of the axis to screen reader users.
10920 *
10921 * @type {String}
10922 * @default undefined
10923 * @since 5.0.0
10924 * @apioption xAxis.description
10925 */
10926
10927 /**
10928 * Whether to force the axis to end on a tick. Use this option with
10929 * the `maxPadding` option to control the axis end.
10930 *
10931 * @productdesc {highstock}
10932 * In Highstock, `endOnTick` is always false when the navigator is
10933 * enabled, to prevent jumpy scrolling.
10934 *
10935 * @sample {highcharts} highcharts/chart/reflow-true/
10936 * True by default
10937 * @sample {highcharts} highcharts/yaxis/endontick/
10938 * False
10939 * @sample {highstock} stock/demo/basic-line/
10940 * True by default
10941 * @sample {highstock} stock/xaxis/endontick/
10942 * False
10943 * @since 1.2.0
10944 */
10945 endOnTick: false,
10946
10947 /**
10948 * Event handlers for the axis.
10949 *
10950 * @apioption xAxis.events
10951 */
10952
10953 /**
10954 * An event fired after the breaks have rendered.
10955 *
10956 * @type {Function}
10957 * @see [breaks](#xAxis.breaks)
10958 * @sample {highcharts} highcharts/axisbreak/break-event/
10959 * AfterBreak Event
10960 * @since 4.1.0
10961 * @product highcharts
10962 * @apioption xAxis.events.afterBreaks
10963 */
10964
10965 /**
10966 * As opposed to the `setExtremes` event, this event fires after the
10967 * final min and max values are computed and corrected for `minRange`.
10968 *
10969 *
10970 * Fires when the minimum and maximum is set for the axis, either by
10971 * calling the `.setExtremes()` method or by selecting an area in the
10972 * chart. One parameter, `event`, is passed to the function, containing
10973 * common event information.
10974 *
10975 * The new user set minimum and maximum values can be found by `event.
10976 * min` and `event.max`. These reflect the axis minimum and maximum
10977 * in axis values. The actual data extremes are found in `event.dataMin`
10978 * and `event.dataMax`.
10979 *
10980 * @type {Function}
10981 * @context Axis
10982 * @since 2.3
10983 * @apioption xAxis.events.afterSetExtremes
10984 */
10985
10986 /**
10987 * An event fired when a break from this axis occurs on a point.
10988 *
10989 * @type {Function}
10990 * @see [breaks](#xAxis.breaks)
10991 * @context Axis
10992 * @sample {highcharts} highcharts/axisbreak/break-visualized/
10993 * Visualization of a Break
10994 * @since 4.1.0
10995 * @product highcharts
10996 * @apioption xAxis.events.pointBreak
10997 */
10998
10999 /**
11000 * An event fired when a point falls inside a break from this axis.
11001 *
11002 * @type {Function}
11003 * @context Axis
11004 * @product highcharts highstock
11005 * @apioption xAxis.events.pointInBreak
11006 */
11007
11008 /**
11009 * Fires when the minimum and maximum is set for the axis, either by
11010 * calling the `.setExtremes()` method or by selecting an area in the
11011 * chart. One parameter, `event`, is passed to the function,
11012 * containing common event information.
11013 *
11014 * The new user set minimum and maximum values can be found by `event.
11015 * min` and `event.max`. These reflect the axis minimum and maximum
11016 * in data values. When an axis is zoomed all the way out from the
11017 * "Reset zoom" button, `event.min` and `event.max` are null, and
11018 * the new extremes are set based on `this.dataMin` and `this.dataMax`.
11019 *
11020 * @type {Function}
11021 * @context Axis
11022 * @sample {highstock} stock/xaxis/events-setextremes/
11023 * Log new extremes on x axis
11024 * @since 1.2.0
11025 * @apioption xAxis.events.setExtremes
11026 */
11027
11028 /**
11029 * The lowest allowed value for automatically computed axis extremes.
11030 *
11031 * @type {Number}
11032 * @see [ceiling](#yAxis.ceiling)
11033 * @sample {highcharts} highcharts/yaxis/floor-ceiling/
11034 * Floor and ceiling
11035 * @sample {highstock} stock/demo/lazy-loading/
11036 * Prevent negative stock price on Y axis
11037 * @default null
11038 * @since 4.0
11039 * @product highcharts highstock
11040 * @apioption xAxis.floor
11041 */
11042
11043 /**
11044 * The dash or dot style of the grid lines. For possible values, see
11045 * [this demonstration](http://jsfiddle.net/gh/get/library/pure/
11046 *highcharts/highcharts/tree/master/samples/highcharts/plotoptions/
11047 *series-dashstyle-all/).
11048 *
11049 * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
11050 * "ShortDashDotDot", "Dot", "Dash" ,"LongDash",
11051 * "DashDot", "LongDashDot", "LongDashDotDot"]
11052 * @type {String}
11053 * @sample {highcharts} highcharts/yaxis/gridlinedashstyle/
11054 * Long dashes
11055 * @sample {highstock} stock/xaxis/gridlinedashstyle/
11056 * Long dashes
11057 * @default Solid
11058 * @since 1.2
11059 * @apioption xAxis.gridLineDashStyle
11060 */
11061
11062 /**
11063 * The Z index of the grid lines.
11064 *
11065 * @type {Number}
11066 * @sample {highcharts|highstock} highcharts/xaxis/gridzindex/
11067 * A Z index of 4 renders the grid above the graph
11068 * @default 1
11069 * @product highcharts highstock
11070 * @apioption xAxis.gridZIndex
11071 */
11072
11073 /**
11074 * An id for the axis. This can be used after render time to get
11075 * a pointer to the axis object through `chart.get()`.
11076 *
11077 * @type {String}
11078 * @sample {highcharts} highcharts/xaxis/id/
11079 * Get the object
11080 * @sample {highstock} stock/xaxis/id/
11081 * Get the object
11082 * @default null
11083 * @since 1.2.0
11084 * @apioption xAxis.id
11085 */
11086
11087 /**
11088 * The axis labels show the number or category for each tick.
11089 *
11090 * @productdesc {highmaps}
11091 * X and Y axis labels are by default disabled in Highmaps, but the
11092 * functionality is inherited from Highcharts and used on `colorAxis`,
11093 * and can be enabled on X and Y axes too.
11094 */
11095 labels: {
11096 /**
11097 * What part of the string the given position is anchored to.
11098 * If `left`, the left side of the string is at the axis position.
11099 * Can be one of `"left"`, `"center"` or `"right"`. Defaults to
11100 * an intelligent guess based on which side of the chart the axis
11101 * is on and the rotation of the label.
11102 *
11103 * @validvalue ["left", "center", "right"]
11104 * @type {String}
11105 * @sample {highcharts} highcharts/xaxis/labels-align-left/
11106 * Left
11107 * @sample {highcharts} highcharts/xaxis/labels-align-right/
11108 * Right
11109 * @apioption xAxis.labels.align
11110 */
11111 // align: 'center',
11112
11113 /**
11114 * For horizontal axes, the allowed degrees of label rotation
11115 * to prevent overlapping labels. If there is enough space,
11116 * labels are not rotated. As the chart gets narrower, it
11117 * will start rotating the labels -45 degrees, then remove
11118 * every second label and try again with rotations 0 and -45 etc.
11119 * Set it to `false` to disable rotation, which will
11120 * cause the labels to word-wrap if possible.
11121 *
11122 * @type {Array<Number>}
11123 * @sample {highcharts|highstock}
11124 * highcharts/xaxis/labels-autorotation-default/
11125 * Default auto rotation of 0 or -45
11126 * @sample {highcharts|highstock}
11127 * highcharts/xaxis/labels-autorotation-0-90/
11128 * Custom graded auto rotation
11129 * @default [-45]
11130 * @since 4.1.0
11131 * @product highcharts highstock
11132 * @apioption xAxis.labels.autoRotation
11133 */
11134
11135 /**
11136 * When each category width is more than this many pixels, we don't
11137 * apply auto rotation. Instead, we lay out the axis label with word
11138 * wrap. A lower limit makes sense when the label contains multiple
11139 * short words that don't extend the available horizontal space for
11140 * each label.
11141 *
11142 * @type {Number}
11143 * @sample {highcharts}
11144 * highcharts/xaxis/labels-autorotationlimit/
11145 * Lower limit
11146 * @default 80
11147 * @since 4.1.5
11148 * @product highcharts
11149 * @apioption xAxis.labels.autoRotationLimit
11150 */
11151
11152 /**
11153 * Polar charts only. The label's pixel distance from the perimeter
11154 * of the plot area.
11155 *
11156 * @type {Number}
11157 * @default 15
11158 * @product highcharts
11159 * @apioption xAxis.labels.distance
11160 */
11161
11162 /**
11163 * Enable or disable the axis labels.
11164 *
11165 * @sample {highcharts} highcharts/xaxis/labels-enabled/
11166 * X axis labels disabled
11167 * @sample {highstock} stock/xaxis/labels-enabled/
11168 * X axis labels disabled
11169 * @default {highcharts|highstock} true
11170 * @default {highmaps} false
11171 */
11172 enabled: true,
11173
11174 /**
11175 * A [format string](http://www.highcharts.com/docs/chart-
11176 * concepts/labels-and-string-formatting) for the axis label.
11177 *
11178 * @type {String}
11179 * @sample {highcharts|highstock} highcharts/yaxis/labels-format/
11180 * Add units to Y axis label
11181 * @default {value}
11182 * @since 3.0
11183 * @apioption xAxis.labels.format
11184 */
11185
11186 /**
11187 * Callback JavaScript function to format the label. The value
11188 * is given by `this.value`. Additional properties for `this` are
11189 * `axis`, `chart`, `isFirst` and `isLast`. The value of the default
11190 * label formatter can be retrieved by calling
11191 * `this.axis.defaultLabelFormatter.call(this)` within the function.
11192 *
11193 * Defaults to:
11194 *
11195 * <pre>function() {
11196 * return this.value;
11197 * }</pre>
11198 *
11199 * @type {Function}
11200 * @sample {highcharts}
11201 * highcharts/xaxis/labels-formatter-linked/
11202 * Linked category names
11203 * @sample {highcharts}
11204 * highcharts/xaxis/labels-formatter-extended/
11205 * Modified numeric labels
11206 * @sample {highstock}
11207 * stock/xaxis/labels-formatter/
11208 * Added units on Y axis
11209 * @apioption xAxis.labels.formatter
11210 */
11211
11212 /**
11213 * How to handle overflowing labels on horizontal axis. Can be
11214 * undefined, `false` or `"justify"`. By default it aligns inside
11215 * the chart area. If "justify", labels will not render outside
11216 * the plot area. If `false`, it will not be aligned at all.
11217 * If there is room to move it, it will be aligned to the edge,
11218 * else it will be removed.
11219 *
11220 * @deprecated
11221 * @validvalue [null, "justify"]
11222 * @type {String}
11223 * @since 2.2.5
11224 * @apioption xAxis.labels.overflow
11225 */
11226
11227 /**
11228 * The pixel padding for axis labels, to ensure white space between
11229 * them.
11230 *
11231 * @type {Number}
11232 * @default 5
11233 * @product highcharts
11234 * @apioption xAxis.labels.padding
11235 */
11236
11237 /**
11238 * Whether to reserve space for the labels. This can be turned off
11239 * when for example the labels are rendered inside the plot area
11240 * instead of outside.
11241 *
11242 * @type {Boolean}
11243 * @sample {highcharts} highcharts/xaxis/labels-reservespace/
11244 * No reserved space, labels inside plot
11245 * @default true
11246 * @since 4.1.10
11247 * @product highcharts
11248 * @apioption xAxis.labels.reserveSpace
11249 */
11250
11251 /**
11252 * Rotation of the labels in degrees.
11253 *
11254 * @type {Number}
11255 * @sample {highcharts} highcharts/xaxis/labels-rotation/
11256 * X axis labels rotated 90°
11257 * @default 0
11258 * @apioption xAxis.labels.rotation
11259 */
11260 // rotation: 0,
11261
11262 /**
11263 * Horizontal axes only. The number of lines to spread the labels
11264 * over to make room or tighter labels.
11265 *
11266 * @type {Number}
11267 * @sample {highcharts} highcharts/xaxis/labels-staggerlines/
11268 * Show labels over two lines
11269 * @sample {highstock} stock/xaxis/labels-staggerlines/
11270 * Show labels over two lines
11271 * @default null
11272 * @since 2.1
11273 * @apioption xAxis.labels.staggerLines
11274 */
11275
11276 /**
11277 * To show only every _n_'th label on the axis, set the step to _n_.
11278 * Setting the step to 2 shows every other label.
11279 *
11280 * By default, the step is calculated automatically to avoid
11281 * overlap. To prevent this, set it to 1\. This usually only
11282 * happens on a category axis, and is often a sign that you have
11283 * chosen the wrong axis type.
11284 *
11285 * Read more at
11286 * [Axis docs](http://www.highcharts.com/docs/chart-concepts/axes)
11287 * => What axis should I use?
11288 *
11289 * @type {Number}
11290 * @sample {highcharts} highcharts/xaxis/labels-step/
11291 * Showing only every other axis label on a categorized
11292 * x axis
11293 * @sample {highcharts} highcharts/xaxis/labels-step-auto/
11294 * Auto steps on a category axis
11295 * @default null
11296 * @since 2.1
11297 * @apioption xAxis.labels.step
11298 */
11299 // step: null,
11300
11301
11302
11303 /**
11304 * CSS styles for the label. Use `whiteSpace: 'nowrap'` to prevent
11305 * wrapping of category labels. Use `textOverflow: 'none'` to
11306 * prevent ellipsis (dots).
11307 *
11308 * In styled mode, the labels are styled with the
11309 * `.highcharts-axis-labels` class.
11310 *
11311 * @type {CSSObject}
11312 * @sample {highcharts} highcharts/xaxis/labels-style/
11313 * Red X axis labels
11314 */
11315 style: {
11316 color: '#666666',
11317 cursor: 'default',
11318 fontSize: '11px'
11319 },
11320
11321
11322 /**
11323 * Whether to [use HTML](http://www.highcharts.com/docs/chart-
11324 * concepts/labels-and-string-formatting#html) to render the labels.
11325 *
11326 * @type {Boolean}
11327 * @default false
11328 * @apioption xAxis.labels.useHTML
11329 */
11330
11331 /**
11332 * The x position offset of the label relative to the tick position
11333 * on the axis.
11334 *
11335 * @sample {highcharts} highcharts/xaxis/labels-x/
11336 * Y axis labels placed on grid lines
11337 */
11338 x: 0
11339
11340 /**
11341 * The y position offset of the label relative to the tick position
11342 * on the axis. The default makes it adapt to the font size on
11343 * bottom axis.
11344 *
11345 * @type {Number}
11346 * @sample {highcharts} highcharts/xaxis/labels-x/
11347 * Y axis labels placed on grid lines
11348 * @default null
11349 * @apioption xAxis.labels.y
11350 */
11351
11352 /**
11353 * The Z index for the axis labels.
11354 *
11355 * @type {Number}
11356 * @default 7
11357 * @apioption xAxis.labels.zIndex
11358 */
11359 },
11360
11361 /**
11362 * Index of another axis that this axis is linked to. When an axis is
11363 * linked to a master axis, it will take the same extremes as
11364 * the master, but as assigned by min or max or by setExtremes.
11365 * It can be used to show additional info, or to ease reading the
11366 * chart by duplicating the scales.
11367 *
11368 * @type {Number}
11369 * @sample {highcharts} highcharts/xaxis/linkedto/
11370 * Different string formats of the same date
11371 * @sample {highcharts} highcharts/yaxis/linkedto/
11372 * Y values on both sides
11373 * @default null
11374 * @since 2.0.2
11375 * @product highcharts highstock
11376 * @apioption xAxis.linkedTo
11377 */
11378
11379 /**
11380 * The maximum value of the axis. If `null`, the max value is
11381 * automatically calculated.
11382 *
11383 * If the `endOnTick` option is true, the `max` value might
11384 * be rounded up.
11385 *
11386 * If a [tickAmount](#yAxis.tickAmount) is set, the axis may be extended
11387 * beyond the set max in order to reach the given number of ticks. The
11388 * same may happen in a chart with multiple axes, determined by [chart.
11389 * alignTicks](#chart), where a `tickAmount` is applied internally.
11390 *
11391 * @type {Number}
11392 * @sample {highcharts} highcharts/yaxis/max-200/
11393 * Y axis max of 200
11394 * @sample {highcharts} highcharts/yaxis/max-logarithmic/
11395 * Y axis max on logarithmic axis
11396 * @sample {highstock} stock/xaxis/min-max/
11397 * Fixed min and max on X axis
11398 * @sample {highmaps} maps/axis/min-max/
11399 * Pre-zoomed to a specific area
11400 * @apioption xAxis.max
11401 */
11402
11403 /**
11404 * Padding of the max value relative to the length of the axis. A
11405 * padding of 0.05 will make a 100px axis 5px longer. This is useful
11406 * when you don't want the highest data value to appear on the edge
11407 * of the plot area. When the axis' `max` option is set or a max extreme
11408 * is set using `axis.setExtremes()`, the maxPadding will be ignored.
11409 *
11410 * @sample {highcharts} highcharts/yaxis/maxpadding/
11411 * Max padding of 0.25 on y axis
11412 * @sample {highstock} stock/xaxis/minpadding-maxpadding/
11413 * Greater min- and maxPadding
11414 * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
11415 * Add some padding
11416 * @default {highcharts} 0.01
11417 * @default {highstock|highmaps} 0
11418 * @since 1.2.0
11419 */
11420 maxPadding: 0.01,
11421
11422 /**
11423 * Deprecated. Use `minRange` instead.
11424 *
11425 * @deprecated
11426 * @type {Number}
11427 * @product highcharts highstock
11428 * @apioption xAxis.maxZoom
11429 */
11430
11431 /**
11432 * The minimum value of the axis. If `null` the min value is
11433 * automatically calculated.
11434 *
11435 * If the `startOnTick` option is true (default), the `min` value might
11436 * be rounded down.
11437 *
11438 * The automatically calculated minimum value is also affected by
11439 * [floor](#yAxis.floor), [softMin](#yAxis.softMin),
11440 * [minPadding](#yAxis.minPadding), [minRange](#yAxis.minRange)
11441 * as well as [series.threshold](#plotOptions.series.threshold)
11442 * and [series.softThreshold](#plotOptions.series.softThreshold).
11443 *
11444 * @type {Number}
11445 * @sample {highcharts} highcharts/yaxis/min-startontick-false/
11446 * -50 with startOnTick to false
11447 * @sample {highcharts} highcharts/yaxis/min-startontick-true/
11448 * -50 with startOnTick true by default
11449 * @sample {highstock} stock/xaxis/min-max/
11450 * Set min and max on X axis
11451 * @sample {highmaps} maps/axis/min-max/
11452 * Pre-zoomed to a specific area
11453 * @apioption xAxis.min
11454 */
11455
11456 /**
11457 * The dash or dot style of the minor grid lines. For possible values,
11458 * see [this demonstration](http://jsfiddle.net/gh/get/library/pure/
11459 * highcharts/highcharts/tree/master/samples/highcharts/plotoptions/
11460 * series-dashstyle-all/).
11461 *
11462 * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
11463 * "ShortDashDotDot", "Dot", "Dash" ,"LongDash",
11464 * "DashDot", "LongDashDot", "LongDashDotDot"]
11465 * @type {String}
11466 * @sample {highcharts} highcharts/yaxis/minorgridlinedashstyle/
11467 * Long dashes on minor grid lines
11468 * @sample {highstock} stock/xaxis/minorgridlinedashstyle/
11469 * Long dashes on minor grid lines
11470 * @default Solid
11471 * @since 1.2
11472 * @apioption xAxis.minorGridLineDashStyle
11473 */
11474
11475 /**
11476 * Specific tick interval in axis units for the minor ticks.
11477 * On a linear axis, if `"auto"`, the minor tick interval is
11478 * calculated as a fifth of the tickInterval. If `null`, minor
11479 * ticks are not shown.
11480 *
11481 * On logarithmic axes, the unit is the power of the value. For example,
11482 * setting the minorTickInterval to 1 puts one tick on each of 0.1,
11483 * 1, 10, 100 etc. Setting the minorTickInterval to 0.1 produces 9
11484 * ticks between 1 and 10, 10 and 100 etc.
11485 *
11486 * If user settings dictate minor ticks to become too dense, they don't
11487 * make sense, and will be ignored to prevent performance problems.
11488 *
11489 * @type {Number|String}
11490 * @sample {highcharts} highcharts/yaxis/minortickinterval-null/
11491 * Null by default
11492 * @sample {highcharts} highcharts/yaxis/minortickinterval-5/
11493 * 5 units
11494 * @sample {highcharts} highcharts/yaxis/minortickinterval-log-auto/
11495 * "auto"
11496 * @sample {highcharts} highcharts/yaxis/minortickinterval-log/
11497 * 0.1
11498 * @sample {highstock} stock/demo/basic-line/
11499 * Null by default
11500 * @sample {highstock} stock/xaxis/minortickinterval-auto/
11501 * "auto"
11502 * @apioption xAxis.minorTickInterval
11503 */
11504
11505 /**
11506 * The pixel length of the minor tick marks.
11507 *
11508 * @sample {highcharts} highcharts/yaxis/minorticklength/
11509 * 10px on Y axis
11510 * @sample {highstock} stock/xaxis/minorticks/
11511 * 10px on Y axis
11512 */
11513 minorTickLength: 2,
11514
11515 /**
11516 * The position of the minor tick marks relative to the axis line.
11517 * Can be one of `inside` and `outside`.
11518 *
11519 * @validvalue ["inside", "outside"]
11520 * @sample {highcharts} highcharts/yaxis/minortickposition-outside/
11521 * Outside by default
11522 * @sample {highcharts} highcharts/yaxis/minortickposition-inside/
11523 * Inside
11524 * @sample {highstock} stock/xaxis/minorticks/
11525 * Inside
11526 */
11527 minorTickPosition: 'outside',
11528
11529 /**
11530 * Enable or disable minor ticks. Unless
11531 * [minorTickInterval](#xAxis.minorTickInterval) is set, the tick
11532 * interval is calculated as a fifth of the `tickInterval`.
11533 *
11534 * On a logarithmic axis, minor ticks are laid out based on a best
11535 * guess, attempting to enter approximately 5 minor ticks between
11536 * each major tick.
11537 *
11538 * Prior to v6.0.0, ticks were unabled in auto layout by setting
11539 * `minorTickInterval` to `"auto"`.
11540 *
11541 * @productdesc {highcharts}
11542 * On axes using [categories](#xAxis.categories), minor ticks are not
11543 * supported.
11544 *
11545 * @type {Boolean}
11546 * @default false
11547 * @since 6.0.0
11548 * @sample {highcharts} highcharts/yaxis/minorticks-true/
11549 * Enabled on linear Y axis
11550 * @apioption xAxis.minorTicks
11551 */
11552
11553 /**
11554 * The pixel width of the minor tick mark.
11555 *
11556 * @type {Number}
11557 * @sample {highcharts} highcharts/yaxis/minortickwidth/
11558 * 3px width
11559 * @sample {highstock} stock/xaxis/minorticks/
11560 * 1px width
11561 * @default 0
11562 * @apioption xAxis.minorTickWidth
11563 */
11564
11565 /**
11566 * Padding of the min value relative to the length of the axis. A
11567 * padding of 0.05 will make a 100px axis 5px longer. This is useful
11568 * when you don't want the lowest data value to appear on the edge
11569 * of the plot area. When the axis' `min` option is set or a min extreme
11570 * is set using `axis.setExtremes()`, the minPadding will be ignored.
11571 *
11572 * @sample {highcharts} highcharts/yaxis/minpadding/
11573 * Min padding of 0.2
11574 * @sample {highstock} stock/xaxis/minpadding-maxpadding/
11575 * Greater min- and maxPadding
11576 * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
11577 * Add some padding
11578 * @default {highcharts} 0.01
11579 * @default {highstock|highmaps} 0
11580 * @since 1.2.0
11581 */
11582 minPadding: 0.01,
11583
11584 /**
11585 * The minimum range to display on this axis. The entire axis will not
11586 * be allowed to span over a smaller interval than this. For example,
11587 * for a datetime axis the main unit is milliseconds. If minRange is
11588 * set to 3600000, you can't zoom in more than to one hour.
11589 *
11590 * The default minRange for the x axis is five times the smallest
11591 * interval between any of the data points.
11592 *
11593 * On a logarithmic axis, the unit for the minimum range is the power.
11594 * So a minRange of 1 means that the axis can be zoomed to 10-100,
11595 * 100-1000, 1000-10000 etc.
11596 *
11597 * Note that the `minPadding`, `maxPadding`, `startOnTick` and
11598 * `endOnTick` settings also affect how the extremes of the axis
11599 * are computed.
11600 *
11601 * @type {Number}
11602 * @sample {highcharts} highcharts/xaxis/minrange/
11603 * Minimum range of 5
11604 * @sample {highstock} stock/xaxis/minrange/
11605 * Max zoom of 6 months overrides user selections
11606 * @sample {highmaps} maps/axis/minrange/
11607 * Minimum range of 1000
11608 * @apioption xAxis.minRange
11609 */
11610
11611 /**
11612 * The minimum tick interval allowed in axis values. For example on
11613 * zooming in on an axis with daily data, this can be used to prevent
11614 * the axis from showing hours. Defaults to the closest distance between
11615 * two points on the axis.
11616 *
11617 * @type {Number}
11618 * @since 2.3.0
11619 * @apioption xAxis.minTickInterval
11620 */
11621
11622 /**
11623 * The distance in pixels from the plot area to the axis line.
11624 * A positive offset moves the axis with it's line, labels and ticks
11625 * away from the plot area. This is typically used when two or more
11626 * axes are displayed on the same side of the plot. With multiple
11627 * axes the offset is dynamically adjusted to avoid collision, this
11628 * can be overridden by setting offset explicitly.
11629 *
11630 * @type {Number}
11631 * @sample {highcharts} highcharts/yaxis/offset/
11632 * Y axis offset of 70
11633 * @sample {highcharts} highcharts/yaxis/offset-centered/
11634 * Axes positioned in the center of the plot
11635 * @sample {highstock} stock/xaxis/offset/
11636 * Y axis offset by 70 px
11637 * @default 0
11638 * @apioption xAxis.offset
11639 */
11640
11641 /**
11642 * Whether to display the axis on the opposite side of the normal. The
11643 * normal is on the left side for vertical axes and bottom for
11644 * horizontal, so the opposite sides will be right and top respectively.
11645 * This is typically used with dual or multiple axes.
11646 *
11647 * @type {Boolean}
11648 * @sample {highcharts} highcharts/yaxis/opposite/
11649 * Secondary Y axis opposite
11650 * @sample {highstock} stock/xaxis/opposite/
11651 * Y axis on left side
11652 * @default false
11653 * @apioption xAxis.opposite
11654 */
11655
11656 /**
11657 * Whether to reverse the axis so that the highest number is closest
11658 * to the origin. If the chart is inverted, the x axis is reversed by
11659 * default.
11660 *
11661 * @type {Boolean}
11662 * @sample {highcharts} highcharts/yaxis/reversed/
11663 * Reversed Y axis
11664 * @sample {highstock} stock/xaxis/reversed/
11665 * Reversed Y axis
11666 * @default false
11667 * @apioption xAxis.reversed
11668 */
11669 // reversed: false,
11670
11671 /**
11672 * Whether to show the last tick label. Defaults to `true` on cartesian
11673 * charts, and `false` on polar charts.
11674 *
11675 * @type {Boolean}
11676 * @sample {highcharts} highcharts/xaxis/showlastlabel-true/
11677 * Set to true on X axis
11678 * @sample {highstock} stock/xaxis/showfirstlabel/
11679 * Labels below plot lines on Y axis
11680 * @default true
11681 * @product highcharts highstock
11682 * @apioption xAxis.showLastLabel
11683 */
11684
11685 /**
11686 * For datetime axes, this decides where to put the tick between weeks.
11687 * 0 = Sunday, 1 = Monday.
11688 *
11689 * @sample {highcharts} highcharts/xaxis/startofweek-monday/
11690 * Monday by default
11691 * @sample {highcharts} highcharts/xaxis/startofweek-sunday/
11692 * Sunday
11693 * @sample {highstock} stock/xaxis/startofweek-1
11694 * Monday by default
11695 * @sample {highstock} stock/xaxis/startofweek-0
11696 * Sunday
11697 * @product highcharts highstock
11698 */
11699 startOfWeek: 1,
11700
11701 /**
11702 * Whether to force the axis to start on a tick. Use this option with
11703 * the `minPadding` option to control the axis start.
11704 *
11705 * @productdesc {highstock}
11706 * In Highstock, `startOnTick` is always false when the navigator is
11707 * enabled, to prevent jumpy scrolling.
11708 *
11709 * @sample {highcharts} highcharts/xaxis/startontick-false/
11710 * False by default
11711 * @sample {highcharts} highcharts/xaxis/startontick-true/
11712 * True
11713 * @sample {highstock} stock/xaxis/endontick/
11714 * False for Y axis
11715 * @since 1.2.0
11716 */
11717 startOnTick: false,
11718
11719 /**
11720 * The pixel length of the main tick marks.
11721 *
11722 * @sample {highcharts} highcharts/xaxis/ticklength/
11723 * 20 px tick length on the X axis
11724 * @sample {highstock} stock/xaxis/ticks/
11725 * Formatted ticks on X axis
11726 */
11727 tickLength: 10,
11728
11729 /**
11730 * For categorized axes only. If `on` the tick mark is placed in the
11731 * center of the category, if `between` the tick mark is placed between
11732 * categories. The default is `between` if the `tickInterval` is 1,
11733 * else `on`.
11734 *
11735 * @validvalue [null, "on", "between"]
11736 * @sample {highcharts} highcharts/xaxis/tickmarkplacement-between/
11737 * "between" by default
11738 * @sample {highcharts} highcharts/xaxis/tickmarkplacement-on/
11739 * "on"
11740 * @product highcharts
11741 */
11742 tickmarkPlacement: 'between',
11743
11744 /**
11745 * If tickInterval is `null` this option sets the approximate pixel
11746 * interval of the tick marks. Not applicable to categorized axis.
11747 *
11748 * The tick interval is also influenced by the [minTickInterval](#xAxis.
11749 * minTickInterval) option, that, by default prevents ticks from being
11750 * denser than the data points.
11751 *
11752 * @see [tickInterval](#xAxis.tickInterval),
11753 * [tickPositioner](#xAxis.tickPositioner),
11754 * [tickPositions](#xAxis.tickPositions).
11755 * @sample {highcharts} highcharts/xaxis/tickpixelinterval-50/
11756 * 50 px on X axis
11757 * @sample {highstock} stock/xaxis/tickpixelinterval/
11758 * 200 px on X axis
11759 */
11760 tickPixelInterval: 100,
11761
11762 /**
11763 * The position of the major tick marks relative to the axis line.
11764 * Can be one of `inside` and `outside`.
11765 *
11766 * @validvalue ["inside", "outside"]
11767 * @sample {highcharts} highcharts/xaxis/tickposition-outside/
11768 * "outside" by default
11769 * @sample {highcharts} highcharts/xaxis/tickposition-inside/
11770 * "inside"
11771 * @sample {highstock} stock/xaxis/ticks/
11772 * Formatted ticks on X axis
11773 */
11774 tickPosition: 'outside',
11775
11776 /**
11777 * The axis title, showing next to the axis line.
11778 *
11779 * @productdesc {highmaps}
11780 * In Highmaps, the axis is hidden by default, but adding an axis title
11781 * is still possible. X axis and Y axis titles will appear at the bottom
11782 * and left by default.
11783 */
11784 title: {
11785
11786 /**
11787 * Alignment of the title relative to the axis values. Possible
11788 * values are "low", "middle" or "high".
11789 *
11790 * @validvalue ["low", "middle", "high"]
11791 * @sample {highcharts} highcharts/xaxis/title-align-low/
11792 * "low"
11793 * @sample {highcharts} highcharts/xaxis/title-align-center/
11794 * "middle" by default
11795 * @sample {highcharts} highcharts/xaxis/title-align-high/
11796 * "high"
11797 * @sample {highcharts} highcharts/yaxis/title-offset/
11798 * Place the Y axis title on top of the axis
11799 * @sample {highstock} stock/xaxis/title-align/
11800 * Aligned to "high" value
11801 */
11802 align: 'middle',
11803
11804
11805
11806 /**
11807 * CSS styles for the title. If the title text is longer than the
11808 * axis length, it will wrap to multiple lines by default. This can
11809 * be customized by setting `textOverflow: 'ellipsis'`, by
11810 * setting a specific `width` or by setting `wordSpace: 'nowrap'`.
11811 *
11812 * In styled mode, the stroke width is given in the
11813 * `.highcharts-axis-title` class.
11814 *
11815 * @type {CSSObject}
11816 * @sample {highcharts} highcharts/xaxis/title-style/
11817 * Red
11818 * @sample {highcharts} highcharts/css/axis/
11819 * Styled mode
11820 * @default { "color": "#666666" }
11821 */
11822 style: {
11823 color: '#666666'
11824 }
11825
11826 },
11827
11828 /**
11829 * The type of axis. Can be one of `linear`, `logarithmic`, `datetime`
11830 * or `category`. In a datetime axis, the numbers are given in
11831 * milliseconds, and tick marks are placed on appropriate values like
11832 * full hours or days. In a category axis, the
11833 * [point names](#series.line.data.name) of the chart's series are used
11834 * for categories, if not a [categories](#xAxis.categories) array is
11835 * defined.
11836 *
11837 * @validvalue ["linear", "logarithmic", "datetime", "category"]
11838 * @sample {highcharts} highcharts/xaxis/type-linear/
11839 * Linear
11840 * @sample {highcharts} highcharts/yaxis/type-log/
11841 * Logarithmic
11842 * @sample {highcharts} highcharts/yaxis/type-log-minorgrid/
11843 * Logarithmic with minor grid lines
11844 * @sample {highcharts} highcharts/xaxis/type-log-both/
11845 * Logarithmic on two axes
11846 * @sample {highcharts} highcharts/yaxis/type-log-negative/
11847 * Logarithmic with extension to emulate negative values
11848 * @product highcharts
11849 */
11850 type: 'linear',
11851
11852
11853
11854 /**
11855 * Color of the minor, secondary grid lines.
11856 *
11857 * In styled mode, the stroke width is given in the
11858 * `.highcharts-minor-grid-line` class.
11859 *
11860 * @type {Color}
11861 * @sample {highcharts} highcharts/yaxis/minorgridlinecolor/
11862 * Bright grey lines from Y axis
11863 * @sample {highcharts|highstock} highcharts/css/axis-grid/
11864 * Styled mode
11865 * @sample {highstock} stock/xaxis/minorgridlinecolor/
11866 * Bright grey lines from Y axis
11867 * @default #f2f2f2
11868 */
11869 minorGridLineColor: '#f2f2f2',
11870 // minorGridLineDashStyle: null,
11871
11872 /**
11873 * Width of the minor, secondary grid lines.
11874 *
11875 * In styled mode, the stroke width is given in the
11876 * `.highcharts-grid-line` class.
11877 *
11878 * @sample {highcharts} highcharts/yaxis/minorgridlinewidth/
11879 * 2px lines from Y axis
11880 * @sample {highcharts|highstock} highcharts/css/axis-grid/
11881 * Styled mode
11882 * @sample {highstock} stock/xaxis/minorgridlinewidth/
11883 * 2px lines from Y axis
11884 */
11885 minorGridLineWidth: 1,
11886
11887 /**
11888 * Color for the minor tick marks.
11889 *
11890 * @type {Color}
11891 * @sample {highcharts} highcharts/yaxis/minortickcolor/
11892 * Black tick marks on Y axis
11893 * @sample {highstock} stock/xaxis/minorticks/
11894 * Black tick marks on Y axis
11895 * @default #999999
11896 */
11897 minorTickColor: '#999999',
11898
11899 /**
11900 * The color of the line marking the axis itself.
11901 *
11902 * In styled mode, the line stroke is given in the
11903 * `.highcharts-axis-line` or `.highcharts-xaxis-line` class.
11904 *
11905 * @productdesc {highmaps}
11906 * In Highmaps, the axis line is hidden by default, because the axis is
11907 * not visible by default.
11908 *
11909 * @type {Color}
11910 * @sample {highcharts} highcharts/yaxis/linecolor/
11911 * A red line on Y axis
11912 * @sample {highcharts|highstock} highcharts/css/axis/
11913 * Axes in styled mode
11914 * @sample {highstock} stock/xaxis/linecolor/
11915 * A red line on X axis
11916 * @default #ccd6eb
11917 */
11918 lineColor: '#ccd6eb',
11919
11920 /**
11921 * The width of the line marking the axis itself.
11922 *
11923 * In styled mode, the stroke width is given in the
11924 * `.highcharts-axis-line` or `.highcharts-xaxis-line` class.
11925 *
11926 * @sample {highcharts} highcharts/yaxis/linecolor/
11927 * A 1px line on Y axis
11928 * @sample {highcharts|highstock} highcharts/css/axis/
11929 * Axes in styled mode
11930 * @sample {highstock} stock/xaxis/linewidth/
11931 * A 2px line on X axis
11932 * @default {highcharts|highstock} 1
11933 * @default {highmaps} 0
11934 */
11935 lineWidth: 1,
11936
11937 /**
11938 * Color of the grid lines extending the ticks across the plot area.
11939 *
11940 * In styled mode, the stroke is given in the `.highcharts-grid-line`
11941 * class.
11942 *
11943 * @productdesc {highmaps}
11944 * In Highmaps, the grid lines are hidden by default.
11945 *
11946 * @type {Color}
11947 * @sample {highcharts} highcharts/yaxis/gridlinecolor/
11948 * Green lines
11949 * @sample {highcharts|highstock} highcharts/css/axis-grid/
11950 * Styled mode
11951 * @sample {highstock} stock/xaxis/gridlinecolor/
11952 * Green lines
11953 * @default #e6e6e6
11954 */
11955 gridLineColor: '#e6e6e6',
11956 // gridLineDashStyle: 'solid',
11957
11958
11959 /**
11960 * The width of the grid lines extending the ticks across the plot area.
11961 *
11962 * In styled mode, the stroke width is given in the
11963 * `.highcharts-grid-line` class.
11964 *
11965 * @type {Number}
11966 * @sample {highcharts} highcharts/yaxis/gridlinewidth/
11967 * 2px lines
11968 * @sample {highcharts|highstock} highcharts/css/axis-grid/
11969 * Styled mode
11970 * @sample {highstock} stock/xaxis/gridlinewidth/
11971 * 2px lines
11972 * @default 0
11973 * @apioption xAxis.gridLineWidth
11974 */
11975 // gridLineWidth: 0,
11976
11977 /**
11978 * Color for the main tick marks.
11979 *
11980 * In styled mode, the stroke is given in the `.highcharts-tick`
11981 * class.
11982 *
11983 * @type {Color}
11984 * @sample {highcharts} highcharts/xaxis/tickcolor/
11985 * Red ticks on X axis
11986 * @sample {highcharts|highstock} highcharts/css/axis-grid/
11987 * Styled mode
11988 * @sample {highstock} stock/xaxis/ticks/
11989 * Formatted ticks on X axis
11990 * @default #ccd6eb
11991 */
11992 tickColor: '#ccd6eb'
11993 // tickWidth: 1
11994
11995 },
11996
11997 /**
11998 * The Y axis or value axis. Normally this is the vertical axis,
11999 * though if the chart is inverted this is the horizontal axis.
12000 * In case of multiple axes, the yAxis node is an array of
12001 * configuration objects.
12002 *
12003 * See [the Axis object](#Axis) for programmatic access to the axis.
12004 *
12005 * @extends xAxis
12006 * @excluding ordinal,overscroll
12007 * @optionparent yAxis
12008 */
12009 defaultYAxisOptions: {
12010 /**
12011 * @productdesc {highstock}
12012 * In Highstock, `endOnTick` is always false when the navigator is
12013 * enabled, to prevent jumpy scrolling.
12014 */
12015 endOnTick: true,
12016
12017 /**
12018 * @productdesc {highstock}
12019 * In Highstock 1.x, the Y axis was placed on the left side by default.
12020 *
12021 * @sample {highcharts} highcharts/yaxis/opposite/
12022 * Secondary Y axis opposite
12023 * @sample {highstock} stock/xaxis/opposite/
12024 * Y axis on left side
12025 * @default {highstock} true
12026 * @default {highcharts} false
12027 * @product highstock highcharts
12028 * @apioption yAxis.opposite
12029 */
12030
12031 /**
12032 * @see [tickInterval](#xAxis.tickInterval),
12033 * [tickPositioner](#xAxis.tickPositioner),
12034 * [tickPositions](#xAxis.tickPositions).
12035 */
12036 tickPixelInterval: 72,
12037
12038 showLastLabel: true,
12039
12040 /**
12041 * @extends xAxis.labels
12042 */
12043 labels: {
12044 /**
12045 * What part of the string the given position is anchored to. Can
12046 * be one of `"left"`, `"center"` or `"right"`. The exact position
12047 * also depends on the `labels.x` setting.
12048 *
12049 * Angular gauges and solid gauges defaults to `center`.
12050 *
12051 * @validvalue ["left", "center", "right"]
12052 * @type {String}
12053 * @sample {highcharts} highcharts/yaxis/labels-align-left/
12054 * Left
12055 * @default {highcharts|highmaps} right
12056 * @default {highstock} left
12057 * @apioption yAxis.labels.align
12058 */
12059
12060 /**
12061 * The x position offset of the label relative to the tick position
12062 * on the axis. Defaults to -15 for left axis, 15 for right axis.
12063 *
12064 * @sample {highcharts} highcharts/xaxis/labels-x/
12065 * Y axis labels placed on grid lines
12066 */
12067 x: -8
12068 },
12069
12070 /**
12071 * @productdesc {highmaps}
12072 * In Highmaps, the axis line is hidden by default, because the axis is
12073 * not visible by default.
12074 *
12075 * @apioption yAxis.lineColor
12076 */
12077
12078 /**
12079 * @sample {highcharts} highcharts/yaxis/min-startontick-false/
12080 * -50 with startOnTick to false
12081 * @sample {highcharts} highcharts/yaxis/min-startontick-true/
12082 * -50 with startOnTick true by default
12083 * @sample {highstock} stock/yaxis/min-max/
12084 * Fixed min and max on Y axis
12085 * @sample {highmaps} maps/axis/min-max/
12086 * Pre-zoomed to a specific area
12087 * @apioption yAxis.min
12088 */
12089
12090 /**
12091 * @sample {highcharts} highcharts/yaxis/max-200/
12092 * Y axis max of 200
12093 * @sample {highcharts} highcharts/yaxis/max-logarithmic/
12094 * Y axis max on logarithmic axis
12095 * @sample {highstock} stock/yaxis/min-max/
12096 * Fixed min and max on Y axis
12097 * @sample {highmaps} maps/axis/min-max/
12098 * Pre-zoomed to a specific area
12099 * @apioption yAxis.max
12100 */
12101
12102 /**
12103 * Padding of the max value relative to the length of the axis. A
12104 * padding of 0.05 will make a 100px axis 5px longer. This is useful
12105 * when you don't want the highest data value to appear on the edge
12106 * of the plot area. When the axis' `max` option is set or a max extreme
12107 * is set using `axis.setExtremes()`, the maxPadding will be ignored.
12108 *
12109 * @sample {highcharts} highcharts/yaxis/maxpadding-02/
12110 * Max padding of 0.2
12111 * @sample {highstock} stock/xaxis/minpadding-maxpadding/
12112 * Greater min- and maxPadding
12113 * @since 1.2.0
12114 * @product highcharts highstock
12115 */
12116 maxPadding: 0.05,
12117
12118 /**
12119 * Padding of the min value relative to the length of the axis. A
12120 * padding of 0.05 will make a 100px axis 5px longer. This is useful
12121 * when you don't want the lowest data value to appear on the edge
12122 * of the plot area. When the axis' `min` option is set or a max extreme
12123 * is set using `axis.setExtremes()`, the maxPadding will be ignored.
12124 *
12125 * @sample {highcharts} highcharts/yaxis/minpadding/
12126 * Min padding of 0.2
12127 * @sample {highstock} stock/xaxis/minpadding-maxpadding/
12128 * Greater min- and maxPadding
12129 * @since 1.2.0
12130 * @product highcharts highstock
12131 */
12132 minPadding: 0.05,
12133
12134 /**
12135 * Whether to force the axis to start on a tick. Use this option with
12136 * the `maxPadding` option to control the axis start.
12137 *
12138 * @sample {highcharts} highcharts/xaxis/startontick-false/
12139 * False by default
12140 * @sample {highcharts} highcharts/xaxis/startontick-true/
12141 * True
12142 * @sample {highstock} stock/xaxis/endontick/
12143 * False for Y axis
12144 * @since 1.2.0
12145 * @product highcharts highstock
12146 */
12147 startOnTick: true,
12148
12149 /**
12150 * @extends xAxis.title
12151 */
12152 title: {
12153
12154 /**
12155 * The rotation of the text in degrees. 0 is horizontal, 270 is
12156 * vertical reading from bottom to top.
12157 *
12158 * @sample {highcharts} highcharts/yaxis/title-offset/
12159 * Horizontal
12160 */
12161 rotation: 270,
12162
12163 /**
12164 * The actual text of the axis title. Horizontal texts can contain
12165 * HTML, but rotated texts are painted using vector techniques and
12166 * must be clean text. The Y axis title is disabled by setting the
12167 * `text` option to `null`.
12168 *
12169 * @sample {highcharts} highcharts/xaxis/title-text/
12170 * Custom HTML
12171 * @default {highcharts} Values
12172 * @default {highstock} null
12173 * @product highcharts highstock
12174 */
12175 text: 'Values'
12176 },
12177
12178 /**
12179 * The stack labels show the total value for each bar in a stacked
12180 * column or bar chart. The label will be placed on top of positive
12181 * columns and below negative columns. In case of an inverted column
12182 * chart or a bar chart the label is placed to the right of positive
12183 * bars and to the left of negative bars.
12184 *
12185 * @product highcharts
12186 */
12187 stackLabels: {
12188
12189 /**
12190 * Allow the stack labels to overlap.
12191 *
12192 * @sample {highcharts}
12193 * highcharts/yaxis/stacklabels-allowoverlap-false/
12194 * Default false
12195 * @since 5.0.13
12196 * @product highcharts
12197 */
12198 allowOverlap: false,
12199
12200 /**
12201 * Enable or disable the stack total labels.
12202 *
12203 * @sample {highcharts} highcharts/yaxis/stacklabels-enabled/
12204 * Enabled stack total labels
12205 * @since 2.1.5
12206 * @product highcharts
12207 */
12208 enabled: false,
12209
12210 /**
12211 * Callback JavaScript function to format the label. The value is
12212 * given by `this.total`.
12213 *
12214 * @default function() { return this.total; }
12215 *
12216 * @type {Function}
12217 * @sample {highcharts} highcharts/yaxis/stacklabels-formatter/
12218 * Added units to stack total value
12219 * @since 2.1.5
12220 * @product highcharts
12221 */
12222 formatter: function() {
12223 return H.numberFormat(this.total, -1);
12224 },
12225
12226
12227 /**
12228 * CSS styles for the label.
12229 *
12230 * In styled mode, the styles are set in the
12231 * `.highcharts-stack-label` class.
12232 *
12233 * @type {CSSObject}
12234 * @sample {highcharts} highcharts/yaxis/stacklabels-style/
12235 * Red stack total labels
12236 * @since 2.1.5
12237 * @product highcharts
12238 */
12239 style: {
12240 fontSize: '11px',
12241 fontWeight: 'bold',
12242 color: '#000000',
12243 textOutline: '1px contrast'
12244 }
12245
12246 },
12247
12248 gridLineWidth: 1,
12249 lineWidth: 0
12250 // tickWidth: 0
12251
12252 },
12253
12254 /**
12255 * These options extend the defaultOptions for left axes.
12256 *
12257 * @private
12258 * @type {Object}
12259 */
12260 defaultLeftAxisOptions: {
12261 labels: {
12262 x: -15
12263 },
12264 title: {
12265 rotation: 270
12266 }
12267 },
12268
12269 /**
12270 * These options extend the defaultOptions for right axes.
12271 *
12272 * @private
12273 * @type {Object}
12274 */
12275 defaultRightAxisOptions: {
12276 labels: {
12277 x: 15
12278 },
12279 title: {
12280 rotation: 90
12281 }
12282 },
12283
12284 /**
12285 * These options extend the defaultOptions for bottom axes.
12286 *
12287 * @private
12288 * @type {Object}
12289 */
12290 defaultBottomAxisOptions: {
12291 labels: {
12292 autoRotation: [-45],
12293 x: 0
12294 // overflow: undefined,
12295 // staggerLines: null
12296 },
12297 title: {
12298 rotation: 0
12299 }
12300 },
12301 /**
12302 * These options extend the defaultOptions for top axes.
12303 *
12304 * @private
12305 * @type {Object}
12306 */
12307 defaultTopAxisOptions: {
12308 labels: {
12309 autoRotation: [-45],
12310 x: 0
12311 // overflow: undefined
12312 // staggerLines: null
12313 },
12314 title: {
12315 rotation: 0
12316 }
12317 },
12318
12319 /**
12320 * Overrideable function to initialize the axis.
12321 *
12322 * @see {@link Axis}
12323 */
12324 init: function(chart, userOptions) {
12325
12326
12327 var isXAxis = userOptions.isX,
12328 axis = this;
12329
12330
12331 /**
12332 * The Chart that the axis belongs to.
12333 *
12334 * @name chart
12335 * @memberOf Axis
12336 * @type {Chart}
12337 */
12338 axis.chart = chart;
12339
12340 /**
12341 * Whether the axis is horizontal.
12342 *
12343 * @name horiz
12344 * @memberOf Axis
12345 * @type {Boolean}
12346 */
12347 axis.horiz = chart.inverted && !axis.isZAxis ? !isXAxis : isXAxis;
12348
12349 // Flag, isXAxis
12350 axis.isXAxis = isXAxis;
12351
12352 /**
12353 * The collection where the axis belongs, for example `xAxis`, `yAxis`
12354 * or `colorAxis`. Corresponds to properties on Chart, for example
12355 * {@link Chart.xAxis}.
12356 *
12357 * @name coll
12358 * @memberOf Axis
12359 * @type {String}
12360 */
12361 axis.coll = axis.coll || (isXAxis ? 'xAxis' : 'yAxis');
12362
12363
12364 axis.opposite = userOptions.opposite; // needed in setOptions
12365
12366 /**
12367 * The side on which the axis is rendered. 0 is top, 1 is right, 2 is
12368 * bottom and 3 is left.
12369 *
12370 * @name side
12371 * @memberOf Axis
12372 * @type {Number}
12373 */
12374 axis.side = userOptions.side || (axis.horiz ?
12375 (axis.opposite ? 0 : 2) : // top : bottom
12376 (axis.opposite ? 1 : 3)); // right : left
12377
12378 axis.setOptions(userOptions);
12379
12380
12381 var options = this.options,
12382 type = options.type,
12383 isDatetimeAxis = type === 'datetime';
12384
12385 axis.labelFormatter = options.labels.formatter ||
12386 axis.defaultLabelFormatter; // can be overwritten by dynamic format
12387
12388
12389 // Flag, stagger lines or not
12390 axis.userOptions = userOptions;
12391
12392 axis.minPixelPadding = 0;
12393
12394
12395 /**
12396 * Whether the axis is reversed. Based on the `axis.reversed`,
12397 * option, but inverted charts have reversed xAxis by default.
12398 *
12399 * @name reversed
12400 * @memberOf Axis
12401 * @type {Boolean}
12402 */
12403 axis.reversed = options.reversed;
12404 axis.visible = options.visible !== false;
12405 axis.zoomEnabled = options.zoomEnabled !== false;
12406
12407 // Initial categories
12408 axis.hasNames = type === 'category' || options.categories === true;
12409 axis.categories = options.categories || axis.hasNames;
12410 axis.names = axis.names || []; // Preserve on update (#3830)
12411
12412 // Placeholder for plotlines and plotbands groups
12413 axis.plotLinesAndBandsGroups = {};
12414
12415 // Shorthand types
12416 axis.isLog = type === 'logarithmic';
12417 axis.isDatetimeAxis = isDatetimeAxis;
12418 axis.positiveValuesOnly = axis.isLog && !axis.allowNegativeLog;
12419
12420 // Flag, if axis is linked to another axis
12421 axis.isLinked = defined(options.linkedTo);
12422
12423 // Major ticks
12424 axis.ticks = {};
12425 axis.labelEdge = [];
12426 // Minor ticks
12427 axis.minorTicks = {};
12428
12429 // List of plotLines/Bands
12430 axis.plotLinesAndBands = [];
12431
12432 // Alternate bands
12433 axis.alternateBands = {};
12434
12435 // Axis metrics
12436 axis.len = 0;
12437 axis.minRange = axis.userMinRange = options.minRange || options.maxZoom;
12438 axis.range = options.range;
12439 axis.offset = options.offset || 0;
12440
12441
12442 // Dictionary for stacks
12443 axis.stacks = {};
12444 axis.oldStacks = {};
12445 axis.stacksTouched = 0;
12446
12447
12448 /**
12449 * The maximum value of the axis. In a logarithmic axis, this is the
12450 * logarithm of the real value, and the real value can be obtained from
12451 * {@link Axis#getExtremes}.
12452 *
12453 * @name max
12454 * @memberOf Axis
12455 * @type {Number}
12456 */
12457 axis.max = null;
12458 /**
12459 * The minimum value of the axis. In a logarithmic axis, this is the
12460 * logarithm of the real value, and the real value can be obtained from
12461 * {@link Axis#getExtremes}.
12462 *
12463 * @name min
12464 * @memberOf Axis
12465 * @type {Number}
12466 */
12467 axis.min = null;
12468
12469
12470 /**
12471 * The processed crosshair options.
12472 *
12473 * @name crosshair
12474 * @memberOf Axis
12475 * @type {AxisCrosshairOptions}
12476 */
12477 axis.crosshair = pick(
12478 options.crosshair,
12479 splat(chart.options.tooltip.crosshairs)[isXAxis ? 0 : 1],
12480 false
12481 );
12482
12483 var events = axis.options.events;
12484
12485 // Register. Don't add it again on Axis.update().
12486 if (inArray(axis, chart.axes) === -1) { //
12487 if (isXAxis) { // #2713
12488 chart.axes.splice(chart.xAxis.length, 0, axis);
12489 } else {
12490 chart.axes.push(axis);
12491 }
12492
12493 chart[axis.coll].push(axis);
12494 }
12495
12496 /**
12497 * All series associated to the axis.
12498 *
12499 * @name series
12500 * @memberOf Axis
12501 * @type {Array.<Series>}
12502 */
12503 axis.series = axis.series || []; // populated by Series
12504
12505 // Reversed axis
12506 if (
12507 chart.inverted &&
12508 !axis.isZAxis &&
12509 isXAxis &&
12510 axis.reversed === undefined
12511 ) {
12512 axis.reversed = true;
12513 }
12514
12515 // register event listeners
12516 objectEach(events, function(event, eventType) {
12517 addEvent(axis, eventType, event);
12518 });
12519
12520 // extend logarithmic axis
12521 axis.lin2log = options.linearToLogConverter || axis.lin2log;
12522 if (axis.isLog) {
12523 axis.val2lin = axis.log2lin;
12524 axis.lin2val = axis.lin2log;
12525 }
12526 },
12527
12528 /**
12529 * Merge and set options.
12530 *
12531 * @private
12532 */
12533 setOptions: function(userOptions) {
12534 this.options = merge(
12535 this.defaultOptions,
12536 this.coll === 'yAxis' && this.defaultYAxisOptions, [
12537 this.defaultTopAxisOptions,
12538 this.defaultRightAxisOptions,
12539 this.defaultBottomAxisOptions,
12540 this.defaultLeftAxisOptions
12541 ][this.side],
12542 merge(
12543 defaultOptions[this.coll], // if set in setOptions (#1053)
12544 userOptions
12545 )
12546 );
12547 },
12548
12549 /**
12550 * The default label formatter. The context is a special config object for
12551 * the label. In apps, use the {@link
12552 * https://api.highcharts.com/highcharts/xAxis.labels.formatter|
12553 * labels.formatter} instead except when a modification is needed.
12554 *
12555 * @private
12556 */
12557 defaultLabelFormatter: function() {
12558 var axis = this.axis,
12559 value = this.value,
12560 categories = axis.categories,
12561 dateTimeLabelFormat = this.dateTimeLabelFormat,
12562 lang = defaultOptions.lang,
12563 numericSymbols = lang.numericSymbols,
12564 numSymMagnitude = lang.numericSymbolMagnitude || 1000,
12565 i = numericSymbols && numericSymbols.length,
12566 multi,
12567 ret,
12568 formatOption = axis.options.labels.format,
12569
12570 // make sure the same symbol is added for all labels on a linear
12571 // axis
12572 numericSymbolDetector = axis.isLog ?
12573 Math.abs(value) :
12574 axis.tickInterval;
12575
12576 if (formatOption) {
12577 ret = format(formatOption, this);
12578
12579 } else if (categories) {
12580 ret = value;
12581
12582 } else if (dateTimeLabelFormat) { // datetime axis
12583 ret = H.dateFormat(dateTimeLabelFormat, value);
12584
12585 } else if (i && numericSymbolDetector >= 1000) {
12586 // Decide whether we should add a numeric symbol like k (thousands)
12587 // or M (millions). If we are to enable this in tooltip or other
12588 // places as well, we can move this logic to the numberFormatter and
12589 // enable it by a parameter.
12590 while (i-- && ret === undefined) {
12591 multi = Math.pow(numSymMagnitude, i + 1);
12592 if (
12593 // Only accept a numeric symbol when the distance is more
12594 // than a full unit. So for example if the symbol is k, we
12595 // don't accept numbers like 0.5k.
12596 numericSymbolDetector >= multi &&
12597 // Accept one decimal before the symbol. Accepts 0.5k but
12598 // not 0.25k. How does this work with the previous?
12599 (value * 10) % multi === 0 &&
12600 numericSymbols[i] !== null &&
12601 value !== 0
12602 ) { // #5480
12603 ret = H.numberFormat(value / multi, -1) + numericSymbols[i];
12604 }
12605 }
12606 }
12607
12608 if (ret === undefined) {
12609 if (Math.abs(value) >= 10000) { // add thousands separators
12610 ret = H.numberFormat(value, -1);
12611 } else { // small numbers
12612 ret = H.numberFormat(value, -1, undefined, ''); // #2466
12613 }
12614 }
12615
12616 return ret;
12617 },
12618
12619 /**
12620 * Get the minimum and maximum for the series of each axis. The function
12621 * analyzes the axis series and updates `this.dataMin` and `this.dataMax`.
12622 *
12623 * @private
12624 */
12625 getSeriesExtremes: function() {
12626 var axis = this,
12627 chart = axis.chart;
12628 axis.hasVisibleSeries = false;
12629
12630 // Reset properties in case we're redrawing (#3353)
12631 axis.dataMin = axis.dataMax = axis.threshold = null;
12632 axis.softThreshold = !axis.isXAxis;
12633
12634 if (axis.buildStacks) {
12635 axis.buildStacks();
12636 }
12637
12638 // loop through this axis' series
12639 each(axis.series, function(series) {
12640
12641 if (series.visible || !chart.options.chart.ignoreHiddenSeries) {
12642
12643 var seriesOptions = series.options,
12644 xData,
12645 threshold = seriesOptions.threshold,
12646 seriesDataMin,
12647 seriesDataMax;
12648
12649 axis.hasVisibleSeries = true;
12650
12651 // Validate threshold in logarithmic axes
12652 if (axis.positiveValuesOnly && threshold <= 0) {
12653 threshold = null;
12654 }
12655
12656 // Get dataMin and dataMax for X axes
12657 if (axis.isXAxis) {
12658 xData = series.xData;
12659 if (xData.length) {
12660 // If xData contains values which is not numbers, then
12661 // filter them out. To prevent performance hit, we only
12662 // do this after we have already found seriesDataMin
12663 // because in most cases all data is valid. #5234.
12664 seriesDataMin = arrayMin(xData);
12665 seriesDataMax = arrayMax(xData);
12666
12667 if (!isNumber(seriesDataMin) &&
12668 !(seriesDataMin instanceof Date) // #5010
12669 ) {
12670 xData = grep(xData, isNumber);
12671 // Do it again with valid data
12672 seriesDataMin = arrayMin(xData);
12673 }
12674
12675 axis.dataMin = Math.min(
12676 pick(axis.dataMin, xData[0], seriesDataMin),
12677 seriesDataMin
12678 );
12679 axis.dataMax = Math.max(
12680 pick(axis.dataMax, xData[0], seriesDataMax),
12681 seriesDataMax
12682 );
12683 }
12684
12685 // Get dataMin and dataMax for Y axes, as well as handle
12686 // stacking and processed data
12687 } else {
12688
12689 // Get this particular series extremes
12690 series.getExtremes();
12691 seriesDataMax = series.dataMax;
12692 seriesDataMin = series.dataMin;
12693
12694 // Get the dataMin and dataMax so far. If percentage is
12695 // used, the min and max are always 0 and 100. If
12696 // seriesDataMin and seriesDataMax is null, then series
12697 // doesn't have active y data, we continue with nulls
12698 if (defined(seriesDataMin) && defined(seriesDataMax)) {
12699 axis.dataMin = Math.min(
12700 pick(axis.dataMin, seriesDataMin),
12701 seriesDataMin
12702 );
12703 axis.dataMax = Math.max(
12704 pick(axis.dataMax, seriesDataMax),
12705 seriesDataMax
12706 );
12707 }
12708
12709 // Adjust to threshold
12710 if (defined(threshold)) {
12711 axis.threshold = threshold;
12712 }
12713 // If any series has a hard threshold, it takes precedence
12714 if (!seriesOptions.softThreshold ||
12715 axis.positiveValuesOnly
12716 ) {
12717 axis.softThreshold = false;
12718 }
12719 }
12720 }
12721 });
12722 },
12723
12724 /**
12725 * Translate from axis value to pixel position on the chart, or back. Use
12726 * the `toPixels` and `toValue` functions in applications.
12727 *
12728 * @private
12729 */
12730 translate: function(
12731 val,
12732 backwards,
12733 cvsCoord,
12734 old,
12735 handleLog,
12736 pointPlacement
12737 ) {
12738 var axis = this.linkedParent || this, // #1417
12739 sign = 1,
12740 cvsOffset = 0,
12741 localA = old ? axis.oldTransA : axis.transA,
12742 localMin = old ? axis.oldMin : axis.min,
12743 returnValue,
12744 minPixelPadding = axis.minPixelPadding,
12745 doPostTranslate = (
12746 axis.isOrdinal ||
12747 axis.isBroken ||
12748 (axis.isLog && handleLog)
12749 ) && axis.lin2val;
12750
12751 if (!localA) {
12752 localA = axis.transA;
12753 }
12754
12755 // In vertical axes, the canvas coordinates start from 0 at the top like
12756 // in SVG.
12757 if (cvsCoord) {
12758 sign *= -1; // canvas coordinates inverts the value
12759 cvsOffset = axis.len;
12760 }
12761
12762 // Handle reversed axis
12763 if (axis.reversed) {
12764 sign *= -1;
12765 cvsOffset -= sign * (axis.sector || axis.len);
12766 }
12767
12768 // From pixels to value
12769 if (backwards) { // reverse translation
12770
12771 val = val * sign + cvsOffset;
12772 val -= minPixelPadding;
12773 returnValue = val / localA + localMin; // from chart pixel to value
12774 if (doPostTranslate) { // log and ordinal axes
12775 returnValue = axis.lin2val(returnValue);
12776 }
12777
12778 // From value to pixels
12779 } else {
12780 if (doPostTranslate) { // log and ordinal axes
12781 val = axis.val2lin(val);
12782 }
12783 returnValue = isNumber(localMin) ?
12784 (
12785 sign * (val - localMin) * localA +
12786 cvsOffset +
12787 (sign * minPixelPadding) +
12788 (isNumber(pointPlacement) ? localA * pointPlacement : 0)
12789 ) :
12790 undefined;
12791 }
12792
12793 return returnValue;
12794 },
12795
12796 /**
12797 * Translate a value in terms of axis units into pixels within the chart.
12798 *
12799 * @param {Number} value
12800 * A value in terms of axis units.
12801 * @param {Boolean} paneCoordinates
12802 * Whether to return the pixel coordinate relative to the chart or
12803 * just the axis/pane itself.
12804 * @return {Number} Pixel position of the value on the chart or axis.
12805 */
12806 toPixels: function(value, paneCoordinates) {
12807 return this.translate(value, false, !this.horiz, null, true) +
12808 (paneCoordinates ? 0 : this.pos);
12809 },
12810
12811 /**
12812 * Translate a pixel position along the axis to a value in terms of axis
12813 * units.
12814 * @param {Number} pixel
12815 * The pixel value coordinate.
12816 * @param {Boolean} paneCoordiantes
12817 * Whether the input pixel is relative to the chart or just the
12818 * axis/pane itself.
12819 * @return {Number} The axis value.
12820 */
12821 toValue: function(pixel, paneCoordinates) {
12822 return this.translate(
12823 pixel - (paneCoordinates ? 0 : this.pos),
12824 true, !this.horiz,
12825 null,
12826 true
12827 );
12828 },
12829
12830 /**
12831 * Create the path for a plot line that goes from the given value on
12832 * this axis, across the plot to the opposite side. Also used internally for
12833 * grid lines and crosshairs.
12834 *
12835 * @param {Number} value
12836 * Axis value.
12837 * @param {Number} [lineWidth=1]
12838 * Used for calculation crisp line coordinates.
12839 * @param {Boolean} [old=false]
12840 * Use old coordinates (for resizing and rescaling).
12841 * @param {Boolean} [force=false]
12842 * If `false`, the function will return null when it falls outside
12843 * the axis bounds.
12844 * @param {Number} [translatedValue]
12845 * If given, return the plot line path of a pixel position on the
12846 * axis.
12847 *
12848 * @return {Array.<String|Number>}
12849 * The SVG path definition for the plot line.
12850 */
12851 getPlotLinePath: function(value, lineWidth, old, force, translatedValue) {
12852 var axis = this,
12853 chart = axis.chart,
12854 axisLeft = axis.left,
12855 axisTop = axis.top,
12856 x1,
12857 y1,
12858 x2,
12859 y2,
12860 cHeight = (old && chart.oldChartHeight) || chart.chartHeight,
12861 cWidth = (old && chart.oldChartWidth) || chart.chartWidth,
12862 skip,
12863 transB = axis.transB,
12864 /**
12865 * Check if x is between a and b. If not, either move to a/b
12866 * or skip, depending on the force parameter.
12867 */
12868 between = function(x, a, b) {
12869 if (x < a || x > b) {
12870 if (force) {
12871 x = Math.min(Math.max(a, x), b);
12872 } else {
12873 skip = true;
12874 }
12875 }
12876 return x;
12877 };
12878
12879 translatedValue = pick(
12880 translatedValue,
12881 axis.translate(value, null, null, old)
12882 );
12883 x1 = x2 = Math.round(translatedValue + transB);
12884 y1 = y2 = Math.round(cHeight - translatedValue - transB);
12885 if (!isNumber(translatedValue)) { // no min or max
12886 skip = true;
12887 force = false; // #7175, don't force it when path is invalid
12888 } else if (axis.horiz) {
12889 y1 = axisTop;
12890 y2 = cHeight - axis.bottom;
12891 x1 = x2 = between(x1, axisLeft, axisLeft + axis.width);
12892 } else {
12893 x1 = axisLeft;
12894 x2 = cWidth - axis.right;
12895 y1 = y2 = between(y1, axisTop, axisTop + axis.height);
12896 }
12897 return skip && !force ?
12898 null :
12899 chart.renderer.crispLine(
12900 ['M', x1, y1, 'L', x2, y2],
12901 lineWidth || 1
12902 );
12903 },
12904
12905 /**
12906 * Internal function to et the tick positions of a linear axis to round
12907 * values like whole tens or every five.
12908 *
12909 * @param {Number} tickInterval
12910 * The normalized tick interval
12911 * @param {Number} min
12912 * Axis minimum.
12913 * @param {Number} max
12914 * Axis maximum.
12915 *
12916 * @return {Array.<Number>}
12917 * An array of axis values where ticks should be placed.
12918 */
12919 getLinearTickPositions: function(tickInterval, min, max) {
12920 var pos,
12921 lastPos,
12922 roundedMin =
12923 correctFloat(Math.floor(min / tickInterval) * tickInterval),
12924 roundedMax =
12925 correctFloat(Math.ceil(max / tickInterval) * tickInterval),
12926 tickPositions = [],
12927 precision;
12928
12929 // When the precision is higher than what we filter out in
12930 // correctFloat, skip it (#6183).
12931 if (correctFloat(roundedMin + tickInterval) === roundedMin) {
12932 precision = 20;
12933 }
12934
12935 // For single points, add a tick regardless of the relative position
12936 // (#2662, #6274)
12937 if (this.single) {
12938 return [min];
12939 }
12940
12941 // Populate the intermediate values
12942 pos = roundedMin;
12943 while (pos <= roundedMax) {
12944
12945 // Place the tick on the rounded value
12946 tickPositions.push(pos);
12947
12948 // Always add the raw tickInterval, not the corrected one.
12949 pos = correctFloat(
12950 pos + tickInterval,
12951 precision
12952 );
12953
12954 // If the interval is not big enough in the current min - max range
12955 // to actually increase the loop variable, we need to break out to
12956 // prevent endless loop. Issue #619
12957 if (pos === lastPos) {
12958 break;
12959 }
12960
12961 // Record the last value
12962 lastPos = pos;
12963 }
12964 return tickPositions;
12965 },
12966
12967 /**
12968 * Resolve the new minorTicks/minorTickInterval options into the legacy
12969 * loosely typed minorTickInterval option.
12970 */
12971 getMinorTickInterval: function() {
12972 var options = this.options;
12973
12974 if (options.minorTicks === true) {
12975 return pick(options.minorTickInterval, 'auto');
12976 }
12977 if (options.minorTicks === false) {
12978 return null;
12979 }
12980 return options.minorTickInterval;
12981 },
12982
12983 /**
12984 * Internal function to return the minor tick positions. For logarithmic
12985 * axes, the same logic as for major ticks is reused.
12986 *
12987 * @return {Array.<Number>}
12988 * An array of axis values where ticks should be placed.
12989 */
12990 getMinorTickPositions: function() {
12991 var axis = this,
12992 options = axis.options,
12993 tickPositions = axis.tickPositions,
12994 minorTickInterval = axis.minorTickInterval,
12995 minorTickPositions = [],
12996 pos,
12997 pointRangePadding = axis.pointRangePadding || 0,
12998 min = axis.min - pointRangePadding, // #1498
12999 max = axis.max + pointRangePadding, // #1498
13000 range = max - min;
13001
13002 // If minor ticks get too dense, they are hard to read, and may cause
13003 // long running script. So we don't draw them.
13004 if (range && range / minorTickInterval < axis.len / 3) { // #3875
13005
13006 if (axis.isLog) {
13007 // For each interval in the major ticks, compute the minor ticks
13008 // separately.
13009 each(this.paddedTicks, function(pos, i, paddedTicks) {
13010 if (i) {
13011 minorTickPositions.push.apply(
13012 minorTickPositions,
13013 axis.getLogTickPositions(
13014 minorTickInterval,
13015 paddedTicks[i - 1],
13016 paddedTicks[i],
13017 true
13018 )
13019 );
13020 }
13021 });
13022
13023 } else if (
13024 axis.isDatetimeAxis &&
13025 this.getMinorTickInterval() === 'auto'
13026 ) { // #1314
13027 minorTickPositions = minorTickPositions.concat(
13028 axis.getTimeTicks(
13029 axis.normalizeTimeTickInterval(minorTickInterval),
13030 min,
13031 max,
13032 options.startOfWeek
13033 )
13034 );
13035 } else {
13036 for (
13037 pos = min + (tickPositions[0] - min) % minorTickInterval; pos <= max; pos += minorTickInterval
13038 ) {
13039 // Very, very, tight grid lines (#5771)
13040 if (pos === minorTickPositions[0]) {
13041 break;
13042 }
13043 minorTickPositions.push(pos);
13044 }
13045 }
13046 }
13047
13048 if (minorTickPositions.length !== 0) {
13049 axis.trimTicks(minorTickPositions); // #3652 #3743 #1498 #6330
13050 }
13051 return minorTickPositions;
13052 },
13053
13054 /**
13055 * Adjust the min and max for the minimum range. Keep in mind that the
13056 * series data is not yet processed, so we don't have information on data
13057 * cropping and grouping, or updated axis.pointRange or series.pointRange.
13058 * The data can't be processed until we have finally established min and
13059 * max.
13060 *
13061 * @private
13062 */
13063 adjustForMinRange: function() {
13064 var axis = this,
13065 options = axis.options,
13066 min = axis.min,
13067 max = axis.max,
13068 zoomOffset,
13069 spaceAvailable,
13070 closestDataRange,
13071 i,
13072 distance,
13073 xData,
13074 loopLength,
13075 minArgs,
13076 maxArgs,
13077 minRange;
13078
13079 // Set the automatic minimum range based on the closest point distance
13080 if (axis.isXAxis && axis.minRange === undefined && !axis.isLog) {
13081
13082 if (defined(options.min) || defined(options.max)) {
13083 axis.minRange = null; // don't do this again
13084
13085 } else {
13086
13087 // Find the closest distance between raw data points, as opposed
13088 // to closestPointRange that applies to processed points
13089 // (cropped and grouped)
13090 each(axis.series, function(series) {
13091 xData = series.xData;
13092 loopLength = series.xIncrement ? 1 : xData.length - 1;
13093 for (i = loopLength; i > 0; i--) {
13094 distance = xData[i] - xData[i - 1];
13095 if (
13096 closestDataRange === undefined ||
13097 distance < closestDataRange
13098 ) {
13099 closestDataRange = distance;
13100 }
13101 }
13102 });
13103 axis.minRange = Math.min(
13104 closestDataRange * 5,
13105 axis.dataMax - axis.dataMin
13106 );
13107 }
13108 }
13109
13110 // if minRange is exceeded, adjust
13111 if (max - min < axis.minRange) {
13112
13113 spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange;
13114 minRange = axis.minRange;
13115 zoomOffset = (minRange - max + min) / 2;
13116
13117 // if min and max options have been set, don't go beyond it
13118 minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)];
13119 // If space is available, stay within the data range
13120 if (spaceAvailable) {
13121 minArgs[2] = axis.isLog ?
13122 axis.log2lin(axis.dataMin) :
13123 axis.dataMin;
13124 }
13125 min = arrayMax(minArgs);
13126
13127 maxArgs = [min + minRange, pick(options.max, min + minRange)];
13128 // If space is availabe, stay within the data range
13129 if (spaceAvailable) {
13130 maxArgs[2] = axis.isLog ?
13131 axis.log2lin(axis.dataMax) :
13132 axis.dataMax;
13133 }
13134
13135 max = arrayMin(maxArgs);
13136
13137 // now if the max is adjusted, adjust the min back
13138 if (max - min < minRange) {
13139 minArgs[0] = max - minRange;
13140 minArgs[1] = pick(options.min, max - minRange);
13141 min = arrayMax(minArgs);
13142 }
13143 }
13144
13145 // Record modified extremes
13146 axis.min = min;
13147 axis.max = max;
13148 },
13149
13150 /**
13151 * Find the closestPointRange across all series.
13152 *
13153 * @private
13154 */
13155 getClosest: function() {
13156 var ret;
13157
13158 if (this.categories) {
13159 ret = 1;
13160 } else {
13161 each(this.series, function(series) {
13162 var seriesClosest = series.closestPointRange,
13163 visible = series.visible ||
13164 !series.chart.options.chart.ignoreHiddenSeries;
13165
13166 if (!series.noSharedTooltip &&
13167 defined(seriesClosest) &&
13168 visible
13169 ) {
13170 ret = defined(ret) ?
13171 Math.min(ret, seriesClosest) :
13172 seriesClosest;
13173 }
13174 });
13175 }
13176 return ret;
13177 },
13178
13179 /**
13180 * When a point name is given and no x, search for the name in the existing
13181 * categories, or if categories aren't provided, search names or create a
13182 * new category (#2522).
13183 *
13184 * @private
13185 *
13186 * @param {Point}
13187 * The point to inspect.
13188 *
13189 * @return {Number}
13190 * The X value that the point is given.
13191 */
13192 nameToX: function(point) {
13193 var explicitCategories = isArray(this.categories),
13194 names = explicitCategories ? this.categories : this.names,
13195 nameX = point.options.x,
13196 x;
13197
13198 point.series.requireSorting = false;
13199
13200 if (!defined(nameX)) {
13201 nameX = this.options.uniqueNames === false ?
13202 point.series.autoIncrement() :
13203 inArray(point.name, names);
13204 }
13205 if (nameX === -1) { // The name is not found in currenct categories
13206 if (!explicitCategories) {
13207 x = names.length;
13208 }
13209 } else {
13210 x = nameX;
13211 }
13212
13213 // Write the last point's name to the names array
13214 if (x !== undefined) {
13215 this.names[x] = point.name;
13216 }
13217
13218 return x;
13219 },
13220
13221 /**
13222 * When changes have been done to series data, update the axis.names.
13223 *
13224 * @private
13225 */
13226 updateNames: function() {
13227 var axis = this;
13228
13229 if (this.names.length > 0) {
13230 this.names.length = 0;
13231 this.minRange = this.userMinRange; // Reset
13232 each(this.series || [], function(series) {
13233
13234 // Reset incrementer (#5928)
13235 series.xIncrement = null;
13236
13237 // When adding a series, points are not yet generated
13238 if (!series.points || series.isDirtyData) {
13239 series.processData();
13240 series.generatePoints();
13241 }
13242
13243 each(series.points, function(point, i) {
13244 var x;
13245 if (point.options) {
13246 x = axis.nameToX(point);
13247 if (x !== undefined && x !== point.x) {
13248 point.x = x;
13249 series.xData[i] = x;
13250 }
13251 }
13252 });
13253 });
13254 }
13255 },
13256
13257 /**
13258 * Update translation information.
13259 *
13260 * @private
13261 */
13262 setAxisTranslation: function(saveOld) {
13263 var axis = this,
13264 range = axis.max - axis.min,
13265 pointRange = axis.axisPointRange || 0,
13266 closestPointRange,
13267 minPointOffset = 0,
13268 pointRangePadding = 0,
13269 linkedParent = axis.linkedParent,
13270 ordinalCorrection,
13271 hasCategories = !!axis.categories,
13272 transA = axis.transA,
13273 isXAxis = axis.isXAxis;
13274
13275 // Adjust translation for padding. Y axis with categories need to go
13276 // through the same (#1784).
13277 if (isXAxis || hasCategories || pointRange) {
13278
13279 // Get the closest points
13280 closestPointRange = axis.getClosest();
13281
13282 if (linkedParent) {
13283 minPointOffset = linkedParent.minPointOffset;
13284 pointRangePadding = linkedParent.pointRangePadding;
13285 } else {
13286 each(axis.series, function(series) {
13287 var seriesPointRange = hasCategories ?
13288 1 :
13289 (
13290 isXAxis ?
13291 pick(
13292 series.options.pointRange,
13293 closestPointRange,
13294 0
13295 ) :
13296 (axis.axisPointRange || 0)
13297 ), // #2806
13298 pointPlacement = series.options.pointPlacement;
13299
13300 pointRange = Math.max(pointRange, seriesPointRange);
13301
13302 if (!axis.single) {
13303 // minPointOffset is the value padding to the left of
13304 // the axis in order to make room for points with a
13305 // pointRange, typically columns. When the
13306 // pointPlacement option is 'between' or 'on', this
13307 // padding does not apply.
13308 minPointOffset = Math.max(
13309 minPointOffset,
13310 isString(pointPlacement) ? 0 : seriesPointRange / 2
13311 );
13312
13313 // Determine the total padding needed to the length of
13314 // the axis to make room for the pointRange. If the
13315 // series' pointPlacement is 'on', no padding is added.
13316 pointRangePadding = Math.max(
13317 pointRangePadding,
13318 pointPlacement === 'on' ? 0 : seriesPointRange
13319 );
13320 }
13321 });
13322 }
13323
13324 // Record minPointOffset and pointRangePadding
13325 ordinalCorrection = axis.ordinalSlope && closestPointRange ?
13326 axis.ordinalSlope / closestPointRange :
13327 1; // #988, #1853
13328 axis.minPointOffset = minPointOffset =
13329 minPointOffset * ordinalCorrection;
13330 axis.pointRangePadding =
13331 pointRangePadding = pointRangePadding * ordinalCorrection;
13332
13333 // pointRange means the width reserved for each point, like in a
13334 // column chart
13335 axis.pointRange = Math.min(pointRange, range);
13336
13337 // closestPointRange means the closest distance between points. In
13338 // columns it is mostly equal to pointRange, but in lines pointRange
13339 // is 0 while closestPointRange is some other value
13340 if (isXAxis) {
13341 axis.closestPointRange = closestPointRange;
13342 }
13343 }
13344
13345 // Secondary values
13346 if (saveOld) {
13347 axis.oldTransA = transA;
13348 }
13349 axis.translationSlope = axis.transA = transA =
13350 axis.options.staticScale ||
13351 axis.len / ((range + pointRangePadding) || 1);
13352
13353 // Translation addend
13354 axis.transB = axis.horiz ? axis.left : axis.bottom;
13355 axis.minPixelPadding = transA * minPointOffset;
13356 },
13357
13358 minFromRange: function() {
13359 return this.max - this.range;
13360 },
13361
13362 /**
13363 * Set the tick positions to round values and optionally extend the extremes
13364 * to the nearest tick.
13365 *
13366 * @private
13367 */
13368 setTickInterval: function(secondPass) {
13369 var axis = this,
13370 chart = axis.chart,
13371 options = axis.options,
13372 isLog = axis.isLog,
13373 log2lin = axis.log2lin,
13374 isDatetimeAxis = axis.isDatetimeAxis,
13375 isXAxis = axis.isXAxis,
13376 isLinked = axis.isLinked,
13377 maxPadding = options.maxPadding,
13378 minPadding = options.minPadding,
13379 length,
13380 linkedParentExtremes,
13381 tickIntervalOption = options.tickInterval,
13382 minTickInterval,
13383 tickPixelIntervalOption = options.tickPixelInterval,
13384 categories = axis.categories,
13385 threshold = axis.threshold,
13386 softThreshold = axis.softThreshold,
13387 thresholdMin,
13388 thresholdMax,
13389 hardMin,
13390 hardMax;
13391
13392 if (!isDatetimeAxis && !categories && !isLinked) {
13393 this.getTickAmount();
13394 }
13395
13396 // Min or max set either by zooming/setExtremes or initial options
13397 hardMin = pick(axis.userMin, options.min);
13398 hardMax = pick(axis.userMax, options.max);
13399
13400 // Linked axis gets the extremes from the parent axis
13401 if (isLinked) {
13402 axis.linkedParent = chart[axis.coll][options.linkedTo];
13403 linkedParentExtremes = axis.linkedParent.getExtremes();
13404 axis.min = pick(
13405 linkedParentExtremes.min,
13406 linkedParentExtremes.dataMin
13407 );
13408 axis.max = pick(
13409 linkedParentExtremes.max,
13410 linkedParentExtremes.dataMax
13411 );
13412 if (options.type !== axis.linkedParent.options.type) {
13413 H.error(11, 1); // Can't link axes of different type
13414 }
13415
13416 // Initial min and max from the extreme data values
13417 } else {
13418
13419 // Adjust to hard threshold
13420 if (!softThreshold && defined(threshold)) {
13421 if (axis.dataMin >= threshold) {
13422 thresholdMin = threshold;
13423 minPadding = 0;
13424 } else if (axis.dataMax <= threshold) {
13425 thresholdMax = threshold;
13426 maxPadding = 0;
13427 }
13428 }
13429
13430 axis.min = pick(hardMin, thresholdMin, axis.dataMin);
13431 axis.max = pick(hardMax, thresholdMax, axis.dataMax);
13432
13433 }
13434
13435 if (isLog) {
13436 if (
13437 axis.positiveValuesOnly &&
13438 !secondPass &&
13439 Math.min(axis.min, pick(axis.dataMin, axis.min)) <= 0
13440 ) { // #978
13441 H.error(10, 1); // Can't plot negative values on log axis
13442 }
13443 // The correctFloat cures #934, float errors on full tens. But it
13444 // was too aggressive for #4360 because of conversion back to lin,
13445 // therefore use precision 15.
13446 axis.min = correctFloat(log2lin(axis.min), 15);
13447 axis.max = correctFloat(log2lin(axis.max), 15);
13448 }
13449
13450 // handle zoomed range
13451 if (axis.range && defined(axis.max)) {
13452 axis.userMin = axis.min = hardMin =
13453 Math.max(axis.dataMin, axis.minFromRange()); // #618, #6773
13454 axis.userMax = hardMax = axis.max;
13455
13456 axis.range = null; // don't use it when running setExtremes
13457 }
13458
13459 // Hook for Highstock Scroller. Consider combining with beforePadding.
13460 fireEvent(axis, 'foundExtremes');
13461
13462 // Hook for adjusting this.min and this.max. Used by bubble series.
13463 if (axis.beforePadding) {
13464 axis.beforePadding();
13465 }
13466
13467 // adjust min and max for the minimum range
13468 axis.adjustForMinRange();
13469
13470 // Pad the values to get clear of the chart's edges. To avoid
13471 // tickInterval taking the padding into account, we do this after
13472 // computing tick interval (#1337).
13473 if (!categories &&
13474 !axis.axisPointRange &&
13475 !axis.usePercentage &&
13476 !isLinked &&
13477 defined(axis.min) &&
13478 defined(axis.max)
13479 ) {
13480 length = axis.max - axis.min;
13481 if (length) {
13482 if (!defined(hardMin) && minPadding) {
13483 axis.min -= length * minPadding;
13484 }
13485 if (!defined(hardMax) && maxPadding) {
13486 axis.max += length * maxPadding;
13487 }
13488 }
13489 }
13490
13491 // Handle options for floor, ceiling, softMin and softMax (#6359)
13492 if (isNumber(options.softMin)) {
13493 axis.min = Math.min(axis.min, options.softMin);
13494 }
13495 if (isNumber(options.softMax)) {
13496 axis.max = Math.max(axis.max, options.softMax);
13497 }
13498 if (isNumber(options.floor)) {
13499 axis.min = Math.max(axis.min, options.floor);
13500 }
13501 if (isNumber(options.ceiling)) {
13502 axis.max = Math.min(axis.max, options.ceiling);
13503 }
13504
13505
13506 // When the threshold is soft, adjust the extreme value only if the data
13507 // extreme and the padded extreme land on either side of the threshold.
13508 // For example, a series of [0, 1, 2, 3] would make the yAxis add a tick
13509 // for -1 because of the default minPadding and startOnTick options.
13510 // This is prevented by the softThreshold option.
13511 if (softThreshold && defined(axis.dataMin)) {
13512 threshold = threshold || 0;
13513 if (!defined(hardMin) &&
13514 axis.min < threshold &&
13515 axis.dataMin >= threshold
13516 ) {
13517 axis.min = threshold;
13518
13519 } else if (!defined(hardMax) &&
13520 axis.max > threshold &&
13521 axis.dataMax <= threshold
13522 ) {
13523 axis.max = threshold;
13524 }
13525 }
13526
13527
13528 // get tickInterval
13529 if (
13530 axis.min === axis.max ||
13531 axis.min === undefined ||
13532 axis.max === undefined
13533 ) {
13534 axis.tickInterval = 1;
13535
13536 } else if (
13537 isLinked &&
13538 !tickIntervalOption &&
13539 tickPixelIntervalOption ===
13540 axis.linkedParent.options.tickPixelInterval
13541 ) {
13542 axis.tickInterval = tickIntervalOption =
13543 axis.linkedParent.tickInterval;
13544
13545 } else {
13546 axis.tickInterval = pick(
13547 tickIntervalOption,
13548 this.tickAmount ?
13549 ((axis.max - axis.min) / Math.max(this.tickAmount - 1, 1)) :
13550 undefined,
13551 // For categoried axis, 1 is default, for linear axis use
13552 // tickPix
13553 categories ?
13554 1 :
13555 // don't let it be more than the data range
13556 (axis.max - axis.min) * tickPixelIntervalOption /
13557 Math.max(axis.len, tickPixelIntervalOption)
13558 );
13559 }
13560
13561 /**
13562 * Now we're finished detecting min and max, crop and group series data.
13563 * This is in turn needed in order to find tick positions in
13564 * ordinal axes.
13565 */
13566 if (isXAxis && !secondPass) {
13567 each(axis.series, function(series) {
13568 series.processData(
13569 axis.min !== axis.oldMin || axis.max !== axis.oldMax
13570 );
13571 });
13572 }
13573
13574 // set the translation factor used in translate function
13575 axis.setAxisTranslation(true);
13576
13577 // hook for ordinal axes and radial axes
13578 if (axis.beforeSetTickPositions) {
13579 axis.beforeSetTickPositions();
13580 }
13581
13582 // hook for extensions, used in Highstock ordinal axes
13583 if (axis.postProcessTickInterval) {
13584 axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval);
13585 }
13586
13587 // In column-like charts, don't cramp in more ticks than there are
13588 // points (#1943, #4184)
13589 if (axis.pointRange && !tickIntervalOption) {
13590 axis.tickInterval = Math.max(axis.pointRange, axis.tickInterval);
13591 }
13592
13593 // Before normalizing the tick interval, handle minimum tick interval.
13594 // This applies only if tickInterval is not defined.
13595 minTickInterval = pick(
13596 options.minTickInterval,
13597 axis.isDatetimeAxis && axis.closestPointRange
13598 );
13599 if (!tickIntervalOption && axis.tickInterval < minTickInterval) {
13600 axis.tickInterval = minTickInterval;
13601 }
13602
13603 // for linear axes, get magnitude and normalize the interval
13604 if (!isDatetimeAxis && !isLog && !tickIntervalOption) {
13605 axis.tickInterval = normalizeTickInterval(
13606 axis.tickInterval,
13607 null,
13608 getMagnitude(axis.tickInterval),
13609 // If the tick interval is between 0.5 and 5 and the axis max is
13610 // in the order of thousands, chances are we are dealing with
13611 // years. Don't allow decimals. #3363.
13612 pick(
13613 options.allowDecimals, !(
13614 axis.tickInterval > 0.5 &&
13615 axis.tickInterval < 5 &&
13616 axis.max > 1000 &&
13617 axis.max < 9999
13618 )
13619 ), !!this.tickAmount
13620 );
13621 }
13622
13623 // Prevent ticks from getting so close that we can't draw the labels
13624 if (!this.tickAmount) {
13625 axis.tickInterval = axis.unsquish();
13626 }
13627
13628 this.setTickPositions();
13629 },
13630
13631 /**
13632 * Now we have computed the normalized tickInterval, get the tick positions
13633 */
13634 setTickPositions: function() {
13635
13636 var options = this.options,
13637 tickPositions,
13638 tickPositionsOption = options.tickPositions,
13639 minorTickIntervalOption = this.getMinorTickInterval(),
13640 tickPositioner = options.tickPositioner,
13641 startOnTick = options.startOnTick,
13642 endOnTick = options.endOnTick;
13643
13644 // Set the tickmarkOffset
13645 this.tickmarkOffset = (
13646 this.categories &&
13647 options.tickmarkPlacement === 'between' &&
13648 this.tickInterval === 1
13649 ) ? 0.5 : 0; // #3202
13650
13651
13652 // get minorTickInterval
13653 this.minorTickInterval =
13654 minorTickIntervalOption === 'auto' &&
13655 this.tickInterval ?
13656 this.tickInterval / 5 :
13657 minorTickIntervalOption;
13658
13659 // When there is only one point, or all points have the same value on
13660 // this axis, then min and max are equal and tickPositions.length is 0
13661 // or 1. In this case, add some padding in order to center the point,
13662 // but leave it with one tick. #1337.
13663 this.single =
13664 this.min === this.max &&
13665 defined(this.min) &&
13666 !this.tickAmount &&
13667 (
13668 // Data is on integer (#6563)
13669 parseInt(this.min, 10) === this.min ||
13670
13671 // Between integers and decimals are not allowed (#6274)
13672 options.allowDecimals !== false
13673 );
13674
13675 // Find the tick positions. Work on a copy (#1565)
13676 this.tickPositions = tickPositions =
13677 tickPositionsOption && tickPositionsOption.slice();
13678 if (!tickPositions) {
13679
13680 if (this.isDatetimeAxis) {
13681 tickPositions = this.getTimeTicks(
13682 this.normalizeTimeTickInterval(
13683 this.tickInterval,
13684 options.units
13685 ),
13686 this.min,
13687 this.max,
13688 options.startOfWeek,
13689 this.ordinalPositions,
13690 this.closestPointRange,
13691 true
13692 );
13693 } else if (this.isLog) {
13694 tickPositions = this.getLogTickPositions(
13695 this.tickInterval,
13696 this.min,
13697 this.max
13698 );
13699 } else {
13700 tickPositions = this.getLinearTickPositions(
13701 this.tickInterval,
13702 this.min,
13703 this.max
13704 );
13705 }
13706
13707 // Too dense ticks, keep only the first and last (#4477)
13708 if (tickPositions.length > this.len) {
13709 tickPositions = [tickPositions[0], tickPositions.pop()];
13710 // Reduce doubled value (#7339)
13711 if (tickPositions[0] === tickPositions[1]) {
13712 tickPositions.length = 1;
13713 }
13714 }
13715
13716 this.tickPositions = tickPositions;
13717
13718 // Run the tick positioner callback, that allows modifying auto tick
13719 // positions.
13720 if (tickPositioner) {
13721 tickPositioner = tickPositioner.apply(
13722 this, [this.min, this.max]
13723 );
13724 if (tickPositioner) {
13725 this.tickPositions = tickPositions = tickPositioner;
13726 }
13727 }
13728
13729 }
13730
13731 // Reset min/max or remove extremes based on start/end on tick
13732 this.paddedTicks = tickPositions.slice(0); // Used for logarithmic minor
13733 this.trimTicks(tickPositions, startOnTick, endOnTick);
13734 if (!this.isLinked) {
13735
13736 // Substract half a unit (#2619, #2846, #2515, #3390),
13737 // but not in case of multiple ticks (#6897)
13738 if (this.single && tickPositions.length < 2) {
13739 this.min -= 0.5;
13740 this.max += 0.5;
13741 }
13742 if (!tickPositionsOption && !tickPositioner) {
13743 this.adjustTickAmount();
13744 }
13745 }
13746 },
13747
13748 /**
13749 * Handle startOnTick and endOnTick by either adapting to padding min/max or
13750 * rounded min/max. Also handle single data points.
13751 *
13752 * @private
13753 */
13754 trimTicks: function(tickPositions, startOnTick, endOnTick) {
13755 var roundedMin = tickPositions[0],
13756 roundedMax = tickPositions[tickPositions.length - 1],
13757 minPointOffset = this.minPointOffset || 0;
13758
13759 if (!this.isLinked) {
13760 if (startOnTick && roundedMin !== -Infinity) { // #6502
13761 this.min = roundedMin;
13762 } else {
13763 while (this.min - minPointOffset > tickPositions[0]) {
13764 tickPositions.shift();
13765 }
13766 }
13767
13768 if (endOnTick) {
13769 this.max = roundedMax;
13770 } else {
13771 while (this.max + minPointOffset <
13772 tickPositions[tickPositions.length - 1]) {
13773 tickPositions.pop();
13774 }
13775 }
13776
13777 // If no tick are left, set one tick in the middle (#3195)
13778 if (tickPositions.length === 0 && defined(roundedMin)) {
13779 tickPositions.push((roundedMax + roundedMin) / 2);
13780 }
13781 }
13782 },
13783
13784 /**
13785 * Check if there are multiple axes in the same pane.
13786 *
13787 * @private
13788 * @return {Boolean}
13789 * True if there are other axes.
13790 */
13791 alignToOthers: function() {
13792 var others = {}, // Whether there is another axis to pair with this one
13793 hasOther,
13794 options = this.options;
13795
13796 if (
13797 // Only if alignTicks is true
13798 this.chart.options.chart.alignTicks !== false &&
13799 options.alignTicks !== false &&
13800
13801 // Don't try to align ticks on a log axis, they are not evenly
13802 // spaced (#6021)
13803 !this.isLog
13804 ) {
13805 each(this.chart[this.coll], function(axis) {
13806 var otherOptions = axis.options,
13807 horiz = axis.horiz,
13808 key = [
13809 horiz ? otherOptions.left : otherOptions.top,
13810 otherOptions.width,
13811 otherOptions.height,
13812 otherOptions.pane
13813 ].join(',');
13814
13815
13816 if (axis.series.length) { // #4442
13817 if (others[key]) {
13818 hasOther = true; // #4201
13819 } else {
13820 others[key] = 1;
13821 }
13822 }
13823 });
13824 }
13825 return hasOther;
13826 },
13827
13828 /**
13829 * Find the max ticks of either the x and y axis collection, and record it
13830 * in `this.tickAmount`.
13831 *
13832 * @private
13833 */
13834 getTickAmount: function() {
13835 var options = this.options,
13836 tickAmount = options.tickAmount,
13837 tickPixelInterval = options.tickPixelInterval;
13838
13839 if (!defined(options.tickInterval) &&
13840 this.len < tickPixelInterval &&
13841 !this.isRadial &&
13842 !this.isLog &&
13843 options.startOnTick &&
13844 options.endOnTick
13845 ) {
13846 tickAmount = 2;
13847 }
13848
13849 if (!tickAmount && this.alignToOthers()) {
13850 // Add 1 because 4 tick intervals require 5 ticks (including first
13851 // and last)
13852 tickAmount = Math.ceil(this.len / tickPixelInterval) + 1;
13853 }
13854
13855 // For tick amounts of 2 and 3, compute five ticks and remove the
13856 // intermediate ones. This prevents the axis from adding ticks that are
13857 // too far away from the data extremes.
13858 if (tickAmount < 4) {
13859 this.finalTickAmt = tickAmount;
13860 tickAmount = 5;
13861 }
13862
13863 this.tickAmount = tickAmount;
13864 },
13865
13866 /**
13867 * When using multiple axes, adjust the number of ticks to match the highest
13868 * number of ticks in that group.
13869 *
13870 * @private
13871 */
13872 adjustTickAmount: function() {
13873 var tickInterval = this.tickInterval,
13874 tickPositions = this.tickPositions,
13875 tickAmount = this.tickAmount,
13876 finalTickAmt = this.finalTickAmt,
13877 currentTickAmount = tickPositions && tickPositions.length,
13878 i,
13879 len;
13880
13881 if (currentTickAmount < tickAmount) {
13882 while (tickPositions.length < tickAmount) {
13883 tickPositions.push(correctFloat(
13884 tickPositions[tickPositions.length - 1] + tickInterval
13885 ));
13886 }
13887 this.transA *= (currentTickAmount - 1) / (tickAmount - 1);
13888 this.max = tickPositions[tickPositions.length - 1];
13889
13890 // We have too many ticks, run second pass to try to reduce ticks
13891 } else if (currentTickAmount > tickAmount) {
13892 this.tickInterval *= 2;
13893 this.setTickPositions();
13894 }
13895
13896 // The finalTickAmt property is set in getTickAmount
13897 if (defined(finalTickAmt)) {
13898 i = len = tickPositions.length;
13899 while (i--) {
13900 if (
13901 // Remove every other tick
13902 (finalTickAmt === 3 && i % 2 === 1) ||
13903 // Remove all but first and last
13904 (finalTickAmt <= 2 && i > 0 && i < len - 1)
13905 ) {
13906 tickPositions.splice(i, 1);
13907 }
13908 }
13909 this.finalTickAmt = undefined;
13910 }
13911 },
13912
13913 /**
13914 * Set the scale based on data min and max, user set min and max or options.
13915 *
13916 * @private
13917 */
13918 setScale: function() {
13919 var axis = this,
13920 isDirtyData,
13921 isDirtyAxisLength;
13922
13923 axis.oldMin = axis.min;
13924 axis.oldMax = axis.max;
13925 axis.oldAxisLength = axis.len;
13926
13927 // set the new axisLength
13928 axis.setAxisSize();
13929 isDirtyAxisLength = axis.len !== axis.oldAxisLength;
13930
13931 // is there new data?
13932 each(axis.series, function(series) {
13933 if (
13934 series.isDirtyData ||
13935 series.isDirty ||
13936 // When x axis is dirty, we need new data extremes for y as well
13937 series.xAxis.isDirty
13938 ) {
13939 isDirtyData = true;
13940 }
13941 });
13942
13943 // do we really need to go through all this?
13944 if (
13945 isDirtyAxisLength ||
13946 isDirtyData ||
13947 axis.isLinked ||
13948 axis.forceRedraw ||
13949 axis.userMin !== axis.oldUserMin ||
13950 axis.userMax !== axis.oldUserMax ||
13951 axis.alignToOthers()
13952 ) {
13953
13954 if (axis.resetStacks) {
13955 axis.resetStacks();
13956 }
13957
13958 axis.forceRedraw = false;
13959
13960 // get data extremes if needed
13961 axis.getSeriesExtremes();
13962
13963 // get fixed positions based on tickInterval
13964 axis.setTickInterval();
13965
13966 // record old values to decide whether a rescale is necessary later
13967 // on (#540)
13968 axis.oldUserMin = axis.userMin;
13969 axis.oldUserMax = axis.userMax;
13970
13971 // Mark as dirty if it is not already set to dirty and extremes have
13972 // changed. #595.
13973 if (!axis.isDirty) {
13974 axis.isDirty =
13975 isDirtyAxisLength ||
13976 axis.min !== axis.oldMin ||
13977 axis.max !== axis.oldMax;
13978 }
13979 } else if (axis.cleanStacks) {
13980 axis.cleanStacks();
13981 }
13982 },
13983
13984 /**
13985 * Set the minimum and maximum of the axes after render time. If the
13986 * `startOnTick` and `endOnTick` options are true, the minimum and maximum
13987 * values are rounded off to the nearest tick. To prevent this, these
13988 * options can be set to false before calling setExtremes. Also, setExtremes
13989 * will not allow a range lower than the `minRange` option, which by default
13990 * is the range of five points.
13991 *
13992 * @param {Number} [newMin]
13993 * The new minimum value.
13994 * @param {Number} [newMax]
13995 * The new maximum value.
13996 * @param {Boolean} [redraw=true]
13997 * Whether to redraw the chart or wait for an explicit call to
13998 * {@link Highcharts.Chart#redraw}
13999 * @param {AnimationOptions} [animation=true]
14000 * Enable or modify animations.
14001 * @param {Object} [eventArguments]
14002 * Arguments to be accessed in event handler.
14003 *
14004 * @sample highcharts/members/axis-setextremes/
14005 * Set extremes from a button
14006 * @sample highcharts/members/axis-setextremes-datetime/
14007 * Set extremes on a datetime axis
14008 * @sample highcharts/members/axis-setextremes-off-ticks/
14009 * Set extremes off ticks
14010 * @sample stock/members/axis-setextremes/
14011 * Set extremes in Highstock
14012 * @sample maps/members/axis-setextremes/
14013 * Set extremes in Highmaps
14014 */
14015 setExtremes: function(newMin, newMax, redraw, animation, eventArguments) {
14016 var axis = this,
14017 chart = axis.chart;
14018
14019 redraw = pick(redraw, true); // defaults to true
14020
14021 each(axis.series, function(serie) {
14022 delete serie.kdTree;
14023 });
14024
14025 // Extend the arguments with min and max
14026 eventArguments = extend(eventArguments, {
14027 min: newMin,
14028 max: newMax
14029 });
14030
14031 // Fire the event
14032 fireEvent(axis, 'setExtremes', eventArguments, function() {
14033
14034 axis.userMin = newMin;
14035 axis.userMax = newMax;
14036 axis.eventArgs = eventArguments;
14037
14038 if (redraw) {
14039 chart.redraw(animation);
14040 }
14041 });
14042 },
14043
14044 /**
14045 * Overridable method for zooming chart. Pulled out in a separate method to
14046 * allow overriding in stock charts.
14047 *
14048 * @private
14049 */
14050 zoom: function(newMin, newMax) {
14051 var dataMin = this.dataMin,
14052 dataMax = this.dataMax,
14053 options = this.options,
14054 min = Math.min(dataMin, pick(options.min, dataMin)),
14055 max = Math.max(dataMax, pick(options.max, dataMax));
14056
14057 if (newMin !== this.min || newMax !== this.max) { // #5790
14058
14059 // Prevent pinch zooming out of range. Check for defined is for
14060 // #1946. #1734.
14061 if (!this.allowZoomOutside) {
14062 // #6014, sometimes newMax will be smaller than min (or newMin
14063 // will be larger than max).
14064 if (defined(dataMin)) {
14065 if (newMin < min) {
14066 newMin = min;
14067 }
14068 if (newMin > max) {
14069 newMin = max;
14070 }
14071 }
14072 if (defined(dataMax)) {
14073 if (newMax < min) {
14074 newMax = min;
14075 }
14076 if (newMax > max) {
14077 newMax = max;
14078 }
14079 }
14080 }
14081
14082 // In full view, displaying the reset zoom button is not required
14083 this.displayBtn = newMin !== undefined || newMax !== undefined;
14084
14085 // Do it
14086 this.setExtremes(
14087 newMin,
14088 newMax,
14089 false,
14090 undefined, {
14091 trigger: 'zoom'
14092 }
14093 );
14094 }
14095
14096 return true;
14097 },
14098
14099 /**
14100 * Update the axis metrics.
14101 *
14102 * @private
14103 */
14104 setAxisSize: function() {
14105 var chart = this.chart,
14106 options = this.options,
14107 // [top, right, bottom, left]
14108 offsets = options.offsets || [0, 0, 0, 0],
14109 horiz = this.horiz,
14110
14111 // Check for percentage based input values. Rounding fixes problems
14112 // with column overflow and plot line filtering (#4898, #4899)
14113 width = this.width = Math.round(H.relativeLength(
14114 pick(
14115 options.width,
14116 chart.plotWidth - offsets[3] + offsets[1]
14117 ),
14118 chart.plotWidth
14119 )),
14120 height = this.height = Math.round(H.relativeLength(
14121 pick(
14122 options.height,
14123 chart.plotHeight - offsets[0] + offsets[2]
14124 ),
14125 chart.plotHeight
14126 )),
14127 top = this.top = Math.round(H.relativeLength(
14128 pick(options.top, chart.plotTop + offsets[0]),
14129 chart.plotHeight,
14130 chart.plotTop
14131 )),
14132 left = this.left = Math.round(H.relativeLength(
14133 pick(options.left, chart.plotLeft + offsets[3]),
14134 chart.plotWidth,
14135 chart.plotLeft
14136 ));
14137
14138 // Expose basic values to use in Series object and navigator
14139 this.bottom = chart.chartHeight - height - top;
14140 this.right = chart.chartWidth - width - left;
14141
14142 // Direction agnostic properties
14143 this.len = Math.max(horiz ? width : height, 0); // Math.max fixes #905
14144 this.pos = horiz ? left : top; // distance from SVG origin
14145 },
14146
14147 /**
14148 * The returned object literal from the {@link Highcharts.Axis#getExtremes}
14149 * function.
14150 *
14151 * @typedef {Object} Extremes
14152 * @property {Number} dataMax
14153 * The maximum value of the axis' associated series.
14154 * @property {Number} dataMin
14155 * The minimum value of the axis' associated series.
14156 * @property {Number} max
14157 * The maximum axis value, either automatic or set manually. If
14158 * the `max` option is not set, `maxPadding` is 0 and `endOnTick`
14159 * is false, this value will be the same as `dataMax`.
14160 * @property {Number} min
14161 * The minimum axis value, either automatic or set manually. If
14162 * the `min` option is not set, `minPadding` is 0 and
14163 * `startOnTick` is false, this value will be the same
14164 * as `dataMin`.
14165 */
14166 /**
14167 * Get the current extremes for the axis.
14168 *
14169 * @returns {Extremes}
14170 * An object containing extremes information.
14171 *
14172 * @sample highcharts/members/axis-getextremes/
14173 * Report extremes by click on a button
14174 * @sample maps/members/axis-getextremes/
14175 * Get extremes in Highmaps
14176 */
14177 getExtremes: function() {
14178 var axis = this,
14179 isLog = axis.isLog,
14180 lin2log = axis.lin2log;
14181
14182 return {
14183 min: isLog ? correctFloat(lin2log(axis.min)) : axis.min,
14184 max: isLog ? correctFloat(lin2log(axis.max)) : axis.max,
14185 dataMin: axis.dataMin,
14186 dataMax: axis.dataMax,
14187 userMin: axis.userMin,
14188 userMax: axis.userMax
14189 };
14190 },
14191
14192 /**
14193 * Get the zero plane either based on zero or on the min or max value.
14194 * Used in bar and area plots.
14195 *
14196 * @param {Number} threshold
14197 * The threshold in axis values.
14198 *
14199 * @return {Number}
14200 * The translated threshold position in terms of pixels, and
14201 * corrected to stay within the axis bounds.
14202 */
14203 getThreshold: function(threshold) {
14204 var axis = this,
14205 isLog = axis.isLog,
14206 lin2log = axis.lin2log,
14207 realMin = isLog ? lin2log(axis.min) : axis.min,
14208 realMax = isLog ? lin2log(axis.max) : axis.max;
14209
14210 if (threshold === null) {
14211 threshold = realMin;
14212 } else if (realMin > threshold) {
14213 threshold = realMin;
14214 } else if (realMax < threshold) {
14215 threshold = realMax;
14216 }
14217
14218 return axis.translate(threshold, 0, 1, 0, 1);
14219 },
14220
14221 /**
14222 * Compute auto alignment for the axis label based on which side the axis is
14223 * on and the given rotation for the label.
14224 *
14225 * @param {Number} rotation
14226 * The rotation in degrees as set by either the `rotation` or
14227 * `autoRotation` options.
14228 * @private
14229 */
14230 autoLabelAlign: function(rotation) {
14231 var ret,
14232 angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360;
14233
14234 if (angle > 15 && angle < 165) {
14235 ret = 'right';
14236 } else if (angle > 195 && angle < 345) {
14237 ret = 'left';
14238 } else {
14239 ret = 'center';
14240 }
14241 return ret;
14242 },
14243
14244 /**
14245 * Get the tick length and width for the axis based on axis options.
14246 *
14247 * @private
14248 *
14249 * @param {String} prefix
14250 * 'tick' or 'minorTick'
14251 * @return {Array.<Number>}
14252 * An array of tickLength and tickWidth
14253 */
14254 tickSize: function(prefix) {
14255 var options = this.options,
14256 tickLength = options[prefix + 'Length'],
14257 tickWidth = pick(
14258 options[prefix + 'Width'],
14259 prefix === 'tick' && this.isXAxis ? 1 : 0 // X axis default 1
14260 );
14261
14262 if (tickWidth && tickLength) {
14263 // Negate the length
14264 if (options[prefix + 'Position'] === 'inside') {
14265 tickLength = -tickLength;
14266 }
14267 return [tickLength, tickWidth];
14268 }
14269
14270 },
14271
14272 /**
14273 * Return the size of the labels.
14274 *
14275 * @private
14276 */
14277 labelMetrics: function() {
14278 var index = this.tickPositions && this.tickPositions[0] || 0;
14279 return this.chart.renderer.fontMetrics(
14280 this.options.labels.style && this.options.labels.style.fontSize,
14281 this.ticks[index] && this.ticks[index].label
14282 );
14283 },
14284
14285 /**
14286 * Prevent the ticks from getting so close we can't draw the labels. On a
14287 * horizontal axis, this is handled by rotating the labels, removing ticks
14288 * and adding ellipsis. On a vertical axis remove ticks and add ellipsis.
14289 *
14290 * @private
14291 */
14292 unsquish: function() {
14293 var labelOptions = this.options.labels,
14294 horiz = this.horiz,
14295 tickInterval = this.tickInterval,
14296 newTickInterval = tickInterval,
14297 slotSize = this.len / (
14298 ((this.categories ? 1 : 0) + this.max - this.min) / tickInterval
14299 ),
14300 rotation,
14301 rotationOption = labelOptions.rotation,
14302 labelMetrics = this.labelMetrics(),
14303 step,
14304 bestScore = Number.MAX_VALUE,
14305 autoRotation,
14306 // Return the multiple of tickInterval that is needed to avoid
14307 // collision
14308 getStep = function(spaceNeeded) {
14309 var step = spaceNeeded / (slotSize || 1);
14310 step = step > 1 ? Math.ceil(step) : 1;
14311 return step * tickInterval;
14312 };
14313
14314 if (horiz) {
14315 autoRotation = !labelOptions.staggerLines &&
14316 !labelOptions.step &&
14317 ( // #3971
14318 defined(rotationOption) ? [rotationOption] :
14319 slotSize < pick(labelOptions.autoRotationLimit, 80) &&
14320 labelOptions.autoRotation
14321 );
14322
14323 if (autoRotation) {
14324
14325 // Loop over the given autoRotation options, and determine
14326 // which gives the best score. The best score is that with
14327 // the lowest number of steps and a rotation closest
14328 // to horizontal.
14329 each(autoRotation, function(rot) {
14330 var score;
14331
14332 if (
14333 rot === rotationOption ||
14334 (rot && rot >= -90 && rot <= 90)
14335 ) { // #3891
14336
14337 step = getStep(
14338 Math.abs(labelMetrics.h / Math.sin(deg2rad * rot))
14339 );
14340
14341 score = step + Math.abs(rot / 360);
14342
14343 if (score < bestScore) {
14344 bestScore = score;
14345 rotation = rot;
14346 newTickInterval = step;
14347 }
14348 }
14349 });
14350 }
14351
14352 } else if (!labelOptions.step) { // #4411
14353 newTickInterval = getStep(labelMetrics.h);
14354 }
14355
14356 this.autoRotation = autoRotation;
14357 this.labelRotation = pick(rotation, rotationOption);
14358
14359 return newTickInterval;
14360 },
14361
14362 /**
14363 * Get the general slot width for labels/categories on this axis. This may
14364 * change between the pre-render (from Axis.getOffset) and the final tick
14365 * rendering and placement.
14366 *
14367 * @private
14368 * @return {Number}
14369 * The pixel width allocated to each axis label.
14370 */
14371 getSlotWidth: function() {
14372 // #5086, #1580, #1931
14373 var chart = this.chart,
14374 horiz = this.horiz,
14375 labelOptions = this.options.labels,
14376 slotCount = Math.max(
14377 this.tickPositions.length - (this.categories ? 0 : 1),
14378 1
14379 ),
14380 marginLeft = chart.margin[3];
14381
14382 return (
14383 horiz &&
14384 (labelOptions.step || 0) < 2 &&
14385 !labelOptions.rotation && // #4415
14386 ((this.staggerLines || 1) * this.len) / slotCount
14387 ) || (!horiz && (
14388 // #7028
14389 (
14390 labelOptions.style &&
14391 parseInt(labelOptions.style.width, 10)
14392 ) ||
14393 (
14394 marginLeft &&
14395 (marginLeft - chart.spacing[3])
14396 ) ||
14397 chart.chartWidth * 0.33
14398 ));
14399
14400 },
14401
14402 /**
14403 * Render the axis labels and determine whether ellipsis or rotation need
14404 * to be applied.
14405 *
14406 * @private
14407 */
14408 renderUnsquish: function() {
14409 var chart = this.chart,
14410 renderer = chart.renderer,
14411 tickPositions = this.tickPositions,
14412 ticks = this.ticks,
14413 labelOptions = this.options.labels,
14414 horiz = this.horiz,
14415 slotWidth = this.getSlotWidth(),
14416 innerWidth = Math.max(
14417 1,
14418 Math.round(slotWidth - 2 * (labelOptions.padding || 5))
14419 ),
14420 attr = {},
14421 labelMetrics = this.labelMetrics(),
14422 textOverflowOption = labelOptions.style &&
14423 labelOptions.style.textOverflow,
14424 css,
14425 maxLabelLength = 0,
14426 label,
14427 i,
14428 pos;
14429
14430 // Set rotation option unless it is "auto", like in gauges
14431 if (!isString(labelOptions.rotation)) {
14432 attr.rotation = labelOptions.rotation || 0; // #4443
14433 }
14434
14435 // Get the longest label length
14436 each(tickPositions, function(tick) {
14437 tick = ticks[tick];
14438 if (tick && tick.labelLength > maxLabelLength) {
14439 maxLabelLength = tick.labelLength;
14440 }
14441 });
14442 this.maxLabelLength = maxLabelLength;
14443
14444
14445 // Handle auto rotation on horizontal axis
14446 if (this.autoRotation) {
14447
14448 // Apply rotation only if the label is too wide for the slot, and
14449 // the label is wider than its height.
14450 if (
14451 maxLabelLength > innerWidth &&
14452 maxLabelLength > labelMetrics.h
14453 ) {
14454 attr.rotation = this.labelRotation;
14455 } else {
14456 this.labelRotation = 0;
14457 }
14458
14459 // Handle word-wrap or ellipsis on vertical axis
14460 } else if (slotWidth) {
14461 // For word-wrap or ellipsis
14462 css = {
14463 width: innerWidth + 'px'
14464 };
14465
14466 if (!textOverflowOption) {
14467 css.textOverflow = 'clip';
14468
14469 // On vertical axis, only allow word wrap if there is room
14470 // for more lines.
14471 i = tickPositions.length;
14472 while (!horiz && i--) {
14473 pos = tickPositions[i];
14474 label = ticks[pos].label;
14475 if (label) {
14476 // Reset ellipsis in order to get the correct
14477 // bounding box (#4070)
14478 if (
14479 label.styles &&
14480 label.styles.textOverflow === 'ellipsis'
14481 ) {
14482 label.css({
14483 textOverflow: 'clip'
14484 });
14485
14486 // Set the correct width in order to read
14487 // the bounding box height (#4678, #5034)
14488 } else if (ticks[pos].labelLength > slotWidth) {
14489 label.css({
14490 width: slotWidth + 'px'
14491 });
14492 }
14493
14494 if (
14495 label.getBBox().height > (
14496 this.len / tickPositions.length -
14497 (labelMetrics.h - labelMetrics.f)
14498 )
14499 ) {
14500 label.specCss = {
14501 textOverflow: 'ellipsis'
14502 };
14503 }
14504 }
14505 }
14506 }
14507 }
14508
14509
14510 // Add ellipsis if the label length is significantly longer than ideal
14511 if (attr.rotation) {
14512 css = {
14513 width: (
14514 maxLabelLength > chart.chartHeight * 0.5 ?
14515 chart.chartHeight * 0.33 :
14516 chart.chartHeight
14517 ) + 'px'
14518 };
14519 if (!textOverflowOption) {
14520 css.textOverflow = 'ellipsis';
14521 }
14522 }
14523
14524 // Set the explicit or automatic label alignment
14525 this.labelAlign = labelOptions.align ||
14526 this.autoLabelAlign(this.labelRotation);
14527 if (this.labelAlign) {
14528 attr.align = this.labelAlign;
14529 }
14530
14531 // Apply general and specific CSS
14532 each(tickPositions, function(pos) {
14533 var tick = ticks[pos],
14534 label = tick && tick.label;
14535 if (label) {
14536 // This needs to go before the CSS in old IE (#4502)
14537 label.attr(attr);
14538
14539 if (css) {
14540 label.css(merge(css, label.specCss));
14541 }
14542 delete label.specCss;
14543 tick.rotation = attr.rotation;
14544 }
14545 });
14546
14547 // Note: Why is this not part of getLabelPosition?
14548 this.tickRotCorr = renderer.rotCorr(
14549 labelMetrics.b,
14550 this.labelRotation || 0,
14551 this.side !== 0
14552 );
14553 },
14554
14555 /**
14556 * Return true if the axis has associated data.
14557 *
14558 * @return {Boolean}
14559 * True if the axis has associated visible series and those series
14560 * have either valid data points or explicit `min` and `max`
14561 * settings.
14562 */
14563 hasData: function() {
14564 return (
14565 this.hasVisibleSeries ||
14566 (
14567 defined(this.min) &&
14568 defined(this.max) &&
14569 this.tickPositions &&
14570 this.tickPositions.length > 0
14571 )
14572 );
14573 },
14574
14575 /**
14576 * Adds the title defined in axis.options.title.
14577 * @param {Boolean} display - whether or not to display the title
14578 */
14579 addTitle: function(display) {
14580 var axis = this,
14581 renderer = axis.chart.renderer,
14582 horiz = axis.horiz,
14583 opposite = axis.opposite,
14584 options = axis.options,
14585 axisTitleOptions = options.title,
14586 textAlign;
14587
14588 if (!axis.axisTitle) {
14589 textAlign = axisTitleOptions.textAlign;
14590 if (!textAlign) {
14591 textAlign = (horiz ? {
14592 low: 'left',
14593 middle: 'center',
14594 high: 'right'
14595 } : {
14596 low: opposite ? 'right' : 'left',
14597 middle: 'center',
14598 high: opposite ? 'left' : 'right'
14599 })[axisTitleOptions.align];
14600 }
14601 axis.axisTitle = renderer.text(
14602 axisTitleOptions.text,
14603 0,
14604 0,
14605 axisTitleOptions.useHTML
14606 )
14607 .attr({
14608 zIndex: 7,
14609 rotation: axisTitleOptions.rotation || 0,
14610 align: textAlign
14611 })
14612 .addClass('highcharts-axis-title')
14613
14614 .css(axisTitleOptions.style)
14615
14616 .add(axis.axisGroup);
14617 axis.axisTitle.isNew = true;
14618 }
14619
14620 // Max width defaults to the length of the axis
14621
14622 if (!axisTitleOptions.style.width && !axis.isRadial) {
14623
14624 axis.axisTitle.css({
14625 width: axis.len
14626 });
14627
14628 }
14629
14630
14631
14632 // hide or show the title depending on whether showEmpty is set
14633 axis.axisTitle[display ? 'show' : 'hide'](true);
14634 },
14635
14636 /**
14637 * Generates a tick for initial positioning.
14638 *
14639 * @private
14640 * @param {number} pos
14641 * The tick position in axis values.
14642 * @param {number} i
14643 * The index of the tick in {@link Axis.tickPositions}.
14644 */
14645 generateTick: function(pos) {
14646 var ticks = this.ticks;
14647
14648 if (!ticks[pos]) {
14649 ticks[pos] = new Tick(this, pos);
14650 } else {
14651 ticks[pos].addLabel(); // update labels depending on tick interval
14652 }
14653 },
14654
14655 /**
14656 * Render the tick labels to a preliminary position to get their sizes.
14657 *
14658 * @private
14659 */
14660 getOffset: function() {
14661 var axis = this,
14662 chart = axis.chart,
14663 renderer = chart.renderer,
14664 options = axis.options,
14665 tickPositions = axis.tickPositions,
14666 ticks = axis.ticks,
14667 horiz = axis.horiz,
14668 side = axis.side,
14669 invertedSide = chart.inverted &&
14670 !axis.isZAxis ? [1, 0, 3, 2][side] : side,
14671 hasData,
14672 showAxis,
14673 titleOffset = 0,
14674 titleOffsetOption,
14675 titleMargin = 0,
14676 axisTitleOptions = options.title,
14677 labelOptions = options.labels,
14678 labelOffset = 0, // reset
14679 labelOffsetPadded,
14680 axisOffset = chart.axisOffset,
14681 clipOffset = chart.clipOffset,
14682 clip,
14683 directionFactor = [-1, 1, 1, -1][side],
14684 className = options.className,
14685 axisParent = axis.axisParent, // Used in color axis
14686 lineHeightCorrection,
14687 tickSize = this.tickSize('tick');
14688
14689 // For reuse in Axis.render
14690 hasData = axis.hasData();
14691 axis.showAxis = showAxis = hasData || pick(options.showEmpty, true);
14692
14693 // Set/reset staggerLines
14694 axis.staggerLines = axis.horiz && labelOptions.staggerLines;
14695
14696 // Create the axisGroup and gridGroup elements on first iteration
14697 if (!axis.axisGroup) {
14698 axis.gridGroup = renderer.g('grid')
14699 .attr({
14700 zIndex: options.gridZIndex || 1
14701 })
14702 .addClass(
14703 'highcharts-' + this.coll.toLowerCase() + '-grid ' +
14704 (className || '')
14705 )
14706 .add(axisParent);
14707 axis.axisGroup = renderer.g('axis')
14708 .attr({
14709 zIndex: options.zIndex || 2
14710 })
14711 .addClass(
14712 'highcharts-' + this.coll.toLowerCase() + ' ' +
14713 (className || '')
14714 )
14715 .add(axisParent);
14716 axis.labelGroup = renderer.g('axis-labels')
14717 .attr({
14718 zIndex: labelOptions.zIndex || 7
14719 })
14720 .addClass(
14721 'highcharts-' + axis.coll.toLowerCase() + '-labels ' +
14722 (className || '')
14723 )
14724 .add(axisParent);
14725 }
14726
14727 if (hasData || axis.isLinked) {
14728
14729 // Generate ticks
14730 each(tickPositions, function(pos, i) {
14731 // i is not used here, but may be used in overrides
14732 axis.generateTick(pos, i);
14733 });
14734
14735 axis.renderUnsquish();
14736
14737
14738 // Left side must be align: right and right side must
14739 // have align: left for labels
14740 if (
14741 labelOptions.reserveSpace !== false &&
14742 (
14743 side === 0 ||
14744 side === 2 || {
14745 1: 'left',
14746 3: 'right'
14747 }[side] === axis.labelAlign ||
14748 axis.labelAlign === 'center'
14749 )
14750 ) {
14751 each(tickPositions, function(pos) {
14752 // get the highest offset
14753 labelOffset = Math.max(
14754 ticks[pos].getLabelSize(),
14755 labelOffset
14756 );
14757 });
14758 }
14759
14760 if (axis.staggerLines) {
14761 labelOffset *= axis.staggerLines;
14762 axis.labelOffset = labelOffset * (axis.opposite ? -1 : 1);
14763 }
14764
14765 } else { // doesn't have data
14766 objectEach(ticks, function(tick, n) {
14767 tick.destroy();
14768 delete ticks[n];
14769 });
14770 }
14771
14772 if (
14773 axisTitleOptions &&
14774 axisTitleOptions.text &&
14775 axisTitleOptions.enabled !== false
14776 ) {
14777 axis.addTitle(showAxis);
14778
14779 if (showAxis && axisTitleOptions.reserveSpace !== false) {
14780 axis.titleOffset = titleOffset =
14781 axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];
14782 titleOffsetOption = axisTitleOptions.offset;
14783 titleMargin = defined(titleOffsetOption) ?
14784 0 :
14785 pick(axisTitleOptions.margin, horiz ? 5 : 10);
14786 }
14787 }
14788
14789 // Render the axis line
14790 axis.renderLine();
14791
14792 // handle automatic or user set offset
14793 axis.offset = directionFactor * pick(options.offset, axisOffset[side]);
14794
14795 axis.tickRotCorr = axis.tickRotCorr || {
14796 x: 0,
14797 y: 0
14798 }; // polar
14799 if (side === 0) {
14800 lineHeightCorrection = -axis.labelMetrics().h;
14801 } else if (side === 2) {
14802 lineHeightCorrection = axis.tickRotCorr.y;
14803 } else {
14804 lineHeightCorrection = 0;
14805 }
14806
14807 // Find the padded label offset
14808 labelOffsetPadded = Math.abs(labelOffset) + titleMargin;
14809 if (labelOffset) {
14810 labelOffsetPadded -= lineHeightCorrection;
14811 labelOffsetPadded += directionFactor * (
14812 horiz ?
14813 pick(
14814 labelOptions.y,
14815 axis.tickRotCorr.y + directionFactor * 8
14816 ) :
14817 labelOptions.x
14818 );
14819 }
14820
14821 axis.axisTitleMargin = pick(titleOffsetOption, labelOffsetPadded);
14822
14823 axisOffset[side] = Math.max(
14824 axisOffset[side],
14825 axis.axisTitleMargin + titleOffset + directionFactor * axis.offset,
14826 labelOffsetPadded, // #3027
14827 hasData && tickPositions.length && tickSize ?
14828 tickSize[0] + directionFactor * axis.offset :
14829 0 // #4866
14830 );
14831
14832 // Decide the clipping needed to keep the graph inside
14833 // the plot area and axis lines
14834 clip = options.offset ?
14835 0 :
14836 Math.floor(axis.axisLine.strokeWidth() / 2) * 2; // #4308, #4371
14837 clipOffset[invertedSide] = Math.max(clipOffset[invertedSide], clip);
14838 },
14839
14840 /**
14841 * Internal function to get the path for the axis line. Extended for polar
14842 * charts.
14843 *
14844 * @param {Number} lineWidth
14845 * The line width in pixels.
14846 * @return {Array}
14847 * The SVG path definition in array form.
14848 */
14849 getLinePath: function(lineWidth) {
14850 var chart = this.chart,
14851 opposite = this.opposite,
14852 offset = this.offset,
14853 horiz = this.horiz,
14854 lineLeft = this.left + (opposite ? this.width : 0) + offset,
14855 lineTop = chart.chartHeight - this.bottom -
14856 (opposite ? this.height : 0) + offset;
14857
14858 if (opposite) {
14859 lineWidth *= -1; // crispify the other way - #1480, #1687
14860 }
14861
14862 return chart.renderer
14863 .crispLine([
14864 'M',
14865 horiz ?
14866 this.left :
14867 lineLeft,
14868 horiz ?
14869 lineTop :
14870 this.top,
14871 'L',
14872 horiz ?
14873 chart.chartWidth - this.right :
14874 lineLeft,
14875 horiz ?
14876 lineTop :
14877 chart.chartHeight - this.bottom
14878 ], lineWidth);
14879 },
14880
14881 /**
14882 * Render the axis line. Called internally when rendering and redrawing the
14883 * axis.
14884 */
14885 renderLine: function() {
14886 if (!this.axisLine) {
14887 this.axisLine = this.chart.renderer.path()
14888 .addClass('highcharts-axis-line')
14889 .add(this.axisGroup);
14890
14891
14892 this.axisLine.attr({
14893 stroke: this.options.lineColor,
14894 'stroke-width': this.options.lineWidth,
14895 zIndex: 7
14896 });
14897
14898 }
14899 },
14900
14901 /**
14902 * Position the axis title.
14903 *
14904 * @private
14905 *
14906 * @return {Object}
14907 * X and Y positions for the title.
14908 */
14909 getTitlePosition: function() {
14910 // compute anchor points for each of the title align options
14911 var horiz = this.horiz,
14912 axisLeft = this.left,
14913 axisTop = this.top,
14914 axisLength = this.len,
14915 axisTitleOptions = this.options.title,
14916 margin = horiz ? axisLeft : axisTop,
14917 opposite = this.opposite,
14918 offset = this.offset,
14919 xOption = axisTitleOptions.x || 0,
14920 yOption = axisTitleOptions.y || 0,
14921 axisTitle = this.axisTitle,
14922 fontMetrics = this.chart.renderer.fontMetrics(
14923 axisTitleOptions.style && axisTitleOptions.style.fontSize,
14924 axisTitle
14925 ),
14926 // The part of a multiline text that is below the baseline of the
14927 // first line. Subtract 1 to preserve pixel-perfectness from the
14928 // old behaviour (v5.0.12), where only one line was allowed.
14929 textHeightOvershoot = Math.max(
14930 axisTitle.getBBox(null, 0).height - fontMetrics.h - 1,
14931 0
14932 ),
14933
14934 // the position in the length direction of the axis
14935 alongAxis = {
14936 low: margin + (horiz ? 0 : axisLength),
14937 middle: margin + axisLength / 2,
14938 high: margin + (horiz ? axisLength : 0)
14939 }[axisTitleOptions.align],
14940
14941 // the position in the perpendicular direction of the axis
14942 offAxis = (horiz ? axisTop + this.height : axisLeft) +
14943 (horiz ? 1 : -1) * // horizontal axis reverses the margin
14944 (opposite ? -1 : 1) * // so does opposite axes
14945 this.axisTitleMargin + [-textHeightOvershoot, // top
14946 textHeightOvershoot, // right
14947 fontMetrics.f, // bottom
14948 -textHeightOvershoot // left
14949 ][this.side];
14950
14951
14952 return {
14953 x: horiz ?
14954 alongAxis + xOption : offAxis + (opposite ? this.width : 0) + offset + xOption,
14955 y: horiz ?
14956 offAxis + yOption - (opposite ? this.height : 0) + offset : alongAxis + yOption
14957 };
14958 },
14959
14960 /**
14961 * Render a minor tick into the given position. If a minor tick already
14962 * exists in this position, move it.
14963 *
14964 * @param {number} pos
14965 * The position in axis values.
14966 */
14967 renderMinorTick: function(pos) {
14968 var slideInTicks = this.chart.hasRendered && isNumber(this.oldMin),
14969 minorTicks = this.minorTicks;
14970
14971 if (!minorTicks[pos]) {
14972 minorTicks[pos] = new Tick(this, pos, 'minor');
14973 }
14974
14975 // Render new ticks in old position
14976 if (slideInTicks && minorTicks[pos].isNew) {
14977 minorTicks[pos].render(null, true);
14978 }
14979
14980 minorTicks[pos].render(null, false, 1);
14981 },
14982
14983 /**
14984 * Render a major tick into the given position. If a tick already exists
14985 * in this position, move it.
14986 *
14987 * @param {number} pos
14988 * The position in axis values.
14989 * @param {number} i
14990 * The tick index.
14991 */
14992 renderTick: function(pos, i) {
14993 var isLinked = this.isLinked,
14994 ticks = this.ticks,
14995 slideInTicks = this.chart.hasRendered && isNumber(this.oldMin);
14996
14997 // Linked axes need an extra check to find out if
14998 if (!isLinked || (pos >= this.min && pos <= this.max)) {
14999
15000 if (!ticks[pos]) {
15001 ticks[pos] = new Tick(this, pos);
15002 }
15003
15004 // render new ticks in old position
15005 if (slideInTicks && ticks[pos].isNew) {
15006 ticks[pos].render(i, true, 0.1);
15007 }
15008
15009 ticks[pos].render(i);
15010 }
15011 },
15012
15013 /**
15014 * Render the axis.
15015 *
15016 * @private
15017 */
15018 render: function() {
15019 var axis = this,
15020 chart = axis.chart,
15021 renderer = chart.renderer,
15022 options = axis.options,
15023 isLog = axis.isLog,
15024 lin2log = axis.lin2log,
15025 isLinked = axis.isLinked,
15026 tickPositions = axis.tickPositions,
15027 axisTitle = axis.axisTitle,
15028 ticks = axis.ticks,
15029 minorTicks = axis.minorTicks,
15030 alternateBands = axis.alternateBands,
15031 stackLabelOptions = options.stackLabels,
15032 alternateGridColor = options.alternateGridColor,
15033 tickmarkOffset = axis.tickmarkOffset,
15034 axisLine = axis.axisLine,
15035 showAxis = axis.showAxis,
15036 animation = animObject(renderer.globalAnimation),
15037 from,
15038 to;
15039
15040 // Reset
15041 axis.labelEdge.length = 0;
15042 axis.overlap = false;
15043
15044 // Mark all elements inActive before we go over and mark the active ones
15045 each([ticks, minorTicks, alternateBands], function(coll) {
15046 objectEach(coll, function(tick) {
15047 tick.isActive = false;
15048 });
15049 });
15050
15051 // If the series has data draw the ticks. Else only the line and title
15052 if (axis.hasData() || isLinked) {
15053
15054 // minor ticks
15055 if (axis.minorTickInterval && !axis.categories) {
15056 each(axis.getMinorTickPositions(), function(pos) {
15057 axis.renderMinorTick(pos);
15058 });
15059 }
15060
15061 // Major ticks. Pull out the first item and render it last so that
15062 // we can get the position of the neighbour label. #808.
15063 if (tickPositions.length) { // #1300
15064 each(tickPositions, function(pos, i) {
15065 axis.renderTick(pos, i);
15066 });
15067 // In a categorized axis, the tick marks are displayed
15068 // between labels. So we need to add a tick mark and
15069 // grid line at the left edge of the X axis.
15070 if (tickmarkOffset && (axis.min === 0 || axis.single)) {
15071 if (!ticks[-1]) {
15072 ticks[-1] = new Tick(axis, -1, null, true);
15073 }
15074 ticks[-1].render(-1);
15075 }
15076
15077 }
15078
15079 // alternate grid color
15080 if (alternateGridColor) {
15081 each(tickPositions, function(pos, i) {
15082 to = tickPositions[i + 1] !== undefined ?
15083 tickPositions[i + 1] + tickmarkOffset :
15084 axis.max - tickmarkOffset;
15085
15086 if (
15087 i % 2 === 0 &&
15088 pos < axis.max &&
15089 to <= axis.max + (
15090 chart.polar ?
15091 -tickmarkOffset :
15092 tickmarkOffset
15093 )
15094 ) { // #2248, #4660
15095 if (!alternateBands[pos]) {
15096 alternateBands[pos] = new H.PlotLineOrBand(axis);
15097 }
15098 from = pos + tickmarkOffset; // #949
15099 alternateBands[pos].options = {
15100 from: isLog ? lin2log(from) : from,
15101 to: isLog ? lin2log(to) : to,
15102 color: alternateGridColor
15103 };
15104 alternateBands[pos].render();
15105 alternateBands[pos].isActive = true;
15106 }
15107 });
15108 }
15109
15110 // custom plot lines and bands
15111 if (!axis._addedPlotLB) { // only first time
15112 each(
15113 (options.plotLines || []).concat(options.plotBands || []),
15114 function(plotLineOptions) {
15115 axis.addPlotBandOrLine(plotLineOptions);
15116 }
15117 );
15118 axis._addedPlotLB = true;
15119 }
15120
15121 } // end if hasData
15122
15123 // Remove inactive ticks
15124 each([ticks, minorTicks, alternateBands], function(coll) {
15125 var i,
15126 forDestruction = [],
15127 delay = animation.duration,
15128 destroyInactiveItems = function() {
15129 i = forDestruction.length;
15130 while (i--) {
15131 // When resizing rapidly, the same items
15132 // may be destroyed in different timeouts,
15133 // or the may be reactivated
15134 if (
15135 coll[forDestruction[i]] &&
15136 !coll[forDestruction[i]].isActive
15137 ) {
15138 coll[forDestruction[i]].destroy();
15139 delete coll[forDestruction[i]];
15140 }
15141 }
15142
15143 };
15144
15145 objectEach(coll, function(tick, pos) {
15146 if (!tick.isActive) {
15147 // Render to zero opacity
15148 tick.render(pos, false, 0);
15149 tick.isActive = false;
15150 forDestruction.push(pos);
15151 }
15152 });
15153
15154 // When the objects are finished fading out, destroy them
15155 syncTimeout(
15156 destroyInactiveItems,
15157 coll === alternateBands ||
15158 !chart.hasRendered ||
15159 !delay ?
15160 0 :
15161 delay
15162 );
15163 });
15164
15165 // Set the axis line path
15166 if (axisLine) {
15167 axisLine[axisLine.isPlaced ? 'animate' : 'attr']({
15168 d: this.getLinePath(axisLine.strokeWidth())
15169 });
15170 axisLine.isPlaced = true;
15171
15172 // Show or hide the line depending on options.showEmpty
15173 axisLine[showAxis ? 'show' : 'hide'](true);
15174 }
15175
15176 if (axisTitle && showAxis) {
15177 var titleXy = axis.getTitlePosition();
15178 if (isNumber(titleXy.y)) {
15179 axisTitle[axisTitle.isNew ? 'attr' : 'animate'](titleXy);
15180 axisTitle.isNew = false;
15181 } else {
15182 axisTitle.attr('y', -9999);
15183 axisTitle.isNew = true;
15184 }
15185 }
15186
15187 // Stacked totals:
15188 if (stackLabelOptions && stackLabelOptions.enabled) {
15189 axis.renderStackTotals();
15190 }
15191 // End stacked totals
15192
15193 axis.isDirty = false;
15194 },
15195
15196 /**
15197 * Redraw the axis to reflect changes in the data or axis extremes. Called
15198 * internally from {@link Chart#redraw}.
15199 *
15200 * @private
15201 */
15202 redraw: function() {
15203
15204 if (this.visible) {
15205 // render the axis
15206 this.render();
15207
15208 // move plot lines and bands
15209 each(this.plotLinesAndBands, function(plotLine) {
15210 plotLine.render();
15211 });
15212 }
15213
15214 // mark associated series as dirty and ready for redraw
15215 each(this.series, function(series) {
15216 series.isDirty = true;
15217 });
15218
15219 },
15220
15221 // Properties to survive after destroy, needed for Axis.update (#4317,
15222 // #5773, #5881).
15223 keepProps: ['extKey', 'hcEvents', 'names', 'series', 'userMax', 'userMin'],
15224
15225 /**
15226 * Destroys an Axis instance. See {@link Axis#remove} for the API endpoint
15227 * to fully remove the axis.
15228 *
15229 * @private
15230 * @param {Boolean} keepEvents
15231 * Whether to preserve events, used internally in Axis.update.
15232 */
15233 destroy: function(keepEvents) {
15234 var axis = this,
15235 stacks = axis.stacks,
15236 plotLinesAndBands = axis.plotLinesAndBands,
15237 plotGroup,
15238 i;
15239
15240 // Remove the events
15241 if (!keepEvents) {
15242 removeEvent(axis);
15243 }
15244
15245 // Destroy each stack total
15246 objectEach(stacks, function(stack, stackKey) {
15247 destroyObjectProperties(stack);
15248
15249 stacks[stackKey] = null;
15250 });
15251
15252 // Destroy collections
15253 each(
15254 [axis.ticks, axis.minorTicks, axis.alternateBands],
15255 function(coll) {
15256 destroyObjectProperties(coll);
15257 }
15258 );
15259 if (plotLinesAndBands) {
15260 i = plotLinesAndBands.length;
15261 while (i--) { // #1975
15262 plotLinesAndBands[i].destroy();
15263 }
15264 }
15265
15266 // Destroy local variables
15267 each(
15268 ['stackTotalGroup', 'axisLine', 'axisTitle', 'axisGroup',
15269 'gridGroup', 'labelGroup', 'cross'
15270 ],
15271 function(prop) {
15272 if (axis[prop]) {
15273 axis[prop] = axis[prop].destroy();
15274 }
15275 }
15276 );
15277
15278 // Destroy each generated group for plotlines and plotbands
15279 for (plotGroup in axis.plotLinesAndBandsGroups) {
15280 axis.plotLinesAndBandsGroups[plotGroup] =
15281 axis.plotLinesAndBandsGroups[plotGroup].destroy();
15282 }
15283
15284 // Delete all properties and fall back to the prototype.
15285 objectEach(axis, function(val, key) {
15286 if (inArray(key, axis.keepProps) === -1) {
15287 delete axis[key];
15288 }
15289 });
15290 },
15291
15292 /**
15293 * Internal function to draw a crosshair.
15294 *
15295 * @param {PointerEvent} [e]
15296 * The event arguments from the modified pointer event, extended
15297 * with `chartX` and `chartY`
15298 * @param {Point} [point]
15299 * The Point object if the crosshair snaps to points.
15300 */
15301 drawCrosshair: function(e, point) {
15302
15303 var path,
15304 options = this.crosshair,
15305 snap = pick(options.snap, true),
15306 pos,
15307 categorized,
15308 graphic = this.cross;
15309
15310 // Use last available event when updating non-snapped crosshairs without
15311 // mouse interaction (#5287)
15312 if (!e) {
15313 e = this.cross && this.cross.e;
15314 }
15315
15316 if (
15317 // Disabled in options
15318 !this.crosshair ||
15319 // Snap
15320 ((defined(point) || !snap) === false)
15321 ) {
15322 this.hideCrosshair();
15323 } else {
15324
15325 // Get the path
15326 if (!snap) {
15327 pos = e &&
15328 (
15329 this.horiz ?
15330 e.chartX - this.pos :
15331 this.len - e.chartY + this.pos
15332 );
15333 } else if (defined(point)) {
15334 // #3834
15335 pos = this.isXAxis ? point.plotX : this.len - point.plotY;
15336 }
15337
15338 if (defined(pos)) {
15339 path = this.getPlotLinePath(
15340 // First argument, value, only used on radial
15341 point && (this.isXAxis ?
15342 point.x :
15343 pick(point.stackY, point.y)
15344 ),
15345 null,
15346 null,
15347 null,
15348 pos // Translated position
15349 ) || null; // #3189
15350 }
15351
15352 if (!defined(path)) {
15353 this.hideCrosshair();
15354 return;
15355 }
15356
15357 categorized = this.categories && !this.isRadial;
15358
15359 // Draw the cross
15360 if (!graphic) {
15361 this.cross = graphic = this.chart.renderer
15362 .path()
15363 .addClass(
15364 'highcharts-crosshair highcharts-crosshair-' +
15365 (categorized ? 'category ' : 'thin ') +
15366 options.className
15367 )
15368 .attr({
15369 zIndex: pick(options.zIndex, 2)
15370 })
15371 .add();
15372
15373
15374 // Presentational attributes
15375 graphic.attr({
15376 'stroke': options.color ||
15377 (
15378 categorized ?
15379 color('#ccd6eb')
15380 .setOpacity(0.25).get() :
15381 '#cccccc'
15382 ),
15383 'stroke-width': pick(options.width, 1)
15384 }).css({
15385 'pointer-events': 'none'
15386 });
15387 if (options.dashStyle) {
15388 graphic.attr({
15389 dashstyle: options.dashStyle
15390 });
15391 }
15392
15393
15394 }
15395
15396 graphic.show().attr({
15397 d: path
15398 });
15399
15400 if (categorized && !options.width) {
15401 graphic.attr({
15402 'stroke-width': this.transA
15403 });
15404 }
15405 this.cross.e = e;
15406 }
15407 },
15408
15409 /**
15410 * Hide the crosshair if visible.
15411 */
15412 hideCrosshair: function() {
15413 if (this.cross) {
15414 this.cross.hide();
15415 }
15416 }
15417 }); // end Axis
15418
15419 H.Axis = Axis;
15420 return Axis;
15421 }(Highcharts));
15422 (function(H) {
15423 /**
15424 * (c) 2010-2017 Torstein Honsi
15425 *
15426 * License: www.highcharts.com/license
15427 */
15428 var Axis = H.Axis,
15429 Date = H.Date,
15430 dateFormat = H.dateFormat,
15431 defaultOptions = H.defaultOptions,
15432 defined = H.defined,
15433 each = H.each,
15434 extend = H.extend,
15435 getMagnitude = H.getMagnitude,
15436 getTZOffset = H.getTZOffset,
15437 normalizeTickInterval = H.normalizeTickInterval,
15438 pick = H.pick,
15439 timeUnits = H.timeUnits;
15440 /**
15441 * Set the tick positions to a time unit that makes sense, for example
15442 * on the first of each month or on every Monday. Return an array
15443 * with the time positions. Used in datetime axes as well as for grouping
15444 * data on a datetime axis.
15445 *
15446 * @param {Object} normalizedInterval The interval in axis values (ms) and the count
15447 * @param {Number} min The minimum in axis values
15448 * @param {Number} max The maximum in axis values
15449 * @param {Number} startOfWeek
15450 */
15451 Axis.prototype.getTimeTicks = function(normalizedInterval, min, max, startOfWeek) {
15452 var tickPositions = [],
15453 i,
15454 higherRanks = {},
15455 useUTC = defaultOptions.global.useUTC,
15456 minYear, // used in months and years as a basis for Date.UTC()
15457 // When crossing DST, use the max. Resolves #6278.
15458 minDate = new Date(min - Math.max(getTZOffset(min), getTZOffset(max))),
15459 makeTime = Date.hcMakeTime,
15460 interval = normalizedInterval.unitRange,
15461 count = normalizedInterval.count,
15462 baseOffset, // #6797
15463 variableDayLength;
15464
15465 if (defined(min)) { // #1300
15466 minDate[Date.hcSetMilliseconds](interval >= timeUnits.second ? 0 : // #3935
15467 count * Math.floor(minDate.getMilliseconds() / count)); // #3652, #3654
15468
15469 if (interval >= timeUnits.second) { // second
15470 minDate[Date.hcSetSeconds](interval >= timeUnits.minute ? 0 : // #3935
15471 count * Math.floor(minDate.getSeconds() / count));
15472 }
15473
15474 if (interval >= timeUnits.minute) { // minute
15475 minDate[Date.hcSetMinutes](interval >= timeUnits.hour ? 0 :
15476 count * Math.floor(minDate[Date.hcGetMinutes]() / count));
15477 }
15478
15479 if (interval >= timeUnits.hour) { // hour
15480 minDate[Date.hcSetHours](interval >= timeUnits.day ? 0 :
15481 count * Math.floor(minDate[Date.hcGetHours]() / count));
15482 }
15483
15484 if (interval >= timeUnits.day) { // day
15485 minDate[Date.hcSetDate](interval >= timeUnits.month ? 1 :
15486 count * Math.floor(minDate[Date.hcGetDate]() / count));
15487 }
15488
15489 if (interval >= timeUnits.month) { // month
15490 minDate[Date.hcSetMonth](interval >= timeUnits.year ? 0 :
15491 count * Math.floor(minDate[Date.hcGetMonth]() / count));
15492 minYear = minDate[Date.hcGetFullYear]();
15493 }
15494
15495 if (interval >= timeUnits.year) { // year
15496 minYear -= minYear % count;
15497 minDate[Date.hcSetFullYear](minYear);
15498 }
15499
15500 // week is a special case that runs outside the hierarchy
15501 if (interval === timeUnits.week) {
15502 // get start of current week, independent of count
15503 minDate[Date.hcSetDate](minDate[Date.hcGetDate]() - minDate[Date.hcGetDay]() +
15504 pick(startOfWeek, 1));
15505 }
15506
15507
15508 // Get basics for variable time spans
15509 minYear = minDate[Date.hcGetFullYear]();
15510 var minMonth = minDate[Date.hcGetMonth](),
15511 minDateDate = minDate[Date.hcGetDate](),
15512 minHours = minDate[Date.hcGetHours]();
15513
15514
15515 // Handle local timezone offset
15516 if (Date.hcTimezoneOffset || Date.hcGetTimezoneOffset) {
15517
15518 // Detect whether we need to take the DST crossover into
15519 // consideration. If we're crossing over DST, the day length may be
15520 // 23h or 25h and we need to compute the exact clock time for each
15521 // tick instead of just adding hours. This comes at a cost, so first
15522 // we found out if it is needed. #4951.
15523 variableDayLength =
15524 (!useUTC || !!Date.hcGetTimezoneOffset) &&
15525 (
15526 // Long range, assume we're crossing over.
15527 max - min > 4 * timeUnits.month ||
15528 // Short range, check if min and max are in different time
15529 // zones.
15530 getTZOffset(min) !== getTZOffset(max)
15531 );
15532
15533 // Adjust minDate to the offset date
15534 minDate = minDate.getTime();
15535 baseOffset = getTZOffset(minDate);
15536 minDate = new Date(minDate + baseOffset);
15537 }
15538
15539
15540 // Iterate and add tick positions at appropriate values
15541 var time = minDate.getTime();
15542 i = 1;
15543 while (time < max) {
15544 tickPositions.push(time);
15545
15546 // if the interval is years, use Date.UTC to increase years
15547 if (interval === timeUnits.year) {
15548 time = makeTime(minYear + i * count, 0);
15549
15550 // if the interval is months, use Date.UTC to increase months
15551 } else if (interval === timeUnits.month) {
15552 time = makeTime(minYear, minMonth + i * count);
15553
15554 // if we're using global time, the interval is not fixed as it jumps
15555 // one hour at the DST crossover
15556 } else if (
15557 variableDayLength &&
15558 (interval === timeUnits.day || interval === timeUnits.week)
15559 ) {
15560 time = makeTime(minYear, minMonth, minDateDate +
15561 i * count * (interval === timeUnits.day ? 1 : 7));
15562
15563 } else if (variableDayLength && interval === timeUnits.hour) {
15564 // corrected by the start date time zone offset (baseOffset)
15565 // to hide duplicated label (#6797)
15566 time = makeTime(minYear, minMonth, minDateDate, minHours +
15567 i * count, 0, 0, baseOffset) - baseOffset;
15568
15569 // else, the interval is fixed and we use simple addition
15570 } else {
15571 time += interval * count;
15572 }
15573
15574 i++;
15575 }
15576
15577 // push the last time
15578 tickPositions.push(time);
15579
15580
15581 // Handle higher ranks. Mark new days if the time is on midnight
15582 // (#950, #1649, #1760, #3349). Use a reasonable dropout threshold to
15583 // prevent looping over dense data grouping (#6156).
15584 if (interval <= timeUnits.hour && tickPositions.length < 10000) {
15585 each(tickPositions, function(time) {
15586 if (
15587 // Speed optimization, no need to run dateFormat unless
15588 // we're on a full or half hour
15589 time % 1800000 === 0 &&
15590 // Check for local or global midnight
15591 dateFormat('%H%M%S%L', time) === '000000000'
15592 ) {
15593 higherRanks[time] = 'day';
15594 }
15595 });
15596 }
15597 }
15598
15599
15600 // record information on the chosen unit - for dynamic label formatter
15601 tickPositions.info = extend(normalizedInterval, {
15602 higherRanks: higherRanks,
15603 totalRange: interval * count
15604 });
15605
15606 return tickPositions;
15607 };
15608
15609 /**
15610 * Get a normalized tick interval for dates. Returns a configuration object with
15611 * unit range (interval), count and name. Used to prepare data for getTimeTicks.
15612 * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs
15613 * of segments in stock charts, the normalizing logic was extracted in order to
15614 * prevent it for running over again for each segment having the same interval.
15615 * #662, #697.
15616 */
15617 Axis.prototype.normalizeTimeTickInterval = function(tickInterval, unitsOption) {
15618 var units = unitsOption || [
15619 [
15620 'millisecond', // unit name
15621 [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
15622 ],
15623 [
15624 'second', [1, 2, 5, 10, 15, 30]
15625 ],
15626 [
15627 'minute', [1, 2, 5, 10, 15, 30]
15628 ],
15629 [
15630 'hour', [1, 2, 3, 4, 6, 8, 12]
15631 ],
15632 [
15633 'day', [1, 2]
15634 ],
15635 [
15636 'week', [1, 2]
15637 ],
15638 [
15639 'month', [1, 2, 3, 4, 6]
15640 ],
15641 [
15642 'year',
15643 null
15644 ]
15645 ],
15646 unit = units[units.length - 1], // default unit is years
15647 interval = timeUnits[unit[0]],
15648 multiples = unit[1],
15649 count,
15650 i;
15651
15652 // loop through the units to find the one that best fits the tickInterval
15653 for (i = 0; i < units.length; i++) {
15654 unit = units[i];
15655 interval = timeUnits[unit[0]];
15656 multiples = unit[1];
15657
15658
15659 if (units[i + 1]) {
15660 // lessThan is in the middle between the highest multiple and the next unit.
15661 var lessThan = (interval * multiples[multiples.length - 1] +
15662 timeUnits[units[i + 1][0]]) / 2;
15663
15664 // break and keep the current unit
15665 if (tickInterval <= lessThan) {
15666 break;
15667 }
15668 }
15669 }
15670
15671 // prevent 2.5 years intervals, though 25, 250 etc. are allowed
15672 if (interval === timeUnits.year && tickInterval < 5 * interval) {
15673 multiples = [1, 2, 5];
15674 }
15675
15676 // get the count
15677 count = normalizeTickInterval(
15678 tickInterval / interval,
15679 multiples,
15680 unit[0] === 'year' ? Math.max(getMagnitude(tickInterval / interval), 1) : 1 // #1913, #2360
15681 );
15682
15683 return {
15684 unitRange: interval,
15685 count: count,
15686 unitName: unit[0]
15687 };
15688 };
15689
15690 }(Highcharts));
15691 (function(H) {
15692 /**
15693 * (c) 2010-2017 Torstein Honsi
15694 *
15695 * License: www.highcharts.com/license
15696 */
15697 var Axis = H.Axis,
15698 getMagnitude = H.getMagnitude,
15699 map = H.map,
15700 normalizeTickInterval = H.normalizeTickInterval,
15701 pick = H.pick;
15702 /**
15703 * Methods defined on the Axis prototype
15704 */
15705
15706 /**
15707 * Set the tick positions of a logarithmic axis
15708 */
15709 Axis.prototype.getLogTickPositions = function(interval, min, max, minor) {
15710 var axis = this,
15711 options = axis.options,
15712 axisLength = axis.len,
15713 lin2log = axis.lin2log,
15714 log2lin = axis.log2lin,
15715 // Since we use this method for both major and minor ticks,
15716 // use a local variable and return the result
15717 positions = [];
15718
15719 // Reset
15720 if (!minor) {
15721 axis._minorAutoInterval = null;
15722 }
15723
15724 // First case: All ticks fall on whole logarithms: 1, 10, 100 etc.
15725 if (interval >= 0.5) {
15726 interval = Math.round(interval);
15727 positions = axis.getLinearTickPositions(interval, min, max);
15728
15729 // Second case: We need intermediary ticks. For example
15730 // 1, 2, 4, 6, 8, 10, 20, 40 etc.
15731 } else if (interval >= 0.08) {
15732 var roundedMin = Math.floor(min),
15733 intermediate,
15734 i,
15735 j,
15736 len,
15737 pos,
15738 lastPos,
15739 break2;
15740
15741 if (interval > 0.3) {
15742 intermediate = [1, 2, 4];
15743 } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc
15744 intermediate = [1, 2, 4, 6, 8];
15745 } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
15746 intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
15747 }
15748
15749 for (i = roundedMin; i < max + 1 && !break2; i++) {
15750 len = intermediate.length;
15751 for (j = 0; j < len && !break2; j++) {
15752 pos = log2lin(lin2log(i) * intermediate[j]);
15753 if (pos > min && (!minor || lastPos <= max) && lastPos !== undefined) { // #1670, lastPos is #3113
15754 positions.push(lastPos);
15755 }
15756
15757 if (lastPos > max) {
15758 break2 = true;
15759 }
15760 lastPos = pos;
15761 }
15762 }
15763
15764 // Third case: We are so deep in between whole logarithmic values that
15765 // we might as well handle the tick positions like a linear axis. For
15766 // example 1.01, 1.02, 1.03, 1.04.
15767 } else {
15768 var realMin = lin2log(min),
15769 realMax = lin2log(max),
15770 tickIntervalOption = minor ?
15771 this.getMinorTickInterval() :
15772 options.tickInterval,
15773 filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption,
15774 tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1),
15775 totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength;
15776
15777 interval = pick(
15778 filteredTickIntervalOption,
15779 axis._minorAutoInterval,
15780 (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)
15781 );
15782
15783 interval = normalizeTickInterval(
15784 interval,
15785 null,
15786 getMagnitude(interval)
15787 );
15788
15789 positions = map(axis.getLinearTickPositions(
15790 interval,
15791 realMin,
15792 realMax
15793 ), log2lin);
15794
15795 if (!minor) {
15796 axis._minorAutoInterval = interval / 5;
15797 }
15798 }
15799
15800 // Set the axis-level tickInterval variable
15801 if (!minor) {
15802 axis.tickInterval = interval;
15803 }
15804 return positions;
15805 };
15806
15807 Axis.prototype.log2lin = function(num) {
15808 return Math.log(num) / Math.LN10;
15809 };
15810
15811 Axis.prototype.lin2log = function(num) {
15812 return Math.pow(10, num);
15813 };
15814
15815 }(Highcharts));
15816 (function(H, Axis) {
15817 /**
15818 * (c) 2010-2017 Torstein Honsi
15819 *
15820 * License: www.highcharts.com/license
15821 */
15822 var arrayMax = H.arrayMax,
15823 arrayMin = H.arrayMin,
15824 defined = H.defined,
15825 destroyObjectProperties = H.destroyObjectProperties,
15826 each = H.each,
15827 erase = H.erase,
15828 merge = H.merge,
15829 pick = H.pick;
15830 /*
15831 * The object wrapper for plot lines and plot bands
15832 * @param {Object} options
15833 */
15834 H.PlotLineOrBand = function(axis, options) {
15835 this.axis = axis;
15836
15837 if (options) {
15838 this.options = options;
15839 this.id = options.id;
15840 }
15841 };
15842
15843 H.PlotLineOrBand.prototype = {
15844
15845 /**
15846 * Render the plot line or plot band. If it is already existing,
15847 * move it.
15848 */
15849 render: function() {
15850 var plotLine = this,
15851 axis = plotLine.axis,
15852 horiz = axis.horiz,
15853 options = plotLine.options,
15854 optionsLabel = options.label,
15855 label = plotLine.label,
15856 to = options.to,
15857 from = options.from,
15858 value = options.value,
15859 isBand = defined(from) && defined(to),
15860 isLine = defined(value),
15861 svgElem = plotLine.svgElem,
15862 isNew = !svgElem,
15863 path = [],
15864 color = options.color,
15865 zIndex = pick(options.zIndex, 0),
15866 events = options.events,
15867 attribs = {
15868 'class': 'highcharts-plot-' + (isBand ? 'band ' : 'line ') +
15869 (options.className || '')
15870 },
15871 groupAttribs = {},
15872 renderer = axis.chart.renderer,
15873 groupName = isBand ? 'bands' : 'lines',
15874 group,
15875 log2lin = axis.log2lin;
15876
15877 // logarithmic conversion
15878 if (axis.isLog) {
15879 from = log2lin(from);
15880 to = log2lin(to);
15881 value = log2lin(value);
15882 }
15883
15884
15885 // Set the presentational attributes
15886 if (isLine) {
15887 attribs = {
15888 stroke: color,
15889 'stroke-width': options.width
15890 };
15891 if (options.dashStyle) {
15892 attribs.dashstyle = options.dashStyle;
15893 }
15894
15895 } else if (isBand) { // plot band
15896 if (color) {
15897 attribs.fill = color;
15898 }
15899 if (options.borderWidth) {
15900 attribs.stroke = options.borderColor;
15901 attribs['stroke-width'] = options.borderWidth;
15902 }
15903 }
15904
15905
15906 // Grouping and zIndex
15907 groupAttribs.zIndex = zIndex;
15908 groupName += '-' + zIndex;
15909
15910 group = axis.plotLinesAndBandsGroups[groupName];
15911 if (!group) {
15912 axis.plotLinesAndBandsGroups[groupName] = group =
15913 renderer.g('plot-' + groupName)
15914 .attr(groupAttribs).add();
15915 }
15916
15917 // Create the path
15918 if (isNew) {
15919 plotLine.svgElem = svgElem =
15920 renderer
15921 .path()
15922 .attr(attribs).add(group);
15923 }
15924
15925
15926 // Set the path or return
15927 if (isLine) {
15928 path = axis.getPlotLinePath(value, svgElem.strokeWidth());
15929 } else if (isBand) { // plot band
15930 path = axis.getPlotBandPath(from, to, options);
15931 } else {
15932 return;
15933 }
15934
15935
15936 // common for lines and bands
15937 if (isNew && path && path.length) {
15938 svgElem.attr({
15939 d: path
15940 });
15941
15942 // events
15943 if (events) {
15944 H.objectEach(events, function(event, eventType) {
15945 svgElem.on(eventType, function(e) {
15946 events[eventType].apply(plotLine, [e]);
15947 });
15948 });
15949 }
15950 } else if (svgElem) {
15951 if (path) {
15952 svgElem.show();
15953 svgElem.animate({
15954 d: path
15955 });
15956 } else {
15957 svgElem.hide();
15958 if (label) {
15959 plotLine.label = label = label.destroy();
15960 }
15961 }
15962 }
15963
15964 // the plot band/line label
15965 if (
15966 optionsLabel &&
15967 defined(optionsLabel.text) &&
15968 path &&
15969 path.length &&
15970 axis.width > 0 &&
15971 axis.height > 0 &&
15972 !path.flat
15973 ) {
15974 // apply defaults
15975 optionsLabel = merge({
15976 align: horiz && isBand && 'center',
15977 x: horiz ? !isBand && 4 : 10,
15978 verticalAlign: !horiz && isBand && 'middle',
15979 y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4,
15980 rotation: horiz && !isBand && 90
15981 }, optionsLabel);
15982
15983 this.renderLabel(optionsLabel, path, isBand, zIndex);
15984
15985 } else if (label) { // move out of sight
15986 label.hide();
15987 }
15988
15989 // chainable
15990 return plotLine;
15991 },
15992
15993 /**
15994 * Render and align label for plot line or band.
15995 */
15996 renderLabel: function(optionsLabel, path, isBand, zIndex) {
15997 var plotLine = this,
15998 label = plotLine.label,
15999 renderer = plotLine.axis.chart.renderer,
16000 attribs,
16001 xBounds,
16002 yBounds,
16003 x,
16004 y;
16005
16006 // add the SVG element
16007 if (!label) {
16008 attribs = {
16009 align: optionsLabel.textAlign || optionsLabel.align,
16010 rotation: optionsLabel.rotation,
16011 'class': 'highcharts-plot-' + (isBand ? 'band' : 'line') +
16012 '-label ' + (optionsLabel.className || '')
16013 };
16014
16015 attribs.zIndex = zIndex;
16016
16017 plotLine.label = label = renderer.text(
16018 optionsLabel.text,
16019 0,
16020 0,
16021 optionsLabel.useHTML
16022 )
16023 .attr(attribs)
16024 .add();
16025
16026
16027 label.css(optionsLabel.style);
16028
16029 }
16030
16031 // get the bounding box and align the label
16032 // #3000 changed to better handle choice between plotband or plotline
16033 xBounds = path.xBounds || [path[1], path[4], (isBand ? path[6] : path[1])];
16034 yBounds = path.yBounds || [path[2], path[5], (isBand ? path[7] : path[2])];
16035
16036 x = arrayMin(xBounds);
16037 y = arrayMin(yBounds);
16038
16039 label.align(optionsLabel, false, {
16040 x: x,
16041 y: y,
16042 width: arrayMax(xBounds) - x,
16043 height: arrayMax(yBounds) - y
16044 });
16045 label.show();
16046 },
16047
16048 /**
16049 * Remove the plot line or band
16050 */
16051 destroy: function() {
16052 // remove it from the lookup
16053 erase(this.axis.plotLinesAndBands, this);
16054
16055 delete this.axis;
16056 destroyObjectProperties(this);
16057 }
16058 };
16059
16060 /**
16061 * Object with members for extending the Axis prototype
16062 * @todo Extend directly instead of adding object to Highcharts first
16063 */
16064
16065 H.extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {
16066
16067 /**
16068 * Internal function to create the SVG path definition for a plot band.
16069 *
16070 * @param {Number} from
16071 * The axis value to start from.
16072 * @param {Number} to
16073 * The axis value to end on.
16074 *
16075 * @return {Array.<String|Number>}
16076 * The SVG path definition in array form.
16077 */
16078 getPlotBandPath: function(from, to) {
16079 var toPath = this.getPlotLinePath(to, null, null, true),
16080 path = this.getPlotLinePath(from, null, null, true),
16081 result = [],
16082 i,
16083 // #4964 check if chart is inverted or plotband is on yAxis
16084 horiz = this.horiz,
16085 plus = 1,
16086 flat,
16087 outside =
16088 (from < this.min && to < this.min) ||
16089 (from > this.max && to > this.max);
16090
16091 if (path && toPath) {
16092
16093 // Flat paths don't need labels (#3836)
16094 if (outside) {
16095 flat = path.toString() === toPath.toString();
16096 plus = 0;
16097 }
16098
16099 // Go over each subpath - for panes in Highstock
16100 for (i = 0; i < path.length; i += 6) {
16101
16102 // Add 1 pixel when coordinates are the same
16103 if (horiz && toPath[i + 1] === path[i + 1]) {
16104 toPath[i + 1] += plus;
16105 toPath[i + 4] += plus;
16106 } else if (!horiz && toPath[i + 2] === path[i + 2]) {
16107 toPath[i + 2] += plus;
16108 toPath[i + 5] += plus;
16109 }
16110
16111 result.push(
16112 'M',
16113 path[i + 1],
16114 path[i + 2],
16115 'L',
16116 path[i + 4],
16117 path[i + 5],
16118 toPath[i + 4],
16119 toPath[i + 5],
16120 toPath[i + 1],
16121 toPath[i + 2],
16122 'z'
16123 );
16124 result.flat = flat;
16125 }
16126
16127 } else { // outside the axis area
16128 path = null;
16129 }
16130
16131 return result;
16132 },
16133
16134 /**
16135 * Add a plot band after render time.
16136 *
16137 * @param {AxisPlotBandsOptions} options
16138 * A configuration object for the plot band, as defined in {@link
16139 * https://api.highcharts.com/highcharts/xAxis.plotBands|
16140 * xAxis.plotBands}.
16141 * @return {Object}
16142 * The added plot band.
16143 * @sample highcharts/members/axis-addplotband/
16144 * Toggle the plot band from a button
16145 */
16146 addPlotBand: function(options) {
16147 return this.addPlotBandOrLine(options, 'plotBands');
16148 },
16149
16150 /**
16151 * Add a plot line after render time.
16152 *
16153 * @param {AxisPlotLinesOptions} options
16154 * A configuration object for the plot line, as defined in {@link
16155 * https://api.highcharts.com/highcharts/xAxis.plotLines|
16156 * xAxis.plotLines}.
16157 * @return {Object}
16158 * The added plot line.
16159 * @sample highcharts/members/axis-addplotline/
16160 * Toggle the plot line from a button
16161 */
16162 addPlotLine: function(options) {
16163 return this.addPlotBandOrLine(options, 'plotLines');
16164 },
16165
16166 /**
16167 * Add a plot band or plot line after render time. Called from addPlotBand
16168 * and addPlotLine internally.
16169 *
16170 * @private
16171 * @param options {AxisPlotLinesOptions|AxisPlotBandsOptions}
16172 * The plotBand or plotLine configuration object.
16173 */
16174 addPlotBandOrLine: function(options, coll) {
16175 var obj = new H.PlotLineOrBand(this, options).render(),
16176 userOptions = this.userOptions;
16177
16178 if (obj) { // #2189
16179 // Add it to the user options for exporting and Axis.update
16180 if (coll) {
16181 userOptions[coll] = userOptions[coll] || [];
16182 userOptions[coll].push(options);
16183 }
16184 this.plotLinesAndBands.push(obj);
16185 }
16186
16187 return obj;
16188 },
16189
16190 /**
16191 * Remove a plot band or plot line from the chart by id. Called internally
16192 * from `removePlotBand` and `removePlotLine`.
16193 *
16194 * @private
16195 * @param {String} id
16196 */
16197 removePlotBandOrLine: function(id) {
16198 var plotLinesAndBands = this.plotLinesAndBands,
16199 options = this.options,
16200 userOptions = this.userOptions,
16201 i = plotLinesAndBands.length;
16202 while (i--) {
16203 if (plotLinesAndBands[i].id === id) {
16204 plotLinesAndBands[i].destroy();
16205 }
16206 }
16207 each([
16208 options.plotLines || [],
16209 userOptions.plotLines || [],
16210 options.plotBands || [],
16211 userOptions.plotBands || []
16212 ], function(arr) {
16213 i = arr.length;
16214 while (i--) {
16215 if (arr[i].id === id) {
16216 erase(arr, arr[i]);
16217 }
16218 }
16219 });
16220 },
16221
16222 /**
16223 * Remove a plot band by its id.
16224 *
16225 * @param {String} id
16226 * The plot band's `id` as given in the original configuration
16227 * object or in the `addPlotBand` option.
16228 * @sample highcharts/members/axis-removeplotband/
16229 * Remove plot band by id
16230 * @sample highcharts/members/axis-addplotband/
16231 * Toggle the plot band from a button
16232 */
16233 removePlotBand: function(id) {
16234 this.removePlotBandOrLine(id);
16235 },
16236
16237 /**
16238 * Remove a plot line by its id.
16239 * @param {String} id
16240 * The plot line's `id` as given in the original configuration
16241 * object or in the `addPlotLine` option.
16242 * @sample highcharts/xaxis/plotlines-id/
16243 * Remove plot line by id
16244 * @sample highcharts/members/axis-addplotline/
16245 * Toggle the plot line from a button
16246 */
16247 removePlotLine: function(id) {
16248 this.removePlotBandOrLine(id);
16249 }
16250 });
16251
16252 }(Highcharts, Axis));
16253 (function(H) {
16254 /**
16255 * (c) 2010-2017 Torstein Honsi
16256 *
16257 * License: www.highcharts.com/license
16258 */
16259 var dateFormat = H.dateFormat,
16260 each = H.each,
16261 extend = H.extend,
16262 format = H.format,
16263 isNumber = H.isNumber,
16264 map = H.map,
16265 merge = H.merge,
16266 pick = H.pick,
16267 splat = H.splat,
16268 syncTimeout = H.syncTimeout,
16269 timeUnits = H.timeUnits;
16270 /**
16271 * The tooltip object
16272 * @param {Object} chart The chart instance
16273 * @param {Object} options Tooltip options
16274 */
16275 H.Tooltip = function() {
16276 this.init.apply(this, arguments);
16277 };
16278
16279 H.Tooltip.prototype = {
16280
16281 init: function(chart, options) {
16282
16283 // Save the chart and options
16284 this.chart = chart;
16285 this.options = options;
16286
16287 // List of crosshairs
16288 this.crosshairs = [];
16289
16290 // Current values of x and y when animating
16291 this.now = {
16292 x: 0,
16293 y: 0
16294 };
16295
16296 // The tooltip is initially hidden
16297 this.isHidden = true;
16298
16299
16300
16301 // Public property for getting the shared state.
16302 this.split = options.split && !chart.inverted;
16303 this.shared = options.shared || this.split;
16304
16305 },
16306
16307 /**
16308 * Destroy the single tooltips in a split tooltip.
16309 * If the tooltip is active then it is not destroyed, unless forced to.
16310 * @param {boolean} force Force destroy all tooltips.
16311 * @return {undefined}
16312 */
16313 cleanSplit: function(force) {
16314 each(this.chart.series, function(series) {
16315 var tt = series && series.tt;
16316 if (tt) {
16317 if (!tt.isActive || force) {
16318 series.tt = tt.destroy();
16319 } else {
16320 tt.isActive = false;
16321 }
16322 }
16323 });
16324 },
16325
16326
16327
16328
16329 /**
16330 * Create the Tooltip label element if it doesn't exist, then return the
16331 * label.
16332 */
16333 getLabel: function() {
16334
16335 var renderer = this.chart.renderer,
16336 options = this.options;
16337
16338 if (!this.label) {
16339 // Create the label
16340 if (this.split) {
16341 this.label = renderer.g('tooltip');
16342 } else {
16343 this.label = renderer.label(
16344 '',
16345 0,
16346 0,
16347 options.shape || 'callout',
16348 null,
16349 null,
16350 options.useHTML,
16351 null,
16352 'tooltip'
16353 )
16354 .attr({
16355 padding: options.padding,
16356 r: options.borderRadius
16357 });
16358
16359
16360 this.label
16361 .attr({
16362 'fill': options.backgroundColor,
16363 'stroke-width': options.borderWidth
16364 })
16365 // #2301, #2657
16366 .css(options.style)
16367 .shadow(options.shadow);
16368
16369 }
16370
16371
16372
16373 this.label
16374 .attr({
16375 zIndex: 8
16376 })
16377 .add();
16378 }
16379 return this.label;
16380 },
16381
16382 update: function(options) {
16383 this.destroy();
16384 // Update user options (#6218)
16385 merge(true, this.chart.options.tooltip.userOptions, options);
16386 this.init(this.chart, merge(true, this.options, options));
16387 },
16388
16389 /**
16390 * Destroy the tooltip and its elements.
16391 */
16392 destroy: function() {
16393 // Destroy and clear local variables
16394 if (this.label) {
16395 this.label = this.label.destroy();
16396 }
16397 if (this.split && this.tt) {
16398 this.cleanSplit(this.chart, true);
16399 this.tt = this.tt.destroy();
16400 }
16401 clearTimeout(this.hideTimer);
16402 clearTimeout(this.tooltipTimeout);
16403 },
16404
16405 /**
16406 * Provide a soft movement for the tooltip
16407 *
16408 * @param {Number} x
16409 * @param {Number} y
16410 * @private
16411 */
16412 move: function(x, y, anchorX, anchorY) {
16413 var tooltip = this,
16414 now = tooltip.now,
16415 animate = tooltip.options.animation !== false &&
16416 !tooltip.isHidden &&
16417 // When we get close to the target position, abort animation and
16418 // land on the right place (#3056)
16419 (Math.abs(x - now.x) > 1 || Math.abs(y - now.y) > 1),
16420 skipAnchor = tooltip.followPointer || tooltip.len > 1;
16421
16422 // Get intermediate values for animation
16423 extend(now, {
16424 x: animate ? (2 * now.x + x) / 3 : x,
16425 y: animate ? (now.y + y) / 2 : y,
16426 anchorX: skipAnchor ?
16427 undefined : animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,
16428 anchorY: skipAnchor ?
16429 undefined : animate ? (now.anchorY + anchorY) / 2 : anchorY
16430 });
16431
16432 // Move to the intermediate value
16433 tooltip.getLabel().attr(now);
16434
16435
16436 // Run on next tick of the mouse tracker
16437 if (animate) {
16438
16439 // Never allow two timeouts
16440 clearTimeout(this.tooltipTimeout);
16441
16442 // Set the fixed interval ticking for the smooth tooltip
16443 this.tooltipTimeout = setTimeout(function() {
16444 // The interval function may still be running during destroy,
16445 // so check that the chart is really there before calling.
16446 if (tooltip) {
16447 tooltip.move(x, y, anchorX, anchorY);
16448 }
16449 }, 32);
16450
16451 }
16452 },
16453
16454 /**
16455 * Hide the tooltip
16456 */
16457 hide: function(delay) {
16458 var tooltip = this;
16459 // disallow duplicate timers (#1728, #1766)
16460 clearTimeout(this.hideTimer);
16461 delay = pick(delay, this.options.hideDelay, 500);
16462 if (!this.isHidden) {
16463 this.hideTimer = syncTimeout(function() {
16464 tooltip.getLabel()[delay ? 'fadeOut' : 'hide']();
16465 tooltip.isHidden = true;
16466 }, delay);
16467 }
16468 },
16469
16470 /**
16471 * Extendable method to get the anchor position of the tooltip
16472 * from a point or set of points
16473 */
16474 getAnchor: function(points, mouseEvent) {
16475 var ret,
16476 chart = this.chart,
16477 inverted = chart.inverted,
16478 plotTop = chart.plotTop,
16479 plotLeft = chart.plotLeft,
16480 plotX = 0,
16481 plotY = 0,
16482 yAxis,
16483 xAxis;
16484
16485 points = splat(points);
16486
16487 // Pie uses a special tooltipPos
16488 ret = points[0].tooltipPos;
16489
16490 // When tooltip follows mouse, relate the position to the mouse
16491 if (this.followPointer && mouseEvent) {
16492 if (mouseEvent.chartX === undefined) {
16493 mouseEvent = chart.pointer.normalize(mouseEvent);
16494 }
16495 ret = [
16496 mouseEvent.chartX - chart.plotLeft,
16497 mouseEvent.chartY - plotTop
16498 ];
16499 }
16500 // When shared, use the average position
16501 if (!ret) {
16502 each(points, function(point) {
16503 yAxis = point.series.yAxis;
16504 xAxis = point.series.xAxis;
16505 plotX += point.plotX +
16506 (!inverted && xAxis ? xAxis.left - plotLeft : 0);
16507 plotY +=
16508 (
16509 point.plotLow ?
16510 (point.plotLow + point.plotHigh) / 2 :
16511 point.plotY
16512 ) +
16513 (!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151
16514 });
16515
16516 plotX /= points.length;
16517 plotY /= points.length;
16518
16519 ret = [
16520 inverted ? chart.plotWidth - plotY : plotX,
16521 this.shared && !inverted && points.length > 1 && mouseEvent ?
16522 // place shared tooltip next to the mouse (#424)
16523 mouseEvent.chartY - plotTop :
16524 inverted ? chart.plotHeight - plotX : plotY
16525 ];
16526 }
16527
16528 return map(ret, Math.round);
16529 },
16530
16531 /**
16532 * Place the tooltip in a chart without spilling over
16533 * and not covering the point it self.
16534 */
16535 getPosition: function(boxWidth, boxHeight, point) {
16536
16537 var chart = this.chart,
16538 distance = this.distance,
16539 ret = {},
16540 // Don't use h if chart isn't inverted (#7242)
16541 h = (chart.inverted && point.h) || 0, // #4117
16542 swapped,
16543 first = ['y', chart.chartHeight, boxHeight,
16544 point.plotY + chart.plotTop, chart.plotTop,
16545 chart.plotTop + chart.plotHeight
16546 ],
16547 second = ['x', chart.chartWidth, boxWidth,
16548 point.plotX + chart.plotLeft, chart.plotLeft,
16549 chart.plotLeft + chart.plotWidth
16550 ],
16551 // The far side is right or bottom
16552 preferFarSide = !this.followPointer && pick(
16553 point.ttBelow, !chart.inverted === !!point.negative
16554 ), // #4984
16555
16556 /**
16557 * Handle the preferred dimension. When the preferred dimension is
16558 * tooltip on top or bottom of the point, it will look for space
16559 * there.
16560 */
16561 firstDimension = function(
16562 dim,
16563 outerSize,
16564 innerSize,
16565 point,
16566 min,
16567 max
16568 ) {
16569 var roomLeft = innerSize < point - distance,
16570 roomRight = point + distance + innerSize < outerSize,
16571 alignedLeft = point - distance - innerSize,
16572 alignedRight = point + distance;
16573
16574 if (preferFarSide && roomRight) {
16575 ret[dim] = alignedRight;
16576 } else if (!preferFarSide && roomLeft) {
16577 ret[dim] = alignedLeft;
16578 } else if (roomLeft) {
16579 ret[dim] = Math.min(
16580 max - innerSize,
16581 alignedLeft - h < 0 ? alignedLeft : alignedLeft - h
16582 );
16583 } else if (roomRight) {
16584 ret[dim] = Math.max(
16585 min,
16586 alignedRight + h + innerSize > outerSize ?
16587 alignedRight :
16588 alignedRight + h
16589 );
16590 } else {
16591 return false;
16592 }
16593 },
16594 /**
16595 * Handle the secondary dimension. If the preferred dimension is
16596 * tooltip on top or bottom of the point, the second dimension is to
16597 * align the tooltip above the point, trying to align center but
16598 * allowing left or right align within the chart box.
16599 */
16600 secondDimension = function(dim, outerSize, innerSize, point) {
16601 var retVal;
16602
16603 // Too close to the edge, return false and swap dimensions
16604 if (point < distance || point > outerSize - distance) {
16605 retVal = false;
16606 // Align left/top
16607 } else if (point < innerSize / 2) {
16608 ret[dim] = 1;
16609 // Align right/bottom
16610 } else if (point > outerSize - innerSize / 2) {
16611 ret[dim] = outerSize - innerSize - 2;
16612 // Align center
16613 } else {
16614 ret[dim] = point - innerSize / 2;
16615 }
16616 return retVal;
16617 },
16618 /**
16619 * Swap the dimensions
16620 */
16621 swap = function(count) {
16622 var temp = first;
16623 first = second;
16624 second = temp;
16625 swapped = count;
16626 },
16627 run = function() {
16628 if (firstDimension.apply(0, first) !== false) {
16629 if (
16630 secondDimension.apply(0, second) === false &&
16631 !swapped
16632 ) {
16633 swap(true);
16634 run();
16635 }
16636 } else if (!swapped) {
16637 swap(true);
16638 run();
16639 } else {
16640 ret.x = ret.y = 0;
16641 }
16642 };
16643
16644 // Under these conditions, prefer the tooltip on the side of the point
16645 if (chart.inverted || this.len > 1) {
16646 swap();
16647 }
16648 run();
16649
16650 return ret;
16651
16652 },
16653
16654 /**
16655 * In case no user defined formatter is given, this will be used. Note that
16656 * the context here is an object holding point, series, x, y etc.
16657 *
16658 * @returns {String|Array<String>}
16659 */
16660 defaultFormatter: function(tooltip) {
16661 var items = this.points || splat(this),
16662 s;
16663
16664 // Build the header
16665 s = [tooltip.tooltipFooterHeaderFormatter(items[0])];
16666
16667 // build the values
16668 s = s.concat(tooltip.bodyFormatter(items));
16669
16670 // footer
16671 s.push(tooltip.tooltipFooterHeaderFormatter(items[0], true));
16672
16673 return s;
16674 },
16675
16676 /**
16677 * Refresh the tooltip's text and position.
16678 * @param {Object|Array} pointOrPoints Rither a point or an array of points
16679 */
16680 refresh: function(pointOrPoints, mouseEvent) {
16681 var tooltip = this,
16682 label,
16683 options = tooltip.options,
16684 x,
16685 y,
16686 point = pointOrPoints,
16687 anchor,
16688 textConfig = {},
16689 text,
16690 pointConfig = [],
16691 formatter = options.formatter || tooltip.defaultFormatter,
16692 shared = tooltip.shared,
16693 currentSeries;
16694
16695 if (!options.enabled) {
16696 return;
16697 }
16698
16699 clearTimeout(this.hideTimer);
16700
16701 // get the reference point coordinates (pie charts use tooltipPos)
16702 tooltip.followPointer = splat(point)[0].series.tooltipOptions
16703 .followPointer;
16704 anchor = tooltip.getAnchor(point, mouseEvent);
16705 x = anchor[0];
16706 y = anchor[1];
16707
16708 // shared tooltip, array is sent over
16709 if (shared && !(point.series && point.series.noSharedTooltip)) {
16710 each(point, function(item) {
16711 item.setState('hover');
16712
16713 pointConfig.push(item.getLabelConfig());
16714 });
16715
16716 textConfig = {
16717 x: point[0].category,
16718 y: point[0].y
16719 };
16720 textConfig.points = pointConfig;
16721 point = point[0];
16722
16723 // single point tooltip
16724 } else {
16725 textConfig = point.getLabelConfig();
16726 }
16727 this.len = pointConfig.length; // #6128
16728 text = formatter.call(textConfig, tooltip);
16729
16730 // register the current series
16731 currentSeries = point.series;
16732 this.distance = pick(currentSeries.tooltipOptions.distance, 16);
16733
16734 // update the inner HTML
16735 if (text === false) {
16736 this.hide();
16737 } else {
16738
16739 label = tooltip.getLabel();
16740
16741 // show it
16742 if (tooltip.isHidden) {
16743 label.attr({
16744 opacity: 1
16745 }).show();
16746 }
16747
16748 // update text
16749 if (tooltip.split) {
16750 this.renderSplit(text, splat(pointOrPoints));
16751 } else {
16752
16753 // Prevent the tooltip from flowing over the chart box (#6659)
16754
16755 if (!options.style.width) {
16756
16757 label.css({
16758 width: this.chart.spacingBox.width
16759 });
16760
16761 }
16762
16763
16764 label.attr({
16765 text: text && text.join ? text.join('') : text
16766 });
16767
16768 // Set the stroke color of the box to reflect the point
16769 label.removeClass(/highcharts-color-[\d]+/g)
16770 .addClass(
16771 'highcharts-color-' +
16772 pick(point.colorIndex, currentSeries.colorIndex)
16773 );
16774
16775
16776 label.attr({
16777 stroke: (
16778 options.borderColor ||
16779 point.color ||
16780 currentSeries.color ||
16781 '#666666'
16782 )
16783 });
16784
16785
16786 tooltip.updatePosition({
16787 plotX: x,
16788 plotY: y,
16789 negative: point.negative,
16790 ttBelow: point.ttBelow,
16791 h: anchor[2] || 0
16792 });
16793 }
16794
16795 this.isHidden = false;
16796 }
16797 },
16798
16799 /**
16800 * Render the split tooltip. Loops over each point's text and adds
16801 * a label next to the point, then uses the distribute function to
16802 * find best non-overlapping positions.
16803 */
16804 renderSplit: function(labels, points) {
16805 var tooltip = this,
16806 boxes = [],
16807 chart = this.chart,
16808 ren = chart.renderer,
16809 rightAligned = true,
16810 options = this.options,
16811 headerHeight = 0,
16812 tooltipLabel = this.getLabel();
16813
16814 // Graceful degradation for legacy formatters
16815 if (H.isString(labels)) {
16816 labels = [false, labels];
16817 }
16818 // Create the individual labels for header and points, ignore footer
16819 each(labels.slice(0, points.length + 1), function(str, i) {
16820 if (str !== false) {
16821 var point = points[i - 1] ||
16822 // Item 0 is the header. Instead of this, we could also
16823 // use the crosshair label
16824 {
16825 isHeader: true,
16826 plotX: points[0].plotX
16827 },
16828 owner = point.series || tooltip,
16829 tt = owner.tt,
16830 series = point.series || {},
16831 colorClass = 'highcharts-color-' + pick(
16832 point.colorIndex,
16833 series.colorIndex,
16834 'none'
16835 ),
16836 target,
16837 x,
16838 bBox,
16839 boxWidth;
16840
16841 // Store the tooltip referance on the series
16842 if (!tt) {
16843 owner.tt = tt = ren.label(
16844 null,
16845 null,
16846 null,
16847 'callout',
16848 null,
16849 null,
16850 options.useHTML
16851 )
16852 .addClass('highcharts-tooltip-box ' + colorClass)
16853 .attr({
16854 'padding': options.padding,
16855 'r': options.borderRadius,
16856
16857 'fill': options.backgroundColor,
16858 'stroke': (
16859 options.borderColor ||
16860 point.color ||
16861 series.color ||
16862 '#333333'
16863 ),
16864 'stroke-width': options.borderWidth
16865
16866 })
16867 .add(tooltipLabel);
16868 }
16869
16870 tt.isActive = true;
16871 tt.attr({
16872 text: str
16873 });
16874
16875 tt.css(options.style)
16876 .shadow(options.shadow);
16877
16878
16879 // Get X position now, so we can move all to the other side in
16880 // case of overflow
16881 bBox = tt.getBBox();
16882 boxWidth = bBox.width + tt.strokeWidth();
16883 if (point.isHeader) {
16884 headerHeight = bBox.height;
16885 x = Math.max(
16886 0, // No left overflow
16887 Math.min(
16888 point.plotX + chart.plotLeft - boxWidth / 2,
16889 // No right overflow (#5794)
16890 chart.chartWidth - boxWidth
16891 )
16892 );
16893 } else {
16894 x = point.plotX + chart.plotLeft -
16895 pick(options.distance, 16) - boxWidth;
16896 }
16897
16898
16899 // If overflow left, we don't use this x in the next loop
16900 if (x < 0) {
16901 rightAligned = false;
16902 }
16903
16904 // Prepare for distribution
16905 target = (point.series && point.series.yAxis &&
16906 point.series.yAxis.pos) + (point.plotY || 0);
16907 target -= chart.plotTop;
16908 boxes.push({
16909 target: point.isHeader ?
16910 chart.plotHeight + headerHeight : target,
16911 rank: point.isHeader ? 1 : 0,
16912 size: owner.tt.getBBox().height + 1,
16913 point: point,
16914 x: x,
16915 tt: tt
16916 });
16917 }
16918 });
16919
16920 // Clean previous run (for missing points)
16921 this.cleanSplit();
16922
16923 // Distribute and put in place
16924 H.distribute(boxes, chart.plotHeight + headerHeight);
16925 each(boxes, function(box) {
16926 var point = box.point,
16927 series = point.series;
16928
16929 // Put the label in place
16930 box.tt.attr({
16931 visibility: box.pos === undefined ? 'hidden' : 'inherit',
16932 x: (rightAligned || point.isHeader ?
16933 box.x :
16934 point.plotX + chart.plotLeft + pick(options.distance, 16)),
16935 y: box.pos + chart.plotTop,
16936 anchorX: point.isHeader ?
16937 point.plotX + chart.plotLeft : point.plotX + series.xAxis.pos,
16938 anchorY: point.isHeader ?
16939 box.pos + chart.plotTop - 15 : point.plotY + series.yAxis.pos
16940 });
16941 });
16942 },
16943
16944 /**
16945 * Find the new position and perform the move
16946 */
16947 updatePosition: function(point) {
16948 var chart = this.chart,
16949 label = this.getLabel(),
16950 pos = (this.options.positioner || this.getPosition).call(
16951 this,
16952 label.width,
16953 label.height,
16954 point
16955 );
16956
16957 // do the move
16958 this.move(
16959 Math.round(pos.x),
16960 Math.round(pos.y || 0), // can be undefined (#3977)
16961 point.plotX + chart.plotLeft,
16962 point.plotY + chart.plotTop
16963 );
16964 },
16965
16966 /**
16967 * Get the optimal date format for a point, based on a range.
16968 * @param {number} range - The time range
16969 * @param {number|Date} date - The date of the point in question
16970 * @param {number} startOfWeek - An integer representing the first day of
16971 * the week, where 0 is Sunday
16972 * @param {Object} dateTimeLabelFormats - A map of time units to formats
16973 * @return {string} - the optimal date format for a point
16974 */
16975 getDateFormat: function(range, date, startOfWeek, dateTimeLabelFormats) {
16976 var dateStr = dateFormat('%m-%d %H:%M:%S.%L', date),
16977 format,
16978 n,
16979 blank = '01-01 00:00:00.000',
16980 strpos = {
16981 millisecond: 15,
16982 second: 12,
16983 minute: 9,
16984 hour: 6,
16985 day: 3
16986 },
16987 lastN = 'millisecond'; // for sub-millisecond data, #4223
16988 for (n in timeUnits) {
16989
16990 // If the range is exactly one week and we're looking at a
16991 // Sunday/Monday, go for the week format
16992 if (
16993 range === timeUnits.week &&
16994 +dateFormat('%w', date) === startOfWeek &&
16995 dateStr.substr(6) === blank.substr(6)
16996 ) {
16997 n = 'week';
16998 break;
16999 }
17000
17001 // The first format that is too great for the range
17002 if (timeUnits[n] > range) {
17003 n = lastN;
17004 break;
17005 }
17006
17007 // If the point is placed every day at 23:59, we need to show
17008 // the minutes as well. #2637.
17009 if (
17010 strpos[n] &&
17011 dateStr.substr(strpos[n]) !== blank.substr(strpos[n])
17012 ) {
17013 break;
17014 }
17015
17016 // Weeks are outside the hierarchy, only apply them on
17017 // Mondays/Sundays like in the first condition
17018 if (n !== 'week') {
17019 lastN = n;
17020 }
17021 }
17022
17023 if (n) {
17024 format = dateTimeLabelFormats[n];
17025 }
17026
17027 return format;
17028 },
17029
17030 /**
17031 * Get the best X date format based on the closest point range on the axis.
17032 */
17033 getXDateFormat: function(point, options, xAxis) {
17034 var xDateFormat,
17035 dateTimeLabelFormats = options.dateTimeLabelFormats,
17036 closestPointRange = xAxis && xAxis.closestPointRange;
17037
17038 if (closestPointRange) {
17039 xDateFormat = this.getDateFormat(
17040 closestPointRange,
17041 point.x,
17042 xAxis.options.startOfWeek,
17043 dateTimeLabelFormats
17044 );
17045 } else {
17046 xDateFormat = dateTimeLabelFormats.day;
17047 }
17048
17049 return xDateFormat || dateTimeLabelFormats.year; // #2546, 2581
17050 },
17051
17052 /**
17053 * Format the footer/header of the tooltip
17054 * #3397: abstraction to enable formatting of footer and header
17055 */
17056 tooltipFooterHeaderFormatter: function(labelConfig, isFooter) {
17057 var footOrHead = isFooter ? 'footer' : 'header',
17058 series = labelConfig.series,
17059 tooltipOptions = series.tooltipOptions,
17060 xDateFormat = tooltipOptions.xDateFormat,
17061 xAxis = series.xAxis,
17062 isDateTime = (
17063 xAxis &&
17064 xAxis.options.type === 'datetime' &&
17065 isNumber(labelConfig.key)
17066 ),
17067 formatString = tooltipOptions[footOrHead + 'Format'];
17068
17069 // Guess the best date format based on the closest point distance (#568,
17070 // #3418)
17071 if (isDateTime && !xDateFormat) {
17072 xDateFormat = this.getXDateFormat(
17073 labelConfig,
17074 tooltipOptions,
17075 xAxis
17076 );
17077 }
17078
17079 // Insert the footer date format if any
17080 if (isDateTime && xDateFormat) {
17081 each(
17082 (labelConfig.point && labelConfig.point.tooltipDateKeys) || ['key'],
17083 function(key) {
17084 formatString = formatString.replace(
17085 '{point.' + key + '}',
17086 '{point.' + key + ':' + xDateFormat + '}'
17087 );
17088 }
17089 );
17090 }
17091
17092 return format(formatString, {
17093 point: labelConfig,
17094 series: series
17095 });
17096 },
17097
17098 /**
17099 * Build the body (lines) of the tooltip by iterating over the items and
17100 * returning one entry for each item, abstracting this functionality allows
17101 * to easily overwrite and extend it.
17102 */
17103 bodyFormatter: function(items) {
17104 return map(items, function(item) {
17105 var tooltipOptions = item.series.tooltipOptions;
17106 return (
17107 tooltipOptions[
17108 (item.point.formatPrefix || 'point') + 'Formatter'
17109 ] ||
17110 item.point.tooltipFormatter
17111 ).call(
17112 item.point,
17113 tooltipOptions[(item.point.formatPrefix || 'point') + 'Format']
17114 );
17115 });
17116 }
17117
17118 };
17119
17120 }(Highcharts));
17121 (function(Highcharts) {
17122 /**
17123 * (c) 2010-2017 Torstein Honsi
17124 *
17125 * License: www.highcharts.com/license
17126 */
17127 var H = Highcharts,
17128 addEvent = H.addEvent,
17129 attr = H.attr,
17130 charts = H.charts,
17131 color = H.color,
17132 css = H.css,
17133 defined = H.defined,
17134 each = H.each,
17135 extend = H.extend,
17136 find = H.find,
17137 fireEvent = H.fireEvent,
17138 isObject = H.isObject,
17139 offset = H.offset,
17140 pick = H.pick,
17141 splat = H.splat,
17142 Tooltip = H.Tooltip;
17143
17144 /**
17145 * The mouse and touch tracker object. Each {@link Chart} item has one
17146 * assosiated Pointer item that can be accessed from the {@link Chart.pointer}
17147 * property.
17148 *
17149 * @class
17150 * @param {Chart} chart
17151 * The Chart instance.
17152 * @param {Options} options
17153 * The root options object. The pointer uses options from the chart and
17154 * tooltip structures.
17155 */
17156 Highcharts.Pointer = function(chart, options) {
17157 this.init(chart, options);
17158 };
17159
17160 Highcharts.Pointer.prototype = {
17161 /**
17162 * Initialize the Pointer.
17163 *
17164 * @private
17165 */
17166 init: function(chart, options) {
17167
17168 // Store references
17169 this.options = options;
17170 this.chart = chart;
17171
17172 // Do we need to handle click on a touch device?
17173 this.runChartClick = options.chart.events && !!options.chart.events.click;
17174
17175 this.pinchDown = [];
17176 this.lastValidTouch = {};
17177
17178 if (Tooltip) {
17179 chart.tooltip = new Tooltip(chart, options.tooltip);
17180 this.followTouchMove = pick(options.tooltip.followTouchMove, true);
17181 }
17182
17183 this.setDOMEvents();
17184 },
17185
17186 /**
17187 * Resolve the zoomType option, this is reset on all touch start and mouse
17188 * down events.
17189 *
17190 * @private
17191 */
17192 zoomOption: function(e) {
17193 var chart = this.chart,
17194 options = chart.options.chart,
17195 zoomType = options.zoomType || '',
17196 inverted = chart.inverted,
17197 zoomX,
17198 zoomY;
17199
17200 // Look for the pinchType option
17201 if (/touch/.test(e.type)) {
17202 zoomType = pick(options.pinchType, zoomType);
17203 }
17204
17205 this.zoomX = zoomX = /x/.test(zoomType);
17206 this.zoomY = zoomY = /y/.test(zoomType);
17207 this.zoomHor = (zoomX && !inverted) || (zoomY && inverted);
17208 this.zoomVert = (zoomY && !inverted) || (zoomX && inverted);
17209 this.hasZoom = zoomX || zoomY;
17210 },
17211
17212 /**
17213 * @typedef {Object} PointerEvent
17214 * A native browser mouse or touch event, extended with position
17215 * information relative to the {@link Chart.container}.
17216 * @property {Number} chartX
17217 * The X coordinate of the pointer interaction relative to the
17218 * chart.
17219 * @property {Number} chartY
17220 * The Y coordinate of the pointer interaction relative to the
17221 * chart.
17222 *
17223 */
17224 /**
17225 * Takes a browser event object and extends it with custom Highcharts
17226 * properties `chartX` and `chartY` in order to work on the internal
17227 * coordinate system.
17228 *
17229 * @param {Object} e
17230 * The event object in standard browsers.
17231 *
17232 * @return {PointerEvent}
17233 * A browser event with extended properties `chartX` and `chartY`.
17234 */
17235 normalize: function(e, chartPosition) {
17236 var ePos;
17237
17238 // iOS (#2757)
17239 ePos = e.touches ? (e.touches.length ? e.touches.item(0) : e.changedTouches[0]) : e;
17240
17241 // Get mouse position
17242 if (!chartPosition) {
17243 this.chartPosition = chartPosition = offset(this.chart.container);
17244 }
17245
17246 return extend(e, {
17247 chartX: Math.round(ePos.pageX - chartPosition.left),
17248 chartY: Math.round(ePos.pageY - chartPosition.top)
17249 });
17250 },
17251
17252 /**
17253 * Get the click position in terms of axis values.
17254 *
17255 * @param {PointerEvent} e
17256 * A pointer event, extended with `chartX` and `chartY`
17257 * properties.
17258 */
17259 getCoordinates: function(e) {
17260 var coordinates = {
17261 xAxis: [],
17262 yAxis: []
17263 };
17264
17265 each(this.chart.axes, function(axis) {
17266 coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({
17267 axis: axis,
17268 value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY'])
17269 });
17270 });
17271 return coordinates;
17272 },
17273 /**
17274 * Finds the closest point to a set of coordinates, using the k-d-tree
17275 * algorithm.
17276 *
17277 * @param {Array.<Series>} series
17278 * All the series to search in.
17279 * @param {boolean} shared
17280 * Whether it is a shared tooltip or not.
17281 * @param {object} coordinates
17282 * Chart coordinates of the pointer.
17283 * @param {number} coordinates.chartX
17284 * @param {number} coordinates.chartY
17285 *
17286 * @return {Point|undefined} The point closest to given coordinates.
17287 */
17288 findNearestKDPoint: function(series, shared, coordinates) {
17289 var closest,
17290 sort = function(p1, p2) {
17291 var isCloserX = p1.distX - p2.distX,
17292 isCloser = p1.dist - p2.dist,
17293 isAbove =
17294 (p2.series.group && p2.series.group.zIndex) -
17295 (p1.series.group && p1.series.group.zIndex),
17296 result;
17297
17298 // We have two points which are not in the same place on xAxis
17299 // and shared tooltip:
17300 if (isCloserX !== 0 && shared) { // #5721
17301 result = isCloserX;
17302 // Points are not exactly in the same place on x/yAxis:
17303 } else if (isCloser !== 0) {
17304 result = isCloser;
17305 // The same xAxis and yAxis position, sort by z-index:
17306 } else if (isAbove !== 0) {
17307 result = isAbove;
17308 // The same zIndex, sort by array index:
17309 } else {
17310 result = p1.series.index > p2.series.index ? -1 : 1;
17311 }
17312 return result;
17313 };
17314 each(series, function(s) {
17315 var noSharedTooltip = s.noSharedTooltip && shared,
17316 compareX = (!noSharedTooltip &&
17317 s.options.findNearestPointBy.indexOf('y') < 0
17318 ),
17319 point = s.searchPoint(
17320 coordinates,
17321 compareX
17322 );
17323 if (
17324 // Check that we actually found a point on the series.
17325 isObject(point, true) &&
17326 // Use the new point if it is closer.
17327 (!isObject(closest, true) || (sort(closest, point) > 0))
17328 ) {
17329 closest = point;
17330 }
17331 });
17332 return closest;
17333 },
17334 getPointFromEvent: function(e) {
17335 var target = e.target,
17336 point;
17337
17338 while (target && !point) {
17339 point = target.point;
17340 target = target.parentNode;
17341 }
17342 return point;
17343 },
17344
17345 getChartCoordinatesFromPoint: function(point, inverted) {
17346 var series = point.series,
17347 xAxis = series.xAxis,
17348 yAxis = series.yAxis,
17349 plotX = pick(point.clientX, point.plotX);
17350
17351 if (xAxis && yAxis) {
17352 return inverted ? {
17353 chartX: xAxis.len + xAxis.pos - plotX,
17354 chartY: yAxis.len + yAxis.pos - point.plotY
17355 } : {
17356 chartX: plotX + xAxis.pos,
17357 chartY: point.plotY + yAxis.pos
17358 };
17359 }
17360 },
17361
17362 /**
17363 * Calculates what is the current hovered point/points and series.
17364 *
17365 * @private
17366 *
17367 * @param {undefined|Point} existingHoverPoint
17368 * The point currrently beeing hovered.
17369 * @param {undefined|Series} existingHoverSeries
17370 * The series currently beeing hovered.
17371 * @param {Array.<Series>} series
17372 * All the series in the chart.
17373 * @param {boolean} isDirectTouch
17374 * Is the pointer directly hovering the point.
17375 * @param {boolean} shared
17376 * Whether it is a shared tooltip or not.
17377 * @param {object} coordinates
17378 * Chart coordinates of the pointer.
17379 * @param {number} coordinates.chartX
17380 * @param {number} coordinates.chartY
17381 *
17382 * @return {object}
17383 * Object containing resulting hover data.
17384 */
17385 getHoverData: function(
17386 existingHoverPoint,
17387 existingHoverSeries,
17388 series,
17389 isDirectTouch,
17390 shared,
17391 coordinates,
17392 params
17393 ) {
17394 var hoverPoint,
17395 hoverPoints = [],
17396 hoverSeries = existingHoverSeries,
17397 isBoosting = params && params.isBoosting,
17398 useExisting = !!(isDirectTouch && existingHoverPoint),
17399 notSticky = hoverSeries && !hoverSeries.stickyTracking,
17400 filter = function(s) {
17401 return (
17402 s.visible &&
17403 !(!shared && s.directTouch) && // #3821
17404 pick(s.options.enableMouseTracking, true)
17405 );
17406 },
17407 // Which series to look in for the hover point
17408 searchSeries = notSticky ?
17409 // Only search on hovered series if it has stickyTracking false
17410 [hoverSeries] :
17411 // Filter what series to look in.
17412 H.grep(series, function(s) {
17413 return filter(s) && s.stickyTracking;
17414 });
17415
17416 // Use existing hovered point or find the one closest to coordinates.
17417 hoverPoint = useExisting ?
17418 existingHoverPoint :
17419 this.findNearestKDPoint(searchSeries, shared, coordinates);
17420
17421 // Assign hover series
17422 hoverSeries = hoverPoint && hoverPoint.series;
17423
17424 // If we have a hoverPoint, assign hoverPoints.
17425 if (hoverPoint) {
17426 // When tooltip is shared, it displays more than one point
17427 if (shared && !hoverSeries.noSharedTooltip) {
17428 searchSeries = H.grep(series, function(s) {
17429 return filter(s) && !s.noSharedTooltip;
17430 });
17431
17432 // Get all points with the same x value as the hoverPoint
17433 each(searchSeries, function(s) {
17434 var point = find(s.points, function(p) {
17435 return p.x === hoverPoint.x && !p.isNull;
17436 });
17437 if (isObject(point)) {
17438 /*
17439 * Boost returns a minimal point. Convert it to a usable
17440 * point for tooltip and states.
17441 */
17442 if (isBoosting) {
17443 point = s.getPoint(point);
17444 }
17445 hoverPoints.push(point);
17446 }
17447 });
17448 } else {
17449 hoverPoints.push(hoverPoint);
17450 }
17451 }
17452 return {
17453 hoverPoint: hoverPoint,
17454 hoverSeries: hoverSeries,
17455 hoverPoints: hoverPoints
17456 };
17457 },
17458 /**
17459 * With line type charts with a single tracker, get the point closest to the
17460 * mouse. Run Point.onMouseOver and display tooltip for the point or points.
17461 *
17462 * @private
17463 */
17464 runPointActions: function(e, p) {
17465 var pointer = this,
17466 chart = pointer.chart,
17467 series = chart.series,
17468 tooltip = chart.tooltip && chart.tooltip.options.enabled ?
17469 chart.tooltip :
17470 undefined,
17471 shared = tooltip ? tooltip.shared : false,
17472 hoverPoint = p || chart.hoverPoint,
17473 hoverSeries = hoverPoint && hoverPoint.series || chart.hoverSeries,
17474 // onMouseOver or already hovering a series with directTouch
17475 isDirectTouch = !!p || (
17476 (hoverSeries && hoverSeries.directTouch) &&
17477 pointer.isDirectTouch
17478 ),
17479 hoverData = this.getHoverData(
17480 hoverPoint,
17481 hoverSeries,
17482 series,
17483 isDirectTouch,
17484 shared,
17485 e, {
17486 isBoosting: chart.isBoosting
17487 }
17488 ),
17489 useSharedTooltip,
17490 followPointer,
17491 anchor,
17492 points;
17493
17494 // Update variables from hoverData.
17495 hoverPoint = hoverData.hoverPoint;
17496 points = hoverData.hoverPoints;
17497 hoverSeries = hoverData.hoverSeries;
17498 followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer;
17499 useSharedTooltip = shared && hoverSeries && !hoverSeries.noSharedTooltip;
17500
17501 // Refresh tooltip for kdpoint if new hover point or tooltip was hidden
17502 // #3926, #4200
17503 if (
17504 hoverPoint &&
17505 // !(hoverSeries && hoverSeries.directTouch) &&
17506 (hoverPoint !== chart.hoverPoint || (tooltip && tooltip.isHidden))
17507 ) {
17508 each(chart.hoverPoints || [], function(p) {
17509 if (H.inArray(p, points) === -1) {
17510 p.setState();
17511 }
17512 });
17513 // Do mouseover on all points (#3919, #3985, #4410, #5622)
17514 each(points || [], function(p) {
17515 p.setState('hover');
17516 });
17517 // set normal state to previous series
17518 if (chart.hoverSeries !== hoverSeries) {
17519 hoverSeries.onMouseOver();
17520 }
17521
17522 // If tracking is on series in stead of on each point,
17523 // fire mouseOver on hover point. // #4448
17524 if (chart.hoverPoint) {
17525 chart.hoverPoint.firePointEvent('mouseOut');
17526 }
17527
17528 // Hover point may have been destroyed in the event handlers (#7127)
17529 if (!hoverPoint.series) {
17530 return;
17531 }
17532
17533 hoverPoint.firePointEvent('mouseOver');
17534 chart.hoverPoints = points;
17535 chart.hoverPoint = hoverPoint;
17536 // Draw tooltip if necessary
17537 if (tooltip) {
17538 tooltip.refresh(useSharedTooltip ? points : hoverPoint, e);
17539 }
17540 // Update positions (regardless of kdpoint or hoverPoint)
17541 } else if (followPointer && tooltip && !tooltip.isHidden) {
17542 anchor = tooltip.getAnchor([{}], e);
17543 tooltip.updatePosition({
17544 plotX: anchor[0],
17545 plotY: anchor[1]
17546 });
17547 }
17548
17549 // Start the event listener to pick up the tooltip and crosshairs
17550 if (!pointer.unDocMouseMove) {
17551 pointer.unDocMouseMove = addEvent(
17552 chart.container.ownerDocument,
17553 'mousemove',
17554 function(e) {
17555 var chart = charts[H.hoverChartIndex];
17556 if (chart) {
17557 chart.pointer.onDocumentMouseMove(e);
17558 }
17559 }
17560 );
17561 }
17562
17563 // Issues related to crosshair #4927, #5269 #5066, #5658
17564 each(chart.axes, function drawAxisCrosshair(axis) {
17565 var snap = pick(axis.crosshair.snap, true),
17566 point = !snap ?
17567 undefined :
17568 H.find(points, function(p) {
17569 return p.series[axis.coll] === axis;
17570 });
17571
17572 // Axis has snapping crosshairs, and one of the hover points belongs
17573 // to axis. Always call drawCrosshair when it is not snap.
17574 if (point || !snap) {
17575 axis.drawCrosshair(e, point);
17576 // Axis has snapping crosshairs, but no hover point belongs to axis
17577 } else {
17578 axis.hideCrosshair();
17579 }
17580 });
17581 },
17582
17583 /**
17584 * Reset the tracking by hiding the tooltip, the hover series state and the
17585 * hover point
17586 *
17587 * @param allowMove {Boolean}
17588 * Instead of destroying the tooltip altogether, allow moving it if
17589 * possible.
17590 */
17591 reset: function(allowMove, delay) {
17592 var pointer = this,
17593 chart = pointer.chart,
17594 hoverSeries = chart.hoverSeries,
17595 hoverPoint = chart.hoverPoint,
17596 hoverPoints = chart.hoverPoints,
17597 tooltip = chart.tooltip,
17598 tooltipPoints = tooltip && tooltip.shared ? hoverPoints : hoverPoint;
17599
17600 // Check if the points have moved outside the plot area (#1003, #4736, #5101)
17601 if (allowMove && tooltipPoints) {
17602 each(splat(tooltipPoints), function(point) {
17603 if (point.series.isCartesian && point.plotX === undefined) {
17604 allowMove = false;
17605 }
17606 });
17607 }
17608
17609 // Just move the tooltip, #349
17610 if (allowMove) {
17611 if (tooltip && tooltipPoints) {
17612 tooltip.refresh(tooltipPoints);
17613 if (hoverPoint) { // #2500
17614 hoverPoint.setState(hoverPoint.state, true);
17615 each(chart.axes, function(axis) {
17616 if (axis.crosshair) {
17617 axis.drawCrosshair(null, hoverPoint);
17618 }
17619 });
17620 }
17621 }
17622
17623 // Full reset
17624 } else {
17625
17626 if (hoverPoint) {
17627 hoverPoint.onMouseOut();
17628 }
17629
17630 if (hoverPoints) {
17631 each(hoverPoints, function(point) {
17632 point.setState();
17633 });
17634 }
17635
17636 if (hoverSeries) {
17637 hoverSeries.onMouseOut();
17638 }
17639
17640 if (tooltip) {
17641 tooltip.hide(delay);
17642 }
17643
17644 if (pointer.unDocMouseMove) {
17645 pointer.unDocMouseMove = pointer.unDocMouseMove();
17646 }
17647
17648 // Remove crosshairs
17649 each(chart.axes, function(axis) {
17650 axis.hideCrosshair();
17651 });
17652
17653 pointer.hoverX = chart.hoverPoints = chart.hoverPoint = null;
17654 }
17655 },
17656
17657 /**
17658 * Scale series groups to a certain scale and translation.
17659 *
17660 * @private
17661 */
17662 scaleGroups: function(attribs, clip) {
17663
17664 var chart = this.chart,
17665 seriesAttribs;
17666
17667 // Scale each series
17668 each(chart.series, function(series) {
17669 seriesAttribs = attribs || series.getPlotBox(); // #1701
17670 if (series.xAxis && series.xAxis.zoomEnabled && series.group) {
17671 series.group.attr(seriesAttribs);
17672 if (series.markerGroup) {
17673 series.markerGroup.attr(seriesAttribs);
17674 series.markerGroup.clip(clip ? chart.clipRect : null);
17675 }
17676 if (series.dataLabelsGroup) {
17677 series.dataLabelsGroup.attr(seriesAttribs);
17678 }
17679 }
17680 });
17681
17682 // Clip
17683 chart.clipRect.attr(clip || chart.clipBox);
17684 },
17685
17686 /**
17687 * Start a drag operation.
17688 *
17689 * @private
17690 */
17691 dragStart: function(e) {
17692 var chart = this.chart;
17693
17694 // Record the start position
17695 chart.mouseIsDown = e.type;
17696 chart.cancelClick = false;
17697 chart.mouseDownX = this.mouseDownX = e.chartX;
17698 chart.mouseDownY = this.mouseDownY = e.chartY;
17699 },
17700
17701 /**
17702 * Perform a drag operation in response to a mousemove event while the mouse
17703 * is down.
17704 *
17705 * @private
17706 */
17707 drag: function(e) {
17708
17709 var chart = this.chart,
17710 chartOptions = chart.options.chart,
17711 chartX = e.chartX,
17712 chartY = e.chartY,
17713 zoomHor = this.zoomHor,
17714 zoomVert = this.zoomVert,
17715 plotLeft = chart.plotLeft,
17716 plotTop = chart.plotTop,
17717 plotWidth = chart.plotWidth,
17718 plotHeight = chart.plotHeight,
17719 clickedInside,
17720 size,
17721 selectionMarker = this.selectionMarker,
17722 mouseDownX = this.mouseDownX,
17723 mouseDownY = this.mouseDownY,
17724 panKey = chartOptions.panKey && e[chartOptions.panKey + 'Key'];
17725
17726 // If the device supports both touch and mouse (like IE11), and we are touch-dragging
17727 // inside the plot area, don't handle the mouse event. #4339.
17728 if (selectionMarker && selectionMarker.touch) {
17729 return;
17730 }
17731
17732 // If the mouse is outside the plot area, adjust to cooordinates
17733 // inside to prevent the selection marker from going outside
17734 if (chartX < plotLeft) {
17735 chartX = plotLeft;
17736 } else if (chartX > plotLeft + plotWidth) {
17737 chartX = plotLeft + plotWidth;
17738 }
17739
17740 if (chartY < plotTop) {
17741 chartY = plotTop;
17742 } else if (chartY > plotTop + plotHeight) {
17743 chartY = plotTop + plotHeight;
17744 }
17745
17746 // determine if the mouse has moved more than 10px
17747 this.hasDragged = Math.sqrt(
17748 Math.pow(mouseDownX - chartX, 2) +
17749 Math.pow(mouseDownY - chartY, 2)
17750 );
17751
17752 if (this.hasDragged > 10) {
17753 clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop);
17754
17755 // make a selection
17756 if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside && !panKey) {
17757 if (!selectionMarker) {
17758 this.selectionMarker = selectionMarker = chart.renderer.rect(
17759 plotLeft,
17760 plotTop,
17761 zoomHor ? 1 : plotWidth,
17762 zoomVert ? 1 : plotHeight,
17763 0
17764 )
17765 .attr({
17766
17767 fill: chartOptions.selectionMarkerFill || color('#335cad').setOpacity(0.25).get(),
17768
17769 'class': 'highcharts-selection-marker',
17770 'zIndex': 7
17771 })
17772 .add();
17773 }
17774 }
17775
17776 // adjust the width of the selection marker
17777 if (selectionMarker && zoomHor) {
17778 size = chartX - mouseDownX;
17779 selectionMarker.attr({
17780 width: Math.abs(size),
17781 x: (size > 0 ? 0 : size) + mouseDownX
17782 });
17783 }
17784 // adjust the height of the selection marker
17785 if (selectionMarker && zoomVert) {
17786 size = chartY - mouseDownY;
17787 selectionMarker.attr({
17788 height: Math.abs(size),
17789 y: (size > 0 ? 0 : size) + mouseDownY
17790 });
17791 }
17792
17793 // panning
17794 if (clickedInside && !selectionMarker && chartOptions.panning) {
17795 chart.pan(e, chartOptions.panning);
17796 }
17797 }
17798 },
17799
17800 /**
17801 * On mouse up or touch end across the entire document, drop the selection.
17802 *
17803 * @private
17804 */
17805 drop: function(e) {
17806 var pointer = this,
17807 chart = this.chart,
17808 hasPinched = this.hasPinched;
17809
17810 if (this.selectionMarker) {
17811 var selectionData = {
17812 originalEvent: e, // #4890
17813 xAxis: [],
17814 yAxis: []
17815 },
17816 selectionBox = this.selectionMarker,
17817 selectionLeft = selectionBox.attr ? selectionBox.attr('x') : selectionBox.x,
17818 selectionTop = selectionBox.attr ? selectionBox.attr('y') : selectionBox.y,
17819 selectionWidth = selectionBox.attr ? selectionBox.attr('width') : selectionBox.width,
17820 selectionHeight = selectionBox.attr ? selectionBox.attr('height') : selectionBox.height,
17821 runZoom;
17822
17823 // a selection has been made
17824 if (this.hasDragged || hasPinched) {
17825
17826 // record each axis' min and max
17827 each(chart.axes, function(axis) {
17828 if (axis.zoomEnabled && defined(axis.min) && (hasPinched || pointer[{
17829 xAxis: 'zoomX',
17830 yAxis: 'zoomY'
17831 }[axis.coll]])) { // #859, #3569
17832 var horiz = axis.horiz,
17833 minPixelPadding = e.type === 'touchend' ? axis.minPixelPadding : 0, // #1207, #3075
17834 selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop) + minPixelPadding),
17835 selectionMax = axis.toValue((horiz ? selectionLeft + selectionWidth : selectionTop + selectionHeight) - minPixelPadding);
17836
17837 selectionData[axis.coll].push({
17838 axis: axis,
17839 min: Math.min(selectionMin, selectionMax), // for reversed axes
17840 max: Math.max(selectionMin, selectionMax)
17841 });
17842 runZoom = true;
17843 }
17844 });
17845 if (runZoom) {
17846 fireEvent(chart, 'selection', selectionData, function(args) {
17847 chart.zoom(extend(args, hasPinched ? {
17848 animation: false
17849 } : null));
17850 });
17851 }
17852
17853 }
17854 this.selectionMarker = this.selectionMarker.destroy();
17855
17856 // Reset scaling preview
17857 if (hasPinched) {
17858 this.scaleGroups();
17859 }
17860 }
17861
17862 // Reset all
17863 if (chart) { // it may be destroyed on mouse up - #877
17864 css(chart.container, {
17865 cursor: chart._cursor
17866 });
17867 chart.cancelClick = this.hasDragged > 10; // #370
17868 chart.mouseIsDown = this.hasDragged = this.hasPinched = false;
17869 this.pinchDown = [];
17870 }
17871 },
17872
17873 onContainerMouseDown: function(e) {
17874
17875 e = this.normalize(e);
17876
17877 this.zoomOption(e);
17878
17879 // issue #295, dragging not always working in Firefox
17880 if (e.preventDefault) {
17881 e.preventDefault();
17882 }
17883
17884 this.dragStart(e);
17885 },
17886
17887
17888
17889 onDocumentMouseUp: function(e) {
17890 if (charts[H.hoverChartIndex]) {
17891 charts[H.hoverChartIndex].pointer.drop(e);
17892 }
17893 },
17894
17895 /**
17896 * Special handler for mouse move that will hide the tooltip when the mouse
17897 * leaves the plotarea. Issue #149 workaround. The mouseleave event does not
17898 * always fire.
17899 *
17900 * @private
17901 */
17902 onDocumentMouseMove: function(e) {
17903 var chart = this.chart,
17904 chartPosition = this.chartPosition;
17905
17906 e = this.normalize(e, chartPosition);
17907
17908 // If we're outside, hide the tooltip
17909 if (chartPosition && !this.inClass(e.target, 'highcharts-tracker') &&
17910 !chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
17911 this.reset();
17912 }
17913 },
17914
17915 /**
17916 * When mouse leaves the container, hide the tooltip.
17917 *
17918 * @private
17919 */
17920 onContainerMouseLeave: function(e) {
17921 var chart = charts[H.hoverChartIndex];
17922 if (chart && (e.relatedTarget || e.toElement)) { // #4886, MS Touch end fires mouseleave but with no related target
17923 chart.pointer.reset();
17924 chart.pointer.chartPosition = null; // also reset the chart position, used in #149 fix
17925 }
17926 },
17927
17928 // The mousemove, touchmove and touchstart event handler
17929 onContainerMouseMove: function(e) {
17930
17931 var chart = this.chart;
17932
17933 if (!defined(H.hoverChartIndex) || !charts[H.hoverChartIndex] || !charts[H.hoverChartIndex].mouseIsDown) {
17934 H.hoverChartIndex = chart.index;
17935 }
17936
17937 e = this.normalize(e);
17938 e.returnValue = false; // #2251, #3224
17939
17940 if (chart.mouseIsDown === 'mousedown') {
17941 this.drag(e);
17942 }
17943
17944 // Show the tooltip and run mouse over events (#977)
17945 if ((this.inClass(e.target, 'highcharts-tracker') ||
17946 chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) && !chart.openMenu) {
17947 this.runPointActions(e);
17948 }
17949 },
17950
17951 /**
17952 * Utility to detect whether an element has, or has a parent with, a specific
17953 * class name. Used on detection of tracker objects and on deciding whether
17954 * hovering the tooltip should cause the active series to mouse out.
17955 *
17956 * @param {SVGDOMElement|HTMLDOMElement} element
17957 * The element to investigate.
17958 * @param {String} className
17959 * The class name to look for.
17960 *
17961 * @return {Boolean}
17962 * True if either the element or one of its parents has the given
17963 * class name.
17964 */
17965 inClass: function(element, className) {
17966 var elemClassName;
17967 while (element) {
17968 elemClassName = attr(element, 'class');
17969 if (elemClassName) {
17970 if (elemClassName.indexOf(className) !== -1) {
17971 return true;
17972 }
17973 if (elemClassName.indexOf('highcharts-container') !== -1) {
17974 return false;
17975 }
17976 }
17977 element = element.parentNode;
17978 }
17979 },
17980
17981 onTrackerMouseOut: function(e) {
17982 var series = this.chart.hoverSeries,
17983 relatedTarget = e.relatedTarget || e.toElement;
17984
17985 this.isDirectTouch = false;
17986
17987 if (
17988 series &&
17989 relatedTarget &&
17990 !series.stickyTracking &&
17991 !this.inClass(relatedTarget, 'highcharts-tooltip') &&
17992 (!this.inClass(
17993 relatedTarget,
17994 'highcharts-series-' + series.index
17995 ) || // #2499, #4465
17996 !this.inClass(relatedTarget, 'highcharts-tracker') // #5553
17997 )
17998 ) {
17999 series.onMouseOut();
18000 }
18001 },
18002
18003 onContainerClick: function(e) {
18004 var chart = this.chart,
18005 hoverPoint = chart.hoverPoint,
18006 plotLeft = chart.plotLeft,
18007 plotTop = chart.plotTop;
18008
18009 e = this.normalize(e);
18010
18011 if (!chart.cancelClick) {
18012
18013 // On tracker click, fire the series and point events. #783, #1583
18014 if (hoverPoint && this.inClass(e.target, 'highcharts-tracker')) {
18015
18016 // the series click event
18017 fireEvent(hoverPoint.series, 'click', extend(e, {
18018 point: hoverPoint
18019 }));
18020
18021 // the point click event
18022 if (chart.hoverPoint) { // it may be destroyed (#1844)
18023 hoverPoint.firePointEvent('click', e);
18024 }
18025
18026 // When clicking outside a tracker, fire a chart event
18027 } else {
18028 extend(e, this.getCoordinates(e));
18029
18030 // fire a click event in the chart
18031 if (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
18032 fireEvent(chart, 'click', e);
18033 }
18034 }
18035
18036
18037 }
18038 },
18039
18040 /**
18041 * Set the JS DOM events on the container and document. This method should contain
18042 * a one-to-one assignment between methods and their handlers. Any advanced logic should
18043 * be moved to the handler reflecting the event's name.
18044 *
18045 * @private
18046 */
18047 setDOMEvents: function() {
18048
18049 var pointer = this,
18050 container = pointer.chart.container,
18051 ownerDoc = container.ownerDocument;
18052
18053 container.onmousedown = function(e) {
18054 pointer.onContainerMouseDown(e);
18055 };
18056 container.onmousemove = function(e) {
18057 pointer.onContainerMouseMove(e);
18058 };
18059 container.onclick = function(e) {
18060 pointer.onContainerClick(e);
18061 };
18062 this.unbindContainerMouseLeave = addEvent(
18063 container,
18064 'mouseleave',
18065 pointer.onContainerMouseLeave
18066 );
18067 if (!H.unbindDocumentMouseUp) {
18068 H.unbindDocumentMouseUp = addEvent(
18069 ownerDoc,
18070 'mouseup',
18071 pointer.onDocumentMouseUp
18072 );
18073 }
18074 if (H.hasTouch) {
18075 container.ontouchstart = function(e) {
18076 pointer.onContainerTouchStart(e);
18077 };
18078 container.ontouchmove = function(e) {
18079 pointer.onContainerTouchMove(e);
18080 };
18081 if (!H.unbindDocumentTouchEnd) {
18082 H.unbindDocumentTouchEnd = addEvent(
18083 ownerDoc,
18084 'touchend',
18085 pointer.onDocumentTouchEnd
18086 );
18087 }
18088 }
18089
18090 },
18091
18092 /**
18093 * Destroys the Pointer object and disconnects DOM events.
18094 */
18095 destroy: function() {
18096 var pointer = this;
18097
18098 if (pointer.unDocMouseMove) {
18099 pointer.unDocMouseMove();
18100 }
18101
18102 this.unbindContainerMouseLeave();
18103
18104 if (!H.chartCount) {
18105 if (H.unbindDocumentMouseUp) {
18106 H.unbindDocumentMouseUp = H.unbindDocumentMouseUp();
18107 }
18108 if (H.unbindDocumentTouchEnd) {
18109 H.unbindDocumentTouchEnd = H.unbindDocumentTouchEnd();
18110 }
18111 }
18112
18113 // memory and CPU leak
18114 clearInterval(pointer.tooltipTimeout);
18115
18116 H.objectEach(pointer, function(val, prop) {
18117 pointer[prop] = null;
18118 });
18119 }
18120 };
18121
18122 }(Highcharts));
18123 (function(H) {
18124 /**
18125 * (c) 2010-2017 Torstein Honsi
18126 *
18127 * License: www.highcharts.com/license
18128 */
18129 var charts = H.charts,
18130 each = H.each,
18131 extend = H.extend,
18132 map = H.map,
18133 noop = H.noop,
18134 pick = H.pick,
18135 Pointer = H.Pointer;
18136
18137 /* Support for touch devices */
18138 extend(Pointer.prototype, /** @lends Pointer.prototype */ {
18139
18140 /**
18141 * Run translation operations
18142 */
18143 pinchTranslate: function(
18144 pinchDown,
18145 touches,
18146 transform,
18147 selectionMarker,
18148 clip,
18149 lastValidTouch
18150 ) {
18151 if (this.zoomHor) {
18152 this.pinchTranslateDirection(
18153 true,
18154 pinchDown,
18155 touches,
18156 transform,
18157 selectionMarker,
18158 clip,
18159 lastValidTouch
18160 );
18161 }
18162 if (this.zoomVert) {
18163 this.pinchTranslateDirection(
18164 false,
18165 pinchDown,
18166 touches,
18167 transform,
18168 selectionMarker,
18169 clip,
18170 lastValidTouch
18171 );
18172 }
18173 },
18174
18175 /**
18176 * Run translation operations for each direction (horizontal and vertical)
18177 * independently
18178 */
18179 pinchTranslateDirection: function(horiz, pinchDown, touches, transform,
18180 selectionMarker, clip, lastValidTouch, forcedScale) {
18181 var chart = this.chart,
18182 xy = horiz ? 'x' : 'y',
18183 XY = horiz ? 'X' : 'Y',
18184 sChartXY = 'chart' + XY,
18185 wh = horiz ? 'width' : 'height',
18186 plotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')],
18187 selectionWH,
18188 selectionXY,
18189 clipXY,
18190 scale = forcedScale || 1,
18191 inverted = chart.inverted,
18192 bounds = chart.bounds[horiz ? 'h' : 'v'],
18193 singleTouch = pinchDown.length === 1,
18194 touch0Start = pinchDown[0][sChartXY],
18195 touch0Now = touches[0][sChartXY],
18196 touch1Start = !singleTouch && pinchDown[1][sChartXY],
18197 touch1Now = !singleTouch && touches[1][sChartXY],
18198 outOfBounds,
18199 transformScale,
18200 scaleKey,
18201 setScale = function() {
18202 // Don't zoom if fingers are too close on this axis
18203 if (!singleTouch && Math.abs(touch0Start - touch1Start) > 20) {
18204 scale = forcedScale ||
18205 Math.abs(touch0Now - touch1Now) /
18206 Math.abs(touch0Start - touch1Start);
18207 }
18208
18209 clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start;
18210 selectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] /
18211 scale;
18212 };
18213
18214 // Set the scale, first pass
18215 setScale();
18216
18217 // The clip position (x or y) is altered if out of bounds, the selection
18218 // position is not
18219 selectionXY = clipXY;
18220
18221 // Out of bounds
18222 if (selectionXY < bounds.min) {
18223 selectionXY = bounds.min;
18224 outOfBounds = true;
18225 } else if (selectionXY + selectionWH > bounds.max) {
18226 selectionXY = bounds.max - selectionWH;
18227 outOfBounds = true;
18228 }
18229
18230 // Is the chart dragged off its bounds, determined by dataMin and
18231 // dataMax?
18232 if (outOfBounds) {
18233
18234 // Modify the touchNow position in order to create an elastic drag
18235 // movement. This indicates to the user that the chart is responsive
18236 // but can't be dragged further.
18237 touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]);
18238 if (!singleTouch) {
18239 touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]);
18240 }
18241
18242 // Set the scale, second pass to adapt to the modified touchNow
18243 // positions
18244 setScale();
18245
18246 } else {
18247 lastValidTouch[xy] = [touch0Now, touch1Now];
18248 }
18249
18250 // Set geometry for clipping, selection and transformation
18251 if (!inverted) {
18252 clip[xy] = clipXY - plotLeftTop;
18253 clip[wh] = selectionWH;
18254 }
18255 scaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY;
18256 transformScale = inverted ? 1 / scale : scale;
18257
18258 selectionMarker[wh] = selectionWH;
18259 selectionMarker[xy] = selectionXY;
18260 transform[scaleKey] = scale;
18261 transform['translate' + XY] = (transformScale * plotLeftTop) +
18262 (touch0Now - (transformScale * touch0Start));
18263 },
18264
18265 /**
18266 * Handle touch events with two touches
18267 */
18268 pinch: function(e) {
18269
18270 var self = this,
18271 chart = self.chart,
18272 pinchDown = self.pinchDown,
18273 touches = e.touches,
18274 touchesLength = touches.length,
18275 lastValidTouch = self.lastValidTouch,
18276 hasZoom = self.hasZoom,
18277 selectionMarker = self.selectionMarker,
18278 transform = {},
18279 fireClickEvent = touchesLength === 1 &&
18280 ((self.inClass(e.target, 'highcharts-tracker') &&
18281 chart.runTrackerClick) || self.runChartClick),
18282 clip = {};
18283
18284 // Don't initiate panning until the user has pinched. This prevents us
18285 // from blocking page scrolling as users scroll down a long page
18286 // (#4210).
18287 if (touchesLength > 1) {
18288 self.initiated = true;
18289 }
18290
18291 // On touch devices, only proceed to trigger click if a handler is
18292 // defined
18293 if (hasZoom && self.initiated && !fireClickEvent) {
18294 e.preventDefault();
18295 }
18296
18297 // Normalize each touch
18298 map(touches, function(e) {
18299 return self.normalize(e);
18300 });
18301
18302 // Register the touch start position
18303 if (e.type === 'touchstart') {
18304 each(touches, function(e, i) {
18305 pinchDown[i] = {
18306 chartX: e.chartX,
18307 chartY: e.chartY
18308 };
18309 });
18310 lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] &&
18311 pinchDown[1].chartX
18312 ];
18313 lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] &&
18314 pinchDown[1].chartY
18315 ];
18316
18317 // Identify the data bounds in pixels
18318 each(chart.axes, function(axis) {
18319 if (axis.zoomEnabled) {
18320 var bounds = chart.bounds[axis.horiz ? 'h' : 'v'],
18321 minPixelPadding = axis.minPixelPadding,
18322 min = axis.toPixels(
18323 pick(axis.options.min, axis.dataMin)
18324 ),
18325 max = axis.toPixels(
18326 pick(axis.options.max, axis.dataMax)
18327 ),
18328 absMin = Math.min(min, max),
18329 absMax = Math.max(min, max);
18330
18331 // Store the bounds for use in the touchmove handler
18332 bounds.min = Math.min(axis.pos, absMin - minPixelPadding);
18333 bounds.max = Math.max(
18334 axis.pos + axis.len,
18335 absMax + minPixelPadding
18336 );
18337 }
18338 });
18339 self.res = true; // reset on next move
18340
18341 // Optionally move the tooltip on touchmove
18342 } else if (self.followTouchMove && touchesLength === 1) {
18343 this.runPointActions(self.normalize(e));
18344
18345 // Event type is touchmove, handle panning and pinching
18346 } else if (pinchDown.length) { // can be 0 when releasing, if touchend
18347 // fires first
18348
18349
18350 // Set the marker
18351 if (!selectionMarker) {
18352 self.selectionMarker = selectionMarker = extend({
18353 destroy: noop,
18354 touch: true
18355 }, chart.plotBox);
18356 }
18357
18358 self.pinchTranslate(
18359 pinchDown,
18360 touches,
18361 transform,
18362 selectionMarker,
18363 clip,
18364 lastValidTouch
18365 );
18366
18367 self.hasPinched = hasZoom;
18368
18369 // Scale and translate the groups to provide visual feedback during
18370 // pinching
18371 self.scaleGroups(transform, clip);
18372
18373 if (self.res) {
18374 self.res = false;
18375 this.reset(false, 0);
18376 }
18377 }
18378 },
18379
18380 /**
18381 * General touch handler shared by touchstart and touchmove.
18382 */
18383 touch: function(e, start) {
18384 var chart = this.chart,
18385 hasMoved,
18386 pinchDown,
18387 isInside;
18388
18389 if (chart.index !== H.hoverChartIndex) {
18390 this.onContainerMouseLeave({
18391 relatedTarget: true
18392 });
18393 }
18394 H.hoverChartIndex = chart.index;
18395
18396 if (e.touches.length === 1) {
18397
18398 e = this.normalize(e);
18399
18400 isInside = chart.isInsidePlot(
18401 e.chartX - chart.plotLeft,
18402 e.chartY - chart.plotTop
18403 );
18404 if (isInside && !chart.openMenu) {
18405
18406 // Run mouse events and display tooltip etc
18407 if (start) {
18408 this.runPointActions(e);
18409 }
18410
18411 // Android fires touchmove events after the touchstart even if
18412 // the finger hasn't moved, or moved only a pixel or two. In iOS
18413 // however, the touchmove doesn't fire unless the finger moves
18414 // more than ~4px. So we emulate this behaviour in Android by
18415 // checking how much it moved, and cancelling on small
18416 // distances. #3450.
18417 if (e.type === 'touchmove') {
18418 pinchDown = this.pinchDown;
18419 hasMoved = pinchDown[0] ? Math.sqrt( // #5266
18420 Math.pow(pinchDown[0].chartX - e.chartX, 2) +
18421 Math.pow(pinchDown[0].chartY - e.chartY, 2)
18422 ) >= 4 : false;
18423 }
18424
18425 if (pick(hasMoved, true)) {
18426 this.pinch(e);
18427 }
18428
18429 } else if (start) {
18430 // Hide the tooltip on touching outside the plot area (#1203)
18431 this.reset();
18432 }
18433
18434 } else if (e.touches.length === 2) {
18435 this.pinch(e);
18436 }
18437 },
18438
18439 onContainerTouchStart: function(e) {
18440 this.zoomOption(e);
18441 this.touch(e, true);
18442 },
18443
18444 onContainerTouchMove: function(e) {
18445 this.touch(e);
18446 },
18447
18448 onDocumentTouchEnd: function(e) {
18449 if (charts[H.hoverChartIndex]) {
18450 charts[H.hoverChartIndex].pointer.drop(e);
18451 }
18452 }
18453
18454 });
18455
18456 }(Highcharts));
18457 (function(H) {
18458 /**
18459 * (c) 2010-2017 Torstein Honsi
18460 *
18461 * License: www.highcharts.com/license
18462 */
18463 var addEvent = H.addEvent,
18464 charts = H.charts,
18465 css = H.css,
18466 doc = H.doc,
18467 extend = H.extend,
18468 hasTouch = H.hasTouch,
18469 noop = H.noop,
18470 Pointer = H.Pointer,
18471 removeEvent = H.removeEvent,
18472 win = H.win,
18473 wrap = H.wrap;
18474
18475 if (!hasTouch && (win.PointerEvent || win.MSPointerEvent)) {
18476
18477 // The touches object keeps track of the points being touched at all times
18478 var touches = {},
18479 hasPointerEvent = !!win.PointerEvent,
18480 getWebkitTouches = function() {
18481 var fake = [];
18482 fake.item = function(i) {
18483 return this[i];
18484 };
18485 H.objectEach(touches, function(touch) {
18486 fake.push({
18487 pageX: touch.pageX,
18488 pageY: touch.pageY,
18489 target: touch.target
18490 });
18491 });
18492 return fake;
18493 },
18494 translateMSPointer = function(e, method, wktype, func) {
18495 var p;
18496 if ((e.pointerType === 'touch' || e.pointerType === e.MSPOINTER_TYPE_TOUCH) && charts[H.hoverChartIndex]) {
18497 func(e);
18498 p = charts[H.hoverChartIndex].pointer;
18499 p[method]({
18500 type: wktype,
18501 target: e.currentTarget,
18502 preventDefault: noop,
18503 touches: getWebkitTouches()
18504 });
18505 }
18506 };
18507
18508 /**
18509 * Extend the Pointer prototype with methods for each event handler and more
18510 */
18511 extend(Pointer.prototype, /** @lends Pointer.prototype */ {
18512 onContainerPointerDown: function(e) {
18513 translateMSPointer(e, 'onContainerTouchStart', 'touchstart', function(e) {
18514 touches[e.pointerId] = {
18515 pageX: e.pageX,
18516 pageY: e.pageY,
18517 target: e.currentTarget
18518 };
18519 });
18520 },
18521 onContainerPointerMove: function(e) {
18522 translateMSPointer(e, 'onContainerTouchMove', 'touchmove', function(e) {
18523 touches[e.pointerId] = {
18524 pageX: e.pageX,
18525 pageY: e.pageY
18526 };
18527 if (!touches[e.pointerId].target) {
18528 touches[e.pointerId].target = e.currentTarget;
18529 }
18530 });
18531 },
18532 onDocumentPointerUp: function(e) {
18533 translateMSPointer(e, 'onDocumentTouchEnd', 'touchend', function(e) {
18534 delete touches[e.pointerId];
18535 });
18536 },
18537
18538 /**
18539 * Add or remove the MS Pointer specific events
18540 */
18541 batchMSEvents: function(fn) {
18542 fn(this.chart.container, hasPointerEvent ? 'pointerdown' : 'MSPointerDown', this.onContainerPointerDown);
18543 fn(this.chart.container, hasPointerEvent ? 'pointermove' : 'MSPointerMove', this.onContainerPointerMove);
18544 fn(doc, hasPointerEvent ? 'pointerup' : 'MSPointerUp', this.onDocumentPointerUp);
18545 }
18546 });
18547
18548 // Disable default IE actions for pinch and such on chart element
18549 wrap(Pointer.prototype, 'init', function(proceed, chart, options) {
18550 proceed.call(this, chart, options);
18551 if (this.hasZoom) { // #4014
18552 css(chart.container, {
18553 '-ms-touch-action': 'none',
18554 'touch-action': 'none'
18555 });
18556 }
18557 });
18558
18559 // Add IE specific touch events to chart
18560 wrap(Pointer.prototype, 'setDOMEvents', function(proceed) {
18561 proceed.apply(this);
18562 if (this.hasZoom || this.followTouchMove) {
18563 this.batchMSEvents(addEvent);
18564 }
18565 });
18566 // Destroy MS events also
18567 wrap(Pointer.prototype, 'destroy', function(proceed) {
18568 this.batchMSEvents(removeEvent);
18569 proceed.call(this);
18570 });
18571 }
18572
18573 }(Highcharts));
18574 (function(Highcharts) {
18575 /**
18576 * (c) 2010-2017 Torstein Honsi
18577 *
18578 * License: www.highcharts.com/license
18579 */
18580 var H = Highcharts,
18581
18582 addEvent = H.addEvent,
18583 css = H.css,
18584 discardElement = H.discardElement,
18585 defined = H.defined,
18586 each = H.each,
18587 isFirefox = H.isFirefox,
18588 marginNames = H.marginNames,
18589 merge = H.merge,
18590 pick = H.pick,
18591 setAnimation = H.setAnimation,
18592 stableSort = H.stableSort,
18593 win = H.win,
18594 wrap = H.wrap;
18595
18596 /**
18597 * The overview of the chart's series. The legend object is instanciated
18598 * internally in the chart constructor, and available from `chart.legend`. Each
18599 * chart has only one legend.
18600 *
18601 * @class
18602 */
18603 Highcharts.Legend = function(chart, options) {
18604 this.init(chart, options);
18605 };
18606
18607 Highcharts.Legend.prototype = {
18608
18609 /**
18610 * Initialize the legend.
18611 *
18612 * @private
18613 */
18614 init: function(chart, options) {
18615
18616 this.chart = chart;
18617
18618 this.setOptions(options);
18619
18620 if (options.enabled) {
18621
18622 // Render it
18623 this.render();
18624
18625 // move checkboxes
18626 addEvent(this.chart, 'endResize', function() {
18627 this.legend.positionCheckboxes();
18628 });
18629 }
18630 },
18631
18632 setOptions: function(options) {
18633
18634 var padding = pick(options.padding, 8);
18635
18636 this.options = options;
18637
18638
18639 this.itemStyle = options.itemStyle;
18640 this.itemHiddenStyle = merge(this.itemStyle, options.itemHiddenStyle);
18641
18642 this.itemMarginTop = options.itemMarginTop || 0;
18643 this.padding = padding;
18644 this.initialItemY = padding - 5; // 5 is pixels above the text
18645 this.maxItemWidth = 0;
18646 this.itemHeight = 0;
18647 this.symbolWidth = pick(options.symbolWidth, 16);
18648 this.pages = [];
18649
18650 },
18651
18652 /**
18653 * Update the legend with new options. Equivalent to running `chart.update`
18654 * with a legend configuration option.
18655 * @param {LegendOptions} options
18656 * Legend options.
18657 * @param {Boolean} [redraw=true]
18658 * Whether to redraw the chart.
18659 *
18660 * @sample highcharts/legend/legend-update/
18661 * Legend update
18662 */
18663 update: function(options, redraw) {
18664 var chart = this.chart;
18665
18666 this.setOptions(merge(true, this.options, options));
18667 this.destroy();
18668 chart.isDirtyLegend = chart.isDirtyBox = true;
18669 if (pick(redraw, true)) {
18670 chart.redraw();
18671 }
18672 },
18673
18674 /**
18675 * Set the colors for the legend item.
18676 *
18677 * @private
18678 * @param {Series|Point} item
18679 * A Series or Point instance
18680 * @param {Boolean} visible
18681 * Dimmed or colored
18682 */
18683 colorizeItem: function(item, visible) {
18684 item.legendGroup[visible ? 'removeClass' : 'addClass'](
18685 'highcharts-legend-item-hidden'
18686 );
18687
18688
18689 var legend = this,
18690 options = legend.options,
18691 legendItem = item.legendItem,
18692 legendLine = item.legendLine,
18693 legendSymbol = item.legendSymbol,
18694 hiddenColor = legend.itemHiddenStyle.color,
18695 textColor = visible ? options.itemStyle.color : hiddenColor,
18696 symbolColor = visible ? (item.color || hiddenColor) : hiddenColor,
18697 markerOptions = item.options && item.options.marker,
18698 symbolAttr = {
18699 fill: symbolColor
18700 };
18701
18702 if (legendItem) {
18703 legendItem.css({
18704 fill: textColor,
18705 color: textColor // #1553, oldIE
18706 });
18707 }
18708 if (legendLine) {
18709 legendLine.attr({
18710 stroke: symbolColor
18711 });
18712 }
18713
18714 if (legendSymbol) {
18715
18716 // Apply marker options
18717 if (markerOptions && legendSymbol.isMarker) { // #585
18718 symbolAttr = item.pointAttribs();
18719 if (!visible) {
18720 symbolAttr.stroke = symbolAttr.fill = hiddenColor; // #6769
18721 }
18722 }
18723
18724 legendSymbol.attr(symbolAttr);
18725 }
18726
18727 },
18728
18729 /**
18730 * Position the legend item.
18731 *
18732 * @private
18733 * @param {Series|Point} item
18734 * The item to position
18735 */
18736 positionItem: function(item) {
18737 var legend = this,
18738 options = legend.options,
18739 symbolPadding = options.symbolPadding,
18740 ltr = !options.rtl,
18741 legendItemPos = item._legendItemPos,
18742 itemX = legendItemPos[0],
18743 itemY = legendItemPos[1],
18744 checkbox = item.checkbox,
18745 legendGroup = item.legendGroup;
18746
18747 if (legendGroup && legendGroup.element) {
18748 legendGroup.translate(
18749 ltr ?
18750 itemX :
18751 legend.legendWidth - itemX - 2 * symbolPadding - 4,
18752 itemY
18753 );
18754 }
18755
18756 if (checkbox) {
18757 checkbox.x = itemX;
18758 checkbox.y = itemY;
18759 }
18760 },
18761
18762 /**
18763 * Destroy a single legend item, used internally on removing series items.
18764 *
18765 * @param {Series|Point} item
18766 * The item to remove
18767 */
18768 destroyItem: function(item) {
18769 var checkbox = item.checkbox;
18770
18771 // destroy SVG elements
18772 each(
18773 ['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'],
18774 function(key) {
18775 if (item[key]) {
18776 item[key] = item[key].destroy();
18777 }
18778 }
18779 );
18780
18781 if (checkbox) {
18782 discardElement(item.checkbox);
18783 }
18784 },
18785
18786 /**
18787 * Destroy the legend. Used internally. To reflow objects, `chart.redraw`
18788 * must be called after destruction.
18789 */
18790 destroy: function() {
18791 function destroyItems(key) {
18792 if (this[key]) {
18793 this[key] = this[key].destroy();
18794 }
18795 }
18796
18797 // Destroy items
18798 each(this.getAllItems(), function(item) {
18799 each(['legendItem', 'legendGroup'], destroyItems, item);
18800 });
18801
18802 // Destroy legend elements
18803 each([
18804 'clipRect',
18805 'up',
18806 'down',
18807 'pager',
18808 'nav',
18809 'box',
18810 'title',
18811 'group'
18812 ], destroyItems, this);
18813 this.display = null; // Reset in .render on update.
18814 },
18815
18816 /**
18817 * Position the checkboxes after the width is determined.
18818 *
18819 * @private
18820 */
18821 positionCheckboxes: function() {
18822 var alignAttr = this.group && this.group.alignAttr,
18823 translateY,
18824 clipHeight = this.clipHeight || this.legendHeight,
18825 titleHeight = this.titleHeight;
18826
18827 if (alignAttr) {
18828 translateY = alignAttr.translateY;
18829 each(this.allItems, function(item) {
18830 var checkbox = item.checkbox,
18831 top;
18832
18833 if (checkbox) {
18834 top = translateY + titleHeight + checkbox.y +
18835 (this.scrollOffset || 0) + 3;
18836 css(checkbox, {
18837 left: (alignAttr.translateX + item.checkboxOffset +
18838 checkbox.x - 20) + 'px',
18839 top: top + 'px',
18840 display: top > translateY - 6 && top < translateY +
18841 clipHeight - 6 ? '' : 'none'
18842 });
18843 }
18844 }, this);
18845 }
18846 },
18847
18848 /**
18849 * Render the legend title on top of the legend.
18850 *
18851 * @private
18852 */
18853 renderTitle: function() {
18854 var options = this.options,
18855 padding = this.padding,
18856 titleOptions = options.title,
18857 titleHeight = 0,
18858 bBox;
18859
18860 if (titleOptions.text) {
18861 if (!this.title) {
18862 this.title = this.chart.renderer.label(
18863 titleOptions.text,
18864 padding - 3,
18865 padding - 4,
18866 null,
18867 null,
18868 null,
18869 options.useHTML,
18870 null,
18871 'legend-title'
18872 )
18873 .attr({
18874 zIndex: 1
18875 })
18876
18877 .css(titleOptions.style)
18878
18879 .add(this.group);
18880 }
18881 bBox = this.title.getBBox();
18882 titleHeight = bBox.height;
18883 this.offsetWidth = bBox.width; // #1717
18884 this.contentGroup.attr({
18885 translateY: titleHeight
18886 });
18887 }
18888 this.titleHeight = titleHeight;
18889 },
18890
18891 /**
18892 * Set the legend item text.
18893 *
18894 * @param {Series|Point} item
18895 * The item for which to update the text in the legend.
18896 */
18897 setText: function(item) {
18898 var options = this.options;
18899 item.legendItem.attr({
18900 text: options.labelFormat ?
18901 H.format(options.labelFormat, item) : options.labelFormatter.call(item)
18902 });
18903 },
18904
18905 /**
18906 * Render a single specific legend item. Called internally from the `render`
18907 * function.
18908 *
18909 * @private
18910 * @param {Series|Point} item
18911 * The item to render.
18912 */
18913 renderItem: function(item) {
18914 var legend = this,
18915 chart = legend.chart,
18916 renderer = chart.renderer,
18917 options = legend.options,
18918 horizontal = options.layout === 'horizontal',
18919 symbolWidth = legend.symbolWidth,
18920 symbolPadding = options.symbolPadding,
18921
18922 itemStyle = legend.itemStyle,
18923 itemHiddenStyle = legend.itemHiddenStyle,
18924
18925 padding = legend.padding,
18926 itemDistance = horizontal ? pick(options.itemDistance, 20) : 0,
18927 ltr = !options.rtl,
18928 itemHeight,
18929 widthOption = options.width,
18930 itemMarginBottom = options.itemMarginBottom || 0,
18931 itemMarginTop = legend.itemMarginTop,
18932 bBox,
18933 itemWidth,
18934 li = item.legendItem,
18935 isSeries = !item.series,
18936 series = !isSeries && item.series.drawLegendSymbol ?
18937 item.series :
18938 item,
18939 seriesOptions = series.options,
18940 showCheckbox = legend.createCheckboxForItem &&
18941 seriesOptions &&
18942 seriesOptions.showCheckbox,
18943 // full width minus text width
18944 itemExtraWidth = symbolWidth + symbolPadding + itemDistance +
18945 (showCheckbox ? 20 : 0),
18946 useHTML = options.useHTML,
18947 fontSize = 12,
18948 itemClassName = item.options.className;
18949
18950 if (!li) { // generate it once, later move it
18951
18952 // Generate the group box, a group to hold the symbol and text. Text
18953 // is to be appended in Legend class.
18954 item.legendGroup = renderer.g('legend-item')
18955 .addClass(
18956 'highcharts-' + series.type + '-series ' +
18957 'highcharts-color-' + item.colorIndex +
18958 (itemClassName ? ' ' + itemClassName : '') +
18959 (isSeries ? ' highcharts-series-' + item.index : '')
18960 )
18961 .attr({
18962 zIndex: 1
18963 })
18964 .add(legend.scrollGroup);
18965
18966 // Generate the list item text and add it to the group
18967 item.legendItem = li = renderer.text(
18968 '',
18969 ltr ? symbolWidth + symbolPadding : -symbolPadding,
18970 legend.baseline || 0,
18971 useHTML
18972 )
18973
18974 // merge to prevent modifying original (#1021)
18975 .css(merge(item.visible ? itemStyle : itemHiddenStyle))
18976
18977 .attr({
18978 align: ltr ? 'left' : 'right',
18979 zIndex: 2
18980 })
18981 .add(item.legendGroup);
18982
18983 // Get the baseline for the first item - the font size is equal for
18984 // all
18985 if (!legend.baseline) {
18986
18987 fontSize = itemStyle.fontSize;
18988
18989 legend.fontMetrics = renderer.fontMetrics(
18990 fontSize,
18991 li
18992 );
18993 legend.baseline = legend.fontMetrics.f + 3 + itemMarginTop;
18994 li.attr('y', legend.baseline);
18995 }
18996
18997 // Draw the legend symbol inside the group box
18998 legend.symbolHeight = options.symbolHeight || legend.fontMetrics.f;
18999 series.drawLegendSymbol(legend, item);
19000
19001 if (legend.setItemEvents) {
19002 legend.setItemEvents(item, li, useHTML);
19003 }
19004
19005 // add the HTML checkbox on top
19006 if (showCheckbox) {
19007 legend.createCheckboxForItem(item);
19008 }
19009 }
19010
19011 // Colorize the items
19012 legend.colorizeItem(item, item.visible);
19013
19014 // Take care of max width and text overflow (#6659)
19015
19016 if (!itemStyle.width) {
19017
19018 li.css({
19019 width: (
19020 options.itemWidth ||
19021 options.width ||
19022 chart.spacingBox.width
19023 ) - itemExtraWidth
19024 });
19025
19026 }
19027
19028
19029 // Always update the text
19030 legend.setText(item);
19031
19032 // calculate the positions for the next line
19033 bBox = li.getBBox();
19034
19035 itemWidth = item.checkboxOffset =
19036 options.itemWidth ||
19037 item.legendItemWidth ||
19038 bBox.width + itemExtraWidth;
19039 legend.itemHeight = itemHeight = Math.round(
19040 item.legendItemHeight || bBox.height || legend.symbolHeight
19041 );
19042
19043 // If the item exceeds the width, start a new line
19044 if (
19045 horizontal &&
19046 legend.itemX - padding + itemWidth > (
19047 widthOption || (
19048 chart.spacingBox.width - 2 * padding - options.x
19049 )
19050 )
19051 ) {
19052 legend.itemX = padding;
19053 legend.itemY += itemMarginTop + legend.lastLineHeight +
19054 itemMarginBottom;
19055 legend.lastLineHeight = 0; // reset for next line (#915, #3976)
19056 }
19057
19058 // If the item exceeds the height, start a new column
19059 /*
19060 if (!horizontal && legend.itemY + options.y +
19061 itemHeight > chart.chartHeight - spacingTop - spacingBottom) {
19062 legend.itemY = legend.initialItemY;
19063 legend.itemX += legend.maxItemWidth;
19064 legend.maxItemWidth = 0;
19065 }
19066 */
19067
19068 // Set the edge positions
19069 legend.maxItemWidth = Math.max(legend.maxItemWidth, itemWidth);
19070 legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom;
19071 legend.lastLineHeight = Math.max( // #915
19072 itemHeight,
19073 legend.lastLineHeight
19074 );
19075
19076 // cache the position of the newly generated or reordered items
19077 item._legendItemPos = [legend.itemX, legend.itemY];
19078
19079 // advance
19080 if (horizontal) {
19081 legend.itemX += itemWidth;
19082
19083 } else {
19084 legend.itemY += itemMarginTop + itemHeight + itemMarginBottom;
19085 legend.lastLineHeight = itemHeight;
19086 }
19087
19088 // the width of the widest item
19089 legend.offsetWidth = widthOption || Math.max(
19090 (
19091 horizontal ? legend.itemX - padding - (item.checkbox ?
19092 // decrease by itemDistance only when no checkbox #4853
19093 0 :
19094 itemDistance
19095 ) : itemWidth
19096 ) + padding,
19097 legend.offsetWidth
19098 );
19099 },
19100
19101 /**
19102 * Get all items, which is one item per series for most series and one
19103 * item per point for pie series and its derivatives.
19104 *
19105 * @return {Array.<Series|Point>}
19106 * The current items in the legend.
19107 */
19108 getAllItems: function() {
19109 var allItems = [];
19110 each(this.chart.series, function(series) {
19111 var seriesOptions = series && series.options;
19112
19113 // Handle showInLegend. If the series is linked to another series,
19114 // defaults to false.
19115 if (series && pick(
19116 seriesOptions.showInLegend, !defined(seriesOptions.linkedTo) ? undefined : false, true
19117 )) {
19118
19119 // Use points or series for the legend item depending on
19120 // legendType
19121 allItems = allItems.concat(
19122 series.legendItems ||
19123 (
19124 seriesOptions.legendType === 'point' ?
19125 series.data :
19126 series
19127 )
19128 );
19129 }
19130 });
19131 return allItems;
19132 },
19133
19134 /**
19135 * Adjust the chart margins by reserving space for the legend on only one
19136 * side of the chart. If the position is set to a corner, top or bottom is
19137 * reserved for horizontal legends and left or right for vertical ones.
19138 *
19139 * @private
19140 */
19141 adjustMargins: function(margin, spacing) {
19142 var chart = this.chart,
19143 options = this.options,
19144 // Use the first letter of each alignment option in order to detect
19145 // the side. (#4189 - use charAt(x) notation instead of [x] for IE7)
19146 alignment = options.align.charAt(0) +
19147 options.verticalAlign.charAt(0) +
19148 options.layout.charAt(0);
19149
19150 if (!options.floating) {
19151
19152 each([
19153 /(lth|ct|rth)/,
19154 /(rtv|rm|rbv)/,
19155 /(rbh|cb|lbh)/,
19156 /(lbv|lm|ltv)/
19157 ], function(alignments, side) {
19158 if (alignments.test(alignment) && !defined(margin[side])) {
19159 // Now we have detected on which side of the chart we should
19160 // reserve space for the legend
19161 chart[marginNames[side]] = Math.max(
19162 chart[marginNames[side]],
19163 (
19164 chart.legend[
19165 (side + 1) % 2 ? 'legendHeight' : 'legendWidth'
19166 ] + [1, -1, -1, 1][side] * options[
19167 (side % 2) ? 'x' : 'y'
19168 ] +
19169 pick(options.margin, 12) +
19170 spacing[side]
19171 )
19172 );
19173 }
19174 });
19175 }
19176 },
19177
19178 /**
19179 * Render the legend. This method can be called both before and after
19180 * `chart.render`. If called after, it will only rearrange items instead
19181 * of creating new ones. Called internally on initial render and after
19182 * redraws.
19183 */
19184 render: function() {
19185 var legend = this,
19186 chart = legend.chart,
19187 renderer = chart.renderer,
19188 legendGroup = legend.group,
19189 allItems,
19190 display,
19191 legendWidth,
19192 legendHeight,
19193 box = legend.box,
19194 options = legend.options,
19195 padding = legend.padding;
19196
19197 legend.itemX = padding;
19198 legend.itemY = legend.initialItemY;
19199 legend.offsetWidth = 0;
19200 legend.lastItemY = 0;
19201
19202 if (!legendGroup) {
19203 legend.group = legendGroup = renderer.g('legend')
19204 .attr({
19205 zIndex: 7
19206 })
19207 .add();
19208 legend.contentGroup = renderer.g()
19209 .attr({
19210 zIndex: 1
19211 }) // above background
19212 .add(legendGroup);
19213 legend.scrollGroup = renderer.g()
19214 .add(legend.contentGroup);
19215 }
19216
19217 legend.renderTitle();
19218
19219 // add each series or point
19220 allItems = legend.getAllItems();
19221
19222 // sort by legendIndex
19223 stableSort(allItems, function(a, b) {
19224 return ((a.options && a.options.legendIndex) || 0) -
19225 ((b.options && b.options.legendIndex) || 0);
19226 });
19227
19228 // reversed legend
19229 if (options.reversed) {
19230 allItems.reverse();
19231 }
19232
19233 legend.allItems = allItems;
19234 legend.display = display = !!allItems.length;
19235
19236 // render the items
19237 legend.lastLineHeight = 0;
19238 each(allItems, function(item) {
19239 legend.renderItem(item);
19240 });
19241
19242 // Get the box
19243 legendWidth = (options.width || legend.offsetWidth) + padding;
19244 legendHeight = legend.lastItemY + legend.lastLineHeight +
19245 legend.titleHeight;
19246 legendHeight = legend.handleOverflow(legendHeight);
19247 legendHeight += padding;
19248
19249 // Draw the border and/or background
19250 if (!box) {
19251 legend.box = box = renderer.rect()
19252 .addClass('highcharts-legend-box')
19253 .attr({
19254 r: options.borderRadius
19255 })
19256 .add(legendGroup);
19257 box.isNew = true;
19258 }
19259
19260
19261 // Presentational
19262 box
19263 .attr({
19264 stroke: options.borderColor,
19265 'stroke-width': options.borderWidth || 0,
19266 fill: options.backgroundColor || 'none'
19267 })
19268 .shadow(options.shadow);
19269
19270
19271 if (legendWidth > 0 && legendHeight > 0) {
19272 box[box.isNew ? 'attr' : 'animate'](
19273 box.crisp.call({}, { // #7260
19274 x: 0,
19275 y: 0,
19276 width: legendWidth,
19277 height: legendHeight
19278 }, box.strokeWidth())
19279 );
19280 box.isNew = false;
19281 }
19282
19283 // hide the border if no items
19284 box[display ? 'show' : 'hide']();
19285
19286
19287
19288 legend.legendWidth = legendWidth;
19289 legend.legendHeight = legendHeight;
19290
19291 // Now that the legend width and height are established, put the items
19292 // in the final position
19293 each(allItems, function(item) {
19294 legend.positionItem(item);
19295 });
19296
19297 if (display) {
19298 legendGroup.align(merge(options, {
19299 width: legendWidth,
19300 height: legendHeight
19301 }), true, 'spacingBox');
19302 }
19303
19304 if (!chart.isResizing) {
19305 this.positionCheckboxes();
19306 }
19307 },
19308
19309 /**
19310 * Set up the overflow handling by adding navigation with up and down arrows
19311 * below the legend.
19312 *
19313 * @private
19314 */
19315 handleOverflow: function(legendHeight) {
19316 var legend = this,
19317 chart = this.chart,
19318 renderer = chart.renderer,
19319 options = this.options,
19320 optionsY = options.y,
19321 alignTop = options.verticalAlign === 'top',
19322 padding = this.padding,
19323 spaceHeight = chart.spacingBox.height +
19324 (alignTop ? -optionsY : optionsY) - padding,
19325 maxHeight = options.maxHeight,
19326 clipHeight,
19327 clipRect = this.clipRect,
19328 navOptions = options.navigation,
19329 animation = pick(navOptions.animation, true),
19330 arrowSize = navOptions.arrowSize || 12,
19331 nav = this.nav,
19332 pages = this.pages,
19333 lastY,
19334 allItems = this.allItems,
19335 clipToHeight = function(height) {
19336 if (typeof height === 'number') {
19337 clipRect.attr({
19338 height: height
19339 });
19340 } else if (clipRect) { // Reset (#5912)
19341 legend.clipRect = clipRect.destroy();
19342 legend.contentGroup.clip();
19343 }
19344
19345 // useHTML
19346 if (legend.contentGroup.div) {
19347 legend.contentGroup.div.style.clip = height ?
19348 'rect(' + padding + 'px,9999px,' +
19349 (padding + height) + 'px,0)' :
19350 'auto';
19351 }
19352 };
19353
19354
19355 // Adjust the height
19356 if (
19357 options.layout === 'horizontal' &&
19358 options.verticalAlign !== 'middle' &&
19359 !options.floating
19360 ) {
19361 spaceHeight /= 2;
19362 }
19363 if (maxHeight) {
19364 spaceHeight = Math.min(spaceHeight, maxHeight);
19365 }
19366
19367 // Reset the legend height and adjust the clipping rectangle
19368 pages.length = 0;
19369 if (legendHeight > spaceHeight && navOptions.enabled !== false) {
19370
19371 this.clipHeight = clipHeight =
19372 Math.max(spaceHeight - 20 - this.titleHeight - padding, 0);
19373 this.currentPage = pick(this.currentPage, 1);
19374 this.fullHeight = legendHeight;
19375
19376 // Fill pages with Y positions so that the top of each a legend item
19377 // defines the scroll top for each page (#2098)
19378 each(allItems, function(item, i) {
19379 var y = item._legendItemPos[1],
19380 h = Math.round(item.legendItem.getBBox().height),
19381 len = pages.length;
19382
19383 if (!len || (y - pages[len - 1] > clipHeight &&
19384 (lastY || y) !== pages[len - 1])) {
19385 pages.push(lastY || y);
19386 len++;
19387 }
19388
19389 if (i === allItems.length - 1 &&
19390 y + h - pages[len - 1] > clipHeight) {
19391 pages.push(y);
19392 }
19393 if (y !== lastY) {
19394 lastY = y;
19395 }
19396 });
19397
19398 // Only apply clipping if needed. Clipping causes blurred legend in
19399 // PDF export (#1787)
19400 if (!clipRect) {
19401 clipRect = legend.clipRect =
19402 renderer.clipRect(0, padding, 9999, 0);
19403 legend.contentGroup.clip(clipRect);
19404 }
19405
19406 clipToHeight(clipHeight);
19407
19408 // Add navigation elements
19409 if (!nav) {
19410 this.nav = nav = renderer.g()
19411 .attr({
19412 zIndex: 1
19413 })
19414 .add(this.group);
19415
19416 this.up = renderer
19417 .symbol(
19418 'triangle',
19419 0,
19420 0,
19421 arrowSize,
19422 arrowSize
19423 )
19424 .on('click', function() {
19425 legend.scroll(-1, animation);
19426 })
19427 .add(nav);
19428
19429 this.pager = renderer.text('', 15, 10)
19430 .addClass('highcharts-legend-navigation')
19431
19432 .css(navOptions.style)
19433
19434 .add(nav);
19435
19436 this.down = renderer
19437 .symbol(
19438 'triangle-down',
19439 0,
19440 0,
19441 arrowSize,
19442 arrowSize
19443 )
19444 .on('click', function() {
19445 legend.scroll(1, animation);
19446 })
19447 .add(nav);
19448 }
19449
19450 // Set initial position
19451 legend.scroll(0);
19452
19453 legendHeight = spaceHeight;
19454
19455 // Reset
19456 } else if (nav) {
19457 clipToHeight();
19458 this.nav = nav.destroy(); // #6322
19459 this.scrollGroup.attr({
19460 translateY: 1
19461 });
19462 this.clipHeight = 0; // #1379
19463 }
19464
19465 return legendHeight;
19466 },
19467
19468 /**
19469 * Scroll the legend by a number of pages.
19470 * @param {Number} scrollBy
19471 * The number of pages to scroll.
19472 * @param {AnimationOptions} animation
19473 * Whether and how to apply animation.
19474 */
19475 scroll: function(scrollBy, animation) {
19476 var pages = this.pages,
19477 pageCount = pages.length,
19478 currentPage = this.currentPage + scrollBy,
19479 clipHeight = this.clipHeight,
19480 navOptions = this.options.navigation,
19481 pager = this.pager,
19482 padding = this.padding;
19483
19484 // When resizing while looking at the last page
19485 if (currentPage > pageCount) {
19486 currentPage = pageCount;
19487 }
19488
19489 if (currentPage > 0) {
19490
19491 if (animation !== undefined) {
19492 setAnimation(animation, this.chart);
19493 }
19494
19495 this.nav.attr({
19496 translateX: padding,
19497 translateY: clipHeight + this.padding + 7 + this.titleHeight,
19498 visibility: 'visible'
19499 });
19500 this.up.attr({
19501 'class': currentPage === 1 ?
19502 'highcharts-legend-nav-inactive' : 'highcharts-legend-nav-active'
19503 });
19504 pager.attr({
19505 text: currentPage + '/' + pageCount
19506 });
19507 this.down.attr({
19508 'x': 18 + this.pager.getBBox().width, // adjust to text width
19509 'class': currentPage === pageCount ?
19510 'highcharts-legend-nav-inactive' : 'highcharts-legend-nav-active'
19511 });
19512
19513
19514 this.up
19515 .attr({
19516 fill: currentPage === 1 ?
19517 navOptions.inactiveColor : navOptions.activeColor
19518 })
19519 .css({
19520 cursor: currentPage === 1 ? 'default' : 'pointer'
19521 });
19522 this.down
19523 .attr({
19524 fill: currentPage === pageCount ?
19525 navOptions.inactiveColor : navOptions.activeColor
19526 })
19527 .css({
19528 cursor: currentPage === pageCount ? 'default' : 'pointer'
19529 });
19530
19531
19532 this.scrollOffset = -pages[currentPage - 1] + this.initialItemY;
19533
19534 this.scrollGroup.animate({
19535 translateY: this.scrollOffset
19536 });
19537
19538 this.currentPage = currentPage;
19539 this.positionCheckboxes();
19540 }
19541
19542 }
19543
19544 };
19545
19546 /*
19547 * LegendSymbolMixin
19548 */
19549
19550 H.LegendSymbolMixin = {
19551
19552 /**
19553 * Get the series' symbol in the legend
19554 *
19555 * @param {Object} legend The legend object
19556 * @param {Object} item The series (this) or point
19557 */
19558 drawRectangle: function(legend, item) {
19559 var options = legend.options,
19560 symbolHeight = legend.symbolHeight,
19561 square = options.squareSymbol,
19562 symbolWidth = square ? symbolHeight : legend.symbolWidth;
19563
19564 item.legendSymbol = this.chart.renderer.rect(
19565 square ? (legend.symbolWidth - symbolHeight) / 2 : 0,
19566 legend.baseline - symbolHeight + 1, // #3988
19567 symbolWidth,
19568 symbolHeight,
19569 pick(legend.options.symbolRadius, symbolHeight / 2)
19570 )
19571 .addClass('highcharts-point')
19572 .attr({
19573 zIndex: 3
19574 }).add(item.legendGroup);
19575
19576 },
19577
19578 /**
19579 * Get the series' symbol in the legend. This method should be overridable
19580 * to create custom symbols through
19581 * Highcharts.seriesTypes[type].prototype.drawLegendSymbols.
19582 *
19583 * @param {Object} legend The legend object
19584 */
19585 drawLineMarker: function(legend) {
19586
19587 var options = this.options,
19588 markerOptions = options.marker,
19589 radius,
19590 legendSymbol,
19591 symbolWidth = legend.symbolWidth,
19592 symbolHeight = legend.symbolHeight,
19593 generalRadius = symbolHeight / 2,
19594 renderer = this.chart.renderer,
19595 legendItemGroup = this.legendGroup,
19596 verticalCenter = legend.baseline -
19597 Math.round(legend.fontMetrics.b * 0.3),
19598 attr = {};
19599
19600 // Draw the line
19601
19602 attr = {
19603 'stroke-width': options.lineWidth || 0
19604 };
19605 if (options.dashStyle) {
19606 attr.dashstyle = options.dashStyle;
19607 }
19608
19609
19610 this.legendLine = renderer.path([
19611 'M',
19612 0,
19613 verticalCenter,
19614 'L',
19615 symbolWidth,
19616 verticalCenter
19617 ])
19618 .addClass('highcharts-graph')
19619 .attr(attr)
19620 .add(legendItemGroup);
19621
19622 // Draw the marker
19623 if (markerOptions && markerOptions.enabled !== false) {
19624
19625 // Do not allow the marker to be larger than the symbolHeight
19626 radius = Math.min(
19627 pick(markerOptions.radius, generalRadius),
19628 generalRadius
19629 );
19630
19631 // Restrict symbol markers size
19632 if (this.symbol.indexOf('url') === 0) {
19633 markerOptions = merge(markerOptions, {
19634 width: symbolHeight,
19635 height: symbolHeight
19636 });
19637 radius = 0;
19638 }
19639
19640 this.legendSymbol = legendSymbol = renderer.symbol(
19641 this.symbol,
19642 (symbolWidth / 2) - radius,
19643 verticalCenter - radius,
19644 2 * radius,
19645 2 * radius,
19646 markerOptions
19647 )
19648 .addClass('highcharts-point')
19649 .add(legendItemGroup);
19650 legendSymbol.isMarker = true;
19651 }
19652 }
19653 };
19654
19655 // Workaround for #2030, horizontal legend items not displaying in IE11 Preview,
19656 // and for #2580, a similar drawing flaw in Firefox 26.
19657 // Explore if there's a general cause for this. The problem may be related
19658 // to nested group elements, as the legend item texts are within 4 group
19659 // elements.
19660 if (/Trident\/7\.0/.test(win.navigator.userAgent) || isFirefox) {
19661 wrap(Highcharts.Legend.prototype, 'positionItem', function(proceed, item) {
19662 var legend = this,
19663 // If chart destroyed in sync, this is undefined (#2030)
19664 runPositionItem = function() {
19665 if (item._legendItemPos) {
19666 proceed.call(legend, item);
19667 }
19668 };
19669
19670 // Do it now, for export and to get checkbox placement
19671 runPositionItem();
19672
19673 // Do it after to work around the core issue
19674 setTimeout(runPositionItem);
19675 });
19676 }
19677
19678 }(Highcharts));
19679 (function(H) {
19680 /**
19681 * (c) 2010-2017 Torstein Honsi
19682 *
19683 * License: www.highcharts.com/license
19684 */
19685 var addEvent = H.addEvent,
19686 animate = H.animate,
19687 animObject = H.animObject,
19688 attr = H.attr,
19689 doc = H.doc,
19690 Axis = H.Axis, // @todo add as requirement
19691 createElement = H.createElement,
19692 defaultOptions = H.defaultOptions,
19693 discardElement = H.discardElement,
19694 charts = H.charts,
19695 css = H.css,
19696 defined = H.defined,
19697 each = H.each,
19698 extend = H.extend,
19699 find = H.find,
19700 fireEvent = H.fireEvent,
19701 grep = H.grep,
19702 isNumber = H.isNumber,
19703 isObject = H.isObject,
19704 isString = H.isString,
19705 Legend = H.Legend, // @todo add as requirement
19706 marginNames = H.marginNames,
19707 merge = H.merge,
19708 objectEach = H.objectEach,
19709 Pointer = H.Pointer, // @todo add as requirement
19710 pick = H.pick,
19711 pInt = H.pInt,
19712 removeEvent = H.removeEvent,
19713 seriesTypes = H.seriesTypes,
19714 splat = H.splat,
19715 svg = H.svg,
19716 syncTimeout = H.syncTimeout,
19717 win = H.win;
19718 /**
19719 * The Chart class. The recommended constructor is {@link Highcharts#chart}.
19720 * @class Highcharts.Chart
19721 * @param {String|HTMLDOMElement} renderTo
19722 * The DOM element to render to, or its id.
19723 * @param {Options} options
19724 * The chart options structure.
19725 * @param {Function} [callback]
19726 * Function to run when the chart has loaded and and all external images
19727 * are loaded. Defining a {@link
19728 * https://api.highcharts.com/highcharts/chart.events.load|chart.event.load}
19729 * handler is equivalent.
19730 *
19731 * @example
19732 * var chart = Highcharts.chart('container', {
19733 * title: {
19734 * text: 'My chart'
19735 * },
19736 * series: [{
19737 * data: [1, 3, 2, 4]
19738 * }]
19739 * })
19740 */
19741 var Chart = H.Chart = function() {
19742 this.getArgs.apply(this, arguments);
19743 };
19744
19745 /**
19746 * Factory function for basic charts.
19747 *
19748 * @function #chart
19749 * @memberOf Highcharts
19750 * @param {String|HTMLDOMElement} renderTo - The DOM element to render to, or
19751 * its id.
19752 * @param {Options} options - The chart options structure.
19753 * @param {Function} [callback] - Function to run when the chart has loaded and
19754 * and all external images are loaded. Defining a {@link
19755 * https://api.highcharts.com/highcharts/chart.events.load|chart.event.load}
19756 * handler is equivalent.
19757 * @return {Highcharts.Chart} - Returns the Chart object.
19758 *
19759 * @example
19760 * // Render a chart in to div#container
19761 * var chart = Highcharts.chart('container', {
19762 * title: {
19763 * text: 'My chart'
19764 * },
19765 * series: [{
19766 * data: [1, 3, 2, 4]
19767 * }]
19768 * });
19769 */
19770 H.chart = function(a, b, c) {
19771 return new Chart(a, b, c);
19772 };
19773
19774 extend(Chart.prototype, /** @lends Highcharts.Chart.prototype */ {
19775
19776 // Hook for adding callbacks in modules
19777 callbacks: [],
19778
19779 /**
19780 * Handle the arguments passed to the constructor.
19781 *
19782 * @private
19783 * @returns {Array} Arguments without renderTo
19784 */
19785 getArgs: function() {
19786 var args = [].slice.call(arguments);
19787
19788 // Remove the optional first argument, renderTo, and
19789 // set it on this.
19790 if (isString(args[0]) || args[0].nodeName) {
19791 this.renderTo = args.shift();
19792 }
19793 this.init(args[0], args[1]);
19794 },
19795
19796 /**
19797 * Overridable function that initializes the chart. The constructor's
19798 * arguments are passed on directly.
19799 */
19800 init: function(userOptions, callback) {
19801
19802 // Handle regular options
19803 var options,
19804 type,
19805 seriesOptions = userOptions.series, // skip merging data points to increase performance
19806 userPlotOptions = userOptions.plotOptions || {};
19807
19808 userOptions.series = null;
19809 options = merge(defaultOptions, userOptions); // do the merge
19810
19811 // Override (by copy of user options) or clear tooltip options
19812 // in chart.options.plotOptions (#6218)
19813 for (type in options.plotOptions) {
19814 options.plotOptions[type].tooltip = (
19815 userPlotOptions[type] &&
19816 merge(userPlotOptions[type].tooltip) // override by copy
19817 ) || undefined; // or clear
19818 }
19819 // User options have higher priority than default options (#6218).
19820 // In case of exporting: path is changed
19821 options.tooltip.userOptions = (userOptions.chart &&
19822 userOptions.chart.forExport && userOptions.tooltip.userOptions) ||
19823 userOptions.tooltip;
19824
19825 options.series = userOptions.series = seriesOptions; // set back the series data
19826 this.userOptions = userOptions;
19827
19828 var optionsChart = options.chart;
19829
19830 var chartEvents = optionsChart.events;
19831
19832 this.margin = [];
19833 this.spacing = [];
19834
19835 this.bounds = {
19836 h: {},
19837 v: {}
19838 }; // Pixel data bounds for touch zoom
19839
19840 // An array of functions that returns labels that should be considered
19841 // for anti-collision
19842 this.labelCollectors = [];
19843
19844 this.callback = callback;
19845 this.isResizing = 0;
19846
19847 /**
19848 * The options structure for the chart. It contains members for the sub
19849 * elements like series, legend, tooltip etc.
19850 *
19851 * @memberof Highcharts.Chart
19852 * @name options
19853 * @type {Options}
19854 */
19855 this.options = options;
19856 /**
19857 * All the axes in the chart.
19858 *
19859 * @memberof Highcharts.Chart
19860 * @name axes
19861 * @see Highcharts.Chart.xAxis
19862 * @see Highcharts.Chart.yAxis
19863 * @type {Array.<Highcharts.Axis>}
19864 */
19865 this.axes = [];
19866
19867 /**
19868 * All the current series in the chart.
19869 *
19870 * @memberof Highcharts.Chart
19871 * @name series
19872 * @type {Array.<Highcharts.Series>}
19873 */
19874 this.series = [];
19875
19876 /**
19877 * The chart title. The title has an `update` method that allows
19878 * modifying the options directly or indirectly via `chart.update`.
19879 *
19880 * @memberof Highcharts.Chart
19881 * @name title
19882 * @type Object
19883 *
19884 * @sample highcharts/members/title-update/
19885 * Updating titles
19886 */
19887
19888 /**
19889 * The chart subtitle. The subtitle has an `update` method that allows
19890 * modifying the options directly or indirectly via `chart.update`.
19891 *
19892 * @memberof Highcharts.Chart
19893 * @name subtitle
19894 * @type Object
19895 */
19896
19897
19898
19899 this.hasCartesianSeries = optionsChart.showAxes;
19900
19901 var chart = this;
19902
19903 // Add the chart to the global lookup
19904 chart.index = charts.length;
19905
19906 charts.push(chart);
19907 H.chartCount++;
19908
19909 // Chart event handlers
19910 if (chartEvents) {
19911 objectEach(chartEvents, function(event, eventType) {
19912 addEvent(chart, eventType, event);
19913 });
19914 }
19915
19916 /**
19917 * A collection of the X axes in the chart.
19918 * @type {Array.<Highcharts.Axis>}
19919 * @name xAxis
19920 * @memberOf Highcharts.Chart
19921 */
19922 chart.xAxis = [];
19923 /**
19924 * A collection of the Y axes in the chart.
19925 * @type {Array.<Highcharts.Axis>}
19926 * @name yAxis
19927 * @memberOf Highcharts.Chart
19928 */
19929 chart.yAxis = [];
19930
19931 chart.pointCount = chart.colorCounter = chart.symbolCounter = 0;
19932
19933 chart.firstRender();
19934 },
19935
19936 /**
19937 * Internal function to unitialize an individual series.
19938 *
19939 * @private
19940 */
19941 initSeries: function(options) {
19942 var chart = this,
19943 optionsChart = chart.options.chart,
19944 type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
19945 series,
19946 Constr = seriesTypes[type];
19947
19948 // No such series type
19949 if (!Constr) {
19950 H.error(17, true);
19951 }
19952
19953 series = new Constr();
19954 series.init(this, options);
19955 return series;
19956 },
19957
19958 /**
19959 * Order all series above a given index. When series are added and ordered
19960 * by configuration, only the last series is handled (#248, #1123, #2456,
19961 * #6112). This function is called on series initialization and destroy.
19962 *
19963 * @private
19964 *
19965 * @param {number} fromIndex
19966 * If this is given, only the series above this index are handled.
19967 */
19968 orderSeries: function(fromIndex) {
19969 var series = this.series,
19970 i = fromIndex || 0;
19971 for (; i < series.length; i++) {
19972 if (series[i]) {
19973 series[i].index = i;
19974 series[i].name = series[i].name ||
19975 'Series ' + (series[i].index + 1);
19976 }
19977 }
19978 },
19979
19980 /**
19981 * Check whether a given point is within the plot area.
19982 *
19983 * @param {Number} plotX
19984 * Pixel x relative to the plot area.
19985 * @param {Number} plotY
19986 * Pixel y relative to the plot area.
19987 * @param {Boolean} inverted
19988 * Whether the chart is inverted.
19989 *
19990 * @return {Boolean}
19991 * Returns true if the given point is inside the plot area.
19992 */
19993 isInsidePlot: function(plotX, plotY, inverted) {
19994 var x = inverted ? plotY : plotX,
19995 y = inverted ? plotX : plotY;
19996
19997 return x >= 0 &&
19998 x <= this.plotWidth &&
19999 y >= 0 &&
20000 y <= this.plotHeight;
20001 },
20002
20003 /**
20004 * Redraw the chart after changes have been done to the data, axis extremes
20005 * chart size or chart elements. All methods for updating axes, series or
20006 * points have a parameter for redrawing the chart. This is `true` by
20007 * default. But in many cases you want to do more than one operation on the
20008 * chart before redrawing, for example add a number of points. In those
20009 * cases it is a waste of resources to redraw the chart for each new point
20010 * added. So you add the points and call `chart.redraw()` after.
20011 *
20012 * @param {AnimationOptions} animation
20013 * If or how to apply animation to the redraw.
20014 */
20015 redraw: function(animation) {
20016 var chart = this,
20017 axes = chart.axes,
20018 series = chart.series,
20019 pointer = chart.pointer,
20020 legend = chart.legend,
20021 redrawLegend = chart.isDirtyLegend,
20022 hasStackedSeries,
20023 hasDirtyStacks,
20024 hasCartesianSeries = chart.hasCartesianSeries,
20025 isDirtyBox = chart.isDirtyBox,
20026 i,
20027 serie,
20028 renderer = chart.renderer,
20029 isHiddenChart = renderer.isHidden(),
20030 afterRedraw = [];
20031
20032 // Handle responsive rules, not only on resize (#6130)
20033 if (chart.setResponsive) {
20034 chart.setResponsive(false);
20035 }
20036
20037 H.setAnimation(animation, chart);
20038
20039 if (isHiddenChart) {
20040 chart.temporaryDisplay();
20041 }
20042
20043 // Adjust title layout (reflow multiline text)
20044 chart.layOutTitles();
20045
20046 // link stacked series
20047 i = series.length;
20048 while (i--) {
20049 serie = series[i];
20050
20051 if (serie.options.stacking) {
20052 hasStackedSeries = true;
20053
20054 if (serie.isDirty) {
20055 hasDirtyStacks = true;
20056 break;
20057 }
20058 }
20059 }
20060 if (hasDirtyStacks) { // mark others as dirty
20061 i = series.length;
20062 while (i--) {
20063 serie = series[i];
20064 if (serie.options.stacking) {
20065 serie.isDirty = true;
20066 }
20067 }
20068 }
20069
20070 // Handle updated data in the series
20071 each(series, function(serie) {
20072 if (serie.isDirty) {
20073 if (serie.options.legendType === 'point') {
20074 if (serie.updateTotals) {
20075 serie.updateTotals();
20076 }
20077 redrawLegend = true;
20078 }
20079 }
20080 if (serie.isDirtyData) {
20081 fireEvent(serie, 'updatedData');
20082 }
20083 });
20084
20085 // handle added or removed series
20086 if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed
20087 // draw legend graphics
20088 legend.render();
20089
20090 chart.isDirtyLegend = false;
20091 }
20092
20093 // reset stacks
20094 if (hasStackedSeries) {
20095 chart.getStacks();
20096 }
20097
20098
20099 if (hasCartesianSeries) {
20100 // set axes scales
20101 each(axes, function(axis) {
20102 axis.updateNames();
20103 axis.setScale();
20104 });
20105 }
20106
20107 chart.getMargins(); // #3098
20108
20109 if (hasCartesianSeries) {
20110 // If one axis is dirty, all axes must be redrawn (#792, #2169)
20111 each(axes, function(axis) {
20112 if (axis.isDirty) {
20113 isDirtyBox = true;
20114 }
20115 });
20116
20117 // redraw axes
20118 each(axes, function(axis) {
20119
20120 // Fire 'afterSetExtremes' only if extremes are set
20121 var key = axis.min + ',' + axis.max;
20122 if (axis.extKey !== key) { // #821, #4452
20123 axis.extKey = key;
20124 afterRedraw.push(function() { // prevent a recursive call to chart.redraw() (#1119)
20125 fireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751
20126 delete axis.eventArgs;
20127 });
20128 }
20129 if (isDirtyBox || hasStackedSeries) {
20130 axis.redraw();
20131 }
20132 });
20133 }
20134
20135 // the plot areas size has changed
20136 if (isDirtyBox) {
20137 chart.drawChartBox();
20138 }
20139
20140 // Fire an event before redrawing series, used by the boost module to
20141 // clear previous series renderings.
20142 fireEvent(chart, 'predraw');
20143
20144 // redraw affected series
20145 each(series, function(serie) {
20146 if ((isDirtyBox || serie.isDirty) && serie.visible) {
20147 serie.redraw();
20148 }
20149 // Set it here, otherwise we will have unlimited 'updatedData' calls
20150 // for a hidden series after setData(). Fixes #6012
20151 serie.isDirtyData = false;
20152 });
20153
20154 // move tooltip or reset
20155 if (pointer) {
20156 pointer.reset(true);
20157 }
20158
20159 // redraw if canvas
20160 renderer.draw();
20161
20162 // Fire the events
20163 fireEvent(chart, 'redraw');
20164 fireEvent(chart, 'render');
20165
20166 if (isHiddenChart) {
20167 chart.temporaryDisplay(true);
20168 }
20169
20170 // Fire callbacks that are put on hold until after the redraw
20171 each(afterRedraw, function(callback) {
20172 callback.call();
20173 });
20174 },
20175
20176 /**
20177 * Get an axis, series or point object by `id` as given in the configuration
20178 * options. Returns `undefined` if no item is found.
20179 * @param id {String} The id as given in the configuration options.
20180 * @return {Highcharts.Axis|Highcharts.Series|Highcharts.Point|undefined}
20181 * The retrieved item.
20182 * @sample highcharts/plotoptions/series-id/
20183 * Get series by id
20184 */
20185 get: function(id) {
20186
20187 var ret,
20188 series = this.series,
20189 i;
20190
20191 function itemById(item) {
20192 return item.id === id || (item.options && item.options.id === id);
20193 }
20194
20195 ret =
20196 // Search axes
20197 find(this.axes, itemById) ||
20198
20199 // Search series
20200 find(this.series, itemById);
20201
20202 // Search points
20203 for (i = 0; !ret && i < series.length; i++) {
20204 ret = find(series[i].points || [], itemById);
20205 }
20206
20207 return ret;
20208 },
20209
20210 /**
20211 * Create the Axis instances based on the config options.
20212 *
20213 * @private
20214 */
20215 getAxes: function() {
20216 var chart = this,
20217 options = this.options,
20218 xAxisOptions = options.xAxis = splat(options.xAxis || {}),
20219 yAxisOptions = options.yAxis = splat(options.yAxis || {}),
20220 optionsArray;
20221
20222 // make sure the options are arrays and add some members
20223 each(xAxisOptions, function(axis, i) {
20224 axis.index = i;
20225 axis.isX = true;
20226 });
20227
20228 each(yAxisOptions, function(axis, i) {
20229 axis.index = i;
20230 });
20231
20232 // concatenate all axis options into one array
20233 optionsArray = xAxisOptions.concat(yAxisOptions);
20234
20235 each(optionsArray, function(axisOptions) {
20236 new Axis(chart, axisOptions); // eslint-disable-line no-new
20237 });
20238 },
20239
20240
20241 /**
20242 * Returns an array of all currently selected points in the chart. Points
20243 * can be selected by clicking or programmatically by the {@link
20244 * Highcharts.Point#select} function.
20245 *
20246 * @return {Array.<Highcharts.Point>}
20247 * The currently selected points.
20248 *
20249 * @sample highcharts/plotoptions/series-allowpointselect-line/
20250 * Get selected points
20251 */
20252 getSelectedPoints: function() {
20253 var points = [];
20254 each(this.series, function(serie) {
20255 // series.data - for points outside of viewed range (#6445)
20256 points = points.concat(grep(serie.data || [], function(point) {
20257 return point.selected;
20258 }));
20259 });
20260 return points;
20261 },
20262
20263 /**
20264 * Returns an array of all currently selected series in the chart. Series
20265 * can be selected either programmatically by the {@link
20266 * Highcharts.Series#select} function or by checking the checkbox next to
20267 * the legend item if {@link
20268 * https://api.highcharts.com/highcharts/plotOptions.series.showCheckbox|
20269 * series.showCheckBox} is true.
20270 *
20271 * @return {Array.<Highcharts.Series>}
20272 * The currently selected series.
20273 *
20274 * @sample highcharts/members/chart-getselectedseries/
20275 * Get selected series
20276 */
20277 getSelectedSeries: function() {
20278 return grep(this.series, function(serie) {
20279 return serie.selected;
20280 });
20281 },
20282
20283 /**
20284 * Set a new title or subtitle for the chart.
20285 *
20286 * @param titleOptions {TitleOptions}
20287 * New title options. The title text itself is set by the
20288 * `titleOptions.text` property.
20289 * @param subtitleOptions {SubtitleOptions}
20290 * New subtitle options. The subtitle text itself is set by the
20291 * `subtitleOptions.text` property.
20292 * @param redraw {Boolean}
20293 * Whether to redraw the chart or wait for a later call to
20294 * `chart.redraw()`.
20295 *
20296 * @sample highcharts/members/chart-settitle/ Set title text and styles
20297 *
20298 */
20299 setTitle: function(titleOptions, subtitleOptions, redraw) {
20300 var chart = this,
20301 options = chart.options,
20302 chartTitleOptions,
20303 chartSubtitleOptions;
20304
20305 chartTitleOptions = options.title = merge(
20306
20307 // Default styles
20308 {
20309 style: {
20310 color: '#333333',
20311 fontSize: options.isStock ? '16px' : '18px' // #2944
20312 }
20313 },
20314
20315 options.title,
20316 titleOptions
20317 );
20318 chartSubtitleOptions = options.subtitle = merge(
20319
20320 // Default styles
20321 {
20322 style: {
20323 color: '#666666'
20324 }
20325 },
20326
20327 options.subtitle,
20328 subtitleOptions
20329 );
20330
20331 // add title and subtitle
20332 each([
20333 ['title', titleOptions, chartTitleOptions],
20334 ['subtitle', subtitleOptions, chartSubtitleOptions]
20335 ], function(arr, i) {
20336 var name = arr[0],
20337 title = chart[name],
20338 titleOptions = arr[1],
20339 chartTitleOptions = arr[2];
20340
20341 if (title && titleOptions) {
20342 chart[name] = title = title.destroy(); // remove old
20343 }
20344
20345 if (chartTitleOptions && !title) {
20346 chart[name] = chart.renderer.text(
20347 chartTitleOptions.text,
20348 0,
20349 0,
20350 chartTitleOptions.useHTML
20351 )
20352 .attr({
20353 align: chartTitleOptions.align,
20354 'class': 'highcharts-' + name,
20355 zIndex: chartTitleOptions.zIndex || 4
20356 })
20357 .add();
20358
20359 // Update methods, shortcut to Chart.setTitle
20360 chart[name].update = function(o) {
20361 chart.setTitle(!i && o, i && o);
20362 };
20363
20364
20365 // Presentational
20366 chart[name].css(chartTitleOptions.style);
20367
20368
20369 }
20370 });
20371 chart.layOutTitles(redraw);
20372 },
20373
20374 /**
20375 * Internal function to lay out the chart titles and cache the full offset
20376 * height for use in `getMargins`. The result is stored in
20377 * `this.titleOffset`.
20378 *
20379 * @private
20380 */
20381 layOutTitles: function(redraw) {
20382 var titleOffset = 0,
20383 requiresDirtyBox,
20384 renderer = this.renderer,
20385 spacingBox = this.spacingBox;
20386
20387 // Lay out the title and the subtitle respectively
20388 each(['title', 'subtitle'], function(key) {
20389 var title = this[key],
20390 titleOptions = this.options[key],
20391 offset = key === 'title' ? -3 :
20392 // Floating subtitle (#6574)
20393 titleOptions.verticalAlign ? 0 : titleOffset + 2,
20394 titleSize;
20395
20396 if (title) {
20397
20398 titleSize = titleOptions.style.fontSize;
20399
20400 titleSize = renderer.fontMetrics(titleSize, title).b;
20401
20402 title
20403 .css({
20404 width: (titleOptions.width ||
20405 spacingBox.width + titleOptions.widthAdjust) + 'px'
20406 })
20407 .align(extend({
20408 y: offset + titleSize
20409 }, titleOptions), false, 'spacingBox');
20410
20411 if (!titleOptions.floating && !titleOptions.verticalAlign) {
20412 titleOffset = Math.ceil(
20413 titleOffset +
20414 // Skip the cache for HTML (#3481)
20415 title.getBBox(titleOptions.useHTML).height
20416 );
20417 }
20418 }
20419 }, this);
20420
20421 requiresDirtyBox = this.titleOffset !== titleOffset;
20422 this.titleOffset = titleOffset; // used in getMargins
20423
20424 if (!this.isDirtyBox && requiresDirtyBox) {
20425 this.isDirtyBox = requiresDirtyBox;
20426 // Redraw if necessary (#2719, #2744)
20427 if (this.hasRendered && pick(redraw, true) && this.isDirtyBox) {
20428 this.redraw();
20429 }
20430 }
20431 },
20432
20433 /**
20434 * Internal function to get the chart width and height according to options
20435 * and container size. Sets {@link Chart.chartWidth} and {@link
20436 * Chart.chartHeight}.
20437 */
20438 getChartSize: function() {
20439 var chart = this,
20440 optionsChart = chart.options.chart,
20441 widthOption = optionsChart.width,
20442 heightOption = optionsChart.height,
20443 renderTo = chart.renderTo;
20444
20445 // Get inner width and height
20446 if (!defined(widthOption)) {
20447 chart.containerWidth = H.getStyle(renderTo, 'width');
20448 }
20449 if (!defined(heightOption)) {
20450 chart.containerHeight = H.getStyle(renderTo, 'height');
20451 }
20452
20453 /**
20454 * The current pixel width of the chart.
20455 *
20456 * @name chartWidth
20457 * @memberOf Chart
20458 * @type {Number}
20459 */
20460 chart.chartWidth = Math.max( // #1393
20461 0,
20462 widthOption || chart.containerWidth || 600 // #1460
20463 );
20464 /**
20465 * The current pixel height of the chart.
20466 *
20467 * @name chartHeight
20468 * @memberOf Chart
20469 * @type {Number}
20470 */
20471 chart.chartHeight = Math.max(
20472 0,
20473 H.relativeLength(
20474 heightOption,
20475 chart.chartWidth
20476 ) ||
20477 (chart.containerHeight > 1 ? chart.containerHeight : 400)
20478 );
20479 },
20480
20481 /**
20482 * If the renderTo element has no offsetWidth, most likely one or more of
20483 * its parents are hidden. Loop up the DOM tree to temporarily display the
20484 * parents, then save the original display properties, and when the true
20485 * size is retrieved, reset them. Used on first render and on redraws.
20486 *
20487 * @private
20488 *
20489 * @param {Boolean} revert
20490 * Revert to the saved original styles.
20491 */
20492 temporaryDisplay: function(revert) {
20493 var node = this.renderTo,
20494 tempStyle;
20495 if (!revert) {
20496 while (node && node.style) {
20497
20498 // When rendering to a detached node, it needs to be temporarily
20499 // attached in order to read styling and bounding boxes (#5783,
20500 // #7024).
20501 if (!doc.body.contains(node) && !node.parentNode) {
20502 node.hcOrigDetached = true;
20503 doc.body.appendChild(node);
20504 }
20505 if (
20506 H.getStyle(node, 'display', false) === 'none' ||
20507 node.hcOricDetached
20508 ) {
20509 node.hcOrigStyle = {
20510 display: node.style.display,
20511 height: node.style.height,
20512 overflow: node.style.overflow
20513 };
20514 tempStyle = {
20515 display: 'block',
20516 overflow: 'hidden'
20517 };
20518 if (node !== this.renderTo) {
20519 tempStyle.height = 0;
20520 }
20521
20522 H.css(node, tempStyle);
20523
20524 // If it still doesn't have an offset width after setting
20525 // display to block, it probably has an !important priority
20526 // #2631, 6803
20527 if (!node.offsetWidth) {
20528 node.style.setProperty('display', 'block', 'important');
20529 }
20530 }
20531 node = node.parentNode;
20532
20533 if (node === doc.body) {
20534 break;
20535 }
20536 }
20537 } else {
20538 while (node && node.style) {
20539 if (node.hcOrigStyle) {
20540 H.css(node, node.hcOrigStyle);
20541 delete node.hcOrigStyle;
20542 }
20543 if (node.hcOrigDetached) {
20544 doc.body.removeChild(node);
20545 node.hcOrigDetached = false;
20546 }
20547 node = node.parentNode;
20548 }
20549 }
20550 },
20551
20552 /**
20553 * Set the {@link Chart.container|chart container's} class name, in
20554 * addition to `highcharts-container`.
20555 */
20556 setClassName: function(className) {
20557 this.container.className = 'highcharts-container ' + (className || '');
20558 },
20559
20560 /**
20561 * Get the containing element, determine the size and create the inner
20562 * container div to hold the chart.
20563 *
20564 * @private
20565 */
20566 getContainer: function() {
20567 var chart = this,
20568 container,
20569 options = chart.options,
20570 optionsChart = options.chart,
20571 chartWidth,
20572 chartHeight,
20573 renderTo = chart.renderTo,
20574 indexAttrName = 'data-highcharts-chart',
20575 oldChartIndex,
20576 Ren,
20577 containerId = H.uniqueKey(),
20578 containerStyle,
20579 key;
20580
20581 if (!renderTo) {
20582 chart.renderTo = renderTo = optionsChart.renderTo;
20583 }
20584
20585 if (isString(renderTo)) {
20586 chart.renderTo = renderTo = doc.getElementById(renderTo);
20587 }
20588
20589 // Display an error if the renderTo is wrong
20590 if (!renderTo) {
20591 H.error(13, true);
20592 }
20593
20594 // If the container already holds a chart, destroy it. The check for
20595 // hasRendered is there because web pages that are saved to disk from
20596 // the browser, will preserve the data-highcharts-chart attribute and
20597 // the SVG contents, but not an interactive chart. So in this case,
20598 // charts[oldChartIndex] will point to the wrong chart if any (#2609).
20599 oldChartIndex = pInt(attr(renderTo, indexAttrName));
20600 if (
20601 isNumber(oldChartIndex) &&
20602 charts[oldChartIndex] &&
20603 charts[oldChartIndex].hasRendered
20604 ) {
20605 charts[oldChartIndex].destroy();
20606 }
20607
20608 // Make a reference to the chart from the div
20609 attr(renderTo, indexAttrName, chart.index);
20610
20611 // remove previous chart
20612 renderTo.innerHTML = '';
20613
20614 // If the container doesn't have an offsetWidth, it has or is a child of
20615 // a node that has display:none. We need to temporarily move it out to a
20616 // visible state to determine the size, else the legend and tooltips
20617 // won't render properly. The skipClone option is used in sparklines as
20618 // a micro optimization, saving about 1-2 ms each chart.
20619 if (!optionsChart.skipClone && !renderTo.offsetWidth) {
20620 chart.temporaryDisplay();
20621 }
20622
20623 // get the width and height
20624 chart.getChartSize();
20625 chartWidth = chart.chartWidth;
20626 chartHeight = chart.chartHeight;
20627
20628 // Create the inner container
20629
20630 containerStyle = extend({
20631 position: 'relative',
20632 overflow: 'hidden', // needed for context menu (avoid scrollbars)
20633 // and content overflow in IE
20634 width: chartWidth + 'px',
20635 height: chartHeight + 'px',
20636 textAlign: 'left',
20637 lineHeight: 'normal', // #427
20638 zIndex: 0, // #1072
20639 '-webkit-tap-highlight-color': 'rgba(0,0,0,0)'
20640 }, optionsChart.style);
20641
20642
20643 /**
20644 * The containing HTML element of the chart. The container is
20645 * dynamically inserted into the element given as the `renderTo`
20646 * parameterin the {@link Highcharts#chart} constructor.
20647 *
20648 * @memberOf Highcharts.Chart
20649 * @type {HTMLDOMElement}
20650 */
20651 container = createElement(
20652 'div', {
20653 id: containerId
20654 },
20655 containerStyle,
20656 renderTo
20657 );
20658 chart.container = container;
20659
20660 // cache the cursor (#1650)
20661 chart._cursor = container.style.cursor;
20662
20663 // Initialize the renderer
20664 Ren = H[optionsChart.renderer] || H.Renderer;
20665
20666 /**
20667 * The renderer instance of the chart. Each chart instance has only one
20668 * associated renderer.
20669 * @type {SVGRenderer}
20670 * @name renderer
20671 * @memberOf Chart
20672 */
20673 chart.renderer = new Ren(
20674 container,
20675 chartWidth,
20676 chartHeight,
20677 null,
20678 optionsChart.forExport,
20679 options.exporting && options.exporting.allowHTML
20680 );
20681
20682
20683 chart.setClassName(optionsChart.className);
20684
20685 chart.renderer.setStyle(optionsChart.style);
20686
20687
20688 // Add a reference to the charts index
20689 chart.renderer.chartIndex = chart.index;
20690 },
20691
20692 /**
20693 * Calculate margins by rendering axis labels in a preliminary position.
20694 * Title, subtitle and legend have already been rendered at this stage, but
20695 * will be moved into their final positions.
20696 *
20697 * @private
20698 */
20699 getMargins: function(skipAxes) {
20700 var chart = this,
20701 spacing = chart.spacing,
20702 margin = chart.margin,
20703 titleOffset = chart.titleOffset;
20704
20705 chart.resetMargins();
20706
20707 // Adjust for title and subtitle
20708 if (titleOffset && !defined(margin[0])) {
20709 chart.plotTop = Math.max(
20710 chart.plotTop,
20711 titleOffset + chart.options.title.margin + spacing[0]
20712 );
20713 }
20714
20715 // Adjust for legend
20716 if (chart.legend && chart.legend.display) {
20717 chart.legend.adjustMargins(margin, spacing);
20718 }
20719
20720 // adjust for scroller
20721 if (chart.extraMargin) {
20722 chart[chart.extraMargin.type] =
20723 (chart[chart.extraMargin.type] || 0) + chart.extraMargin.value;
20724 }
20725
20726 // adjust for rangeSelector
20727 if (chart.adjustPlotArea) {
20728 chart.adjustPlotArea();
20729 }
20730
20731 if (!skipAxes) {
20732 this.getAxisMargins();
20733 }
20734 },
20735
20736 getAxisMargins: function() {
20737
20738 var chart = this,
20739 // [top, right, bottom, left]
20740 axisOffset = chart.axisOffset = [0, 0, 0, 0],
20741 margin = chart.margin;
20742
20743 // pre-render axes to get labels offset width
20744 if (chart.hasCartesianSeries) {
20745 each(chart.axes, function(axis) {
20746 if (axis.visible) {
20747 axis.getOffset();
20748 }
20749 });
20750 }
20751
20752 // Add the axis offsets
20753 each(marginNames, function(m, side) {
20754 if (!defined(margin[side])) {
20755 chart[m] += axisOffset[side];
20756 }
20757 });
20758
20759 chart.setChartSize();
20760
20761 },
20762
20763 /**
20764 * Reflows the chart to its container. By default, the chart reflows
20765 * automatically to its container following a `window.resize` event, as per
20766 * the {@link https://api.highcharts/highcharts/chart.reflow|chart.reflow}
20767 * option. However, there are no reliable events for div resize, so if the
20768 * container is resized without a window resize event, this must be called
20769 * explicitly.
20770 *
20771 * @param {Object} e
20772 * Event arguments. Used primarily when the function is called
20773 * internally as a response to window resize.
20774 *
20775 * @sample highcharts/members/chart-reflow/
20776 * Resize div and reflow
20777 * @sample highcharts/chart/events-container/
20778 * Pop up and reflow
20779 */
20780 reflow: function(e) {
20781 var chart = this,
20782 optionsChart = chart.options.chart,
20783 renderTo = chart.renderTo,
20784 hasUserSize = (
20785 defined(optionsChart.width) &&
20786 defined(optionsChart.height)
20787 ),
20788 width = optionsChart.width || H.getStyle(renderTo, 'width'),
20789 height = optionsChart.height || H.getStyle(renderTo, 'height'),
20790 target = e ? e.target : win;
20791
20792 // Width and height checks for display:none. Target is doc in IE8 and
20793 // Opera, win in Firefox, Chrome and IE9.
20794 if (!hasUserSize &&
20795 !chart.isPrinting &&
20796 width &&
20797 height &&
20798 (target === win || target === doc)
20799 ) {
20800 if (
20801 width !== chart.containerWidth ||
20802 height !== chart.containerHeight
20803 ) {
20804 clearTimeout(chart.reflowTimeout);
20805 // When called from window.resize, e is set, else it's called
20806 // directly (#2224)
20807 chart.reflowTimeout = syncTimeout(function() {
20808 // Set size, it may have been destroyed in the meantime
20809 // (#1257)
20810 if (chart.container) {
20811 chart.setSize(undefined, undefined, false);
20812 }
20813 }, e ? 100 : 0);
20814 }
20815 chart.containerWidth = width;
20816 chart.containerHeight = height;
20817 }
20818 },
20819
20820 /**
20821 * Add the event handlers necessary for auto resizing, depending on the
20822 * `chart.events.reflow` option.
20823 *
20824 * @private
20825 */
20826 initReflow: function() {
20827 var chart = this,
20828 unbind;
20829
20830 unbind = addEvent(win, 'resize', function(e) {
20831 chart.reflow(e);
20832 });
20833 addEvent(chart, 'destroy', unbind);
20834
20835 // The following will add listeners to re-fit the chart before and after
20836 // printing (#2284). However it only works in WebKit. Should have worked
20837 // in Firefox, but not supported in IE.
20838 /*
20839 if (win.matchMedia) {
20840 win.matchMedia('print').addListener(function reflow() {
20841 chart.reflow();
20842 });
20843 }
20844 */
20845 },
20846
20847 /**
20848 * Resize the chart to a given width and height. In order to set the width
20849 * only, the height argument may be skipped. To set the height only, pass
20850 * `undefined for the width.
20851 * @param {Number|undefined|null} [width]
20852 * The new pixel width of the chart. Since v4.2.6, the argument can
20853 * be `undefined` in order to preserve the current value (when
20854 * setting height only), or `null` to adapt to the width of the
20855 * containing element.
20856 * @param {Number|undefined|null} [height]
20857 * The new pixel height of the chart. Since v4.2.6, the argument can
20858 * be `undefined` in order to preserve the current value, or `null`
20859 * in order to adapt to the height of the containing element.
20860 * @param {AnimationOptions} [animation=true]
20861 * Whether and how to apply animation.
20862 *
20863 * @sample highcharts/members/chart-setsize-button/
20864 * Test resizing from buttons
20865 * @sample highcharts/members/chart-setsize-jquery-resizable/
20866 * Add a jQuery UI resizable
20867 * @sample stock/members/chart-setsize/
20868 * Highstock with UI resizable
20869 */
20870 setSize: function(width, height, animation) {
20871 var chart = this,
20872 renderer = chart.renderer,
20873 globalAnimation;
20874
20875 // Handle the isResizing counter
20876 chart.isResizing += 1;
20877
20878 // set the animation for the current process
20879 H.setAnimation(animation, chart);
20880
20881 chart.oldChartHeight = chart.chartHeight;
20882 chart.oldChartWidth = chart.chartWidth;
20883 if (width !== undefined) {
20884 chart.options.chart.width = width;
20885 }
20886 if (height !== undefined) {
20887 chart.options.chart.height = height;
20888 }
20889 chart.getChartSize();
20890
20891 // Resize the container with the global animation applied if enabled
20892 // (#2503)
20893
20894 globalAnimation = renderer.globalAnimation;
20895 (globalAnimation ? animate : css)(chart.container, {
20896 width: chart.chartWidth + 'px',
20897 height: chart.chartHeight + 'px'
20898 }, globalAnimation);
20899
20900
20901 chart.setChartSize(true);
20902 renderer.setSize(chart.chartWidth, chart.chartHeight, animation);
20903
20904 // handle axes
20905 each(chart.axes, function(axis) {
20906 axis.isDirty = true;
20907 axis.setScale();
20908 });
20909
20910 chart.isDirtyLegend = true; // force legend redraw
20911 chart.isDirtyBox = true; // force redraw of plot and chart border
20912
20913 chart.layOutTitles(); // #2857
20914 chart.getMargins();
20915
20916 chart.redraw(animation);
20917
20918
20919 chart.oldChartHeight = null;
20920 fireEvent(chart, 'resize');
20921
20922 // Fire endResize and set isResizing back. If animation is disabled,
20923 // fire without delay
20924 syncTimeout(function() {
20925 if (chart) {
20926 fireEvent(chart, 'endResize', null, function() {
20927 chart.isResizing -= 1;
20928 });
20929 }
20930 }, animObject(globalAnimation).duration);
20931 },
20932
20933 /**
20934 * Set the public chart properties. This is done before and after the
20935 * pre-render to determine margin sizes.
20936 *
20937 * @private
20938 */
20939 setChartSize: function(skipAxes) {
20940 var chart = this,
20941 inverted = chart.inverted,
20942 renderer = chart.renderer,
20943 chartWidth = chart.chartWidth,
20944 chartHeight = chart.chartHeight,
20945 optionsChart = chart.options.chart,
20946 spacing = chart.spacing,
20947 clipOffset = chart.clipOffset,
20948 clipX,
20949 clipY,
20950 plotLeft,
20951 plotTop,
20952 plotWidth,
20953 plotHeight,
20954 plotBorderWidth;
20955
20956 /**
20957 * The current left position of the plot area in pixels.
20958 *
20959 * @name plotLeft
20960 * @memberOf Chart
20961 * @type {Number}
20962 */
20963 chart.plotLeft = plotLeft = Math.round(chart.plotLeft);
20964
20965 /**
20966 * The current top position of the plot area in pixels.
20967 *
20968 * @name plotTop
20969 * @memberOf Chart
20970 * @type {Number}
20971 */
20972 chart.plotTop = plotTop = Math.round(chart.plotTop);
20973
20974 /**
20975 * The current width of the plot area in pixels.
20976 *
20977 * @name plotWidth
20978 * @memberOf Chart
20979 * @type {Number}
20980 */
20981 chart.plotWidth = plotWidth = Math.max(
20982 0,
20983 Math.round(chartWidth - plotLeft - chart.marginRight)
20984 );
20985
20986 /**
20987 * The current height of the plot area in pixels.
20988 *
20989 * @name plotHeight
20990 * @memberOf Chart
20991 * @type {Number}
20992 */
20993 chart.plotHeight = plotHeight = Math.max(
20994 0,
20995 Math.round(chartHeight - plotTop - chart.marginBottom)
20996 );
20997
20998 chart.plotSizeX = inverted ? plotHeight : plotWidth;
20999 chart.plotSizeY = inverted ? plotWidth : plotHeight;
21000
21001 chart.plotBorderWidth = optionsChart.plotBorderWidth || 0;
21002
21003 // Set boxes used for alignment
21004 chart.spacingBox = renderer.spacingBox = {
21005 x: spacing[3],
21006 y: spacing[0],
21007 width: chartWidth - spacing[3] - spacing[1],
21008 height: chartHeight - spacing[0] - spacing[2]
21009 };
21010 chart.plotBox = renderer.plotBox = {
21011 x: plotLeft,
21012 y: plotTop,
21013 width: plotWidth,
21014 height: plotHeight
21015 };
21016
21017 plotBorderWidth = 2 * Math.floor(chart.plotBorderWidth / 2);
21018 clipX = Math.ceil(Math.max(plotBorderWidth, clipOffset[3]) / 2);
21019 clipY = Math.ceil(Math.max(plotBorderWidth, clipOffset[0]) / 2);
21020 chart.clipBox = {
21021 x: clipX,
21022 y: clipY,
21023 width: Math.floor(
21024 chart.plotSizeX -
21025 Math.max(plotBorderWidth, clipOffset[1]) / 2 -
21026 clipX
21027 ),
21028 height: Math.max(
21029 0,
21030 Math.floor(
21031 chart.plotSizeY -
21032 Math.max(plotBorderWidth, clipOffset[2]) / 2 -
21033 clipY
21034 )
21035 )
21036 };
21037
21038 if (!skipAxes) {
21039 each(chart.axes, function(axis) {
21040 axis.setAxisSize();
21041 axis.setAxisTranslation();
21042 });
21043 }
21044 },
21045
21046 /**
21047 * Initial margins before auto size margins are applied.
21048 *
21049 * @private
21050 */
21051 resetMargins: function() {
21052 var chart = this,
21053 chartOptions = chart.options.chart;
21054
21055 // Create margin and spacing array
21056 each(['margin', 'spacing'], function splashArrays(target) {
21057 var value = chartOptions[target],
21058 values = isObject(value) ? value : [value, value, value, value];
21059
21060 each(['Top', 'Right', 'Bottom', 'Left'], function(sideName, side) {
21061 chart[target][side] = pick(
21062 chartOptions[target + sideName],
21063 values[side]
21064 );
21065 });
21066 });
21067
21068 // Set margin names like chart.plotTop, chart.plotLeft,
21069 // chart.marginRight, chart.marginBottom.
21070 each(marginNames, function(m, side) {
21071 chart[m] = pick(chart.margin[side], chart.spacing[side]);
21072 });
21073 chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
21074 chart.clipOffset = [0, 0, 0, 0];
21075 },
21076
21077 /**
21078 * Internal function to draw or redraw the borders and backgrounds for chart
21079 * and plot area.
21080 *
21081 * @private
21082 */
21083 drawChartBox: function() {
21084 var chart = this,
21085 optionsChart = chart.options.chart,
21086 renderer = chart.renderer,
21087 chartWidth = chart.chartWidth,
21088 chartHeight = chart.chartHeight,
21089 chartBackground = chart.chartBackground,
21090 plotBackground = chart.plotBackground,
21091 plotBorder = chart.plotBorder,
21092 chartBorderWidth,
21093
21094 plotBGImage = chart.plotBGImage,
21095 chartBackgroundColor = optionsChart.backgroundColor,
21096 plotBackgroundColor = optionsChart.plotBackgroundColor,
21097 plotBackgroundImage = optionsChart.plotBackgroundImage,
21098
21099 mgn,
21100 bgAttr,
21101 plotLeft = chart.plotLeft,
21102 plotTop = chart.plotTop,
21103 plotWidth = chart.plotWidth,
21104 plotHeight = chart.plotHeight,
21105 plotBox = chart.plotBox,
21106 clipRect = chart.clipRect,
21107 clipBox = chart.clipBox,
21108 verb = 'animate';
21109
21110 // Chart area
21111 if (!chartBackground) {
21112 chart.chartBackground = chartBackground = renderer.rect()
21113 .addClass('highcharts-background')
21114 .add();
21115 verb = 'attr';
21116 }
21117
21118
21119 // Presentational
21120 chartBorderWidth = optionsChart.borderWidth || 0;
21121 mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);
21122
21123 bgAttr = {
21124 fill: chartBackgroundColor || 'none'
21125 };
21126
21127 if (chartBorderWidth || chartBackground['stroke-width']) { // #980
21128 bgAttr.stroke = optionsChart.borderColor;
21129 bgAttr['stroke-width'] = chartBorderWidth;
21130 }
21131 chartBackground
21132 .attr(bgAttr)
21133 .shadow(optionsChart.shadow);
21134
21135 chartBackground[verb]({
21136 x: mgn / 2,
21137 y: mgn / 2,
21138 width: chartWidth - mgn - chartBorderWidth % 2,
21139 height: chartHeight - mgn - chartBorderWidth % 2,
21140 r: optionsChart.borderRadius
21141 });
21142
21143 // Plot background
21144 verb = 'animate';
21145 if (!plotBackground) {
21146 verb = 'attr';
21147 chart.plotBackground = plotBackground = renderer.rect()
21148 .addClass('highcharts-plot-background')
21149 .add();
21150 }
21151 plotBackground[verb](plotBox);
21152
21153
21154 // Presentational attributes for the background
21155 plotBackground
21156 .attr({
21157 fill: plotBackgroundColor || 'none'
21158 })
21159 .shadow(optionsChart.plotShadow);
21160
21161 // Create the background image
21162 if (plotBackgroundImage) {
21163 if (!plotBGImage) {
21164 chart.plotBGImage = renderer.image(
21165 plotBackgroundImage,
21166 plotLeft,
21167 plotTop,
21168 plotWidth,
21169 plotHeight
21170 ).add();
21171 } else {
21172 plotBGImage.animate(plotBox);
21173 }
21174 }
21175
21176
21177 // Plot clip
21178 if (!clipRect) {
21179 chart.clipRect = renderer.clipRect(clipBox);
21180 } else {
21181 clipRect.animate({
21182 width: clipBox.width,
21183 height: clipBox.height
21184 });
21185 }
21186
21187 // Plot area border
21188 verb = 'animate';
21189 if (!plotBorder) {
21190 verb = 'attr';
21191 chart.plotBorder = plotBorder = renderer.rect()
21192 .addClass('highcharts-plot-border')
21193 .attr({
21194 zIndex: 1 // Above the grid
21195 })
21196 .add();
21197 }
21198
21199
21200 // Presentational
21201 plotBorder.attr({
21202 stroke: optionsChart.plotBorderColor,
21203 'stroke-width': optionsChart.plotBorderWidth || 0,
21204 fill: 'none'
21205 });
21206
21207
21208 plotBorder[verb](plotBorder.crisp({
21209 x: plotLeft,
21210 y: plotTop,
21211 width: plotWidth,
21212 height: plotHeight
21213 }, -plotBorder.strokeWidth())); // #3282 plotBorder should be negative;
21214
21215 // reset
21216 chart.isDirtyBox = false;
21217 },
21218
21219 /**
21220 * Detect whether a certain chart property is needed based on inspecting its
21221 * options and series. This mainly applies to the chart.inverted property,
21222 * and in extensions to the chart.angular and chart.polar properties.
21223 *
21224 * @private
21225 */
21226 propFromSeries: function() {
21227 var chart = this,
21228 optionsChart = chart.options.chart,
21229 klass,
21230 seriesOptions = chart.options.series,
21231 i,
21232 value;
21233
21234
21235 each(['inverted', 'angular', 'polar'], function(key) {
21236
21237 // The default series type's class
21238 klass = seriesTypes[optionsChart.type ||
21239 optionsChart.defaultSeriesType];
21240
21241 // Get the value from available chart-wide properties
21242 value =
21243 optionsChart[key] || // It is set in the options
21244 (klass && klass.prototype[key]); // The default series class
21245 // requires it
21246
21247 // 4. Check if any the chart's series require it
21248 i = seriesOptions && seriesOptions.length;
21249 while (!value && i--) {
21250 klass = seriesTypes[seriesOptions[i].type];
21251 if (klass && klass.prototype[key]) {
21252 value = true;
21253 }
21254 }
21255
21256 // Set the chart property
21257 chart[key] = value;
21258 });
21259
21260 },
21261
21262 /**
21263 * Internal function to link two or more series together, based on the
21264 * `linkedTo` option. This is done from `Chart.render`, and after
21265 * `Chart.addSeries` and `Series.remove`.
21266 *
21267 * @private
21268 */
21269 linkSeries: function() {
21270 var chart = this,
21271 chartSeries = chart.series;
21272
21273 // Reset links
21274 each(chartSeries, function(series) {
21275 series.linkedSeries.length = 0;
21276 });
21277
21278 // Apply new links
21279 each(chartSeries, function(series) {
21280 var linkedTo = series.options.linkedTo;
21281 if (isString(linkedTo)) {
21282 if (linkedTo === ':previous') {
21283 linkedTo = chart.series[series.index - 1];
21284 } else {
21285 linkedTo = chart.get(linkedTo);
21286 }
21287 // #3341 avoid mutual linking
21288 if (linkedTo && linkedTo.linkedParent !== series) {
21289 linkedTo.linkedSeries.push(series);
21290 series.linkedParent = linkedTo;
21291 series.visible = pick(
21292 series.options.visible,
21293 linkedTo.options.visible,
21294 series.visible
21295 ); // #3879
21296 }
21297 }
21298 });
21299 },
21300
21301 /**
21302 * Render series for the chart.
21303 *
21304 * @private
21305 */
21306 renderSeries: function() {
21307 each(this.series, function(serie) {
21308 serie.translate();
21309 serie.render();
21310 });
21311 },
21312
21313 /**
21314 * Render labels for the chart.
21315 *
21316 * @private
21317 */
21318 renderLabels: function() {
21319 var chart = this,
21320 labels = chart.options.labels;
21321 if (labels.items) {
21322 each(labels.items, function(label) {
21323 var style = extend(labels.style, label.style),
21324 x = pInt(style.left) + chart.plotLeft,
21325 y = pInt(style.top) + chart.plotTop + 12;
21326
21327 // delete to prevent rewriting in IE
21328 delete style.left;
21329 delete style.top;
21330
21331 chart.renderer.text(
21332 label.html,
21333 x,
21334 y
21335 )
21336 .attr({
21337 zIndex: 2
21338 })
21339 .css(style)
21340 .add();
21341
21342 });
21343 }
21344 },
21345
21346 /**
21347 * Render all graphics for the chart. Runs internally on initialization.
21348 *
21349 * @private
21350 */
21351 render: function() {
21352 var chart = this,
21353 axes = chart.axes,
21354 renderer = chart.renderer,
21355 options = chart.options,
21356 tempWidth,
21357 tempHeight,
21358 redoHorizontal,
21359 redoVertical;
21360
21361 // Title
21362 chart.setTitle();
21363
21364
21365 // Legend
21366 chart.legend = new Legend(chart, options.legend);
21367
21368 // Get stacks
21369 if (chart.getStacks) {
21370 chart.getStacks();
21371 }
21372
21373 // Get chart margins
21374 chart.getMargins(true);
21375 chart.setChartSize();
21376
21377 // Record preliminary dimensions for later comparison
21378 tempWidth = chart.plotWidth;
21379 // 21 is the most common correction for X axis labels
21380 // use Math.max to prevent negative plotHeight
21381 tempHeight = chart.plotHeight = Math.max(chart.plotHeight - 21, 0);
21382
21383 // Get margins by pre-rendering axes
21384 each(axes, function(axis) {
21385 axis.setScale();
21386 });
21387 chart.getAxisMargins();
21388
21389 // If the plot area size has changed significantly, calculate tick positions again
21390 redoHorizontal = tempWidth / chart.plotWidth > 1.1;
21391 redoVertical = tempHeight / chart.plotHeight > 1.05; // Height is more sensitive
21392
21393 if (redoHorizontal || redoVertical) {
21394
21395 each(axes, function(axis) {
21396 if ((axis.horiz && redoHorizontal) || (!axis.horiz && redoVertical)) {
21397 axis.setTickInterval(true); // update to reflect the new margins
21398 }
21399 });
21400 chart.getMargins(); // second pass to check for new labels
21401 }
21402
21403 // Draw the borders and backgrounds
21404 chart.drawChartBox();
21405
21406
21407 // Axes
21408 if (chart.hasCartesianSeries) {
21409 each(axes, function(axis) {
21410 if (axis.visible) {
21411 axis.render();
21412 }
21413 });
21414 }
21415
21416 // The series
21417 if (!chart.seriesGroup) {
21418 chart.seriesGroup = renderer.g('series-group')
21419 .attr({
21420 zIndex: 3
21421 })
21422 .add();
21423 }
21424 chart.renderSeries();
21425
21426 // Labels
21427 chart.renderLabels();
21428
21429 // Credits
21430 chart.addCredits();
21431
21432 // Handle responsiveness
21433 if (chart.setResponsive) {
21434 chart.setResponsive();
21435 }
21436
21437 // Set flag
21438 chart.hasRendered = true;
21439
21440 },
21441
21442 /**
21443 * Set a new credits label for the chart.
21444 *
21445 * @param {CreditOptions} options
21446 * A configuration object for the new credits.
21447 * @sample highcharts/credits/credits-update/ Add and update credits
21448 */
21449 addCredits: function(credits) {
21450 var chart = this;
21451
21452 credits = merge(true, this.options.credits, credits);
21453 if (credits.enabled && !this.credits) {
21454
21455 /**
21456 * The chart's credits label. The label has an `update` method that
21457 * allows setting new options as per the {@link
21458 * https://api.highcharts.com/highcharts/credits|
21459 * credits options set}.
21460 *
21461 * @memberof Highcharts.Chart
21462 * @name credits
21463 * @type {Highcharts.SVGElement}
21464 */
21465 this.credits = this.renderer.text(
21466 credits.text + (this.mapCredits || ''),
21467 0,
21468 0
21469 )
21470 .addClass('highcharts-credits')
21471 .on('click', function() {
21472 if (credits.href) {
21473 win.location.href = credits.href;
21474 }
21475 })
21476 .attr({
21477 align: credits.position.align,
21478 zIndex: 8
21479 })
21480
21481 .css(credits.style)
21482
21483 .add()
21484 .align(credits.position);
21485
21486 // Dynamically update
21487 this.credits.update = function(options) {
21488 chart.credits = chart.credits.destroy();
21489 chart.addCredits(options);
21490 };
21491 }
21492 },
21493
21494 /**
21495 * Remove the chart and purge memory. This method is called internally
21496 * before adding a second chart into the same container, as well as on
21497 * window unload to prevent leaks.
21498 *
21499 * @sample highcharts/members/chart-destroy/
21500 * Destroy the chart from a button
21501 * @sample stock/members/chart-destroy/
21502 * Destroy with Highstock
21503 */
21504 destroy: function() {
21505 var chart = this,
21506 axes = chart.axes,
21507 series = chart.series,
21508 container = chart.container,
21509 i,
21510 parentNode = container && container.parentNode;
21511
21512 // fire the chart.destoy event
21513 fireEvent(chart, 'destroy');
21514
21515 // Delete the chart from charts lookup array
21516 if (chart.renderer.forExport) {
21517 H.erase(charts, chart); // #6569
21518 } else {
21519 charts[chart.index] = undefined;
21520 }
21521 H.chartCount--;
21522 chart.renderTo.removeAttribute('data-highcharts-chart');
21523
21524 // remove events
21525 removeEvent(chart);
21526
21527 // ==== Destroy collections:
21528 // Destroy axes
21529 i = axes.length;
21530 while (i--) {
21531 axes[i] = axes[i].destroy();
21532 }
21533
21534 // Destroy scroller & scroller series before destroying base series
21535 if (this.scroller && this.scroller.destroy) {
21536 this.scroller.destroy();
21537 }
21538
21539 // Destroy each series
21540 i = series.length;
21541 while (i--) {
21542 series[i] = series[i].destroy();
21543 }
21544
21545 // ==== Destroy chart properties:
21546 each([
21547 'title', 'subtitle', 'chartBackground', 'plotBackground',
21548 'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits',
21549 'pointer', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip',
21550 'renderer'
21551 ], function(name) {
21552 var prop = chart[name];
21553
21554 if (prop && prop.destroy) {
21555 chart[name] = prop.destroy();
21556 }
21557 });
21558
21559 // remove container and all SVG
21560 if (container) { // can break in IE when destroyed before finished loading
21561 container.innerHTML = '';
21562 removeEvent(container);
21563 if (parentNode) {
21564 discardElement(container);
21565 }
21566
21567 }
21568
21569 // clean it all up
21570 objectEach(chart, function(val, key) {
21571 delete chart[key];
21572 });
21573
21574 },
21575
21576
21577 /**
21578 * VML namespaces can't be added until after complete. Listening
21579 * for Perini's doScroll hack is not enough.
21580 *
21581 * @private
21582 */
21583 isReadyToRender: function() {
21584 var chart = this;
21585
21586 // Note: win == win.top is required
21587 if ((!svg && (win == win.top && doc.readyState !== 'complete'))) { // eslint-disable-line eqeqeq
21588 doc.attachEvent('onreadystatechange', function() {
21589 doc.detachEvent('onreadystatechange', chart.firstRender);
21590 if (doc.readyState === 'complete') {
21591 chart.firstRender();
21592 }
21593 });
21594 return false;
21595 }
21596 return true;
21597 },
21598
21599 /**
21600 * Prepare for first rendering after all data are loaded.
21601 *
21602 * @private
21603 */
21604 firstRender: function() {
21605 var chart = this,
21606 options = chart.options;
21607
21608 // Check whether the chart is ready to render
21609 if (!chart.isReadyToRender()) {
21610 return;
21611 }
21612
21613 // Create the container
21614 chart.getContainer();
21615
21616 // Run an early event after the container and renderer are established
21617 fireEvent(chart, 'init');
21618
21619
21620 chart.resetMargins();
21621 chart.setChartSize();
21622
21623 // Set the common chart properties (mainly invert) from the given series
21624 chart.propFromSeries();
21625
21626 // get axes
21627 chart.getAxes();
21628
21629 // Initialize the series
21630 each(options.series || [], function(serieOptions) {
21631 chart.initSeries(serieOptions);
21632 });
21633
21634 chart.linkSeries();
21635
21636 // Run an event after axes and series are initialized, but before render. At this stage,
21637 // the series data is indexed and cached in the xData and yData arrays, so we can access
21638 // those before rendering. Used in Highstock.
21639 fireEvent(chart, 'beforeRender');
21640
21641 // depends on inverted and on margins being set
21642 if (Pointer) {
21643
21644 /**
21645 * The Pointer that keeps track of mouse and touch interaction.
21646 *
21647 * @memberof Chart
21648 * @name pointer
21649 * @type Pointer
21650 */
21651 chart.pointer = new Pointer(chart, options);
21652 }
21653
21654 chart.render();
21655
21656 // Fire the load event if there are no external images
21657 if (!chart.renderer.imgCount && chart.onload) {
21658 chart.onload();
21659 }
21660
21661 // If the chart was rendered outside the top container, put it back in (#3679)
21662 chart.temporaryDisplay(true);
21663
21664 },
21665
21666 /**
21667 * Internal function that runs on chart load, async if any images are loaded
21668 * in the chart. Runs the callbacks and triggers the `load` and `render`
21669 * events.
21670 *
21671 * @private
21672 */
21673 onload: function() {
21674
21675 // Run callbacks
21676 each([this.callback].concat(this.callbacks), function(fn) {
21677 if (fn && this.index !== undefined) { // Chart destroyed in its own callback (#3600)
21678 fn.apply(this, [this]);
21679 }
21680 }, this);
21681
21682 fireEvent(this, 'load');
21683 fireEvent(this, 'render');
21684
21685
21686 // Set up auto resize, check for not destroyed (#6068)
21687 if (defined(this.index) && this.options.chart.reflow !== false) {
21688 this.initReflow();
21689 }
21690
21691 // Don't run again
21692 this.onload = null;
21693 }
21694
21695 }); // end Chart
21696
21697 }(Highcharts));
21698 (function(Highcharts) {
21699 /**
21700 * (c) 2010-2017 Torstein Honsi
21701 *
21702 * License: www.highcharts.com/license
21703 */
21704 var Point,
21705 H = Highcharts,
21706
21707 each = H.each,
21708 extend = H.extend,
21709 erase = H.erase,
21710 fireEvent = H.fireEvent,
21711 format = H.format,
21712 isArray = H.isArray,
21713 isNumber = H.isNumber,
21714 pick = H.pick,
21715 removeEvent = H.removeEvent;
21716
21717 /**
21718 * The Point object. The point objects are generated from the `series.data`
21719 * configuration objects or raw numbers. They can be accessed from the
21720 * `Series.points` array. Other ways to instaniate points are through {@link
21721 * Highcharts.Series#addPoint} or {@link Highcharts.Series#setData}.
21722 *
21723 * @class
21724 */
21725
21726 Highcharts.Point = Point = function() {};
21727 Highcharts.Point.prototype = {
21728
21729 /**
21730 * Initialize the point. Called internally based on the `series.data`
21731 * option.
21732 * @param {Series} series
21733 * The series object containing this point.
21734 * @param {Number|Array|Object} options
21735 * The data in either number, array or object format.
21736 * @param {Number} x Optionally, the X value of the point.
21737 * @return {Point} The Point instance.
21738 */
21739 init: function(series, options, x) {
21740
21741 var point = this,
21742 colors,
21743 colorCount = series.chart.options.chart.colorCount,
21744 colorIndex;
21745
21746 /**
21747 * The series object associated with the point.
21748 *
21749 * @name series
21750 * @memberof Highcharts.Point
21751 * @type Highcharts.Series
21752 */
21753 point.series = series;
21754
21755
21756 /**
21757 * The point's current color.
21758 * @name color
21759 * @memberof Highcharts.Point
21760 * @type {Color}
21761 */
21762 point.color = series.color; // #3445
21763
21764 point.applyOptions(options, x);
21765
21766 if (series.options.colorByPoint) {
21767
21768 colors = series.options.colors || series.chart.options.colors;
21769 point.color = point.color || colors[series.colorCounter];
21770 colorCount = colors.length;
21771
21772 colorIndex = series.colorCounter;
21773 series.colorCounter++;
21774 // loop back to zero
21775 if (series.colorCounter === colorCount) {
21776 series.colorCounter = 0;
21777 }
21778 } else {
21779 colorIndex = series.colorIndex;
21780 }
21781
21782 /**
21783 * The point's current color index, used in styled mode instead of
21784 * `color`. The color index is inserted in class names used for styling.
21785 * @name colorIndex
21786 * @memberof Highcharts.Point
21787 * @type {Number}
21788 */
21789 point.colorIndex = pick(point.colorIndex, colorIndex);
21790
21791 series.chart.pointCount++;
21792 return point;
21793 },
21794 /**
21795 * Apply the options containing the x and y data and possible some extra
21796 * properties. Called on point init or from point.update.
21797 *
21798 * @private
21799 * @param {Object} options The point options as defined in series.data.
21800 * @param {Number} x Optionally, the X value.
21801 * @returns {Object} The Point instance.
21802 */
21803 applyOptions: function(options, x) {
21804 var point = this,
21805 series = point.series,
21806 pointValKey = series.options.pointValKey || series.pointValKey;
21807
21808 options = Point.prototype.optionsToObject.call(this, options);
21809
21810 // copy options directly to point
21811 extend(point, options);
21812 point.options = point.options ? extend(point.options, options) : options;
21813
21814 // Since options are copied into the Point instance, some accidental options must be shielded (#5681)
21815 if (options.group) {
21816 delete point.group;
21817 }
21818
21819 // For higher dimension series types. For instance, for ranges, point.y is mapped to point.low.
21820 if (pointValKey) {
21821 point.y = point[pointValKey];
21822 }
21823 point.isNull = pick(
21824 point.isValid && !point.isValid(),
21825 point.x === null || !isNumber(point.y, true)
21826 ); // #3571, check for NaN
21827
21828 // The point is initially selected by options (#5777)
21829 if (point.selected) {
21830 point.state = 'select';
21831 }
21832
21833 // If no x is set by now, get auto incremented value. All points must have an
21834 // x value, however the y value can be null to create a gap in the series
21835 if ('name' in point && x === undefined && series.xAxis && series.xAxis.hasNames) {
21836 point.x = series.xAxis.nameToX(point);
21837 }
21838 if (point.x === undefined && series) {
21839 if (x === undefined) {
21840 point.x = series.autoIncrement(point);
21841 } else {
21842 point.x = x;
21843 }
21844 }
21845
21846 return point;
21847 },
21848
21849 /**
21850 * Transform number or array configs into objects. Used internally to unify
21851 * the different configuration formats for points. For example, a simple
21852 * number `10` in a line series will be transformed to `{ y: 10 }`, and an
21853 * array config like `[1, 10]` in a scatter series will be transformed to
21854 * `{ x: 1, y: 10 }`.
21855 *
21856 * @param {Number|Array|Object} options
21857 * The input options
21858 * @return {Object} Transformed options.
21859 */
21860 optionsToObject: function(options) {
21861 var ret = {},
21862 series = this.series,
21863 keys = series.options.keys,
21864 pointArrayMap = keys || series.pointArrayMap || ['y'],
21865 valueCount = pointArrayMap.length,
21866 firstItemType,
21867 i = 0,
21868 j = 0;
21869
21870 if (isNumber(options) || options === null) {
21871 ret[pointArrayMap[0]] = options;
21872
21873 } else if (isArray(options)) {
21874 // with leading x value
21875 if (!keys && options.length > valueCount) {
21876 firstItemType = typeof options[0];
21877 if (firstItemType === 'string') {
21878 ret.name = options[0];
21879 } else if (firstItemType === 'number') {
21880 ret.x = options[0];
21881 }
21882 i++;
21883 }
21884 while (j < valueCount) {
21885 if (!keys || options[i] !== undefined) { // Skip undefined positions for keys
21886 ret[pointArrayMap[j]] = options[i];
21887 }
21888 i++;
21889 j++;
21890 }
21891 } else if (typeof options === 'object') {
21892 ret = options;
21893
21894 // This is the fastest way to detect if there are individual point dataLabels that need
21895 // to be considered in drawDataLabels. These can only occur in object configs.
21896 if (options.dataLabels) {
21897 series._hasPointLabels = true;
21898 }
21899
21900 // Same approach as above for markers
21901 if (options.marker) {
21902 series._hasPointMarkers = true;
21903 }
21904 }
21905 return ret;
21906 },
21907
21908 /**
21909 * Get the CSS class names for individual points. Used internally where the
21910 * returned value is set on every point.
21911 *
21912 * @returns {String} The class names.
21913 */
21914 getClassName: function() {
21915 return 'highcharts-point' +
21916 (this.selected ? ' highcharts-point-select' : '') +
21917 (this.negative ? ' highcharts-negative' : '') +
21918 (this.isNull ? ' highcharts-null-point' : '') +
21919 (this.colorIndex !== undefined ? ' highcharts-color-' +
21920 this.colorIndex : '') +
21921 (this.options.className ? ' ' + this.options.className : '') +
21922 (this.zone && this.zone.className ? ' ' +
21923 this.zone.className.replace('highcharts-negative', '') : '');
21924 },
21925
21926 /**
21927 * In a series with `zones`, return the zone that the point belongs to.
21928 *
21929 * @return {Object}
21930 * The zone item.
21931 */
21932 getZone: function() {
21933 var series = this.series,
21934 zones = series.zones,
21935 zoneAxis = series.zoneAxis || 'y',
21936 i = 0,
21937 zone;
21938
21939 zone = zones[i];
21940 while (this[zoneAxis] >= zone.value) {
21941 zone = zones[++i];
21942 }
21943
21944 if (zone && zone.color && !this.options.color) {
21945 this.color = zone.color;
21946 }
21947
21948 return zone;
21949 },
21950
21951 /**
21952 * Destroy a point to clear memory. Its reference still stays in
21953 * `series.data`.
21954 *
21955 * @private
21956 */
21957 destroy: function() {
21958 var point = this,
21959 series = point.series,
21960 chart = series.chart,
21961 hoverPoints = chart.hoverPoints,
21962 prop;
21963
21964 chart.pointCount--;
21965
21966 if (hoverPoints) {
21967 point.setState();
21968 erase(hoverPoints, point);
21969 if (!hoverPoints.length) {
21970 chart.hoverPoints = null;
21971 }
21972
21973 }
21974 if (point === chart.hoverPoint) {
21975 point.onMouseOut();
21976 }
21977
21978 // remove all events
21979 if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive
21980 removeEvent(point);
21981 point.destroyElements();
21982 }
21983
21984 if (point.legendItem) { // pies have legend items
21985 chart.legend.destroyItem(point);
21986 }
21987
21988 for (prop in point) {
21989 point[prop] = null;
21990 }
21991
21992
21993 },
21994
21995 /**
21996 * Destroy SVG elements associated with the point.
21997 *
21998 * @private
21999 */
22000 destroyElements: function() {
22001 var point = this,
22002 props = ['graphic', 'dataLabel', 'dataLabelUpper', 'connector', 'shadowGroup'],
22003 prop,
22004 i = 6;
22005 while (i--) {
22006 prop = props[i];
22007 if (point[prop]) {
22008 point[prop] = point[prop].destroy();
22009 }
22010 }
22011 },
22012
22013 /**
22014 * Return the configuration hash needed for the data label and tooltip
22015 * formatters.
22016 *
22017 * @returns {Object}
22018 * Abstract object used in formatters and formats.
22019 */
22020 getLabelConfig: function() {
22021 return {
22022 x: this.category,
22023 y: this.y,
22024 color: this.color,
22025 colorIndex: this.colorIndex,
22026 key: this.name || this.category,
22027 series: this.series,
22028 point: this,
22029 percentage: this.percentage,
22030 total: this.total || this.stackTotal
22031 };
22032 },
22033
22034 /**
22035 * Extendable method for formatting each point's tooltip line.
22036 *
22037 * @param {String} pointFormat
22038 * The point format.
22039 * @return {String}
22040 * A string to be concatenated in to the common tooltip text.
22041 */
22042 tooltipFormatter: function(pointFormat) {
22043
22044 // Insert options for valueDecimals, valuePrefix, and valueSuffix
22045 var series = this.series,
22046 seriesTooltipOptions = series.tooltipOptions,
22047 valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''),
22048 valuePrefix = seriesTooltipOptions.valuePrefix || '',
22049 valueSuffix = seriesTooltipOptions.valueSuffix || '';
22050
22051 // Loop over the point array map and replace unformatted values with sprintf formatting markup
22052 each(series.pointArrayMap || ['y'], function(key) {
22053 key = '{point.' + key; // without the closing bracket
22054 if (valuePrefix || valueSuffix) {
22055 pointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix);
22056 }
22057 pointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}');
22058 });
22059
22060 return format(pointFormat, {
22061 point: this,
22062 series: this.series
22063 });
22064 },
22065
22066 /**
22067 * Fire an event on the Point object.
22068 *
22069 * @private
22070 * @param {String} eventType
22071 * @param {Object} eventArgs Additional event arguments
22072 * @param {Function} defaultFunction Default event handler
22073 */
22074 firePointEvent: function(eventType, eventArgs, defaultFunction) {
22075 var point = this,
22076 series = this.series,
22077 seriesOptions = series.options;
22078
22079 // load event handlers on demand to save time on mouseover/out
22080 if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {
22081 this.importEvents();
22082 }
22083
22084 // add default handler if in selection mode
22085 if (eventType === 'click' && seriesOptions.allowPointSelect) {
22086 defaultFunction = function(event) {
22087 // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
22088 if (point.select) { // Could be destroyed by prior event handlers (#2911)
22089 point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
22090 }
22091 };
22092 }
22093
22094 fireEvent(this, eventType, eventArgs, defaultFunction);
22095 },
22096
22097 /**
22098 * For certain series types, like pie charts, where individual points can
22099 * be shown or hidden.
22100 *
22101 * @name visible
22102 * @memberOf Highcharts.Point
22103 * @type {Boolean}
22104 */
22105 visible: true
22106 };
22107
22108 /**
22109 * For categorized axes this property holds the category name for the
22110 * point. For other axes it holds the X value.
22111 *
22112 * @name category
22113 * @memberOf Highcharts.Point
22114 * @type {String|Number}
22115 */
22116
22117 /**
22118 * The name of the point. The name can be given as the first position of the
22119 * point configuration array, or as a `name` property in the configuration:
22120 *
22121 * @example
22122 * // Array config
22123 * data: [
22124 * ['John', 1],
22125 * ['Jane', 2]
22126 * ]
22127 *
22128 * // Object config
22129 * data: [{
22130 * name: 'John',
22131 * y: 1
22132 * }, {
22133 * name: 'Jane',
22134 * y: 2
22135 * }]
22136 *
22137 * @name name
22138 * @memberOf Highcharts.Point
22139 * @type {String}
22140 */
22141
22142
22143 /**
22144 * The percentage for points in a stacked series or pies.
22145 *
22146 * @name percentage
22147 * @memberOf Highcharts.Point
22148 * @type {Number}
22149 */
22150
22151 /**
22152 * The total of values in either a stack for stacked series, or a pie in a pie
22153 * series.
22154 *
22155 * @name total
22156 * @memberOf Highcharts.Point
22157 * @type {Number}
22158 */
22159
22160 /**
22161 * The x value of the point.
22162 *
22163 * @name x
22164 * @memberOf Highcharts.Point
22165 * @type {Number}
22166 */
22167
22168 /**
22169 * The y value of the point.
22170 *
22171 * @name y
22172 * @memberOf Highcharts.Point
22173 * @type {Number}
22174 */
22175
22176 }(Highcharts));
22177 (function(H) {
22178 /**
22179 * (c) 2010-2017 Torstein Honsi
22180 *
22181 * License: www.highcharts.com/license
22182 */
22183 var addEvent = H.addEvent,
22184 animObject = H.animObject,
22185 arrayMax = H.arrayMax,
22186 arrayMin = H.arrayMin,
22187 correctFloat = H.correctFloat,
22188 Date = H.Date,
22189 defaultOptions = H.defaultOptions,
22190 defaultPlotOptions = H.defaultPlotOptions,
22191 defined = H.defined,
22192 each = H.each,
22193 erase = H.erase,
22194 extend = H.extend,
22195 fireEvent = H.fireEvent,
22196 grep = H.grep,
22197 isArray = H.isArray,
22198 isNumber = H.isNumber,
22199 isString = H.isString,
22200 LegendSymbolMixin = H.LegendSymbolMixin, // @todo add as a requirement
22201 merge = H.merge,
22202 objectEach = H.objectEach,
22203 pick = H.pick,
22204 Point = H.Point, // @todo add as a requirement
22205 removeEvent = H.removeEvent,
22206 splat = H.splat,
22207 SVGElement = H.SVGElement,
22208 syncTimeout = H.syncTimeout,
22209 win = H.win;
22210
22211 /**
22212 * This is the base series prototype that all other series types inherit from.
22213 * A new series is initialized either through the
22214 * {@link https://api.highcharts.com/highcharts/series|series} option structure,
22215 * or after the chart is initialized, through
22216 * {@link Highcharts.Chart#addSeries}.
22217 *
22218 * The object can be accessed in a number of ways. All series and point event
22219 * handlers give a reference to the `series` object. The chart object has a
22220 * {@link Highcharts.Chart.series|series} property that is a collection of all
22221 * the chart's series. The point objects and axis objects also have the same
22222 * reference.
22223 *
22224 * Another way to reference the series programmatically is by `id`. Add an id
22225 * in the series configuration options, and get the series object by {@link
22226 * Highcharts.Chart#get}.
22227 *
22228 * Configuration options for the series are given in three levels. Options for
22229 * all series in a chart are given in the
22230 * {@link https://api.highcharts.com/highcharts/plotOptions.series|
22231 * plotOptions.series} object. Then options for all series of a specific type
22232 * are given in the plotOptions of that type, for example `plotOptions.line`.
22233 * Next, options for one single series are given in the series array, or as
22234 * arguements to `chart.addSeries`.
22235 *
22236 * The data in the series is stored in various arrays.
22237 *
22238 * - First, `series.options.data` contains all the original config options for
22239 * each point whether added by options or methods like `series.addPoint`.
22240 * - Next, `series.data` contains those values converted to points, but in case
22241 * the series data length exceeds the `cropThreshold`, or if the data is
22242 * grouped, `series.data` doesn't contain all the points. It only contains the
22243 * points that have been created on demand.
22244 * - Then there's `series.points` that contains all currently visible point
22245 * objects. In case of cropping, the cropped-away points are not part of this
22246 * array. The `series.points` array starts at `series.cropStart` compared to
22247 * `series.data` and `series.options.data`. If however the series data is
22248 * grouped, these can't be correlated one to one.
22249 * - `series.xData` and `series.processedXData` contain clean x values,
22250 * equivalent to `series.data` and `series.points`.
22251 * - `series.yData` and `series.processedYData` contain clean y values,
22252 * equivalent to `series.data` and `series.points`.
22253 *
22254 * @class Highcharts.Series
22255 * @param {Highcharts.Chart} chart
22256 * The chart instance.
22257 * @param {Options.plotOptions.series} options
22258 * The series options.
22259 *
22260 */
22261
22262 /**
22263 * General options for all series types.
22264 * @optionparent plotOptions.series
22265 */
22266 H.Series = H.seriesType('line', null, { // base series options
22267
22268 /**
22269 * The SVG value used for the `stroke-linecap` and `stroke-linejoin`
22270 * of a line graph. Round means that lines are rounded in the ends and
22271 * bends.
22272 *
22273 * @validvalue ["round", "butt", "square"]
22274 * @type {String}
22275 * @default round
22276 * @since 3.0.7
22277 * @apioption plotOptions.line.linecap
22278 */
22279
22280 /**
22281 * Pixel with of the graph line.
22282 *
22283 * @type {Number}
22284 * @see In styled mode, the line stroke-width can be set with the
22285 * `.highcharts-graph` class name.
22286 * @sample {highcharts} highcharts/plotoptions/series-linewidth-general/
22287 * On all series
22288 * @sample {highcharts} highcharts/plotoptions/series-linewidth-specific/
22289 * On one single series
22290 * @default 2
22291 * @product highcharts highstock
22292 */
22293 lineWidth: 2,
22294
22295
22296 /**
22297 * For some series, there is a limit that shuts down initial animation
22298 * by default when the total number of points in the chart is too high.
22299 * For example, for a column chart and its derivatives, animation doesn't
22300 * run if there is more than 250 points totally. To disable this cap, set
22301 * `animationLimit` to `Infinity`.
22302 *
22303 * @type {Number}
22304 * @apioption plotOptions.series.animationLimit
22305 */
22306
22307 /**
22308 * Allow this series' points to be selected by clicking on the graphic
22309 * (columns, point markers, pie slices, map areas etc).
22310 *
22311 * @see [Chart#getSelectedPoints]
22312 * (../class-reference/Highcharts.Chart#getSelectedPoints).
22313 *
22314 * @type {Boolean}
22315 * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-line/
22316 * Line
22317 * @sample {highcharts}
22318 * highcharts/plotoptions/series-allowpointselect-column/
22319 * Column
22320 * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-pie/
22321 * Pie
22322 * @sample {highmaps} maps/plotoptions/series-allowpointselect/
22323 * Map area
22324 * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/
22325 * Map bubble
22326 * @default false
22327 * @since 1.2.0
22328 */
22329 allowPointSelect: false,
22330
22331
22332
22333 /**
22334 * If true, a checkbox is displayed next to the legend item to allow
22335 * selecting the series. The state of the checkbox is determined by
22336 * the `selected` option.
22337 *
22338 * @productdesc {highmaps}
22339 * Note that if a `colorAxis` is defined, the color axis is represented in
22340 * the legend, not the series.
22341 *
22342 * @type {Boolean}
22343 * @sample {highcharts} highcharts/plotoptions/series-showcheckbox-true/
22344 * Show select box
22345 * @default false
22346 * @since 1.2.0
22347 */
22348 showCheckbox: false,
22349
22350
22351
22352 /**
22353 * Enable or disable the initial animation when a series is displayed.
22354 * The animation can also be set as a configuration object. Please
22355 * note that this option only applies to the initial animation of the
22356 * series itself. For other animations, see [chart.animation](#chart.
22357 * animation) and the animation parameter under the API methods. The
22358 * following properties are supported:
22359 *
22360 * <dl>
22361 *
22362 * <dt>duration</dt>
22363 *
22364 * <dd>The duration of the animation in milliseconds.</dd>
22365 *
22366 * <dt>easing</dt>
22367 *
22368 * <dd>A string reference to an easing function set on the `Math` object.
22369 * See the _Custom easing function_ demo below.</dd>
22370 *
22371 * </dl>
22372 *
22373 * Due to poor performance, animation is disabled in old IE browsers
22374 * for several chart types.
22375 *
22376 * @type {Boolean}
22377 * @sample {highcharts} highcharts/plotoptions/series-animation-disabled/
22378 * Animation disabled
22379 * @sample {highcharts} highcharts/plotoptions/series-animation-slower/
22380 * Slower animation
22381 * @sample {highcharts} highcharts/plotoptions/series-animation-easing/
22382 * Custom easing function
22383 * @sample {highstock} stock/plotoptions/animation-slower/
22384 * Slower animation
22385 * @sample {highstock} stock/plotoptions/animation-easing/
22386 * Custom easing function
22387 * @sample {highmaps} maps/plotoptions/series-animation-true/
22388 * Animation enabled on map series
22389 * @sample {highmaps} maps/plotoptions/mapbubble-animation-false/
22390 * Disabled on mapbubble series
22391 * @default {highcharts} true
22392 * @default {highstock} true
22393 * @default {highmaps} false
22394 */
22395 animation: {
22396 duration: 1000
22397 },
22398
22399 /**
22400 * A class name to apply to the series' graphical elements.
22401 *
22402 * @type {String}
22403 * @since 5.0.0
22404 * @apioption plotOptions.series.className
22405 */
22406
22407 /**
22408 * The main color of the series. In line type series it applies to the
22409 * line and the point markers unless otherwise specified. In bar type
22410 * series it applies to the bars unless a color is specified per point.
22411 * The default value is pulled from the `options.colors` array.
22412 *
22413 * In styled mode, the color can be defined by the
22414 * [colorIndex](#plotOptions.series.colorIndex) option. Also, the series
22415 * color can be set with the `.highcharts-series`, `.highcharts-color-{n}`,
22416 * `.highcharts-{type}-series` or `.highcharts-series-{n}` class, or
22417 * individual classes given by the `className` option.
22418 *
22419 * @productdesc {highmaps}
22420 * In maps, the series color is rarely used, as most choropleth maps use the
22421 * color to denote the value of each point. The series color can however be
22422 * used in a map with multiple series holding categorized data.
22423 *
22424 * @type {Color}
22425 * @sample {highcharts} highcharts/plotoptions/series-color-general/
22426 * General plot option
22427 * @sample {highcharts} highcharts/plotoptions/series-color-specific/
22428 * One specific series
22429 * @sample {highcharts} highcharts/plotoptions/series-color-area/
22430 * Area color
22431 * @sample {highmaps} maps/demo/category-map/
22432 * Category map by multiple series
22433 * @apioption plotOptions.series.color
22434 */
22435
22436 /**
22437 * Styled mode only. A specific color index to use for the series, so its
22438 * graphic representations are given the class name `highcharts-color-
22439 * {n}`.
22440 *
22441 * @type {Number}
22442 * @since 5.0.0
22443 * @apioption plotOptions.series.colorIndex
22444 */
22445
22446
22447 /**
22448 * Whether to connect a graph line across null points, or render a gap
22449 * between the two points on either side of the null.
22450 *
22451 * @type {Boolean}
22452 * @sample {highcharts} highcharts/plotoptions/series-connectnulls-false/
22453 * False by default
22454 * @sample {highcharts} highcharts/plotoptions/series-connectnulls-true/
22455 * True
22456 * @product highcharts highstock
22457 * @apioption plotOptions.series.connectNulls
22458 */
22459
22460
22461 /**
22462 * You can set the cursor to "pointer" if you have click events attached
22463 * to the series, to signal to the user that the points and lines can
22464 * be clicked.
22465 *
22466 * @validvalue [null, "default", "none", "help", "pointer", "crosshair"]
22467 * @type {String}
22468 * @see In styled mode, the series cursor can be set with the same classes
22469 * as listed under [series.color](#plotOptions.series.color).
22470 * @sample {highcharts} highcharts/plotoptions/series-cursor-line/
22471 * On line graph
22472 * @sample {highcharts} highcharts/plotoptions/series-cursor-column/
22473 * On columns
22474 * @sample {highcharts} highcharts/plotoptions/series-cursor-scatter/
22475 * On scatter markers
22476 * @sample {highstock} stock/plotoptions/cursor/
22477 * Pointer on a line graph
22478 * @sample {highmaps} maps/plotoptions/series-allowpointselect/
22479 * Map area
22480 * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/
22481 * Map bubble
22482 * @apioption plotOptions.series.cursor
22483 */
22484
22485
22486 /**
22487 * A name for the dash style to use for the graph, or for some series types
22488 * the outline of each shape. The value for the `dashStyle` include:
22489 *
22490 * * Solid
22491 * * ShortDash
22492 * * ShortDot
22493 * * ShortDashDot
22494 * * ShortDashDotDot
22495 * * Dot
22496 * * Dash
22497 * * LongDash
22498 * * DashDot
22499 * * LongDashDot
22500 * * LongDashDotDot
22501 *
22502 * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
22503 * "ShortDashDotDot", "Dot", "Dash" ,"LongDash", "DashDot",
22504 * "LongDashDot", "LongDashDotDot"]
22505 * @type {String}
22506 * @see In styled mode, the [stroke dash-array](http://jsfiddle.net/gh/get/
22507 * library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/
22508 * series-dashstyle/) can be set with the same classes as listed under
22509 * [series.color](#plotOptions.series.color).
22510 *
22511 * @sample {highcharts} highcharts/plotoptions/series-dashstyle-all/
22512 * Possible values demonstrated
22513 * @sample {highcharts} highcharts/plotoptions/series-dashstyle/
22514 * Chart suitable for printing in black and white
22515 * @sample {highstock} highcharts/plotoptions/series-dashstyle-all/
22516 * Possible values demonstrated
22517 * @sample {highmaps} highcharts/plotoptions/series-dashstyle-all/
22518 * Possible values demonstrated
22519 * @sample {highmaps} maps/plotoptions/series-dashstyle/
22520 * Dotted borders on a map
22521 * @default Solid
22522 * @since 2.1
22523 * @apioption plotOptions.series.dashStyle
22524 */
22525
22526 /**
22527 * Requires the Accessibility module.
22528 *
22529 * A description of the series to add to the screen reader information
22530 * about the series.
22531 *
22532 * @type {String}
22533 * @default undefined
22534 * @since 5.0.0
22535 * @apioption plotOptions.series.description
22536 */
22537
22538
22539
22540
22541
22542 /**
22543 * Enable or disable the mouse tracking for a specific series. This
22544 * includes point tooltips and click events on graphs and points. For
22545 * large datasets it improves performance.
22546 *
22547 * @type {Boolean}
22548 * @sample {highcharts}
22549 * highcharts/plotoptions/series-enablemousetracking-false/
22550 * No mouse tracking
22551 * @sample {highmaps}
22552 * maps/plotoptions/series-enablemousetracking-false/
22553 * No mouse tracking
22554 * @default true
22555 * @apioption plotOptions.series.enableMouseTracking
22556 */
22557
22558 /**
22559 * By default, series are exposed to screen readers as regions. By enabling
22560 * this option, the series element itself will be exposed in the same
22561 * way as the data points. This is useful if the series is not used
22562 * as a grouping entity in the chart, but you still want to attach a
22563 * description to the series.
22564 *
22565 * Requires the Accessibility module.
22566 *
22567 * @type {Boolean}
22568 * @sample highcharts/accessibility/art-grants/
22569 * Accessible data visualization
22570 * @default undefined
22571 * @since 5.0.12
22572 * @apioption plotOptions.series.exposeElementToA11y
22573 */
22574
22575 /**
22576 * Whether to use the Y extremes of the total chart width or only the
22577 * zoomed area when zooming in on parts of the X axis. By default, the
22578 * Y axis adjusts to the min and max of the visible data. Cartesian
22579 * series only.
22580 *
22581 * @type {Boolean}
22582 * @default false
22583 * @since 4.1.6
22584 * @product highcharts highstock
22585 * @apioption plotOptions.series.getExtremesFromAll
22586 */
22587
22588 /**
22589 * An id for the series. This can be used after render time to get a
22590 * pointer to the series object through `chart.get()`.
22591 *
22592 * @type {String}
22593 * @sample {highcharts} highcharts/plotoptions/series-id/ Get series by id
22594 * @since 1.2.0
22595 * @apioption series.id
22596 */
22597
22598 /**
22599 * The index of the series in the chart, affecting the internal index
22600 * in the `chart.series` array, the visible Z index as well as the order
22601 * in the legend.
22602 *
22603 * @type {Number}
22604 * @default undefined
22605 * @since 2.3.0
22606 * @apioption series.index
22607 */
22608
22609 /**
22610 * An array specifying which option maps to which key in the data point
22611 * array. This makes it convenient to work with unstructured data arrays
22612 * from different sources.
22613 *
22614 * @type {Array<String>}
22615 * @see [series.data](#series.line.data)
22616 * @sample {highcharts|highstock} highcharts/series/data-keys/
22617 * An extended data array with keys
22618 * @since 4.1.6
22619 * @product highcharts highstock
22620 * @apioption plotOptions.series.keys
22621 */
22622
22623 /**
22624 * The sequential index of the series in the legend.
22625 *
22626 * @sample {highcharts|highstock} highcharts/series/legendindex/
22627 * Legend in opposite order
22628 * @type {Number}
22629 * @see [legend.reversed](#legend.reversed), [yAxis.reversedStacks](#yAxis.
22630 * reversedStacks)
22631 * @apioption series.legendIndex
22632 */
22633
22634 /**
22635 * The line cap used for line ends and line joins on the graph.
22636 *
22637 * @validvalue ["round", "square"]
22638 * @type {String}
22639 * @default round
22640 * @product highcharts highstock
22641 * @apioption plotOptions.series.linecap
22642 */
22643
22644 /**
22645 * The [id](#series.id) of another series to link to. Additionally,
22646 * the value can be ":previous" to link to the previous series. When
22647 * two series are linked, only the first one appears in the legend.
22648 * Toggling the visibility of this also toggles the linked series.
22649 *
22650 * @type {String}
22651 * @sample {highcharts} highcharts/demo/arearange-line/ Linked series
22652 * @sample {highstock} highcharts/demo/arearange-line/ Linked series
22653 * @since 3.0
22654 * @product highcharts highstock
22655 * @apioption plotOptions.series.linkedTo
22656 */
22657
22658 /**
22659 * The name of the series as shown in the legend, tooltip etc.
22660 *
22661 * @type {String}
22662 * @sample {highcharts} highcharts/series/name/ Series name
22663 * @sample {highmaps} maps/demo/category-map/ Series name
22664 * @apioption series.name
22665 */
22666
22667 /**
22668 * The color for the parts of the graph or points that are below the
22669 * [threshold](#plotOptions.series.threshold).
22670 *
22671 * @type {Color}
22672 * @see In styled mode, a negative color is applied by setting this
22673 * option to `true` combined with the `.highcharts-negative` class name.
22674 *
22675 * @sample {highcharts} highcharts/plotoptions/series-negative-color/
22676 * Spline, area and column
22677 * @sample {highcharts} highcharts/plotoptions/arearange-negativecolor/
22678 * Arearange
22679 * @sample {highcharts} highcharts/css/series-negative-color/
22680 * Styled mode
22681 * @sample {highstock} highcharts/plotoptions/series-negative-color/
22682 * Spline, area and column
22683 * @sample {highstock} highcharts/plotoptions/arearange-negativecolor/
22684 * Arearange
22685 * @sample {highmaps} highcharts/plotoptions/series-negative-color/
22686 * Spline, area and column
22687 * @sample {highmaps} highcharts/plotoptions/arearange-negativecolor/
22688 * Arearange
22689 * @default null
22690 * @since 3.0
22691 * @apioption plotOptions.series.negativeColor
22692 */
22693
22694 /**
22695 * Same as [accessibility.pointDescriptionFormatter](#accessibility.
22696 * pointDescriptionFormatter), but for an individual series. Overrides
22697 * the chart wide configuration.
22698 *
22699 * @type {Function}
22700 * @since 5.0.12
22701 * @apioption plotOptions.series.pointDescriptionFormatter
22702 */
22703
22704 /**
22705 * If no x values are given for the points in a series, `pointInterval`
22706 * defines the interval of the x values. For example, if a series contains
22707 * one value every decade starting from year 0, set `pointInterval` to
22708 * `10`. In true `datetime` axes, the `pointInterval` is set in
22709 * milliseconds.
22710 *
22711 * It can be also be combined with `pointIntervalUnit` to draw irregular
22712 * time intervals.
22713 *
22714 * @type {Number}
22715 * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/
22716 * Datetime X axis
22717 * @sample {highstock} stock/plotoptions/pointinterval-pointstart/
22718 * Using pointStart and pointInterval
22719 * @default 1
22720 * @product highcharts highstock
22721 * @apioption plotOptions.series.pointInterval
22722 */
22723
22724 /**
22725 * On datetime series, this allows for setting the
22726 * [pointInterval](#plotOptions.series.pointInterval) to irregular time
22727 * units, `day`, `month` and `year`. A day is usually the same as 24 hours,
22728 * but `pointIntervalUnit` also takes the DST crossover into consideration
22729 * when dealing with local time. Combine this option with `pointInterval`
22730 * to draw weeks, quarters, 6 months, 10 years etc.
22731 *
22732 * @validvalue [null, "day", "month", "year"]
22733 * @type {String}
22734 * @sample {highcharts} highcharts/plotoptions/series-pointintervalunit/
22735 * One point a month
22736 * @sample {highstock} highcharts/plotoptions/series-pointintervalunit/
22737 * One point a month
22738 * @since 4.1.0
22739 * @product highcharts highstock
22740 * @apioption plotOptions.series.pointIntervalUnit
22741 */
22742
22743 /**
22744 * Possible values: `null`, `"on"`, `"between"`.
22745 *
22746 * In a column chart, when pointPlacement is `"on"`, the point will
22747 * not create any padding of the X axis. In a polar column chart this
22748 * means that the first column points directly north. If the pointPlacement
22749 * is `"between"`, the columns will be laid out between ticks. This
22750 * is useful for example for visualising an amount between two points
22751 * in time or in a certain sector of a polar chart.
22752 *
22753 * Since Highcharts 3.0.2, the point placement can also be numeric,
22754 * where 0 is on the axis value, -0.5 is between this value and the
22755 * previous, and 0.5 is between this value and the next. Unlike the
22756 * textual options, numeric point placement options won't affect axis
22757 * padding.
22758 *
22759 * Note that pointPlacement needs a [pointRange](#plotOptions.series.
22760 * pointRange) to work. For column series this is computed, but for
22761 * line-type series it needs to be set.
22762 *
22763 * Defaults to `null` in cartesian charts, `"between"` in polar charts.
22764 *
22765 * @validvalue [null, "on", "between"]
22766 * @type {String|Number}
22767 * @see [xAxis.tickmarkPlacement](#xAxis.tickmarkPlacement)
22768 * @sample {highcharts|highstock}
22769 * highcharts/plotoptions/series-pointplacement-between/
22770 * Between in a column chart
22771 * @sample {highcharts|highstock}
22772 * highcharts/plotoptions/series-pointplacement-numeric/
22773 * Numeric placement for custom layout
22774 * @default null
22775 * @since 2.3.0
22776 * @product highcharts highstock
22777 * @apioption plotOptions.series.pointPlacement
22778 */
22779
22780 /**
22781 * If no x values are given for the points in a series, pointStart defines
22782 * on what value to start. For example, if a series contains one yearly
22783 * value starting from 1945, set pointStart to 1945.
22784 *
22785 * @type {Number}
22786 * @sample {highcharts} highcharts/plotoptions/series-pointstart-linear/
22787 * Linear
22788 * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/
22789 * Datetime
22790 * @sample {highstock} stock/plotoptions/pointinterval-pointstart/
22791 * Using pointStart and pointInterval
22792 * @default 0
22793 * @product highcharts highstock
22794 * @apioption plotOptions.series.pointStart
22795 */
22796
22797 /**
22798 * Whether to select the series initially. If `showCheckbox` is true,
22799 * the checkbox next to the series name in the legend will be checked for a
22800 * selected series.
22801 *
22802 * @type {Boolean}
22803 * @sample {highcharts} highcharts/plotoptions/series-selected/
22804 * One out of two series selected
22805 * @default false
22806 * @since 1.2.0
22807 * @apioption plotOptions.series.selected
22808 */
22809
22810 /**
22811 * Whether to apply a drop shadow to the graph line. Since 2.3 the shadow
22812 * can be an object configuration containing `color`, `offsetX`, `offsetY`,
22813 * `opacity` and `width`.
22814 *
22815 * @type {Boolean|Object}
22816 * @sample {highcharts} highcharts/plotoptions/series-shadow/ Shadow enabled
22817 * @default false
22818 * @apioption plotOptions.series.shadow
22819 */
22820
22821 /**
22822 * Whether to display this particular series or series type in the legend.
22823 * The default value is `true` for standalone series, `false` for linked
22824 * series.
22825 *
22826 * @type {Boolean}
22827 * @sample {highcharts} highcharts/plotoptions/series-showinlegend/
22828 * One series in the legend, one hidden
22829 * @default true
22830 * @apioption plotOptions.series.showInLegend
22831 */
22832
22833 /**
22834 * If set to `True`, the accessibility module will skip past the points
22835 * in this series for keyboard navigation.
22836 *
22837 * @type {Boolean}
22838 * @since 5.0.12
22839 * @apioption plotOptions.series.skipKeyboardNavigation
22840 */
22841
22842 /**
22843 * This option allows grouping series in a stacked chart. The stack
22844 * option can be a string or a number or anything else, as long as the
22845 * grouped series' stack options match each other.
22846 *
22847 * @type {String}
22848 * @sample {highcharts} highcharts/series/stack/ Stacked and grouped columns
22849 * @default null
22850 * @since 2.1
22851 * @product highcharts highstock
22852 * @apioption series.stack
22853 */
22854
22855 /**
22856 * Whether to stack the values of each series on top of each other.
22857 * Possible values are `null` to disable, `"normal"` to stack by value or
22858 * `"percent"`. When stacking is enabled, data must be sorted in ascending
22859 * X order. A special stacking option is with the streamgraph series type,
22860 * where the stacking option is set to `"stream"`.
22861 *
22862 * @validvalue [null, "normal", "percent"]
22863 * @type {String}
22864 * @see [yAxis.reversedStacks](#yAxis.reversedStacks)
22865 * @sample {highcharts} highcharts/plotoptions/series-stacking-line/
22866 * Line
22867 * @sample {highcharts} highcharts/plotoptions/series-stacking-column/
22868 * Column
22869 * @sample {highcharts} highcharts/plotoptions/series-stacking-bar/
22870 * Bar
22871 * @sample {highcharts} highcharts/plotoptions/series-stacking-area/
22872 * Area
22873 * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-line/
22874 * Line
22875 * @sample {highcharts}
22876 * highcharts/plotoptions/series-stacking-percent-column/
22877 * Column
22878 * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-bar/
22879 * Bar
22880 * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-area/
22881 * Area
22882 * @sample {highstock} stock/plotoptions/stacking/
22883 * Area
22884 * @default null
22885 * @product highcharts highstock
22886 * @apioption plotOptions.series.stacking
22887 */
22888
22889 /**
22890 * Whether to apply steps to the line. Possible values are `left`, `center`
22891 * and `right`.
22892 *
22893 * @validvalue [null, "left", "center", "right"]
22894 * @type {String}
22895 * @sample {highcharts} highcharts/plotoptions/line-step/
22896 * Different step line options
22897 * @sample {highcharts} highcharts/plotoptions/area-step/
22898 * Stepped, stacked area
22899 * @sample {highstock} stock/plotoptions/line-step/
22900 * Step line
22901 * @default {highcharts} null
22902 * @default {highstock} false
22903 * @since 1.2.5
22904 * @product highcharts highstock
22905 * @apioption plotOptions.series.step
22906 */
22907
22908 /**
22909 * The threshold, also called zero level or base level. For line type
22910 * series this is only used in conjunction with
22911 * [negativeColor](#plotOptions.series.negativeColor).
22912 *
22913 * @type {Number}
22914 * @see [softThreshold](#plotOptions.series.softThreshold).
22915 * @default 0
22916 * @since 3.0
22917 * @product highcharts highstock
22918 * @apioption plotOptions.series.threshold
22919 */
22920
22921 /**
22922 * The type of series, for example `line` or `column`.
22923 *
22924 * @validvalue [null, "line", "spline", "column", "area", "areaspline",
22925 * "pie", "arearange", "areasplinerange", "boxplot", "bubble",
22926 * "columnrange", "errorbar", "funnel", "gauge", "scatter",
22927 * "waterfall"]
22928 * @type {String}
22929 * @sample {highcharts} highcharts/series/type/
22930 * Line and column in the same chart
22931 * @sample {highmaps} maps/demo/mapline-mappoint/
22932 * Multiple types in the same map
22933 * @apioption series.type
22934 */
22935
22936 /**
22937 * Set the initial visibility of the series.
22938 *
22939 * @type {Boolean}
22940 * @sample {highcharts} highcharts/plotoptions/series-visible/
22941 * Two series, one hidden and one visible
22942 * @sample {highstock} stock/plotoptions/series-visibility/
22943 * Hidden series
22944 * @default true
22945 * @apioption plotOptions.series.visible
22946 */
22947
22948 /**
22949 * When using dual or multiple x axes, this number defines which xAxis
22950 * the particular series is connected to. It refers to either the [axis
22951 * id](#xAxis.id) or the index of the axis in the xAxis array, with
22952 * 0 being the first.
22953 *
22954 * @type {Number|String}
22955 * @default 0
22956 * @product highcharts highstock
22957 * @apioption series.xAxis
22958 */
22959
22960 /**
22961 * When using dual or multiple y axes, this number defines which yAxis
22962 * the particular series is connected to. It refers to either the [axis
22963 * id](#yAxis.id) or the index of the axis in the yAxis array, with
22964 * 0 being the first.
22965 *
22966 * @type {Number|String}
22967 * @sample {highcharts} highcharts/series/yaxis/
22968 * Apply the column series to the secondary Y axis
22969 * @default 0
22970 * @product highcharts highstock
22971 * @apioption series.yAxis
22972 */
22973
22974 /**
22975 * Defines the Axis on which the zones are applied.
22976 *
22977 * @type {String}
22978 * @see [zones](#plotOptions.series.zones)
22979 * @sample {highcharts} highcharts/series/color-zones-zoneaxis-x/
22980 * Zones on the X-Axis
22981 * @sample {highstock} highcharts/series/color-zones-zoneaxis-x/
22982 * Zones on the X-Axis
22983 * @default y
22984 * @since 4.1.0
22985 * @product highcharts highstock
22986 * @apioption plotOptions.series.zoneAxis
22987 */
22988
22989 /**
22990 * Define the visual z index of the series.
22991 *
22992 * @type {Number}
22993 * @sample {highcharts} highcharts/plotoptions/series-zindex-default/
22994 * With no z index, the series defined last are on top
22995 * @sample {highcharts} highcharts/plotoptions/series-zindex/
22996 * With a z index, the series with the highest z index is on top
22997 * @sample {highstock} highcharts/plotoptions/series-zindex-default/
22998 * With no z index, the series defined last are on top
22999 * @sample {highstock} highcharts/plotoptions/series-zindex/
23000 * With a z index, the series with the highest z index is on top
23001 * @product highcharts highstock
23002 * @apioption series.zIndex
23003 */
23004
23005 /**
23006 * General event handlers for the series items. These event hooks can also
23007 * be attached to the series at run time using the `Highcharts.addEvent`
23008 * function.
23009 */
23010 events: {
23011
23012 /**
23013 * Fires after the series has finished its initial animation, or in
23014 * case animation is disabled, immediately as the series is displayed.
23015 *
23016 * @type {Function}
23017 * @context Series
23018 * @sample {highcharts}
23019 * highcharts/plotoptions/series-events-afteranimate/
23020 * Show label after animate
23021 * @sample {highstock}
23022 * highcharts/plotoptions/series-events-afteranimate/
23023 * Show label after animate
23024 * @since 4.0
23025 * @product highcharts highstock
23026 * @apioption plotOptions.series.events.afterAnimate
23027 */
23028
23029 /**
23030 * Fires when the checkbox next to the series' name in the legend is
23031 * clicked. One parameter, `event`, is passed to the function. The state
23032 * of the checkbox is found by `event.checked`. The checked item is
23033 * found by `event.item`. Return `false` to prevent the default action
23034 * which is to toggle the select state of the series.
23035 *
23036 * @type {Function}
23037 * @context Series
23038 * @sample {highcharts}
23039 * highcharts/plotoptions/series-events-checkboxclick/
23040 * Alert checkbox status
23041 * @since 1.2.0
23042 * @apioption plotOptions.series.events.checkboxClick
23043 */
23044
23045 /**
23046 * Fires when the series is clicked. One parameter, `event`, is passed
23047 * to the function, containing common event information. Additionally,
23048 * `event.point` holds a pointer to the nearest point on the graph.
23049 *
23050 * @type {Function}
23051 * @context Series
23052 * @sample {highcharts} highcharts/plotoptions/series-events-click/
23053 * Alert click info
23054 * @sample {highstock} stock/plotoptions/series-events-click/
23055 * Alert click info
23056 * @sample {highmaps} maps/plotoptions/series-events-click/
23057 * Display click info in subtitle
23058 * @apioption plotOptions.series.events.click
23059 */
23060
23061 /**
23062 * Fires when the series is hidden after chart generation time, either
23063 * by clicking the legend item or by calling `.hide()`.
23064 *
23065 * @type {Function}
23066 * @context Series
23067 * @sample {highcharts} highcharts/plotoptions/series-events-hide/
23068 * Alert when the series is hidden by clicking the legend item
23069 * @since 1.2.0
23070 * @apioption plotOptions.series.events.hide
23071 */
23072
23073 /**
23074 * Fires when the legend item belonging to the series is clicked. One
23075 * parameter, `event`, is passed to the function. The default action
23076 * is to toggle the visibility of the series. This can be prevented
23077 * by returning `false` or calling `event.preventDefault()`.
23078 *
23079 * @type {Function}
23080 * @context Series
23081 * @sample {highcharts}
23082 * highcharts/plotoptions/series-events-legenditemclick/
23083 * Confirm hiding and showing
23084 * @apioption plotOptions.series.events.legendItemClick
23085 */
23086
23087 /**
23088 * Fires when the mouse leaves the graph. One parameter, `event`, is
23089 * passed to the function, containing common event information. If the
23090 * [stickyTracking](#plotOptions.series) option is true, `mouseOut`
23091 * doesn't happen before the mouse enters another graph or leaves the
23092 * plot area.
23093 *
23094 * @type {Function}
23095 * @context Series
23096 * @sample {highcharts}
23097 * highcharts/plotoptions/series-events-mouseover-sticky/
23098 * With sticky tracking by default
23099 * @sample {highcharts}
23100 * highcharts/plotoptions/series-events-mouseover-no-sticky/
23101 * Without sticky tracking
23102 * @apioption plotOptions.series.events.mouseOut
23103 */
23104
23105 /**
23106 * Fires when the mouse enters the graph. One parameter, `event`, is
23107 * passed to the function, containing common event information.
23108 *
23109 * @type {Function}
23110 * @context Series
23111 * @sample {highcharts}
23112 * highcharts/plotoptions/series-events-mouseover-sticky/
23113 * With sticky tracking by default
23114 * @sample {highcharts}
23115 * highcharts/plotoptions/series-events-mouseover-no-sticky/
23116 * Without sticky tracking
23117 * @apioption plotOptions.series.events.mouseOver
23118 */
23119
23120 /**
23121 * Fires when the series is shown after chart generation time, either
23122 * by clicking the legend item or by calling `.show()`.
23123 *
23124 * @type {Function}
23125 * @context Series
23126 * @sample {highcharts} highcharts/plotoptions/series-events-show/
23127 * Alert when the series is shown by clicking the legend item.
23128 * @since 1.2.0
23129 * @apioption plotOptions.series.events.show
23130 */
23131
23132 },
23133
23134
23135
23136 /**
23137 * Options for the point markers of line-like series. Properties like
23138 * `fillColor`, `lineColor` and `lineWidth` define the visual appearance
23139 * of the markers. Other series types, like column series, don't have
23140 * markers, but have visual options on the series level instead.
23141 *
23142 * In styled mode, the markers can be styled with the `.highcharts-point`,
23143 * `.highcharts-point-hover` and `.highcharts-point-select`
23144 * class names.
23145 *
23146 * @product highcharts highstock
23147 */
23148 marker: {
23149
23150
23151
23152 /**
23153 * The width of the point marker's outline.
23154 *
23155 * @type {Number}
23156 * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
23157 * 2px blue marker
23158 * @default 0
23159 * @product highcharts highstock
23160 */
23161 lineWidth: 0,
23162
23163
23164 /**
23165 * The color of the point marker's outline. When `null`, the series'
23166 * or point's color is used.
23167 *
23168 * @type {Color}
23169 * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
23170 * Inherit from series color (null)
23171 * @product highcharts highstock
23172 */
23173 lineColor: '#ffffff',
23174
23175 /**
23176 * The fill color of the point marker. When `null`, the series' or
23177 * point's color is used.
23178 *
23179 * @type {Color}
23180 * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
23181 * White fill
23182 * @default null
23183 * @product highcharts highstock
23184 * @apioption plotOptions.series.marker.fillColor
23185 */
23186
23187
23188
23189 /**
23190 * Enable or disable the point marker. If `null`, the markers are hidden
23191 * when the data is dense, and shown for more widespread data points.
23192 *
23193 * @type {Boolean}
23194 * @sample {highcharts} highcharts/plotoptions/series-marker-enabled/
23195 * Disabled markers
23196 * @sample {highcharts}
23197 * highcharts/plotoptions/series-marker-enabled-false/
23198 * Disabled in normal state but enabled on hover
23199 * @sample {highstock} stock/plotoptions/series-marker/
23200 * Enabled markers
23201 * @default {highcharts} null
23202 * @default {highstock} false
23203 * @product highcharts highstock
23204 * @apioption plotOptions.series.marker.enabled
23205 */
23206
23207 /**
23208 * Image markers only. Set the image width explicitly. When using this
23209 * option, a `width` must also be set.
23210 *
23211 * @type {Number}
23212 * @sample {highcharts}
23213 * highcharts/plotoptions/series-marker-width-height/
23214 * Fixed width and height
23215 * @sample {highstock}
23216 * highcharts/plotoptions/series-marker-width-height/
23217 * Fixed width and height
23218 * @default null
23219 * @since 4.0.4
23220 * @product highcharts highstock
23221 * @apioption plotOptions.series.marker.height
23222 */
23223
23224 /**
23225 * A predefined shape or symbol for the marker. When null, the symbol
23226 * is pulled from options.symbols. Other possible values are "circle",
23227 * "square", "diamond", "triangle" and "triangle-down".
23228 *
23229 * Additionally, the URL to a graphic can be given on this form:
23230 * "url(graphic.png)". Note that for the image to be applied to exported
23231 * charts, its URL needs to be accessible by the export server.
23232 *
23233 * Custom callbacks for symbol path generation can also be added to
23234 * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
23235 * used by its method name, as shown in the demo.
23236 *
23237 * @validvalue [null, "circle", "square", "diamond", "triangle",
23238 * "triangle-down"]
23239 * @type {String}
23240 * @sample {highcharts} highcharts/plotoptions/series-marker-symbol/
23241 * Predefined, graphic and custom markers
23242 * @sample {highstock} highcharts/plotoptions/series-marker-symbol/
23243 * Predefined, graphic and custom markers
23244 * @default null
23245 * @product highcharts highstock
23246 * @apioption plotOptions.series.marker.symbol
23247 */
23248
23249 /**
23250 * The radius of the point marker.
23251 *
23252 * @type {Number}
23253 * @sample {highcharts} highcharts/plotoptions/series-marker-radius/
23254 * Bigger markers
23255 * @default 4
23256 * @product highcharts highstock
23257 */
23258 radius: 4,
23259
23260 /**
23261 * Image markers only. Set the image width explicitly. When using this
23262 * option, a `height` must also be set.
23263 *
23264 * @type {Number}
23265 * @sample {highcharts}
23266 * highcharts/plotoptions/series-marker-width-height/
23267 * Fixed width and height
23268 * @sample {highstock}
23269 * highcharts/plotoptions/series-marker-width-height/
23270 * Fixed width and height
23271 * @default null
23272 * @since 4.0.4
23273 * @product highcharts highstock
23274 * @apioption plotOptions.series.marker.width
23275 */
23276
23277
23278 /**
23279 * States for a single point marker.
23280 * @product highcharts highstock
23281 */
23282 states: {
23283 /**
23284 * The hover state for a single point marker.
23285 * @product highcharts highstock
23286 */
23287 hover: {
23288
23289 /**
23290 * Animation when hovering over the marker.
23291 * @type {Boolean|Object}
23292 */
23293 animation: {
23294 duration: 50
23295 },
23296
23297 /**
23298 * Enable or disable the point marker.
23299 *
23300 * @type {Boolean}
23301 * @sample {highcharts}
23302 * highcharts/plotoptions/series-marker-states-hover-enabled/
23303 * Disabled hover state
23304 * @default true
23305 * @product highcharts highstock
23306 */
23307 enabled: true,
23308
23309 /**
23310 * The fill color of the marker in hover state.
23311 *
23312 * @type {Color}
23313 * @default null
23314 * @product highcharts highstock
23315 * @apioption plotOptions.series.marker.states.hover.fillColor
23316 */
23317
23318 /**
23319 * The color of the point marker's outline. When `null`, the
23320 * series' or point's color is used.
23321 *
23322 * @type {Color}
23323 * @sample {highcharts}
23324 * highcharts/plotoptions/series-marker-states-hover-linecolor/
23325 * White fill color, black line color
23326 * @default #ffffff
23327 * @product highcharts highstock
23328 * @apioption plotOptions.series.marker.states.hover.lineColor
23329 */
23330
23331 /**
23332 * The width of the point marker's outline.
23333 *
23334 * @type {Number}
23335 * @sample {highcharts}
23336 * highcharts/plotoptions/series-marker-states-hover-linewidth/
23337 * 3px line width
23338 * @default 0
23339 * @product highcharts highstock
23340 * @apioption plotOptions.series.marker.states.hover.lineWidth
23341 */
23342
23343 /**
23344 * The radius of the point marker. In hover state, it defaults to the
23345 * normal state's radius + 2 as per the [radiusPlus](#plotOptions.series.
23346 * marker.states.hover.radiusPlus) option.
23347 *
23348 * @type {Number}
23349 * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-radius/ 10px radius
23350 * @product highcharts highstock
23351 * @apioption plotOptions.series.marker.states.hover.radius
23352 */
23353
23354 /**
23355 * The number of pixels to increase the radius of the hovered point.
23356 *
23357 * @type {Number}
23358 * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/ 5 pixels greater radius on hover
23359 * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/ 5 pixels greater radius on hover
23360 * @default 2
23361 * @since 4.0.3
23362 * @product highcharts highstock
23363 */
23364 radiusPlus: 2,
23365
23366
23367
23368 /**
23369 * The additional line width for a hovered point.
23370 *
23371 * @type {Number}
23372 * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/ 2 pixels wider on hover
23373 * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/ 2 pixels wider on hover
23374 * @default 1
23375 * @since 4.0.3
23376 * @product highcharts highstock
23377 */
23378 lineWidthPlus: 1
23379
23380 },
23381
23382
23383
23384
23385 /**
23386 * The appearance of the point marker when selected. In order to
23387 * allow a point to be selected, set the `series.allowPointSelect`
23388 * option to true.
23389 *
23390 * @product highcharts highstock
23391 */
23392 select: {
23393
23394 /**
23395 * Enable or disable visible feedback for selection.
23396 *
23397 * @type {Boolean}
23398 * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-enabled/
23399 * Disabled select state
23400 * @default true
23401 * @product highcharts highstock
23402 * @apioption plotOptions.series.marker.states.select.enabled
23403 */
23404
23405 /**
23406 * The fill color of the point marker.
23407 *
23408 * @type {Color}
23409 * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-fillcolor/
23410 * Solid red discs for selected points
23411 * @default null
23412 * @product highcharts highstock
23413 */
23414 fillColor: '#cccccc',
23415
23416
23417
23418 /**
23419 * The color of the point marker's outline. When `null`, the series'
23420 * or point's color is used.
23421 *
23422 * @type {Color}
23423 * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linecolor/
23424 * Red line color for selected points
23425 * @default #000000
23426 * @product highcharts highstock
23427 */
23428 lineColor: '#000000',
23429
23430
23431
23432 /**
23433 * The width of the point marker's outline.
23434 *
23435 * @type {Number}
23436 * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linewidth/
23437 * 3px line width for selected points
23438 * @default 0
23439 * @product highcharts highstock
23440 */
23441 lineWidth: 2
23442
23443 /**
23444 * The radius of the point marker. In hover state, it defaults to the
23445 * normal state's radius + 2.
23446 *
23447 * @type {Number}
23448 * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-radius/
23449 * 10px radius for selected points
23450 * @product highcharts highstock
23451 * @apioption plotOptions.series.marker.states.select.radius
23452 */
23453
23454 }
23455
23456 }
23457 },
23458
23459
23460
23461 /**
23462 * Properties for each single point.
23463 */
23464 point: {
23465
23466
23467 /**
23468 * Events for each single point.
23469 */
23470 events: {
23471
23472 /**
23473 * Fires when a point is clicked. One parameter, `event`, is passed
23474 * to the function, containing common event information.
23475 *
23476 * If the `series.allowPointSelect` option is true, the default action
23477 * for the point's click event is to toggle the point's select state.
23478 * Returning `false` cancels this action.
23479 *
23480 * @type {Function}
23481 * @context Point
23482 * @sample {highcharts} highcharts/plotoptions/series-point-events-click/ Click marker to alert values
23483 * @sample {highcharts} highcharts/plotoptions/series-point-events-click-column/ Click column
23484 * @sample {highcharts} highcharts/plotoptions/series-point-events-click-url/ Go to URL
23485 * @sample {highmaps} maps/plotoptions/series-point-events-click/ Click marker to display values
23486 * @sample {highmaps} maps/plotoptions/series-point-events-click-url/ Go to URL
23487 * @apioption plotOptions.series.point.events.click
23488 */
23489
23490 /**
23491 * Fires when the mouse leaves the area close to the point. One parameter,
23492 * `event`, is passed to the function, containing common event information.
23493 *
23494 * @type {Function}
23495 * @context Point
23496 * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ Show values in the chart's corner on mouse over
23497 * @apioption plotOptions.series.point.events.mouseOut
23498 */
23499
23500 /**
23501 * Fires when the mouse enters the area close to the point. One parameter,
23502 * `event`, is passed to the function, containing common event information.
23503 *
23504 * @type {Function}
23505 * @context Point
23506 * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ Show values in the chart's corner on mouse over
23507 * @apioption plotOptions.series.point.events.mouseOver
23508 */
23509
23510 /**
23511 * Fires when the point is removed using the `.remove()` method. One
23512 * parameter, `event`, is passed to the function. Returning `false`
23513 * cancels the operation.
23514 *
23515 * @type {Function}
23516 * @context Point
23517 * @sample {highcharts} highcharts/plotoptions/series-point-events-remove/ Remove point and confirm
23518 * @since 1.2.0
23519 * @apioption plotOptions.series.point.events.remove
23520 */
23521
23522 /**
23523 * Fires when the point is selected either programmatically or following
23524 * a click on the point. One parameter, `event`, is passed to the function.
23525 * Returning `false` cancels the operation.
23526 *
23527 * @type {Function}
23528 * @context Point
23529 * @sample {highcharts} highcharts/plotoptions/series-point-events-select/ Report the last selected point
23530 * @sample {highmaps} maps/plotoptions/series-allowpointselect/ Report select and unselect
23531 * @since 1.2.0
23532 * @apioption plotOptions.series.point.events.select
23533 */
23534
23535 /**
23536 * Fires when the point is unselected either programmatically or following
23537 * a click on the point. One parameter, `event`, is passed to the function.
23538 * Returning `false` cancels the operation.
23539 *
23540 * @type {Function}
23541 * @context Point
23542 * @sample {highcharts} highcharts/plotoptions/series-point-events-unselect/ Report the last unselected point
23543 * @sample {highmaps} maps/plotoptions/series-allowpointselect/ Report select and unselect
23544 * @since 1.2.0
23545 * @apioption plotOptions.series.point.events.unselect
23546 */
23547
23548 /**
23549 * Fires when the point is updated programmatically through the `.update()`
23550 * method. One parameter, `event`, is passed to the function. The new
23551 * point options can be accessed through `event.options`. Returning
23552 * `false` cancels the operation.
23553 *
23554 * @type {Function}
23555 * @context Point
23556 * @sample {highcharts} highcharts/plotoptions/series-point-events-update/ Confirm point updating
23557 * @since 1.2.0
23558 * @apioption plotOptions.series.point.events.update
23559 */
23560
23561 }
23562 },
23563
23564
23565
23566 /**
23567 * Options for the series data labels, appearing next to each data
23568 * point.
23569 *
23570 * 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.
23571 * net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/series-
23572 * datalabels)).
23573 */
23574 dataLabels: {
23575
23576
23577 /**
23578 * The alignment of the data label compared to the point. If `right`,
23579 * the right side of the label should be touching the point. For
23580 * points with an extent, like columns, the alignments also dictates
23581 * how to align it inside the box, as given with the [inside](#plotOptions.
23582 * column.dataLabels.inside) option. Can be one of "left", "center"
23583 * or "right".
23584 *
23585 * @validvalue ["left", "center", "right"]
23586 * @type {String}
23587 * @sample {highcharts} highcharts/plotoptions/series-datalabels-align-left/ Left aligned
23588 * @default center
23589 */
23590 align: 'center',
23591
23592
23593 /**
23594 * Whether to allow data labels to overlap. To make the labels less
23595 * sensitive for overlapping, the [dataLabels.padding](#plotOptions.
23596 * series.dataLabels.padding) can be set to 0.
23597 *
23598 * @type {Boolean}
23599 * @sample {highcharts} highcharts/plotoptions/series-datalabels-allowoverlap-false/ Don't allow overlap
23600 * @sample {highstock} highcharts/plotoptions/series-datalabels-allowoverlap-false/ Don't allow overlap
23601 * @sample {highmaps} highcharts/plotoptions/series-datalabels-allowoverlap-false/ Don't allow overlap
23602 * @default false
23603 * @since 4.1.0
23604 * @apioption plotOptions.series.dataLabels.allowOverlap
23605 */
23606
23607
23608 /**
23609 * The border radius in pixels for the data label.
23610 *
23611 * @type {Number}
23612 * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
23613 * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
23614 * @sample {highmaps} maps/plotoptions/series-datalabels-box/ Data labels box options
23615 * @default 0
23616 * @since 2.2.1
23617 * @apioption plotOptions.series.dataLabels.borderRadius
23618 */
23619
23620
23621 /**
23622 * The border width in pixels for the data label.
23623 *
23624 * @type {Number}
23625 * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
23626 * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
23627 * @default 0
23628 * @since 2.2.1
23629 * @apioption plotOptions.series.dataLabels.borderWidth
23630 */
23631
23632 /**
23633 * A class name for the data label. Particularly in styled mode, this can
23634 * be used to give each series' or point's data label unique styling.
23635 * In addition to this option, a default color class name is added
23636 * so that we can give the labels a [contrast text shadow](http://jsfiddle.
23637 * net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/data-
23638 * label-contrast/).
23639 *
23640 * @type {String}
23641 * @sample {highcharts} highcharts/css/series-datalabels/ Styling by CSS
23642 * @sample {highstock} highcharts/css/series-datalabels/ Styling by CSS
23643 * @sample {highmaps} highcharts/css/series-datalabels/ Styling by CSS
23644 * @since 5.0.0
23645 * @apioption plotOptions.series.dataLabels.className
23646 */
23647
23648 /**
23649 * The text color for the data labels. Defaults to `null`. For certain series
23650 * types, like column or map, the data labels can be drawn inside the points.
23651 * In this case the data label will be drawn with maximum contrast by default.
23652 * Additionally, it will be given a `text-outline` style with the opposite
23653 * color, to further increase the contrast. This can be overridden by setting
23654 * the `text-outline` style to `none` in the `dataLabels.style` option.
23655 *
23656 * @type {Color}
23657 * @sample {highcharts} highcharts/plotoptions/series-datalabels-color/
23658 * Red data labels
23659 * @sample {highmaps} maps/demo/color-axis/
23660 * White data labels
23661 * @apioption plotOptions.series.dataLabels.color
23662 */
23663
23664 /**
23665 * Whether to hide data labels that are outside the plot area. By default,
23666 * the data label is moved inside the plot area according to the [overflow](#plotOptions.
23667 * series.dataLabels.overflow) option.
23668 *
23669 * @type {Boolean}
23670 * @default true
23671 * @since 2.3.3
23672 * @apioption plotOptions.series.dataLabels.crop
23673 */
23674
23675 /**
23676 * Whether to defer displaying the data labels until the initial series
23677 * animation has finished.
23678 *
23679 * @type {Boolean}
23680 * @default true
23681 * @since 4.0
23682 * @product highcharts highstock
23683 * @apioption plotOptions.series.dataLabels.defer
23684 */
23685
23686 /**
23687 * Enable or disable the data labels.
23688 *
23689 * @type {Boolean}
23690 * @sample {highcharts} highcharts/plotoptions/series-datalabels-enabled/ Data labels enabled
23691 * @sample {highmaps} maps/demo/color-axis/ Data labels enabled
23692 * @default false
23693 * @apioption plotOptions.series.dataLabels.enabled
23694 */
23695
23696 /**
23697 * A [format string](http://www.highcharts.com/docs/chart-concepts/labels-
23698 * and-string-formatting) for the data label. Available variables are
23699 * the same as for `formatter`.
23700 *
23701 * @type {String}
23702 * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ Add a unit
23703 * @sample {highstock} highcharts/plotoptions/series-datalabels-format/ Add a unit
23704 * @sample {highmaps} maps/plotoptions/series-datalabels-format/ Formatted value in the data label
23705 * @default {highcharts} {y}
23706 * @default {highstock} {y}
23707 * @default {highmaps} {point.value}
23708 * @since 3.0
23709 * @apioption plotOptions.series.dataLabels.format
23710 */
23711
23712 /**
23713 * Callback JavaScript function to format the data label. Note that
23714 * if a `format` is defined, the format takes precedence and the formatter
23715 * is ignored. Available data are:
23716 *
23717 * <table>
23718 *
23719 * <tbody>
23720 *
23721 * <tr>
23722 *
23723 * <td>`this.percentage`</td>
23724 *
23725 * <td>Stacked series and pies only. The point's percentage of the
23726 * total.</td>
23727 *
23728 * </tr>
23729 *
23730 * <tr>
23731 *
23732 * <td>`this.point`</td>
23733 *
23734 * <td>The point object. The point name, if defined, is available
23735 * through `this.point.name`.</td>
23736 *
23737 * </tr>
23738 *
23739 * <tr>
23740 *
23741 * <td>`this.series`:</td>
23742 *
23743 * <td>The series object. The series name is available through `this.
23744 * series.name`.</td>
23745 *
23746 * </tr>
23747 *
23748 * <tr>
23749 *
23750 * <td>`this.total`</td>
23751 *
23752 * <td>Stacked series only. The total value at this point's x value.
23753 * </td>
23754 *
23755 * </tr>
23756 *
23757 * <tr>
23758 *
23759 * <td>`this.x`:</td>
23760 *
23761 * <td>The x value.</td>
23762 *
23763 * </tr>
23764 *
23765 * <tr>
23766 *
23767 * <td>`this.y`:</td>
23768 *
23769 * <td>The y value.</td>
23770 *
23771 * </tr>
23772 *
23773 * </tbody>
23774 *
23775 * </table>
23776 *
23777 * @type {Function}
23778 * @sample {highmaps} maps/plotoptions/series-datalabels-format/ Formatted value
23779 */
23780 formatter: function() {
23781 return this.y === null ? '' : H.numberFormat(this.y, -1);
23782 },
23783
23784
23785
23786 /**
23787 * Styles for the label. The default `color` setting is `"contrast"`,
23788 * which is a pseudo color that Highcharts picks up and applies the
23789 * maximum contrast to the underlying point item, for example the
23790 * bar in a bar chart.
23791 *
23792 * The `textOutline` is a pseudo property that
23793 * applies an outline of the given width with the given color, which
23794 * by default is the maximum contrast to the text. So a bright text
23795 * color will result in a black text outline for maximum readability
23796 * on a mixed background. In some cases, especially with grayscale
23797 * text, the text outline doesn't work well, in which cases it can
23798 * be disabled by setting it to `"none"`. When `useHTML` is true, the
23799 * `textOutline` will not be picked up. In this, case, the same effect
23800 * can be acheived through the `text-shadow` CSS property.
23801 *
23802 * @type {CSSObject}
23803 * @sample {highcharts} highcharts/plotoptions/series-datalabels-style/
23804 * Bold labels
23805 * @sample {highmaps} maps/demo/color-axis/ Bold labels
23806 * @default {"color": "contrast", "fontSize": "11px", "fontWeight": "bold", "textOutline": "1px contrast" }
23807 * @since 4.1.0
23808 */
23809 style: {
23810 fontSize: '11px',
23811 fontWeight: 'bold',
23812 color: 'contrast',
23813 textOutline: '1px contrast'
23814 },
23815
23816 /**
23817 * The background color or gradient for the data label.
23818 *
23819 * @type {Color}
23820 * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
23821 * @sample {highmaps} maps/plotoptions/series-datalabels-box/ Data labels box options
23822 * @since 2.2.1
23823 * @apioption plotOptions.series.dataLabels.backgroundColor
23824 */
23825
23826 /**
23827 * The border color for the data label. Defaults to `undefined`.
23828 *
23829 * @type {Color}
23830 * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
23831 * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
23832 * @default undefined
23833 * @since 2.2.1
23834 * @apioption plotOptions.series.dataLabels.borderColor
23835 */
23836
23837 /**
23838 * The shadow of the box. Works best with `borderWidth` or `backgroundColor`.
23839 * Since 2.3 the shadow can be an object configuration containing `color`,
23840 * `offsetX`, `offsetY`, `opacity` and `width`.
23841 *
23842 * @type {Boolean|Object}
23843 * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
23844 * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
23845 * @default false
23846 * @since 2.2.1
23847 * @apioption plotOptions.series.dataLabels.shadow
23848 */
23849
23850
23851 /**
23852 * For points with an extent, like columns or map areas, whether to align the data
23853 * label inside the box or to the actual value point. Defaults to `false`
23854 * in most cases, `true` in stacked columns.
23855 *
23856 * @type {Boolean}
23857 * @since 3.0
23858 * @apioption plotOptions.series.dataLabels.inside
23859 */
23860
23861 /**
23862 * How to handle data labels that flow outside the plot area. The default
23863 * is `justify`, which aligns them inside the plot area. For columns
23864 * and bars, this means it will be moved inside the bar. To display
23865 * data labels outside the plot area, set `crop` to `false` and `overflow`
23866 * to `"none"`.
23867 *
23868 * @validvalue ["justify", "none"]
23869 * @type {String}
23870 * @default justify
23871 * @since 3.0.6
23872 * @apioption plotOptions.series.dataLabels.overflow
23873 */
23874
23875 /**
23876 * Text rotation in degrees. Note that due to a more complex structure,
23877 * backgrounds, borders and padding will be lost on a rotated data
23878 * label.
23879 *
23880 * @type {Number}
23881 * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ Vertical labels
23882 * @default 0
23883 * @apioption plotOptions.series.dataLabels.rotation
23884 */
23885
23886 /**
23887 * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
23888 * and-string-formatting#html) to render the labels.
23889 *
23890 * @type {Boolean}
23891 * @default false
23892 * @apioption plotOptions.series.dataLabels.useHTML
23893 */
23894
23895 /**
23896 * The vertical alignment of a data label. Can be one of `top`, `middle`
23897 * or `bottom`. The default value depends on the data, for instance
23898 * in a column chart, the label is above positive values and below
23899 * negative values.
23900 *
23901 * @validvalue ["top", "middle", "bottom"]
23902 * @type {String}
23903 * @since 2.3.3
23904 */
23905 verticalAlign: 'bottom', // above singular point
23906
23907
23908 /**
23909 * The x position offset of the label relative to the point.
23910 *
23911 * @type {Number}
23912 * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ Vertical and positioned
23913 * @default 0
23914 */
23915 x: 0,
23916
23917
23918 /**
23919 * The y position offset of the label relative to the point.
23920 *
23921 * @type {Number}
23922 * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ Vertical and positioned
23923 * @default -6
23924 */
23925 y: 0,
23926
23927
23928 /**
23929 * When either the `borderWidth` or the `backgroundColor` is set,
23930 * this is the padding within the box.
23931 *
23932 * @type {Number}
23933 * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
23934 * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
23935 * @sample {highmaps} maps/plotoptions/series-datalabels-box/ Data labels box options
23936 * @default {highcharts} 5
23937 * @default {highstock} 5
23938 * @default {highmaps} 0
23939 * @since 2.2.1
23940 */
23941 padding: 5
23942
23943 /**
23944 * The name of a symbol to use for the border around the label. Symbols
23945 * are predefined functions on the Renderer object.
23946 *
23947 * @type {String}
23948 * @sample {highcharts} highcharts/plotoptions/series-datalabels-shape/ A callout for annotations
23949 * @sample {highstock} highcharts/plotoptions/series-datalabels-shape/ A callout for annotations
23950 * @sample {highmaps} highcharts/plotoptions/series-datalabels-shape/ A callout for annotations (Highcharts demo)
23951 * @default square
23952 * @since 4.1.2
23953 * @apioption plotOptions.series.dataLabels.shape
23954 */
23955
23956 /**
23957 * The Z index of the data labels. The default Z index puts it above
23958 * the series. Use a Z index of 2 to display it behind the series.
23959 *
23960 * @type {Number}
23961 * @default 6
23962 * @since 2.3.5
23963 * @apioption plotOptions.series.dataLabels.zIndex
23964 */
23965
23966 /**
23967 * A declarative filter for which data labels to display. The
23968 * declarative filter is designed for use when callback functions are
23969 * not available, like when the chart options require a pure JSON
23970 * structure or for use with graphical editors. For programmatic
23971 * control, use the `formatter` instead, and return `false` to disable
23972 * a single data label.
23973 *
23974 * @example
23975 * filter: {
23976 * property: 'percentage',
23977 * operator: '>',
23978 * value: 4
23979 * }
23980 *
23981 * @sample highcharts/demo/pie-monochrome
23982 * Data labels filtered by percentage
23983 *
23984 * @type {Object}
23985 * @since 6.0.3
23986 * @apioption plotOptions.series.dataLabels.filter
23987 */
23988
23989 /**
23990 * The point property to filter by. Point options are passed directly to
23991 * properties, additionally there are `y` value, `percentage` and others
23992 * listed under [Point](https://api.highcharts.com/class-reference/Highcharts.Point)
23993 * members.
23994 *
23995 * @type {String}
23996 * @apioption plotOptions.series.dataLabels.filter.property
23997 */
23998
23999 /**
24000 * The operator to compare by. Can be one of `>`, `<`, `>=`, `<=`, `==`,
24001 * and `===`.
24002 *
24003 * @type {String}
24004 * @validvalue [">", "<", ">=", "<=", "==", "===""]
24005 * @apioption plotOptions.series.dataLabels.filter.operator
24006 */
24007
24008 /**
24009 * The value to compare against.
24010 *
24011 * @type {Mixed}
24012 * @apioption plotOptions.series.dataLabels.filter.value
24013 */
24014 },
24015 // draw points outside the plot area when the number of points is less than
24016 // this
24017
24018
24019
24020 /**
24021 * When the series contains less points than the crop threshold, all
24022 * points are drawn, even if the points fall outside the visible plot
24023 * area at the current zoom. The advantage of drawing all points (including
24024 * markers and columns), is that animation is performed on updates.
24025 * On the other hand, when the series contains more points than the
24026 * crop threshold, the series data is cropped to only contain points
24027 * that fall within the plot area. The advantage of cropping away invisible
24028 * points is to increase performance on large series.
24029 *
24030 * @type {Number}
24031 * @default 300
24032 * @since 2.2
24033 * @product highcharts highstock
24034 */
24035 cropThreshold: 300,
24036
24037
24038
24039 /**
24040 * The width of each point on the x axis. For example in a column chart
24041 * with one value each day, the pointRange would be 1 day (= 24 * 3600
24042 * * 1000 milliseconds). This is normally computed automatically, but
24043 * this option can be used to override the automatic value.
24044 *
24045 * @type {Number}
24046 * @default 0
24047 * @product highstock
24048 */
24049 pointRange: 0,
24050
24051 /**
24052 * When this is true, the series will not cause the Y axis to cross
24053 * the zero plane (or [threshold](#plotOptions.series.threshold) option)
24054 * unless the data actually crosses the plane.
24055 *
24056 * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
24057 * 3 will make the Y axis show negative values according to the `minPadding`
24058 * option. If `softThreshold` is `true`, the Y axis starts at 0.
24059 *
24060 * @type {Boolean}
24061 * @default true
24062 * @since 4.1.9
24063 * @product highcharts highstock
24064 */
24065 softThreshold: true,
24066
24067
24068
24069 /**
24070 * A wrapper object for all the series options in specific states.
24071 *
24072 * @type {plotOptions.series.states}
24073 */
24074 states: {
24075
24076
24077 /**
24078 * Options for the hovered series. These settings override the normal
24079 * state options when a series is moused over or touched.
24080 *
24081 */
24082 hover: {
24083
24084 /**
24085 * Enable separate styles for the hovered series to visualize that the
24086 * user hovers either the series itself or the legend. .
24087 *
24088 * @type {Boolean}
24089 * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled/ Line
24090 * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled-column/ Column
24091 * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled-pie/ Pie
24092 * @default true
24093 * @since 1.2
24094 * @apioption plotOptions.series.states.hover.enabled
24095 */
24096
24097
24098 /**
24099 * Animation setting for hovering the graph in line-type series.
24100 *
24101 * @type {Boolean|Object}
24102 * @default { "duration": 50 }
24103 * @since 5.0.8
24104 * @product highcharts
24105 */
24106 animation: {
24107 /**
24108 * The duration of the hover animation in milliseconds. By
24109 * default the hover state animates quickly in, and slowly back
24110 * to normal.
24111 */
24112 duration: 50
24113 },
24114
24115 /**
24116 * Pixel with of the graph line. By default this property is
24117 * undefined, and the `lineWidthPlus` property dictates how much
24118 * to increase the linewidth from normal state.
24119 *
24120 * @type {Number}
24121 * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidth/
24122 * 5px line on hover
24123 * @default undefined
24124 * @product highcharts highstock
24125 * @apioption plotOptions.series.states.hover.lineWidth
24126 */
24127
24128
24129 /**
24130 * The additional line width for the graph of a hovered series.
24131 *
24132 * @type {Number}
24133 * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/
24134 * 5 pixels wider
24135 * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/
24136 * 5 pixels wider
24137 * @default 1
24138 * @since 4.0.3
24139 * @product highcharts highstock
24140 */
24141 lineWidthPlus: 1,
24142
24143
24144
24145 /**
24146 * In Highcharts 1.0, the appearance of all markers belonging to
24147 * the hovered series. For settings on the hover state of the individual
24148 * point, see [marker.states.hover](#plotOptions.series.marker.states.
24149 * hover).
24150 *
24151 * @extends plotOptions.series.marker
24152 * @deprecated
24153 * @product highcharts highstock
24154 */
24155 marker: {
24156 // lineWidth: base + 1,
24157 // radius: base + 1
24158 },
24159
24160
24161
24162 /**
24163 * Options for the halo appearing around the hovered point in line-
24164 * type series as well as outside the hovered slice in pie charts.
24165 * By default the halo is filled by the current point or series
24166 * color with an opacity of 0.25\. The halo can be disabled by setting
24167 * the `halo` option to `false`.
24168 *
24169 * In styled mode, the halo is styled with the `.highcharts-halo` class, with colors inherited from `.highcharts-color-{n}`.
24170 *
24171 * @type {Object}
24172 * @sample {highcharts} highcharts/plotoptions/halo/ Halo options
24173 * @sample {highstock} highcharts/plotoptions/halo/ Halo options
24174 * @since 4.0
24175 * @product highcharts highstock
24176 */
24177 halo: {
24178
24179 /**
24180 * A collection of SVG attributes to override the appearance of the
24181 * halo, for example `fill`, `stroke` and `stroke-width`.
24182 *
24183 * @type {Object}
24184 * @since 4.0
24185 * @product highcharts highstock
24186 * @apioption plotOptions.series.states.hover.halo.attributes
24187 */
24188
24189
24190 /**
24191 * The pixel size of the halo. For point markers this is the radius
24192 * of the halo. For pie slices it is the width of the halo outside
24193 * the slice. For bubbles it defaults to 5 and is the width of the
24194 * halo outside the bubble.
24195 *
24196 * @type {Number}
24197 * @default 10
24198 * @since 4.0
24199 * @product highcharts highstock
24200 */
24201 size: 10,
24202
24203
24204
24205
24206 /**
24207 * Opacity for the halo unless a specific fill is overridden using
24208 * the `attributes` setting. Note that Highcharts is only able to
24209 * apply opacity to colors of hex or rgb(a) formats.
24210 *
24211 * @type {Number}
24212 * @default 0.25
24213 * @since 4.0
24214 * @product highcharts highstock
24215 */
24216 opacity: 0.25
24217
24218 }
24219 },
24220
24221
24222 /**
24223 * Specific options for point in selected states, after being selected
24224 * by [allowPointSelect](#plotOptions.series.allowPointSelect) or
24225 * programmatically.
24226 *
24227 * @type {Object}
24228 * @extends plotOptions.series.states.hover
24229 * @excluding brightness
24230 * @sample {highmaps} maps/plotoptions/series-allowpointselect/
24231 * Allow point select demo
24232 * @product highmaps
24233 */
24234 select: {
24235 marker: {}
24236 }
24237 },
24238
24239
24240
24241 /**
24242 * Sticky tracking of mouse events. When true, the `mouseOut` event
24243 * on a series isn't triggered until the mouse moves over another series,
24244 * or out of the plot area. When false, the `mouseOut` event on a
24245 * series is triggered when the mouse leaves the area around the series'
24246 * graph or markers. This also implies the tooltip when not shared. When
24247 * `stickyTracking` is false and `tooltip.shared` is false, the tooltip will
24248 * be hidden when moving the mouse between series. Defaults to true for line
24249 * and area type series, but to false for columns, pies etc.
24250 *
24251 * @type {Boolean}
24252 * @sample {highcharts} highcharts/plotoptions/series-stickytracking-true/
24253 * True by default
24254 * @sample {highcharts} highcharts/plotoptions/series-stickytracking-false/
24255 * False
24256 * @default {highcharts} true
24257 * @default {highstock} true
24258 * @default {highmaps} false
24259 * @since 2.0
24260 */
24261 stickyTracking: true,
24262
24263 /**
24264 * A configuration object for the tooltip rendering of each single series.
24265 * Properties are inherited from [tooltip](#tooltip), but only the
24266 * following properties can be defined on a series level.
24267 *
24268 * @type {Object}
24269 * @extends tooltip
24270 * @excluding animation,backgroundColor,borderColor,borderRadius,borderWidth,crosshairs,enabled,formatter,positioner,shadow,shared,shape,snap,style,useHTML
24271 * @since 2.3
24272 * @apioption plotOptions.series.tooltip
24273 */
24274
24275 /**
24276 * When a series contains a data array that is longer than this, only
24277 * one dimensional arrays of numbers, or two dimensional arrays with
24278 * x and y values are allowed. Also, only the first point is tested,
24279 * and the rest are assumed to be the same format. This saves expensive
24280 * data checking and indexing in long series. Set it to `0` disable.
24281 *
24282 * @type {Number}
24283 * @default 1000
24284 * @since 2.2
24285 * @product highcharts highstock
24286 */
24287 turboThreshold: 1000,
24288
24289 /**
24290 * An array defining zones within a series. Zones can be applied to
24291 * the X axis, Y axis or Z axis for bubbles, according to the `zoneAxis`
24292 * option.
24293 *
24294 * In styled mode, the color zones are styled with the `.highcharts-
24295 * zone-{n}` class, or custom classed from the `className` option ([view
24296 * live demo](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/color-
24297 * zones/)).
24298 *
24299 * @type {Array}
24300 * @see [zoneAxis](#plotOptions.series.zoneAxis)
24301 * @sample {highcharts} highcharts/series/color-zones-simple/ Color zones
24302 * @sample {highstock} highcharts/series/color-zones-simple/ Color zones
24303 * @since 4.1.0
24304 * @product highcharts highstock
24305 * @apioption plotOptions.series.zones
24306 */
24307
24308 /**
24309 * Styled mode only. A custom class name for the zone.
24310 *
24311 * @type {String}
24312 * @sample {highcharts} highcharts/css/color-zones/ Zones styled by class name
24313 * @sample {highstock} highcharts/css/color-zones/ Zones styled by class name
24314 * @sample {highmaps} highcharts/css/color-zones/ Zones styled by class name
24315 * @since 5.0.0
24316 * @apioption plotOptions.series.zones.className
24317 */
24318
24319 /**
24320 * Defines the color of the series.
24321 *
24322 * @type {Color}
24323 * @see [series color](#plotOptions.series.color)
24324 * @since 4.1.0
24325 * @product highcharts highstock
24326 * @apioption plotOptions.series.zones.color
24327 */
24328
24329 /**
24330 * A name for the dash style to use for the graph.
24331 *
24332 * @type {String}
24333 * @see [series.dashStyle](#plotOptions.series.dashStyle)
24334 * @sample {highcharts} highcharts/series/color-zones-dashstyle-dot/
24335 * Dashed line indicates prognosis
24336 * @sample {highstock} highcharts/series/color-zones-dashstyle-dot/
24337 * Dashed line indicates prognosis
24338 * @since 4.1.0
24339 * @product highcharts highstock
24340 * @apioption plotOptions.series.zones.dashStyle
24341 */
24342
24343 /**
24344 * Defines the fill color for the series (in area type series)
24345 *
24346 * @type {Color}
24347 * @see [fillColor](#plotOptions.area.fillColor)
24348 * @since 4.1.0
24349 * @product highcharts highstock
24350 * @apioption plotOptions.series.zones.fillColor
24351 */
24352
24353 /**
24354 * The value up to where the zone extends, if undefined the zones stretches
24355 * to the last value in the series.
24356 *
24357 * @type {Number}
24358 * @default undefined
24359 * @since 4.1.0
24360 * @product highcharts highstock
24361 * @apioption plotOptions.series.zones.value
24362 */
24363
24364
24365
24366 /**
24367 * Determines whether the series should look for the nearest point
24368 * in both dimensions or just the x-dimension when hovering the series.
24369 * Defaults to `'xy'` for scatter series and `'x'` for most other
24370 * series. If the data has duplicate x-values, it is recommended to
24371 * set this to `'xy'` to allow hovering over all points.
24372 *
24373 * Applies only to series types using nearest neighbor search (not
24374 * direct hover) for tooltip.
24375 *
24376 * @validvalue ['x', 'xy']
24377 * @type {String}
24378 * @sample {highcharts} highcharts/series/findnearestpointby/
24379 * Different hover behaviors
24380 * @sample {highstock} highcharts/series/findnearestpointby/
24381 * Different hover behaviors
24382 * @sample {highmaps} highcharts/series/findnearestpointby/
24383 * Different hover behaviors
24384 * @since 5.0.10
24385 */
24386 findNearestPointBy: 'x'
24387
24388 }, /** @lends Highcharts.Series.prototype */ {
24389 isCartesian: true,
24390 pointClass: Point,
24391 sorted: true, // requires the data to be sorted
24392 requireSorting: true,
24393 directTouch: false,
24394 axisTypes: ['xAxis', 'yAxis'],
24395 colorCounter: 0,
24396 // each point's x and y values are stored in this.xData and this.yData
24397 parallelArrays: ['x', 'y'],
24398 coll: 'series',
24399 init: function(chart, options) {
24400 var series = this,
24401 events,
24402 chartSeries = chart.series,
24403 lastSeries;
24404
24405 /**
24406 * Read only. The chart that the series belongs to.
24407 *
24408 * @name chart
24409 * @memberOf Series
24410 * @type {Chart}
24411 */
24412 series.chart = chart;
24413
24414 /**
24415 * Read only. The series' type, like "line", "area", "column" etc. The
24416 * type in the series options anc can be altered using {@link
24417 * Series#update}.
24418 *
24419 * @name type
24420 * @memberOf Series
24421 * @type String
24422 */
24423
24424 /**
24425 * Read only. The series' current options. To update, use {@link
24426 * Series#update}.
24427 *
24428 * @name options
24429 * @memberOf Series
24430 * @type SeriesOptions
24431 */
24432 series.options = options = series.setOptions(options);
24433 series.linkedSeries = [];
24434
24435 // bind the axes
24436 series.bindAxes();
24437
24438 // set some variables
24439 extend(series, {
24440 /**
24441 * The series name as given in the options. Defaults to
24442 * "Series {n}".
24443 *
24444 * @name name
24445 * @memberOf Series
24446 * @type {String}
24447 */
24448 name: options.name,
24449 state: '',
24450 /**
24451 * Read only. The series' visibility state as set by {@link
24452 * Series#show}, {@link Series#hide}, or in the initial
24453 * configuration.
24454 *
24455 * @name visible
24456 * @memberOf Series
24457 * @type {Boolean}
24458 */
24459 visible: options.visible !== false, // true by default
24460 /**
24461 * Read only. The series' selected state as set by {@link
24462 * Highcharts.Series#select}.
24463 *
24464 * @name selected
24465 * @memberOf Series
24466 * @type {Boolean}
24467 */
24468 selected: options.selected === true // false by default
24469 });
24470
24471 // register event listeners
24472 events = options.events;
24473
24474 objectEach(events, function(event, eventType) {
24475 addEvent(series, eventType, event);
24476 });
24477 if (
24478 (events && events.click) ||
24479 (
24480 options.point &&
24481 options.point.events &&
24482 options.point.events.click
24483 ) ||
24484 options.allowPointSelect
24485 ) {
24486 chart.runTrackerClick = true;
24487 }
24488
24489 series.getColor();
24490 series.getSymbol();
24491
24492 // Set the data
24493 each(series.parallelArrays, function(key) {
24494 series[key + 'Data'] = [];
24495 });
24496 series.setData(options.data, false);
24497
24498 // Mark cartesian
24499 if (series.isCartesian) {
24500 chart.hasCartesianSeries = true;
24501 }
24502
24503 // Get the index and register the series in the chart. The index is one
24504 // more than the current latest series index (#5960).
24505 if (chartSeries.length) {
24506 lastSeries = chartSeries[chartSeries.length - 1];
24507 }
24508 series._i = pick(lastSeries && lastSeries._i, -1) + 1;
24509
24510 // Insert the series and re-order all series above the insertion point.
24511 chart.orderSeries(this.insert(chartSeries));
24512 },
24513
24514 /**
24515 * Insert the series in a collection with other series, either the chart
24516 * series or yAxis series, in the correct order according to the index
24517 * option. Used internally when adding series.
24518 *
24519 * @private
24520 * @param {Array.<Series>} collection
24521 * A collection of series, like `chart.series` or `xAxis.series`.
24522 * @returns {Number} The index of the series in the collection.
24523 */
24524 insert: function(collection) {
24525 var indexOption = this.options.index,
24526 i;
24527
24528 // Insert by index option
24529 if (isNumber(indexOption)) {
24530 i = collection.length;
24531 while (i--) {
24532 // Loop down until the interted element has higher index
24533 if (indexOption >=
24534 pick(collection[i].options.index, collection[i]._i)) {
24535 collection.splice(i + 1, 0, this);
24536 break;
24537 }
24538 }
24539 if (i === -1) {
24540 collection.unshift(this);
24541 }
24542 i = i + 1;
24543
24544 // Or just push it to the end
24545 } else {
24546 collection.push(this);
24547 }
24548 return pick(i, collection.length - 1);
24549 },
24550
24551 /**
24552 * Set the xAxis and yAxis properties of cartesian series, and register the
24553 * series in the `axis.series` array.
24554 *
24555 * @private
24556 */
24557 bindAxes: function() {
24558 var series = this,
24559 seriesOptions = series.options,
24560 chart = series.chart,
24561 axisOptions;
24562
24563 // repeat for xAxis and yAxis
24564 each(series.axisTypes || [], function(AXIS) {
24565
24566 // loop through the chart's axis objects
24567 each(chart[AXIS], function(axis) {
24568 axisOptions = axis.options;
24569
24570 // apply if the series xAxis or yAxis option mathches the number
24571 // of the axis, or if undefined, use the first axis
24572 if (
24573 seriesOptions[AXIS] === axisOptions.index ||
24574 (
24575 seriesOptions[AXIS] !== undefined &&
24576 seriesOptions[AXIS] === axisOptions.id
24577 ) ||
24578 (
24579 seriesOptions[AXIS] === undefined &&
24580 axisOptions.index === 0
24581 )
24582 ) {
24583
24584 // register this series in the axis.series lookup
24585 series.insert(axis.series);
24586
24587 // set this series.xAxis or series.yAxis reference
24588 /**
24589 * Read only. The unique xAxis object associated with the
24590 * series.
24591 *
24592 * @name xAxis
24593 * @memberOf Series
24594 * @type Axis
24595 */
24596 /**
24597 * Read only. The unique yAxis object associated with the
24598 * series.
24599 *
24600 * @name yAxis
24601 * @memberOf Series
24602 * @type Axis
24603 */
24604 series[AXIS] = axis;
24605
24606 // mark dirty for redraw
24607 axis.isDirty = true;
24608 }
24609 });
24610
24611 // The series needs an X and an Y axis
24612 if (!series[AXIS] && series.optionalAxis !== AXIS) {
24613 H.error(18, true);
24614 }
24615
24616 });
24617 },
24618
24619 /**
24620 * For simple series types like line and column, the data values are held in
24621 * arrays like xData and yData for quick lookup to find extremes and more.
24622 * For multidimensional series like bubble and map, this can be extended
24623 * with arrays like zData and valueData by adding to the
24624 * `series.parallelArrays` array.
24625 *
24626 * @private
24627 */
24628 updateParallelArrays: function(point, i) {
24629 var series = point.series,
24630 args = arguments,
24631 fn = isNumber(i) ?
24632 // Insert the value in the given position
24633 function(key) {
24634 var val = key === 'y' && series.toYData ?
24635 series.toYData(point) :
24636 point[key];
24637 series[key + 'Data'][i] = val;
24638 } :
24639 // Apply the method specified in i with the following arguments
24640 // as arguments
24641 function(key) {
24642 Array.prototype[i].apply(
24643 series[key + 'Data'],
24644 Array.prototype.slice.call(args, 2)
24645 );
24646 };
24647
24648 each(series.parallelArrays, fn);
24649 },
24650
24651 /**
24652 * Return an auto incremented x value based on the pointStart and
24653 * pointInterval options. This is only used if an x value is not given for
24654 * the point that calls autoIncrement.
24655 *
24656 * @private
24657 */
24658 autoIncrement: function() {
24659
24660 var options = this.options,
24661 xIncrement = this.xIncrement,
24662 date,
24663 pointInterval,
24664 pointIntervalUnit = options.pointIntervalUnit;
24665
24666 xIncrement = pick(xIncrement, options.pointStart, 0);
24667
24668 this.pointInterval = pointInterval = pick(
24669 this.pointInterval,
24670 options.pointInterval,
24671 1
24672 );
24673
24674 // Added code for pointInterval strings
24675 if (pointIntervalUnit) {
24676 date = new Date(xIncrement);
24677
24678 if (pointIntervalUnit === 'day') {
24679 date = +date[Date.hcSetDate](
24680 date[Date.hcGetDate]() + pointInterval
24681 );
24682 } else if (pointIntervalUnit === 'month') {
24683 date = +date[Date.hcSetMonth](
24684 date[Date.hcGetMonth]() + pointInterval
24685 );
24686 } else if (pointIntervalUnit === 'year') {
24687 date = +date[Date.hcSetFullYear](
24688 date[Date.hcGetFullYear]() + pointInterval
24689 );
24690 }
24691 pointInterval = date - xIncrement;
24692
24693 }
24694
24695 this.xIncrement = xIncrement + pointInterval;
24696 return xIncrement;
24697 },
24698
24699 /**
24700 * Set the series options by merging from the options tree. Called
24701 * internally on initiating and updating series. This function will not
24702 * redraw the series. For API usage, use {@link Series#update}.
24703 *
24704 * @param {Options.plotOptions.series} itemOptions
24705 * The series options.
24706 */
24707 setOptions: function(itemOptions) {
24708 var chart = this.chart,
24709 chartOptions = chart.options,
24710 plotOptions = chartOptions.plotOptions,
24711 userOptions = chart.userOptions || {},
24712 userPlotOptions = userOptions.plotOptions || {},
24713 typeOptions = plotOptions[this.type],
24714 options,
24715 zones;
24716
24717 this.userOptions = itemOptions;
24718
24719 // General series options take precedence over type options because
24720 // otherwise, default type options like column.animation would be
24721 // overwritten by the general option. But issues have been raised here
24722 // (#3881), and the solution may be to distinguish between default
24723 // option and userOptions like in the tooltip below.
24724 options = merge(
24725 typeOptions,
24726 plotOptions.series,
24727 itemOptions
24728 );
24729
24730 // The tooltip options are merged between global and series specific
24731 // options. Importance order asscendingly:
24732 // globals: (1)tooltip, (2)plotOptions.series, (3)plotOptions[this.type]
24733 // init userOptions with possible later updates: 4-6 like 1-3 and
24734 // (7)this series options
24735 this.tooltipOptions = merge(
24736 defaultOptions.tooltip, // 1
24737 defaultOptions.plotOptions.series &&
24738 defaultOptions.plotOptions.series.tooltip, // 2
24739 defaultOptions.plotOptions[this.type].tooltip, // 3
24740 chartOptions.tooltip.userOptions, // 4
24741 plotOptions.series && plotOptions.series.tooltip, // 5
24742 plotOptions[this.type].tooltip, // 6
24743 itemOptions.tooltip // 7
24744 );
24745
24746 // When shared tooltip, stickyTracking is true by default,
24747 // unless user says otherwise.
24748 this.stickyTracking = pick(
24749 itemOptions.stickyTracking,
24750 userPlotOptions[this.type] &&
24751 userPlotOptions[this.type].stickyTracking,
24752 userPlotOptions.series && userPlotOptions.series.stickyTracking,
24753 (
24754 this.tooltipOptions.shared && !this.noSharedTooltip ?
24755 true :
24756 options.stickyTracking
24757 )
24758 );
24759
24760 // Delete marker object if not allowed (#1125)
24761 if (typeOptions.marker === null) {
24762 delete options.marker;
24763 }
24764
24765 // Handle color zones
24766 this.zoneAxis = options.zoneAxis;
24767 zones = this.zones = (options.zones || []).slice();
24768 if (
24769 (options.negativeColor || options.negativeFillColor) &&
24770 !options.zones
24771 ) {
24772 zones.push({
24773 value: options[this.zoneAxis + 'Threshold'] ||
24774 options.threshold ||
24775 0,
24776 className: 'highcharts-negative',
24777
24778 color: options.negativeColor,
24779 fillColor: options.negativeFillColor
24780
24781 });
24782 }
24783 if (zones.length) { // Push one extra zone for the rest
24784 if (defined(zones[zones.length - 1].value)) {
24785 zones.push({
24786
24787 color: this.color,
24788 fillColor: this.fillColor
24789
24790 });
24791 }
24792 }
24793 return options;
24794 },
24795
24796 getCyclic: function(prop, value, defaults) {
24797 var i,
24798 chart = this.chart,
24799 userOptions = this.userOptions,
24800 indexName = prop + 'Index',
24801 counterName = prop + 'Counter',
24802 len = defaults ? defaults.length : pick(
24803 chart.options.chart[prop + 'Count'],
24804 chart[prop + 'Count']
24805 ),
24806 setting;
24807
24808 if (!value) {
24809 // Pick up either the colorIndex option, or the _colorIndex after
24810 // Series.update()
24811 setting = pick(
24812 userOptions[indexName],
24813 userOptions['_' + indexName]
24814 );
24815 if (defined(setting)) { // after Series.update()
24816 i = setting;
24817 } else {
24818 // #6138
24819 if (!chart.series.length) {
24820 chart[counterName] = 0;
24821 }
24822 userOptions['_' + indexName] = i = chart[counterName] % len;
24823 chart[counterName] += 1;
24824 }
24825 if (defaults) {
24826 value = defaults[i];
24827 }
24828 }
24829 // Set the colorIndex
24830 if (i !== undefined) {
24831 this[indexName] = i;
24832 }
24833 this[prop] = value;
24834 },
24835
24836 /**
24837 * Get the series' color based on either the options or pulled from global
24838 * options.
24839 *
24840 * @return {Color} The series color.
24841 */
24842
24843 getColor: function() {
24844 if (this.options.colorByPoint) {
24845 // #4359, selected slice got series.color even when colorByPoint was
24846 // set.
24847 this.options.color = null;
24848 } else {
24849 this.getCyclic(
24850 'color',
24851 this.options.color || defaultPlotOptions[this.type].color,
24852 this.chart.options.colors
24853 );
24854 }
24855 },
24856
24857 /**
24858 * Get the series' symbol based on either the options or pulled from global
24859 * options.
24860 */
24861 getSymbol: function() {
24862 var seriesMarkerOption = this.options.marker;
24863
24864 this.getCyclic(
24865 'symbol',
24866 seriesMarkerOption.symbol,
24867 this.chart.options.symbols
24868 );
24869 },
24870
24871 drawLegendSymbol: LegendSymbolMixin.drawLineMarker,
24872
24873 /**
24874 * Apply a new set of data to the series and optionally redraw it. The new
24875 * data array is passed by reference (except in case of `updatePoints`), and
24876 * may later be mutated when updating the chart data.
24877 *
24878 * Note the difference in behaviour when setting the same amount of points,
24879 * or a different amount of points, as handled by the `updatePoints`
24880 * parameter.
24881 *
24882 * @param {SeriesDataOptions} data
24883 * Takes an array of data in the same format as described under
24884 * `series.typedata` for the given series type.
24885 * @param {Boolean} [redraw=true]
24886 * Whether to redraw the chart after the series is altered. If doing
24887 * more operations on the chart, it is a good idea to set redraw to
24888 * false and call {@link Chart#redraw} after.
24889 * @param {AnimationOptions} [animation]
24890 * When the updated data is the same length as the existing data,
24891 * points will be updated by default, and animation visualizes how
24892 * the points are changed. Set false to disable animation, or a
24893 * configuration object to set duration or easing.
24894 * @param {Boolean} [updatePoints=true]
24895 * When the updated data is the same length as the existing data,
24896 * points will be updated instead of replaced. This allows updating
24897 * with animation and performs better. In this case, the original
24898 * array is not passed by reference. Set false to prevent.
24899 *
24900 * @sample highcharts/members/series-setdata/
24901 * Set new data from a button
24902 * @sample highcharts/members/series-setdata-pie/
24903 * Set data in a pie
24904 * @sample stock/members/series-setdata/
24905 * Set new data in Highstock
24906 * @sample maps/members/series-setdata/
24907 * Set new data in Highmaps
24908 */
24909 setData: function(data, redraw, animation, updatePoints) {
24910 var series = this,
24911 oldData = series.points,
24912 oldDataLength = (oldData && oldData.length) || 0,
24913 dataLength,
24914 options = series.options,
24915 chart = series.chart,
24916 firstPoint = null,
24917 xAxis = series.xAxis,
24918 i,
24919 turboThreshold = options.turboThreshold,
24920 pt,
24921 xData = this.xData,
24922 yData = this.yData,
24923 pointArrayMap = series.pointArrayMap,
24924 valueCount = pointArrayMap && pointArrayMap.length;
24925
24926 data = data || [];
24927 dataLength = data.length;
24928 redraw = pick(redraw, true);
24929
24930 // If the point count is the same as is was, just run Point.update which
24931 // is cheaper, allows animation, and keeps references to points.
24932 if (
24933 updatePoints !== false &&
24934 dataLength &&
24935 oldDataLength === dataLength &&
24936 !series.cropped &&
24937 !series.hasGroupedData &&
24938 series.visible
24939 ) {
24940 each(data, function(point, i) {
24941 // .update doesn't exist on a linked, hidden series (#3709)
24942 if (oldData[i].update && point !== options.data[i]) {
24943 oldData[i].update(point, false, null, false);
24944 }
24945 });
24946
24947 } else {
24948
24949 // Reset properties
24950 series.xIncrement = null;
24951
24952 series.colorCounter = 0; // for series with colorByPoint (#1547)
24953
24954 // Update parallel arrays
24955 each(this.parallelArrays, function(key) {
24956 series[key + 'Data'].length = 0;
24957 });
24958
24959 // In turbo mode, only one- or twodimensional arrays of numbers are
24960 // allowed. The first value is tested, and we assume that all the
24961 // rest are defined the same way. Although the 'for' loops are
24962 // similar, they are repeated inside each if-else conditional for
24963 // max performance.
24964 if (turboThreshold && dataLength > turboThreshold) {
24965
24966 // find the first non-null point
24967 i = 0;
24968 while (firstPoint === null && i < dataLength) {
24969 firstPoint = data[i];
24970 i++;
24971 }
24972
24973
24974 if (isNumber(firstPoint)) { // assume all points are numbers
24975 for (i = 0; i < dataLength; i++) {
24976 xData[i] = this.autoIncrement();
24977 yData[i] = data[i];
24978 }
24979
24980 // Assume all points are arrays when first point is
24981 } else if (isArray(firstPoint)) {
24982 if (valueCount) { // [x, low, high] or [x, o, h, l, c]
24983 for (i = 0; i < dataLength; i++) {
24984 pt = data[i];
24985 xData[i] = pt[0];
24986 yData[i] = pt.slice(1, valueCount + 1);
24987 }
24988 } else { // [x, y]
24989 for (i = 0; i < dataLength; i++) {
24990 pt = data[i];
24991 xData[i] = pt[0];
24992 yData[i] = pt[1];
24993 }
24994 }
24995 } else {
24996 // Highcharts expects configs to be numbers or arrays in
24997 // turbo mode
24998 H.error(12);
24999 }
25000 } else {
25001 for (i = 0; i < dataLength; i++) {
25002 if (data[i] !== undefined) { // stray commas in oldIE
25003 pt = {
25004 series: series
25005 };
25006 series.pointClass.prototype.applyOptions.apply(
25007 pt, [data[i]]
25008 );
25009 series.updateParallelArrays(pt, i);
25010 }
25011 }
25012 }
25013
25014 // Forgetting to cast strings to numbers is a common caveat when
25015 // handling CSV or JSON
25016 if (yData && isString(yData[0])) {
25017 H.error(14, true);
25018 }
25019
25020 series.data = [];
25021 series.options.data = series.userOptions.data = data;
25022
25023 // destroy old points
25024 i = oldDataLength;
25025 while (i--) {
25026 if (oldData[i] && oldData[i].destroy) {
25027 oldData[i].destroy();
25028 }
25029 }
25030
25031 // reset minRange (#878)
25032 if (xAxis) {
25033 xAxis.minRange = xAxis.userMinRange;
25034 }
25035
25036 // redraw
25037 series.isDirty = chart.isDirtyBox = true;
25038 series.isDirtyData = !!oldData;
25039 animation = false;
25040 }
25041
25042 // Typically for pie series, points need to be processed and generated
25043 // prior to rendering the legend
25044 if (options.legendType === 'point') {
25045 this.processData();
25046 this.generatePoints();
25047 }
25048
25049 if (redraw) {
25050 chart.redraw(animation);
25051 }
25052 },
25053
25054 /**
25055 * Internal function to process the data by cropping away unused data points
25056 * if the series is longer than the crop threshold. This saves computing
25057 * time for large series. In Highstock, this function is extended to
25058 * provide data grouping.
25059 *
25060 * @private
25061 * @param {Boolean} force
25062 * Force data grouping.
25063 */
25064 processData: function(force) {
25065 var series = this,
25066 processedXData = series.xData, // copied during slice operation
25067 processedYData = series.yData,
25068 dataLength = processedXData.length,
25069 croppedData,
25070 cropStart = 0,
25071 cropped,
25072 distance,
25073 closestPointRange,
25074 xAxis = series.xAxis,
25075 i, // loop variable
25076 options = series.options,
25077 cropThreshold = options.cropThreshold,
25078 getExtremesFromAll =
25079 series.getExtremesFromAll ||
25080 options.getExtremesFromAll, // #4599
25081 isCartesian = series.isCartesian,
25082 xExtremes,
25083 val2lin = xAxis && xAxis.val2lin,
25084 isLog = xAxis && xAxis.isLog,
25085 throwOnUnsorted = series.requireSorting,
25086 min,
25087 max;
25088
25089 // If the series data or axes haven't changed, don't go through this.
25090 // Return false to pass the message on to override methods like in data
25091 // grouping.
25092 if (
25093 isCartesian &&
25094 !series.isDirty &&
25095 !xAxis.isDirty &&
25096 !series.yAxis.isDirty &&
25097 !force
25098 ) {
25099 return false;
25100 }
25101
25102 if (xAxis) {
25103 xExtremes = xAxis.getExtremes(); // corrected for log axis (#3053)
25104 min = xExtremes.min;
25105 max = xExtremes.max;
25106 }
25107
25108 // optionally filter out points outside the plot area
25109 if (
25110 isCartesian &&
25111 series.sorted &&
25112 !getExtremesFromAll &&
25113 (!cropThreshold || dataLength > cropThreshold || series.forceCrop)
25114 ) {
25115
25116 // it's outside current extremes
25117 if (
25118 processedXData[dataLength - 1] < min ||
25119 processedXData[0] > max
25120 ) {
25121 processedXData = [];
25122 processedYData = [];
25123
25124 // only crop if it's actually spilling out
25125 } else if (
25126 processedXData[0] < min ||
25127 processedXData[dataLength - 1] > max
25128 ) {
25129 croppedData = this.cropData(
25130 series.xData,
25131 series.yData,
25132 min,
25133 max
25134 );
25135 processedXData = croppedData.xData;
25136 processedYData = croppedData.yData;
25137 cropStart = croppedData.start;
25138 cropped = true;
25139 }
25140 }
25141
25142
25143 // Find the closest distance between processed points
25144 i = processedXData.length || 1;
25145 while (--i) {
25146 distance = isLog ?
25147 val2lin(processedXData[i]) - val2lin(processedXData[i - 1]) :
25148 processedXData[i] - processedXData[i - 1];
25149
25150 if (
25151 distance > 0 &&
25152 (
25153 closestPointRange === undefined ||
25154 distance < closestPointRange
25155 )
25156 ) {
25157 closestPointRange = distance;
25158
25159 // Unsorted data is not supported by the line tooltip, as well as
25160 // data grouping and navigation in Stock charts (#725) and width
25161 // calculation of columns (#1900)
25162 } else if (distance < 0 && throwOnUnsorted) {
25163 H.error(15);
25164 throwOnUnsorted = false; // Only once
25165 }
25166 }
25167
25168 // Record the properties
25169 series.cropped = cropped; // undefined or true
25170 series.cropStart = cropStart;
25171 series.processedXData = processedXData;
25172 series.processedYData = processedYData;
25173
25174 series.closestPointRange = closestPointRange;
25175
25176 },
25177
25178 /**
25179 * Iterate over xData and crop values between min and max. Returns object
25180 * containing crop start/end cropped xData with corresponding part of yData,
25181 * dataMin and dataMax within the cropped range.
25182 *
25183 * @private
25184 */
25185 cropData: function(xData, yData, min, max) {
25186 var dataLength = xData.length,
25187 cropStart = 0,
25188 cropEnd = dataLength,
25189 // line-type series need one point outside
25190 cropShoulder = pick(this.cropShoulder, 1),
25191 i,
25192 j;
25193
25194 // iterate up to find slice start
25195 for (i = 0; i < dataLength; i++) {
25196 if (xData[i] >= min) {
25197 cropStart = Math.max(0, i - cropShoulder);
25198 break;
25199 }
25200 }
25201
25202 // proceed to find slice end
25203 for (j = i; j < dataLength; j++) {
25204 if (xData[j] > max) {
25205 cropEnd = j + cropShoulder;
25206 break;
25207 }
25208 }
25209
25210 return {
25211 xData: xData.slice(cropStart, cropEnd),
25212 yData: yData.slice(cropStart, cropEnd),
25213 start: cropStart,
25214 end: cropEnd
25215 };
25216 },
25217
25218
25219 /**
25220 * Generate the data point after the data has been processed by cropping
25221 * away unused points and optionally grouped in Highcharts Stock.
25222 *
25223 * @private
25224 */
25225 generatePoints: function() {
25226 var series = this,
25227 options = series.options,
25228 dataOptions = options.data,
25229 data = series.data,
25230 dataLength,
25231 processedXData = series.processedXData,
25232 processedYData = series.processedYData,
25233 PointClass = series.pointClass,
25234 processedDataLength = processedXData.length,
25235 cropStart = series.cropStart || 0,
25236 cursor,
25237 hasGroupedData = series.hasGroupedData,
25238 keys = options.keys,
25239 point,
25240 points = [],
25241 i;
25242
25243 if (!data && !hasGroupedData) {
25244 var arr = [];
25245 arr.length = dataOptions.length;
25246 data = series.data = arr;
25247 }
25248
25249 if (keys && hasGroupedData) {
25250 // grouped data has already applied keys (#6590)
25251 series.options.keys = false;
25252 }
25253
25254 for (i = 0; i < processedDataLength; i++) {
25255 cursor = cropStart + i;
25256 if (!hasGroupedData) {
25257 point = data[cursor];
25258 if (!point && dataOptions[cursor] !== undefined) { // #970
25259 data[cursor] = point = (new PointClass()).init(
25260 series,
25261 dataOptions[cursor],
25262 processedXData[i]
25263 );
25264 }
25265 } else {
25266 // splat the y data in case of ohlc data array
25267 point = (new PointClass()).init(
25268 series, [processedXData[i]].concat(splat(processedYData[i]))
25269 );
25270
25271 /**
25272 * Highstock only. If a point object is created by data
25273 * grouping, it doesn't reflect actual points in the raw data.
25274 * In this case, the `dataGroup` property holds information
25275 * that points back to the raw data.
25276 *
25277 * - `dataGroup.start` is the index of the first raw data point
25278 * in the group.
25279 * - `dataGroup.length` is the amount of points in the group.
25280 *
25281 * @name dataGroup
25282 * @memberOf Point
25283 * @type {Object}
25284 *
25285 */
25286 point.dataGroup = series.groupMap[i];
25287 }
25288 if (point) { // #6279
25289 point.index = cursor; // For faster access in Point.update
25290 points[i] = point;
25291 }
25292 }
25293
25294 // restore keys options (#6590)
25295 series.options.keys = keys;
25296
25297 // Hide cropped-away points - this only runs when the number of points
25298 // is above cropThreshold, or when swithching view from non-grouped
25299 // data to grouped data (#637)
25300 if (
25301 data &&
25302 (
25303 processedDataLength !== (dataLength = data.length) ||
25304 hasGroupedData
25305 )
25306 ) {
25307 for (i = 0; i < dataLength; i++) {
25308 // when has grouped data, clear all points
25309 if (i === cropStart && !hasGroupedData) {
25310 i += processedDataLength;
25311 }
25312 if (data[i]) {
25313 data[i].destroyElements();
25314 data[i].plotX = undefined; // #1003
25315 }
25316 }
25317 }
25318
25319 /**
25320 * Read only. An array containing those values converted to points, but
25321 * in case the series data length exceeds the `cropThreshold`, or if the
25322 * data is grouped, `series.data` doesn't contain all the points. It
25323 * only contains the points that have been created on demand. To
25324 * modify the data, use {@link Highcharts.Series#setData} or {@link
25325 * Highcharts.Point#update}.
25326 *
25327 * @name data
25328 * @memberOf Highcharts.Series
25329 * @see Series.points
25330 * @type {Array.<Highcharts.Point>}
25331 */
25332 series.data = data;
25333
25334 /**
25335 * An array containing all currently visible point objects. In case of
25336 * cropping, the cropped-away points are not part of this array. The
25337 * `series.points` array starts at `series.cropStart` compared to
25338 * `series.data` and `series.options.data`. If however the series data
25339 * is grouped, these can't be correlated one to one. To
25340 * modify the data, use {@link Highcharts.Series#setData} or {@link
25341 * Highcharts.Point#update}.
25342 * @name points
25343 * @memberof Series
25344 * @type {Array.<Point>}
25345 */
25346 series.points = points;
25347 },
25348
25349 /**
25350 * Calculate Y extremes for the visible data. The result is set as
25351 * `dataMin` and `dataMax` on the Series item.
25352 *
25353 * @param {Array.<Number>} [yData]
25354 * The data to inspect. Defaults to the current data within the
25355 * visible range.
25356 *
25357 */
25358 getExtremes: function(yData) {
25359 var xAxis = this.xAxis,
25360 yAxis = this.yAxis,
25361 xData = this.processedXData,
25362 yDataLength,
25363 activeYData = [],
25364 activeCounter = 0,
25365 // #2117, need to compensate for log X axis
25366 xExtremes = xAxis.getExtremes(),
25367 xMin = xExtremes.min,
25368 xMax = xExtremes.max,
25369 validValue,
25370 withinRange,
25371 x,
25372 y,
25373 i,
25374 j;
25375
25376 yData = yData || this.stackedYData || this.processedYData || [];
25377 yDataLength = yData.length;
25378
25379 for (i = 0; i < yDataLength; i++) {
25380
25381 x = xData[i];
25382 y = yData[i];
25383
25384 // For points within the visible range, including the first point
25385 // outside the visible range (#7061), consider y extremes.
25386 validValue =
25387 (isNumber(y, true) || isArray(y)) &&
25388 (!yAxis.positiveValuesOnly || (y.length || y > 0));
25389 withinRange =
25390 this.getExtremesFromAll ||
25391 this.options.getExtremesFromAll ||
25392 this.cropped ||
25393 ((xData[i + 1] || x) >= xMin && (xData[i - 1] || x) <= xMax);
25394
25395 if (validValue && withinRange) {
25396
25397 j = y.length;
25398 if (j) { // array, like ohlc or range data
25399 while (j--) {
25400 if (y[j] !== null) {
25401 activeYData[activeCounter++] = y[j];
25402 }
25403 }
25404 } else {
25405 activeYData[activeCounter++] = y;
25406 }
25407 }
25408 }
25409
25410 this.dataMin = arrayMin(activeYData);
25411 this.dataMax = arrayMax(activeYData);
25412 },
25413
25414 /**
25415 * Translate data points from raw data values to chart specific positioning
25416 * data needed later in the `drawPoints` and `drawGraph` functions. This
25417 * function can be overridden in plugins and custom series type
25418 * implementations.
25419 */
25420 translate: function() {
25421 if (!this.processedXData) { // hidden series
25422 this.processData();
25423 }
25424 this.generatePoints();
25425 var series = this,
25426 options = series.options,
25427 stacking = options.stacking,
25428 xAxis = series.xAxis,
25429 categories = xAxis.categories,
25430 yAxis = series.yAxis,
25431 points = series.points,
25432 dataLength = points.length,
25433 hasModifyValue = !!series.modifyValue,
25434 i,
25435 pointPlacement = options.pointPlacement,
25436 dynamicallyPlaced =
25437 pointPlacement === 'between' ||
25438 isNumber(pointPlacement),
25439 threshold = options.threshold,
25440 stackThreshold = options.startFromThreshold ? threshold : 0,
25441 plotX,
25442 plotY,
25443 lastPlotX,
25444 stackIndicator,
25445 closestPointRangePx = Number.MAX_VALUE;
25446
25447 // Point placement is relative to each series pointRange (#5889)
25448 if (pointPlacement === 'between') {
25449 pointPlacement = 0.5;
25450 }
25451 if (isNumber(pointPlacement)) {
25452 pointPlacement *= pick(options.pointRange || xAxis.pointRange);
25453 }
25454
25455 // Translate each point
25456 for (i = 0; i < dataLength; i++) {
25457 var point = points[i],
25458 xValue = point.x,
25459 yValue = point.y,
25460 yBottom = point.low,
25461 stack = stacking && yAxis.stacks[(
25462 series.negStacks &&
25463 yValue < (stackThreshold ? 0 : threshold) ? '-' : ''
25464 ) + series.stackKey],
25465 pointStack,
25466 stackValues;
25467
25468 // Discard disallowed y values for log axes (#3434)
25469 if (yAxis.positiveValuesOnly && yValue !== null && yValue <= 0) {
25470 point.isNull = true;
25471 }
25472
25473 // Get the plotX translation
25474 point.plotX = plotX = correctFloat( // #5236
25475 Math.min(Math.max(-1e5, xAxis.translate(
25476 xValue,
25477 0,
25478 0,
25479 0,
25480 1,
25481 pointPlacement,
25482 this.type === 'flags'
25483 )), 1e5) // #3923
25484 );
25485
25486 // Calculate the bottom y value for stacked series
25487 if (
25488 stacking &&
25489 series.visible &&
25490 !point.isNull &&
25491 stack &&
25492 stack[xValue]
25493 ) {
25494 stackIndicator = series.getStackIndicator(
25495 stackIndicator,
25496 xValue,
25497 series.index
25498 );
25499 pointStack = stack[xValue];
25500 stackValues = pointStack.points[stackIndicator.key];
25501 yBottom = stackValues[0];
25502 yValue = stackValues[1];
25503
25504 if (
25505 yBottom === stackThreshold &&
25506 stackIndicator.key === stack[xValue].base
25507 ) {
25508 yBottom = pick(threshold, yAxis.min);
25509 }
25510 if (yAxis.positiveValuesOnly && yBottom <= 0) { // #1200, #1232
25511 yBottom = null;
25512 }
25513
25514 point.total = point.stackTotal = pointStack.total;
25515 point.percentage =
25516 pointStack.total &&
25517 (point.y / pointStack.total * 100);
25518 point.stackY = yValue;
25519
25520 // Place the stack label
25521 pointStack.setOffset(
25522 series.pointXOffset || 0,
25523 series.barW || 0
25524 );
25525
25526 }
25527
25528 // Set translated yBottom or remove it
25529 point.yBottom = defined(yBottom) ?
25530 yAxis.translate(yBottom, 0, 1, 0, 1) :
25531 null;
25532
25533 // general hook, used for Highstock compare mode
25534 if (hasModifyValue) {
25535 yValue = series.modifyValue(yValue, point);
25536 }
25537
25538 // Set the the plotY value, reset it for redraws
25539 point.plotY = plotY =
25540 (typeof yValue === 'number' && yValue !== Infinity) ?
25541 Math.min(Math.max(-1e5,
25542 yAxis.translate(yValue, 0, 1, 0, 1)), 1e5) : // #3201
25543 undefined;
25544
25545 point.isInside =
25546 plotY !== undefined &&
25547 plotY >= 0 &&
25548 plotY <= yAxis.len && // #3519
25549 plotX >= 0 &&
25550 plotX <= xAxis.len;
25551
25552
25553 // Set client related positions for mouse tracking
25554 point.clientX = dynamicallyPlaced ?
25555 correctFloat(
25556 xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement)
25557 ) :
25558 plotX; // #1514, #5383, #5518
25559
25560 point.negative = point.y < (threshold || 0);
25561
25562 // some API data
25563 point.category = categories && categories[point.x] !== undefined ?
25564 categories[point.x] : point.x;
25565
25566 // Determine auto enabling of markers (#3635, #5099)
25567 if (!point.isNull) {
25568 if (lastPlotX !== undefined) {
25569 closestPointRangePx = Math.min(
25570 closestPointRangePx,
25571 Math.abs(plotX - lastPlotX)
25572 );
25573 }
25574 lastPlotX = plotX;
25575 }
25576
25577 // Find point zone
25578 point.zone = this.zones.length && point.getZone();
25579 }
25580 series.closestPointRangePx = closestPointRangePx;
25581 },
25582
25583 /**
25584 * Return the series points with null points filtered out.
25585 *
25586 * @param {Array.<Point>} [points]
25587 * The points to inspect, defaults to {@link Series.points}.
25588 * @param {Boolean} [insideOnly=false]
25589 * Whether to inspect only the points that are inside the visible
25590 * view.
25591 *
25592 * @return {Array.<Point>}
25593 * The valid points.
25594 */
25595 getValidPoints: function(points, insideOnly) {
25596 var chart = this.chart;
25597 // #3916, #5029, #5085
25598 return grep(points || this.points || [], function isValidPoint(point) {
25599 if (insideOnly && !chart.isInsidePlot(
25600 point.plotX,
25601 point.plotY,
25602 chart.inverted
25603 )) {
25604 return false;
25605 }
25606 return !point.isNull;
25607 });
25608 },
25609
25610 /**
25611 * Set the clipping for the series. For animated series it is called twice,
25612 * first to initiate animating the clip then the second time without the
25613 * animation to set the final clip.
25614 *
25615 * @private
25616 */
25617 setClip: function(animation) {
25618 var chart = this.chart,
25619 options = this.options,
25620 renderer = chart.renderer,
25621 inverted = chart.inverted,
25622 seriesClipBox = this.clipBox,
25623 clipBox = seriesClipBox || chart.clipBox,
25624 sharedClipKey =
25625 this.sharedClipKey || [
25626 '_sharedClip',
25627 animation && animation.duration,
25628 animation && animation.easing,
25629 clipBox.height,
25630 options.xAxis,
25631 options.yAxis
25632 ].join(','), // #4526
25633 clipRect = chart[sharedClipKey],
25634 markerClipRect = chart[sharedClipKey + 'm'];
25635
25636 // If a clipping rectangle with the same properties is currently present
25637 // in the chart, use that.
25638 if (!clipRect) {
25639
25640 // When animation is set, prepare the initial positions
25641 if (animation) {
25642 clipBox.width = 0;
25643 if (inverted) {
25644 clipBox.x = chart.plotSizeX;
25645 }
25646
25647 chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect(
25648 inverted ? chart.plotSizeX + 99 : -99, // include the width of the first marker
25649 inverted ? -chart.plotLeft : -chart.plotTop,
25650 99,
25651 inverted ? chart.chartWidth : chart.chartHeight
25652 );
25653 }
25654 chart[sharedClipKey] = clipRect = renderer.clipRect(clipBox);
25655 // Create hashmap for series indexes
25656 clipRect.count = {
25657 length: 0
25658 };
25659
25660 }
25661 if (animation) {
25662 if (!clipRect.count[this.index]) {
25663 clipRect.count[this.index] = true;
25664 clipRect.count.length += 1;
25665 }
25666 }
25667
25668 if (options.clip !== false) {
25669 this.group.clip(animation || seriesClipBox ? clipRect : chart.clipRect);
25670 this.markerGroup.clip(markerClipRect);
25671 this.sharedClipKey = sharedClipKey;
25672 }
25673
25674 // Remove the shared clipping rectangle when all series are shown
25675 if (!animation) {
25676 if (clipRect.count[this.index]) {
25677 delete clipRect.count[this.index];
25678 clipRect.count.length -= 1;
25679 }
25680
25681 if (clipRect.count.length === 0 && sharedClipKey && chart[sharedClipKey]) {
25682 if (!seriesClipBox) {
25683 chart[sharedClipKey] = chart[sharedClipKey].destroy();
25684 }
25685 if (chart[sharedClipKey + 'm']) {
25686 chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();
25687 }
25688 }
25689 }
25690 },
25691
25692 /**
25693 * Animate in the series. Called internally twice. First with the `init`
25694 * parameter set to true, which sets up the initial state of the animation.
25695 * Then when ready, it is called with the `init` parameter undefined, in
25696 * order to perform the actual animation. After the second run, the function
25697 * is removed.
25698 *
25699 * @param {Boolean} init
25700 * Initialize the animation.
25701 */
25702 animate: function(init) {
25703 var series = this,
25704 chart = series.chart,
25705 clipRect,
25706 animation = animObject(series.options.animation),
25707 sharedClipKey;
25708
25709 // Initialize the animation. Set up the clipping rectangle.
25710 if (init) {
25711
25712 series.setClip(animation);
25713
25714 // Run the animation
25715 } else {
25716 sharedClipKey = this.sharedClipKey;
25717 clipRect = chart[sharedClipKey];
25718 if (clipRect) {
25719 clipRect.animate({
25720 width: chart.plotSizeX,
25721 x: 0
25722 }, animation);
25723 }
25724 if (chart[sharedClipKey + 'm']) {
25725 chart[sharedClipKey + 'm'].animate({
25726 width: chart.plotSizeX + 99,
25727 x: 0
25728 }, animation);
25729 }
25730
25731 // Delete this function to allow it only once
25732 series.animate = null;
25733
25734 }
25735 },
25736
25737 /**
25738 * This runs after animation to land on the final plot clipping.
25739 *
25740 * @private
25741 */
25742 afterAnimate: function() {
25743 this.setClip();
25744 fireEvent(this, 'afterAnimate');
25745 this.finishedAnimating = true;
25746 },
25747
25748 /**
25749 * Draw the markers for line-like series types, and columns or other
25750 * graphical representation for {@link Point} objects for other series
25751 * types. The resulting element is typically stored as {@link
25752 * Point.graphic}, and is created on the first call and updated and moved on
25753 * subsequent calls.
25754 */
25755 drawPoints: function() {
25756 var series = this,
25757 points = series.points,
25758 chart = series.chart,
25759 i,
25760 point,
25761 symbol,
25762 graphic,
25763 options = series.options,
25764 seriesMarkerOptions = options.marker,
25765 pointMarkerOptions,
25766 hasPointMarker,
25767 enabled,
25768 isInside,
25769 markerGroup = series[series.specialGroup] || series.markerGroup,
25770 xAxis = series.xAxis,
25771 markerAttribs,
25772 globallyEnabled = pick(
25773 seriesMarkerOptions.enabled,
25774 xAxis.isRadial ? true : null,
25775 // Use larger or equal as radius is null in bubbles (#6321)
25776 series.closestPointRangePx >= 2 * seriesMarkerOptions.radius
25777 );
25778
25779 if (seriesMarkerOptions.enabled !== false || series._hasPointMarkers) {
25780
25781 for (i = 0; i < points.length; i++) {
25782 point = points[i];
25783 graphic = point.graphic;
25784 pointMarkerOptions = point.marker || {};
25785 hasPointMarker = !!point.marker;
25786 enabled = (globallyEnabled && pointMarkerOptions.enabled === undefined) || pointMarkerOptions.enabled;
25787 isInside = point.isInside;
25788
25789 // only draw the point if y is defined
25790 if (enabled && !point.isNull) {
25791
25792 // Shortcuts
25793 symbol = pick(pointMarkerOptions.symbol, series.symbol);
25794 point.hasImage = symbol.indexOf('url') === 0;
25795
25796 markerAttribs = series.markerAttribs(
25797 point,
25798 point.selected && 'select'
25799 );
25800
25801 if (graphic) { // update
25802 graphic[isInside ? 'show' : 'hide'](true) // Since the marker group isn't clipped, each individual marker must be toggled
25803 .animate(markerAttribs);
25804 } else if (isInside && (markerAttribs.width > 0 || point.hasImage)) {
25805
25806 /**
25807 * The graphic representation of the point. Typically
25808 * this is a simple shape, like a `rect` for column
25809 * charts or `path` for line markers, but for some
25810 * complex series types like boxplot or 3D charts, the
25811 * graphic may be a `g` element containing other shapes.
25812 * The graphic is generated the first time {@link
25813 * Series#drawPoints} runs, and updated and moved on
25814 * subsequent runs.
25815 *
25816 * @memberof Point
25817 * @name graphic
25818 * @type {SVGElement}
25819 */
25820 point.graphic = graphic = chart.renderer.symbol(
25821 symbol,
25822 markerAttribs.x,
25823 markerAttribs.y,
25824 markerAttribs.width,
25825 markerAttribs.height,
25826 hasPointMarker ? pointMarkerOptions : seriesMarkerOptions
25827 )
25828 .add(markerGroup);
25829 }
25830
25831
25832 // Presentational attributes
25833 if (graphic) {
25834 graphic.attr(series.pointAttribs(point, point.selected && 'select'));
25835 }
25836
25837
25838 if (graphic) {
25839 graphic.addClass(point.getClassName(), true);
25840 }
25841
25842 } else if (graphic) {
25843 point.graphic = graphic.destroy(); // #1269
25844 }
25845 }
25846 }
25847
25848 },
25849
25850 /**
25851 * Get non-presentational attributes for a point. Used internally for both
25852 * styled mode and classic. Can be overridden for different series types.
25853 *
25854 * @see Series#pointAttribs
25855 *
25856 * @param {Point} point
25857 * The Point to inspect.
25858 * @param {String} [state]
25859 * The state, can be either `hover`, `select` or undefined.
25860 *
25861 * @return {SVGAttributes}
25862 * A hash containing those attributes that are not settable from
25863 * CSS.
25864 */
25865 markerAttribs: function(point, state) {
25866 var seriesMarkerOptions = this.options.marker,
25867 seriesStateOptions,
25868 pointMarkerOptions = point.marker || {},
25869 pointStateOptions,
25870 radius = pick(
25871 pointMarkerOptions.radius,
25872 seriesMarkerOptions.radius
25873 ),
25874 attribs;
25875
25876 // Handle hover and select states
25877 if (state) {
25878 seriesStateOptions = seriesMarkerOptions.states[state];
25879 pointStateOptions = pointMarkerOptions.states &&
25880 pointMarkerOptions.states[state];
25881
25882 radius = pick(
25883 pointStateOptions && pointStateOptions.radius,
25884 seriesStateOptions && seriesStateOptions.radius,
25885 radius + (seriesStateOptions && seriesStateOptions.radiusPlus || 0)
25886 );
25887 }
25888
25889 if (point.hasImage) {
25890 radius = 0; // and subsequently width and height is not set
25891 }
25892
25893 attribs = {
25894 x: Math.floor(point.plotX) - radius, // Math.floor for #1843
25895 y: point.plotY - radius
25896 };
25897
25898 if (radius) {
25899 attribs.width = attribs.height = 2 * radius;
25900 }
25901
25902 return attribs;
25903
25904 },
25905
25906
25907 /**
25908 * Internal function to get presentational attributes for each point. Unlike
25909 * {@link Series#markerAttribs}, this function should return those
25910 * attributes that can also be set in CSS. In styled mode, `pointAttribs`
25911 * won't be called.
25912 *
25913 * @param {Point} point
25914 * The point instance to inspect.
25915 * @param {String} [state]
25916 * The point state, can be either `hover`, `select` or undefined for
25917 * normal state.
25918 *
25919 * @return {SVGAttributes}
25920 * The presentational attributes to be set on the point.
25921 */
25922 pointAttribs: function(point, state) {
25923 var seriesMarkerOptions = this.options.marker,
25924 seriesStateOptions,
25925 pointOptions = point && point.options,
25926 pointMarkerOptions = (pointOptions && pointOptions.marker) || {},
25927 pointStateOptions,
25928 color = this.color,
25929 pointColorOption = pointOptions && pointOptions.color,
25930 pointColor = point && point.color,
25931 strokeWidth = pick(
25932 pointMarkerOptions.lineWidth,
25933 seriesMarkerOptions.lineWidth
25934 ),
25935 zoneColor = point && point.zone && point.zone.color,
25936 fill,
25937 stroke;
25938
25939 color = pointColorOption || zoneColor || pointColor || color;
25940 fill = pointMarkerOptions.fillColor || seriesMarkerOptions.fillColor || color;
25941 stroke = pointMarkerOptions.lineColor || seriesMarkerOptions.lineColor || color;
25942
25943 // Handle hover and select states
25944 if (state) {
25945 seriesStateOptions = seriesMarkerOptions.states[state];
25946 pointStateOptions = (pointMarkerOptions.states && pointMarkerOptions.states[state]) || {};
25947 strokeWidth = pick(
25948 pointStateOptions.lineWidth,
25949 seriesStateOptions.lineWidth,
25950 strokeWidth + pick(
25951 pointStateOptions.lineWidthPlus,
25952 seriesStateOptions.lineWidthPlus,
25953 0
25954 )
25955 );
25956 fill = pointStateOptions.fillColor || seriesStateOptions.fillColor || fill;
25957 stroke = pointStateOptions.lineColor || seriesStateOptions.lineColor || stroke;
25958 }
25959
25960 return {
25961 'stroke': stroke,
25962 'stroke-width': strokeWidth,
25963 'fill': fill
25964 };
25965 },
25966
25967 /**
25968 * Clear DOM objects and free up memory.
25969 *
25970 * @private
25971 */
25972 destroy: function() {
25973 var series = this,
25974 chart = series.chart,
25975 issue134 = /AppleWebKit\/533/.test(win.navigator.userAgent),
25976 destroy,
25977 i,
25978 data = series.data || [],
25979 point,
25980 axis;
25981
25982 // add event hook
25983 fireEvent(series, 'destroy');
25984
25985 // remove all events
25986 removeEvent(series);
25987
25988 // erase from axes
25989 each(series.axisTypes || [], function(AXIS) {
25990 axis = series[AXIS];
25991 if (axis && axis.series) {
25992 erase(axis.series, series);
25993 axis.isDirty = axis.forceRedraw = true;
25994 }
25995 });
25996
25997 // remove legend items
25998 if (series.legendItem) {
25999 series.chart.legend.destroyItem(series);
26000 }
26001
26002 // destroy all points with their elements
26003 i = data.length;
26004 while (i--) {
26005 point = data[i];
26006 if (point && point.destroy) {
26007 point.destroy();
26008 }
26009 }
26010 series.points = null;
26011
26012 // Clear the animation timeout if we are destroying the series during initial animation
26013 clearTimeout(series.animationTimeout);
26014
26015 // Destroy all SVGElements associated to the series
26016 objectEach(series, function(val, prop) {
26017 if (val instanceof SVGElement && !val.survive) { // Survive provides a hook for not destroying
26018
26019 // issue 134 workaround
26020 destroy = issue134 && prop === 'group' ?
26021 'hide' :
26022 'destroy';
26023
26024 val[destroy]();
26025 }
26026 });
26027
26028 // remove from hoverSeries
26029 if (chart.hoverSeries === series) {
26030 chart.hoverSeries = null;
26031 }
26032 erase(chart.series, series);
26033 chart.orderSeries();
26034
26035 // clear all members
26036 objectEach(series, function(val, prop) {
26037 delete series[prop];
26038 });
26039 },
26040
26041 /**
26042 * Get the graph path.
26043 *
26044 * @private
26045 */
26046 getGraphPath: function(points, nullsAsZeroes, connectCliffs) {
26047 var series = this,
26048 options = series.options,
26049 step = options.step,
26050 reversed,
26051 graphPath = [],
26052 xMap = [],
26053 gap;
26054
26055 points = points || series.points;
26056
26057 // Bottom of a stack is reversed
26058 reversed = points.reversed;
26059 if (reversed) {
26060 points.reverse();
26061 }
26062 // Reverse the steps (#5004)
26063 step = {
26064 right: 1,
26065 center: 2
26066 }[step] || (step && 3);
26067 if (step && reversed) {
26068 step = 4 - step;
26069 }
26070
26071 // Remove invalid points, especially in spline (#5015)
26072 if (options.connectNulls && !nullsAsZeroes && !connectCliffs) {
26073 points = this.getValidPoints(points);
26074 }
26075
26076 // Build the line
26077 each(points, function(point, i) {
26078
26079 var plotX = point.plotX,
26080 plotY = point.plotY,
26081 lastPoint = points[i - 1],
26082 pathToPoint; // the path to this point from the previous
26083
26084 if ((point.leftCliff || (lastPoint && lastPoint.rightCliff)) && !connectCliffs) {
26085 gap = true; // ... and continue
26086 }
26087
26088 // Line series, nullsAsZeroes is not handled
26089 if (point.isNull && !defined(nullsAsZeroes) && i > 0) {
26090 gap = !options.connectNulls;
26091
26092 // Area series, nullsAsZeroes is set
26093 } else if (point.isNull && !nullsAsZeroes) {
26094 gap = true;
26095
26096 } else {
26097
26098 if (i === 0 || gap) {
26099 pathToPoint = ['M', point.plotX, point.plotY];
26100
26101 } else if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
26102
26103 pathToPoint = series.getPointSpline(points, point, i);
26104
26105 } else if (step) {
26106
26107 if (step === 1) { // right
26108 pathToPoint = [
26109 'L',
26110 lastPoint.plotX,
26111 plotY
26112 ];
26113
26114 } else if (step === 2) { // center
26115 pathToPoint = [
26116 'L',
26117 (lastPoint.plotX + plotX) / 2,
26118 lastPoint.plotY,
26119 'L',
26120 (lastPoint.plotX + plotX) / 2,
26121 plotY
26122 ];
26123
26124 } else {
26125 pathToPoint = [
26126 'L',
26127 plotX,
26128 lastPoint.plotY
26129 ];
26130 }
26131 pathToPoint.push('L', plotX, plotY);
26132
26133 } else {
26134 // normal line to next point
26135 pathToPoint = [
26136 'L',
26137 plotX,
26138 plotY
26139 ];
26140 }
26141
26142 // Prepare for animation. When step is enabled, there are two path nodes for each x value.
26143 xMap.push(point.x);
26144 if (step) {
26145 xMap.push(point.x);
26146 }
26147
26148 graphPath.push.apply(graphPath, pathToPoint);
26149 gap = false;
26150 }
26151 });
26152
26153 graphPath.xMap = xMap;
26154 series.graphPath = graphPath;
26155
26156 return graphPath;
26157
26158 },
26159
26160 /**
26161 * Draw the graph. Called internally when rendering line-like series types.
26162 * The first time it generates the `series.graph` item and optionally other
26163 * series-wide items like `series.area` for area charts. On subsequent calls
26164 * these items are updated with new positions and attributes.
26165 */
26166 drawGraph: function() {
26167 var series = this,
26168 options = this.options,
26169 graphPath = (this.gappedPath || this.getGraphPath).call(this),
26170 props = [
26171 [
26172 'graph',
26173 'highcharts-graph',
26174
26175 options.lineColor || this.color,
26176 options.dashStyle
26177
26178 ]
26179 ];
26180
26181 // Add the zone properties if any
26182 each(this.zones, function(zone, i) {
26183 props.push([
26184 'zone-graph-' + i,
26185 'highcharts-graph highcharts-zone-graph-' + i + ' ' + (zone.className || ''),
26186
26187 zone.color || series.color,
26188 zone.dashStyle || options.dashStyle
26189
26190 ]);
26191 });
26192
26193 // Draw the graph
26194 each(props, function(prop, i) {
26195 var graphKey = prop[0],
26196 graph = series[graphKey],
26197 attribs;
26198
26199 if (graph) {
26200 graph.endX = series.preventGraphAnimation ?
26201 null :
26202 graphPath.xMap;
26203 graph.animate({
26204 d: graphPath
26205 });
26206
26207 } else if (graphPath.length) { // #1487
26208
26209 series[graphKey] = series.chart.renderer.path(graphPath)
26210 .addClass(prop[1])
26211 .attr({
26212 zIndex: 1
26213 }) // #1069
26214 .add(series.group);
26215
26216
26217 attribs = {
26218 'stroke': prop[2],
26219 'stroke-width': options.lineWidth,
26220 'fill': (series.fillGraph && series.color) || 'none' // Polygon series use filled graph
26221 };
26222
26223 if (prop[3]) {
26224 attribs.dashstyle = prop[3];
26225 } else if (options.linecap !== 'square') {
26226 attribs['stroke-linecap'] = attribs['stroke-linejoin'] = 'round';
26227 }
26228
26229 graph = series[graphKey]
26230 .attr(attribs)
26231 .shadow((i < 2) && options.shadow); // add shadow to normal series (0) or to first zone (1) #3932
26232
26233 }
26234
26235 // Helpers for animation
26236 if (graph) {
26237 graph.startX = graphPath.xMap;
26238 graph.isArea = graphPath.isArea; // For arearange animation
26239 }
26240 });
26241 },
26242
26243 /**
26244 * Clip the graphs into zones for colors and styling.
26245 *
26246 * @private
26247 */
26248 applyZones: function() {
26249 var series = this,
26250 chart = this.chart,
26251 renderer = chart.renderer,
26252 zones = this.zones,
26253 translatedFrom,
26254 translatedTo,
26255 clips = this.clips || [],
26256 clipAttr,
26257 graph = this.graph,
26258 area = this.area,
26259 chartSizeMax = Math.max(chart.chartWidth, chart.chartHeight),
26260 axis = this[(this.zoneAxis || 'y') + 'Axis'],
26261 extremes,
26262 reversed,
26263 inverted = chart.inverted,
26264 horiz,
26265 pxRange,
26266 pxPosMin,
26267 pxPosMax,
26268 ignoreZones = false;
26269
26270 if (zones.length && (graph || area) && axis && axis.min !== undefined) {
26271 reversed = axis.reversed;
26272 horiz = axis.horiz;
26273 // The use of the Color Threshold assumes there are no gaps
26274 // so it is safe to hide the original graph and area
26275 if (graph) {
26276 graph.hide();
26277 }
26278 if (area) {
26279 area.hide();
26280 }
26281
26282 // Create the clips
26283 extremes = axis.getExtremes();
26284 each(zones, function(threshold, i) {
26285
26286 translatedFrom = reversed ?
26287 (horiz ? chart.plotWidth : 0) :
26288 (horiz ? 0 : axis.toPixels(extremes.min));
26289 translatedFrom = Math.min(Math.max(pick(translatedTo, translatedFrom), 0), chartSizeMax);
26290 translatedTo = Math.min(Math.max(Math.round(axis.toPixels(pick(threshold.value, extremes.max), true)), 0), chartSizeMax);
26291
26292 if (ignoreZones) {
26293 translatedFrom = translatedTo = axis.toPixels(extremes.max);
26294 }
26295
26296 pxRange = Math.abs(translatedFrom - translatedTo);
26297 pxPosMin = Math.min(translatedFrom, translatedTo);
26298 pxPosMax = Math.max(translatedFrom, translatedTo);
26299 if (axis.isXAxis) {
26300 clipAttr = {
26301 x: inverted ? pxPosMax : pxPosMin,
26302 y: 0,
26303 width: pxRange,
26304 height: chartSizeMax
26305 };
26306 if (!horiz) {
26307 clipAttr.x = chart.plotHeight - clipAttr.x;
26308 }
26309 } else {
26310 clipAttr = {
26311 x: 0,
26312 y: inverted ? pxPosMax : pxPosMin,
26313 width: chartSizeMax,
26314 height: pxRange
26315 };
26316 if (horiz) {
26317 clipAttr.y = chart.plotWidth - clipAttr.y;
26318 }
26319 }
26320
26321
26322 // VML SUPPPORT
26323 if (inverted && renderer.isVML) {
26324 if (axis.isXAxis) {
26325 clipAttr = {
26326 x: 0,
26327 y: reversed ? pxPosMin : pxPosMax,
26328 height: clipAttr.width,
26329 width: chart.chartWidth
26330 };
26331 } else {
26332 clipAttr = {
26333 x: clipAttr.y - chart.plotLeft - chart.spacingBox.x,
26334 y: 0,
26335 width: clipAttr.height,
26336 height: chart.chartHeight
26337 };
26338 }
26339 }
26340 // END OF VML SUPPORT
26341
26342
26343 if (clips[i]) {
26344 clips[i].animate(clipAttr);
26345 } else {
26346 clips[i] = renderer.clipRect(clipAttr);
26347
26348 if (graph) {
26349 series['zone-graph-' + i].clip(clips[i]);
26350 }
26351
26352 if (area) {
26353 series['zone-area-' + i].clip(clips[i]);
26354 }
26355 }
26356 // if this zone extends out of the axis, ignore the others
26357 ignoreZones = threshold.value > extremes.max;
26358 });
26359 this.clips = clips;
26360 }
26361 },
26362
26363 /**
26364 * Initialize and perform group inversion on series.group and
26365 * series.markerGroup.
26366 *
26367 * @private
26368 */
26369 invertGroups: function(inverted) {
26370 var series = this,
26371 chart = series.chart,
26372 remover;
26373
26374 function setInvert() {
26375 each(['group', 'markerGroup'], function(groupName) {
26376 if (series[groupName]) {
26377
26378 // VML/HTML needs explicit attributes for flipping
26379 if (chart.renderer.isVML) {
26380 series[groupName].attr({
26381 width: series.yAxis.len,
26382 height: series.xAxis.len
26383 });
26384 }
26385
26386 series[groupName].width = series.yAxis.len;
26387 series[groupName].height = series.xAxis.len;
26388 series[groupName].invert(inverted);
26389 }
26390 });
26391 }
26392
26393 // Pie, go away (#1736)
26394 if (!series.xAxis) {
26395 return;
26396 }
26397
26398 // A fixed size is needed for inversion to work
26399 remover = addEvent(chart, 'resize', setInvert);
26400 addEvent(series, 'destroy', remover);
26401
26402 // Do it now
26403 setInvert(inverted); // do it now
26404
26405 // On subsequent render and redraw, just do setInvert without setting up events again
26406 series.invertGroups = setInvert;
26407 },
26408
26409 /**
26410 * General abstraction for creating plot groups like series.group,
26411 * series.dataLabelsGroup and series.markerGroup. On subsequent calls, the
26412 * group will only be adjusted to the updated plot size.
26413 *
26414 * @private
26415 */
26416 plotGroup: function(prop, name, visibility, zIndex, parent) {
26417 var group = this[prop],
26418 isNew = !group;
26419
26420 // Generate it on first call
26421 if (isNew) {
26422 this[prop] = group = this.chart.renderer.g()
26423 .attr({
26424 zIndex: zIndex || 0.1 // IE8 and pointer logic use this
26425 })
26426 .add(parent);
26427
26428 }
26429
26430 // Add the class names, and replace existing ones as response to
26431 // Series.update (#6660)
26432 group.addClass(
26433 (
26434 'highcharts-' + name +
26435 ' highcharts-series-' + this.index +
26436 ' highcharts-' + this.type + '-series ' +
26437 (
26438 defined(this.colorIndex) ?
26439 'highcharts-color-' + this.colorIndex + ' ' :
26440 ''
26441 ) +
26442 (this.options.className || '') +
26443 (group.hasClass('highcharts-tracker') ? ' highcharts-tracker' : '')
26444 ),
26445 true
26446 );
26447
26448 // Place it on first and subsequent (redraw) calls
26449 group.attr({
26450 visibility: visibility
26451 })[isNew ? 'attr' : 'animate'](
26452 this.getPlotBox()
26453 );
26454 return group;
26455 },
26456
26457 /**
26458 * Get the translation and scale for the plot area of this series.
26459 */
26460 getPlotBox: function() {
26461 var chart = this.chart,
26462 xAxis = this.xAxis,
26463 yAxis = this.yAxis;
26464
26465 // Swap axes for inverted (#2339)
26466 if (chart.inverted) {
26467 xAxis = yAxis;
26468 yAxis = this.xAxis;
26469 }
26470 return {
26471 translateX: xAxis ? xAxis.left : chart.plotLeft,
26472 translateY: yAxis ? yAxis.top : chart.plotTop,
26473 scaleX: 1, // #1623
26474 scaleY: 1
26475 };
26476 },
26477
26478 /**
26479 * Render the graph and markers. Called internally when first rendering and
26480 * later when redrawing the chart. This function can be extended in plugins,
26481 * but normally shouldn't be called directly.
26482 */
26483 render: function() {
26484 var series = this,
26485 chart = series.chart,
26486 group,
26487 options = series.options,
26488 // Animation doesn't work in IE8 quirks when the group div is
26489 // hidden, and looks bad in other oldIE
26490 animDuration = (!!series.animate &&
26491 chart.renderer.isSVG &&
26492 animObject(options.animation).duration
26493 ),
26494 visibility = series.visible ? 'inherit' : 'hidden', // #2597
26495 zIndex = options.zIndex,
26496 hasRendered = series.hasRendered,
26497 chartSeriesGroup = chart.seriesGroup,
26498 inverted = chart.inverted;
26499
26500 // the group
26501 group = series.plotGroup(
26502 'group',
26503 'series',
26504 visibility,
26505 zIndex,
26506 chartSeriesGroup
26507 );
26508
26509 series.markerGroup = series.plotGroup(
26510 'markerGroup',
26511 'markers',
26512 visibility,
26513 zIndex,
26514 chartSeriesGroup
26515 );
26516
26517 // initiate the animation
26518 if (animDuration) {
26519 series.animate(true);
26520 }
26521
26522 // SVGRenderer needs to know this before drawing elements (#1089, #1795)
26523 group.inverted = series.isCartesian ? inverted : false;
26524
26525 // draw the graph if any
26526 if (series.drawGraph) {
26527 series.drawGraph();
26528 series.applyZones();
26529 }
26530
26531 /* each(series.points, function (point) {
26532 if (point.redraw) {
26533 point.redraw();
26534 }
26535 });*/
26536
26537 // draw the data labels (inn pies they go before the points)
26538 if (series.drawDataLabels) {
26539 series.drawDataLabels();
26540 }
26541
26542 // draw the points
26543 if (series.visible) {
26544 series.drawPoints();
26545 }
26546
26547
26548 // draw the mouse tracking area
26549 if (
26550 series.drawTracker &&
26551 series.options.enableMouseTracking !== false
26552 ) {
26553 series.drawTracker();
26554 }
26555
26556 // Handle inverted series and tracker groups
26557 series.invertGroups(inverted);
26558
26559 // Initial clipping, must be defined after inverting groups for VML.
26560 // Applies to columns etc. (#3839).
26561 if (options.clip !== false && !series.sharedClipKey && !hasRendered) {
26562 group.clip(chart.clipRect);
26563 }
26564
26565 // Run the animation
26566 if (animDuration) {
26567 series.animate();
26568 }
26569
26570 // Call the afterAnimate function on animation complete (but don't
26571 // overwrite the animation.complete option which should be available to
26572 // the user).
26573 if (!hasRendered) {
26574 series.animationTimeout = syncTimeout(function() {
26575 series.afterAnimate();
26576 }, animDuration);
26577 }
26578
26579 series.isDirty = false; // means data is in accordance with what you see
26580 // (See #322) series.isDirty = series.isDirtyData = false; // means
26581 // data is in accordance with what you see
26582 series.hasRendered = true;
26583 },
26584
26585 /**
26586 * Redraw the series. This function is called internally from `chart.redraw`
26587 * and normally shouldn't be called directly.
26588 *
26589 * @private
26590 */
26591 redraw: function() {
26592 var series = this,
26593 chart = series.chart,
26594 // cache it here as it is set to false in render, but used after
26595 wasDirty = series.isDirty || series.isDirtyData,
26596 group = series.group,
26597 xAxis = series.xAxis,
26598 yAxis = series.yAxis;
26599
26600 // reposition on resize
26601 if (group) {
26602 if (chart.inverted) {
26603 group.attr({
26604 width: chart.plotWidth,
26605 height: chart.plotHeight
26606 });
26607 }
26608
26609 group.animate({
26610 translateX: pick(xAxis && xAxis.left, chart.plotLeft),
26611 translateY: pick(yAxis && yAxis.top, chart.plotTop)
26612 });
26613 }
26614
26615 series.translate();
26616 series.render();
26617 if (wasDirty) { // #3868, #3945
26618 delete this.kdTree;
26619 }
26620 },
26621
26622 kdAxisArray: ['clientX', 'plotY'],
26623
26624 searchPoint: function(e, compareX) {
26625 var series = this,
26626 xAxis = series.xAxis,
26627 yAxis = series.yAxis,
26628 inverted = series.chart.inverted;
26629
26630 return this.searchKDTree({
26631 clientX: inverted ?
26632 xAxis.len - e.chartY + xAxis.pos : e.chartX - xAxis.pos,
26633 plotY: inverted ?
26634 yAxis.len - e.chartX + yAxis.pos : e.chartY - yAxis.pos
26635 }, compareX);
26636 },
26637
26638 /**
26639 * Build the k-d-tree that is used by mouse and touch interaction to get the
26640 * closest point. Line-like series typically have a one-dimensional tree
26641 * where points are searched along the X axis, while scatter-like series
26642 * typically search in two dimensions, X and Y.
26643 *
26644 * @private
26645 */
26646 buildKDTree: function() {
26647
26648 // Prevent multiple k-d-trees from being built simultaneously (#6235)
26649 this.buildingKdTree = true;
26650
26651 var series = this,
26652 dimensions = series.options.findNearestPointBy.indexOf('y') > -1 ?
26653 2 : 1;
26654
26655 // Internal function
26656 function _kdtree(points, depth, dimensions) {
26657 var axis,
26658 median,
26659 length = points && points.length;
26660
26661 if (length) {
26662
26663 // alternate between the axis
26664 axis = series.kdAxisArray[depth % dimensions];
26665
26666 // sort point array
26667 points.sort(function(a, b) {
26668 return a[axis] - b[axis];
26669 });
26670
26671 median = Math.floor(length / 2);
26672
26673 // build and return nod
26674 return {
26675 point: points[median],
26676 left: _kdtree(
26677 points.slice(0, median), depth + 1, dimensions
26678 ),
26679 right: _kdtree(
26680 points.slice(median + 1), depth + 1, dimensions
26681 )
26682 };
26683
26684 }
26685 }
26686
26687 // Start the recursive build process with a clone of the points array
26688 // and null points filtered out (#3873)
26689 function startRecursive() {
26690 series.kdTree = _kdtree(
26691 series.getValidPoints(
26692 null,
26693 // For line-type series restrict to plot area, but
26694 // column-type series not (#3916, #4511)
26695 !series.directTouch
26696 ),
26697 dimensions,
26698 dimensions
26699 );
26700 series.buildingKdTree = false;
26701 }
26702 delete series.kdTree;
26703
26704 // For testing tooltips, don't build async
26705 syncTimeout(startRecursive, series.options.kdNow ? 0 : 1);
26706 },
26707
26708 searchKDTree: function(point, compareX) {
26709 var series = this,
26710 kdX = this.kdAxisArray[0],
26711 kdY = this.kdAxisArray[1],
26712 kdComparer = compareX ? 'distX' : 'dist',
26713 kdDimensions = series.options.findNearestPointBy.indexOf('y') > -1 ?
26714 2 : 1;
26715
26716 // Set the one and two dimensional distance on the point object
26717 function setDistance(p1, p2) {
26718 var x = (defined(p1[kdX]) && defined(p2[kdX])) ?
26719 Math.pow(p1[kdX] - p2[kdX], 2) :
26720 null,
26721 y = (defined(p1[kdY]) && defined(p2[kdY])) ?
26722 Math.pow(p1[kdY] - p2[kdY], 2) :
26723 null,
26724 r = (x || 0) + (y || 0);
26725
26726 p2.dist = defined(r) ? Math.sqrt(r) : Number.MAX_VALUE;
26727 p2.distX = defined(x) ? Math.sqrt(x) : Number.MAX_VALUE;
26728 }
26729
26730 function _search(search, tree, depth, dimensions) {
26731 var point = tree.point,
26732 axis = series.kdAxisArray[depth % dimensions],
26733 tdist,
26734 sideA,
26735 sideB,
26736 ret = point,
26737 nPoint1,
26738 nPoint2;
26739
26740 setDistance(search, point);
26741
26742 // Pick side based on distance to splitting point
26743 tdist = search[axis] - point[axis];
26744 sideA = tdist < 0 ? 'left' : 'right';
26745 sideB = tdist < 0 ? 'right' : 'left';
26746
26747 // End of tree
26748 if (tree[sideA]) {
26749 nPoint1 = _search(search, tree[sideA], depth + 1, dimensions);
26750
26751 ret = (nPoint1[kdComparer] < ret[kdComparer] ? nPoint1 : point);
26752 }
26753 if (tree[sideB]) {
26754 // compare distance to current best to splitting point to decide
26755 // wether to check side B or not
26756 if (Math.sqrt(tdist * tdist) < ret[kdComparer]) {
26757 nPoint2 = _search(
26758 search,
26759 tree[sideB],
26760 depth + 1,
26761 dimensions
26762 );
26763 ret = nPoint2[kdComparer] < ret[kdComparer] ?
26764 nPoint2 :
26765 ret;
26766 }
26767 }
26768
26769 return ret;
26770 }
26771
26772 if (!this.kdTree && !this.buildingKdTree) {
26773 this.buildKDTree();
26774 }
26775
26776 if (this.kdTree) {
26777 return _search(point, this.kdTree, kdDimensions, kdDimensions);
26778 }
26779 }
26780
26781 }); // end Series prototype
26782
26783 /**
26784 * A line series displays information as a series of data points connected by
26785 * straight line segments.
26786 *
26787 * @sample {highcharts} highcharts/demo/line-basic/ Line chart
26788 * @sample {highstock} stock/demo/basic-line/ Line chart
26789 *
26790 * @extends plotOptions.series
26791 * @product highcharts highstock
26792 * @apioption plotOptions.line
26793 */
26794
26795 /**
26796 * A `line` series. If the [type](#series.line.type) option is not
26797 * specified, it is inherited from [chart.type](#chart.type).
26798 *
26799 * For options that apply to multiple series, it is recommended to add
26800 * them to the [plotOptions.series](#plotOptions.series) options structure.
26801 * To apply to all series of this specific type, apply it to [plotOptions.
26802 * line](#plotOptions.line).
26803 *
26804 * @type {Object}
26805 * @extends series,plotOptions.line
26806 * @excluding dataParser,dataURL
26807 * @product highcharts highstock
26808 * @apioption series.line
26809 */
26810
26811 /**
26812 * An array of data points for the series. For the `line` series type,
26813 * points can be given in the following ways:
26814 *
26815 * 1. An array of numerical values. In this case, the numerical values
26816 * will be interpreted as `y` options. The `x` values will be automatically
26817 * calculated, either starting at 0 and incremented by 1, or from `pointStart`
26818 * and `pointInterval` given in the series options. If the axis has
26819 * categories, these will be used. Example:
26820 *
26821 * ```js
26822 * data: [0, 5, 3, 5]
26823 * ```
26824 *
26825 * 2. An array of arrays with 2 values. In this case, the values correspond
26826 * to `x,y`. If the first value is a string, it is applied as the name
26827 * of the point, and the `x` value is inferred.
26828 *
26829 * ```js
26830 * data: [
26831 * [0, 1],
26832 * [1, 2],
26833 * [2, 8]
26834 * ]
26835 * ```
26836 *
26837 * 3. An array of objects with named values. The objects are point
26838 * configuration objects as seen below. If the total number of data
26839 * points exceeds the series' [turboThreshold](#series.line.turboThreshold),
26840 * this option is not available.
26841 *
26842 * ```js
26843 * data: [{
26844 * x: 1,
26845 * y: 9,
26846 * name: "Point2",
26847 * color: "#00FF00"
26848 * }, {
26849 * x: 1,
26850 * y: 6,
26851 * name: "Point1",
26852 * color: "#FF00FF"
26853 * }]
26854 * ```
26855 *
26856 * @type {Array<Object|Array|Number>}
26857 * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
26858 * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
26859 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
26860 * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
26861 * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
26862 * @apioption series.line.data
26863 */
26864
26865 /**
26866 * An additional, individual class name for the data point's graphic
26867 * representation.
26868 *
26869 * @type {String}
26870 * @since 5.0.0
26871 * @product highcharts
26872 * @apioption series.line.data.className
26873 */
26874
26875 /**
26876 * Individual color for the point. By default the color is pulled from
26877 * the global `colors` array.
26878 *
26879 * In styled mode, the `color` option doesn't take effect. Instead, use
26880 * `colorIndex`.
26881 *
26882 * @type {Color}
26883 * @sample {highcharts} highcharts/point/color/ Mark the highest point
26884 * @default undefined
26885 * @product highcharts highstock
26886 * @apioption series.line.data.color
26887 */
26888
26889 /**
26890 * Styled mode only. A specific color index to use for the point, so its
26891 * graphic representations are given the class name
26892 * `highcharts-color-{n}`.
26893 *
26894 * @type {Number}
26895 * @since 5.0.0
26896 * @product highcharts
26897 * @apioption series.line.data.colorIndex
26898 */
26899
26900 /**
26901 * Individual data label for each point. The options are the same as
26902 * the ones for [plotOptions.series.dataLabels](#plotOptions.series.
26903 * dataLabels)
26904 *
26905 * @type {Object}
26906 * @sample {highcharts} highcharts/point/datalabels/ Show a label for the last value
26907 * @sample {highstock} highcharts/point/datalabels/ Show a label for the last value
26908 * @product highcharts highstock
26909 * @apioption series.line.data.dataLabels
26910 */
26911
26912 /**
26913 * A description of the point to add to the screen reader information
26914 * about the point. Requires the Accessibility module.
26915 *
26916 * @type {String}
26917 * @default undefined
26918 * @since 5.0.0
26919 * @apioption series.line.data.description
26920 */
26921
26922 /**
26923 * An id for the point. This can be used after render time to get a
26924 * pointer to the point object through `chart.get()`.
26925 *
26926 * @type {String}
26927 * @sample {highcharts} highcharts/point/id/ Remove an id'd point
26928 * @default null
26929 * @since 1.2.0
26930 * @product highcharts highstock
26931 * @apioption series.line.data.id
26932 */
26933
26934 /**
26935 * The rank for this point's data label in case of collision. If two
26936 * data labels are about to overlap, only the one with the highest `labelrank`
26937 * will be drawn.
26938 *
26939 * @type {Number}
26940 * @apioption series.line.data.labelrank
26941 */
26942
26943 /**
26944 * The name of the point as shown in the legend, tooltip, dataLabel
26945 * etc.
26946 *
26947 * @type {String}
26948 * @sample {highcharts} highcharts/series/data-array-of-objects/ Point names
26949 * @see [xAxis.uniqueNames](#xAxis.uniqueNames)
26950 * @apioption series.line.data.name
26951 */
26952
26953 /**
26954 * Whether the data point is selected initially.
26955 *
26956 * @type {Boolean}
26957 * @default false
26958 * @product highcharts highstock
26959 * @apioption series.line.data.selected
26960 */
26961
26962 /**
26963 * The x value of the point. For datetime axes, the X value is the timestamp
26964 * in milliseconds since 1970.
26965 *
26966 * @type {Number}
26967 * @product highcharts highstock
26968 * @apioption series.line.data.x
26969 */
26970
26971 /**
26972 * The y value of the point.
26973 *
26974 * @type {Number}
26975 * @default null
26976 * @product highcharts highstock
26977 * @apioption series.line.data.y
26978 */
26979
26980 /**
26981 * Individual point events
26982 *
26983 * @extends plotOptions.series.point.events
26984 * @product highcharts highstock
26985 * @apioption series.line.data.events
26986 */
26987
26988 /**
26989 * @extends plotOptions.series.marker
26990 * @product highcharts highstock
26991 * @apioption series.line.data.marker
26992 */
26993
26994 }(Highcharts));
26995 (function(H) {
26996 /**
26997 * (c) 2010-2017 Torstein Honsi
26998 *
26999 * License: www.highcharts.com/license
27000 */
27001 var Axis = H.Axis,
27002 Chart = H.Chart,
27003 correctFloat = H.correctFloat,
27004 defined = H.defined,
27005 destroyObjectProperties = H.destroyObjectProperties,
27006 each = H.each,
27007 format = H.format,
27008 objectEach = H.objectEach,
27009 pick = H.pick,
27010 Series = H.Series;
27011
27012 /**
27013 * The class for stacks. Each stack, on a specific X value and either negative
27014 * or positive, has its own stack item.
27015 *
27016 * @class
27017 */
27018 H.StackItem = function(axis, options, isNegative, x, stackOption) {
27019
27020 var inverted = axis.chart.inverted;
27021
27022 this.axis = axis;
27023
27024 // Tells if the stack is negative
27025 this.isNegative = isNegative;
27026
27027 // Save the options to be able to style the label
27028 this.options = options;
27029
27030 // Save the x value to be able to position the label later
27031 this.x = x;
27032
27033 // Initialize total value
27034 this.total = null;
27035
27036 // This will keep each points' extremes stored by series.index and point
27037 // index
27038 this.points = {};
27039
27040 // Save the stack option on the series configuration object, and whether to
27041 // treat it as percent
27042 this.stack = stackOption;
27043 this.leftCliff = 0;
27044 this.rightCliff = 0;
27045
27046 // The align options and text align varies on whether the stack is negative
27047 // and if the chart is inverted or not.
27048 // First test the user supplied value, then use the dynamic.
27049 this.alignOptions = {
27050 align: options.align ||
27051 (inverted ? (isNegative ? 'left' : 'right') : 'center'),
27052 verticalAlign: options.verticalAlign ||
27053 (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
27054 y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),
27055 x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)
27056 };
27057
27058 this.textAlign = options.textAlign ||
27059 (inverted ? (isNegative ? 'right' : 'left') : 'center');
27060 };
27061
27062 H.StackItem.prototype = {
27063 destroy: function() {
27064 destroyObjectProperties(this, this.axis);
27065 },
27066
27067 /**
27068 * Renders the stack total label and adds it to the stack label group.
27069 */
27070 render: function(group) {
27071 var options = this.options,
27072 formatOption = options.format,
27073 str = formatOption ?
27074 format(formatOption, this) :
27075 options.formatter.call(this); // format the text in the label
27076
27077 // Change the text to reflect the new total and set visibility to hidden
27078 // in case the serie is hidden
27079 if (this.label) {
27080 this.label.attr({
27081 text: str,
27082 visibility: 'hidden'
27083 });
27084 // Create new label
27085 } else {
27086 this.label =
27087 this.axis.chart.renderer.text(str, null, null, options.useHTML)
27088 .css(options.style)
27089 .attr({
27090 align: this.textAlign,
27091 rotation: options.rotation,
27092 visibility: 'hidden' // hidden until setOffset is called
27093 })
27094 .add(group); // add to the labels-group
27095 }
27096 },
27097
27098 /**
27099 * Sets the offset that the stack has from the x value and repositions the
27100 * label.
27101 */
27102 setOffset: function(xOffset, xWidth) {
27103 var stackItem = this,
27104 axis = stackItem.axis,
27105 chart = axis.chart,
27106 // stack value translated mapped to chart coordinates
27107 y = axis.translate(
27108 axis.usePercentage ? 100 : stackItem.total,
27109 0,
27110 0,
27111 0,
27112 1
27113 ),
27114 yZero = axis.translate(0), // stack origin
27115 h = Math.abs(y - yZero), // stack height
27116 x = chart.xAxis[0].translate(stackItem.x) + xOffset, // x position
27117 stackBox = stackItem.getStackBox(chart, stackItem, x, y, xWidth, h),
27118 label = stackItem.label,
27119 alignAttr;
27120
27121 if (label) {
27122 // Align the label to the box
27123 label.align(stackItem.alignOptions, null, stackBox);
27124
27125 // Set visibility (#678)
27126 alignAttr = label.alignAttr;
27127 label[
27128 stackItem.options.crop === false || chart.isInsidePlot(
27129 alignAttr.x,
27130 alignAttr.y
27131 ) ? 'show' : 'hide'](true);
27132 }
27133 },
27134 getStackBox: function(chart, stackItem, x, y, xWidth, h) {
27135 var reversed = stackItem.axis.reversed,
27136 inverted = chart.inverted,
27137 plotHeight = chart.plotHeight,
27138 neg = (stackItem.isNegative && !reversed) ||
27139 (!stackItem.isNegative && reversed); // #4056
27140
27141 return { // this is the box for the complete stack
27142 x: inverted ? (neg ? y : y - h) : x,
27143 y: inverted ?
27144 plotHeight - x - xWidth :
27145 (neg ?
27146 (plotHeight - y - h) :
27147 plotHeight - y
27148 ),
27149 width: inverted ? h : xWidth,
27150 height: inverted ? xWidth : h
27151 };
27152 }
27153 };
27154
27155 /**
27156 * Generate stacks for each series and calculate stacks total values
27157 */
27158 Chart.prototype.getStacks = function() {
27159 var chart = this;
27160
27161 // reset stacks for each yAxis
27162 each(chart.yAxis, function(axis) {
27163 if (axis.stacks && axis.hasVisibleSeries) {
27164 axis.oldStacks = axis.stacks;
27165 }
27166 });
27167
27168 each(chart.series, function(series) {
27169 if (series.options.stacking && (series.visible === true ||
27170 chart.options.chart.ignoreHiddenSeries === false)) {
27171 series.stackKey = series.type + pick(series.options.stack, '');
27172 }
27173 });
27174 };
27175
27176
27177 // Stacking methods defined on the Axis prototype
27178
27179 /**
27180 * Build the stacks from top down
27181 */
27182 Axis.prototype.buildStacks = function() {
27183 var axisSeries = this.series,
27184 reversedStacks = pick(this.options.reversedStacks, true),
27185 len = axisSeries.length,
27186 i;
27187 if (!this.isXAxis) {
27188 this.usePercentage = false;
27189 i = len;
27190 while (i--) {
27191 axisSeries[reversedStacks ? i : len - i - 1].setStackedPoints();
27192 }
27193
27194 // Loop up again to compute percent and stream stack
27195 for (i = 0; i < len; i++) {
27196 axisSeries[i].modifyStacks();
27197 }
27198 }
27199 };
27200
27201 Axis.prototype.renderStackTotals = function() {
27202 var axis = this,
27203 chart = axis.chart,
27204 renderer = chart.renderer,
27205 stacks = axis.stacks,
27206 stackTotalGroup = axis.stackTotalGroup;
27207
27208 // Create a separate group for the stack total labels
27209 if (!stackTotalGroup) {
27210 axis.stackTotalGroup = stackTotalGroup =
27211 renderer.g('stack-labels')
27212 .attr({
27213 visibility: 'visible',
27214 zIndex: 6
27215 })
27216 .add();
27217 }
27218
27219 // plotLeft/Top will change when y axis gets wider so we need to translate
27220 // the stackTotalGroup at every render call. See bug #506 and #516
27221 stackTotalGroup.translate(chart.plotLeft, chart.plotTop);
27222
27223 // Render each stack total
27224 objectEach(stacks, function(type) {
27225 objectEach(type, function(stack) {
27226 stack.render(stackTotalGroup);
27227 });
27228 });
27229 };
27230
27231 /**
27232 * Set all the stacks to initial states and destroy unused ones.
27233 */
27234 Axis.prototype.resetStacks = function() {
27235 var axis = this,
27236 stacks = axis.stacks;
27237 if (!axis.isXAxis) {
27238 objectEach(stacks, function(type) {
27239 objectEach(type, function(stack, key) {
27240 // Clean up memory after point deletion (#1044, #4320)
27241 if (stack.touched < axis.stacksTouched) {
27242 stack.destroy();
27243 delete type[key];
27244
27245 // Reset stacks
27246 } else {
27247 stack.total = null;
27248 stack.cum = null;
27249 }
27250 });
27251 });
27252 }
27253 };
27254
27255 Axis.prototype.cleanStacks = function() {
27256 var stacks;
27257
27258 if (!this.isXAxis) {
27259 if (this.oldStacks) {
27260 stacks = this.stacks = this.oldStacks;
27261 }
27262
27263 // reset stacks
27264 objectEach(stacks, function(type) {
27265 objectEach(type, function(stack) {
27266 stack.cum = stack.total;
27267 });
27268 });
27269 }
27270 };
27271
27272
27273 // Stacking methods defnied for Series prototype
27274
27275 /**
27276 * Adds series' points value to corresponding stack
27277 */
27278 Series.prototype.setStackedPoints = function() {
27279 if (!this.options.stacking || (this.visible !== true &&
27280 this.chart.options.chart.ignoreHiddenSeries !== false)) {
27281 return;
27282 }
27283
27284 var series = this,
27285 xData = series.processedXData,
27286 yData = series.processedYData,
27287 stackedYData = [],
27288 yDataLength = yData.length,
27289 seriesOptions = series.options,
27290 threshold = seriesOptions.threshold,
27291 stackThreshold = seriesOptions.startFromThreshold ? threshold : 0,
27292 stackOption = seriesOptions.stack,
27293 stacking = seriesOptions.stacking,
27294 stackKey = series.stackKey,
27295 negKey = '-' + stackKey,
27296 negStacks = series.negStacks,
27297 yAxis = series.yAxis,
27298 stacks = yAxis.stacks,
27299 oldStacks = yAxis.oldStacks,
27300 stackIndicator,
27301 isNegative,
27302 stack,
27303 other,
27304 key,
27305 pointKey,
27306 i,
27307 x,
27308 y;
27309
27310
27311 yAxis.stacksTouched += 1;
27312
27313 // loop over the non-null y values and read them into a local array
27314 for (i = 0; i < yDataLength; i++) {
27315 x = xData[i];
27316 y = yData[i];
27317 stackIndicator = series.getStackIndicator(
27318 stackIndicator,
27319 x,
27320 series.index
27321 );
27322 pointKey = stackIndicator.key;
27323 // Read stacked values into a stack based on the x value,
27324 // the sign of y and the stack key. Stacking is also handled for null
27325 // values (#739)
27326 isNegative = negStacks && y < (stackThreshold ? 0 : threshold);
27327 key = isNegative ? negKey : stackKey;
27328
27329 // Create empty object for this stack if it doesn't exist yet
27330 if (!stacks[key]) {
27331 stacks[key] = {};
27332 }
27333
27334 // Initialize StackItem for this x
27335 if (!stacks[key][x]) {
27336 if (oldStacks[key] && oldStacks[key][x]) {
27337 stacks[key][x] = oldStacks[key][x];
27338 stacks[key][x].total = null;
27339 } else {
27340 stacks[key][x] = new H.StackItem(
27341 yAxis,
27342 yAxis.options.stackLabels,
27343 isNegative,
27344 x,
27345 stackOption
27346 );
27347 }
27348 }
27349
27350 // If the StackItem doesn't exist, create it first
27351 stack = stacks[key][x];
27352 if (y !== null) {
27353 stack.points[pointKey] = stack.points[series.index] = [pick(stack.cum, stackThreshold)];
27354
27355 // Record the base of the stack
27356 if (!defined(stack.cum)) {
27357 stack.base = pointKey;
27358 }
27359 stack.touched = yAxis.stacksTouched;
27360
27361
27362 // In area charts, if there are multiple points on the same X value,
27363 // let the area fill the full span of those points
27364 if (stackIndicator.index > 0 && series.singleStacks === false) {
27365 stack.points[pointKey][0] =
27366 stack.points[series.index + ',' + x + ',0'][0];
27367 }
27368 }
27369
27370 // Add value to the stack total
27371 if (stacking === 'percent') {
27372
27373 // Percent stacked column, totals are the same for the positive and
27374 // negative stacks
27375 other = isNegative ? stackKey : negKey;
27376 if (negStacks && stacks[other] && stacks[other][x]) {
27377 other = stacks[other][x];
27378 stack.total = other.total =
27379 Math.max(other.total, stack.total) + Math.abs(y) || 0;
27380
27381 // Percent stacked areas
27382 } else {
27383 stack.total = correctFloat(stack.total + (Math.abs(y) || 0));
27384 }
27385 } else {
27386 stack.total = correctFloat(stack.total + (y || 0));
27387 }
27388
27389 stack.cum = pick(stack.cum, stackThreshold) + (y || 0);
27390
27391 if (y !== null) {
27392 stack.points[pointKey].push(stack.cum);
27393 stackedYData[i] = stack.cum;
27394 }
27395
27396 }
27397
27398 if (stacking === 'percent') {
27399 yAxis.usePercentage = true;
27400 }
27401
27402 this.stackedYData = stackedYData; // To be used in getExtremes
27403
27404 // Reset old stacks
27405 yAxis.oldStacks = {};
27406 };
27407
27408 /**
27409 * Iterate over all stacks and compute the absolute values to percent
27410 */
27411 Series.prototype.modifyStacks = function() {
27412 var series = this,
27413 stackKey = series.stackKey,
27414 stacks = series.yAxis.stacks,
27415 processedXData = series.processedXData,
27416 stackIndicator,
27417 stacking = series.options.stacking;
27418
27419 if (series[stacking + 'Stacker']) { // Modifier function exists
27420 each([stackKey, '-' + stackKey], function(key) {
27421 var i = processedXData.length,
27422 x,
27423 stack,
27424 pointExtremes;
27425
27426 while (i--) {
27427 x = processedXData[i];
27428 stackIndicator = series.getStackIndicator(
27429 stackIndicator,
27430 x,
27431 series.index,
27432 key
27433 );
27434 stack = stacks[key] && stacks[key][x];
27435 pointExtremes = stack && stack.points[stackIndicator.key];
27436 if (pointExtremes) {
27437 series[stacking + 'Stacker'](pointExtremes, stack, i);
27438 }
27439 }
27440 });
27441 }
27442 };
27443
27444 /**
27445 * Modifier function for percent stacks. Blows up the stack to 100%.
27446 */
27447 Series.prototype.percentStacker = function(pointExtremes, stack, i) {
27448 var totalFactor = stack.total ? 100 / stack.total : 0;
27449 // Y bottom value
27450 pointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor);
27451 // Y value
27452 pointExtremes[1] = correctFloat(pointExtremes[1] * totalFactor);
27453 this.stackedYData[i] = pointExtremes[1];
27454 };
27455
27456 /**
27457 * Get stack indicator, according to it's x-value, to determine points with the
27458 * same x-value
27459 */
27460 Series.prototype.getStackIndicator = function(stackIndicator, x, index, key) {
27461 // Update stack indicator, when:
27462 // first point in a stack || x changed || stack type (negative vs positive)
27463 // changed:
27464 if (!defined(stackIndicator) || stackIndicator.x !== x ||
27465 (key && stackIndicator.key !== key)) {
27466 stackIndicator = {
27467 x: x,
27468 index: 0,
27469 key: key
27470 };
27471 } else {
27472 stackIndicator.index++;
27473 }
27474
27475 stackIndicator.key = [index, x, stackIndicator.index].join(',');
27476
27477 return stackIndicator;
27478 };
27479
27480 }(Highcharts));
27481 (function(H) {
27482 /**
27483 * (c) 2010-2017 Torstein Honsi
27484 *
27485 * License: www.highcharts.com/license
27486 */
27487 var addEvent = H.addEvent,
27488 animate = H.animate,
27489 Axis = H.Axis,
27490 Chart = H.Chart,
27491 createElement = H.createElement,
27492 css = H.css,
27493 defined = H.defined,
27494 each = H.each,
27495 erase = H.erase,
27496 extend = H.extend,
27497 fireEvent = H.fireEvent,
27498 inArray = H.inArray,
27499 isNumber = H.isNumber,
27500 isObject = H.isObject,
27501 isArray = H.isArray,
27502 merge = H.merge,
27503 objectEach = H.objectEach,
27504 pick = H.pick,
27505 Point = H.Point,
27506 Series = H.Series,
27507 seriesTypes = H.seriesTypes,
27508 setAnimation = H.setAnimation,
27509 splat = H.splat;
27510
27511 // Extend the Chart prototype for dynamic methods
27512 extend(Chart.prototype, /** @lends Highcharts.Chart.prototype */ {
27513
27514 /**
27515 * Add a series to the chart after render time. Note that this method should
27516 * never be used when adding data synchronously at chart render time, as it
27517 * adds expense to the calculations and rendering. When adding data at the
27518 * same time as the chart is initialized, add the series as a configuration
27519 * option instead. With multiple axes, the `offset` is dynamically adjusted.
27520 *
27521 * @param {SeriesOptions} options
27522 * The config options for the series.
27523 * @param {Boolean} [redraw=true]
27524 * Whether to redraw the chart after adding.
27525 * @param {AnimationOptions} animation
27526 * Whether to apply animation, and optionally animation
27527 * configuration.
27528 *
27529 * @return {Highcharts.Series}
27530 * The newly created series object.
27531 *
27532 * @sample highcharts/members/chart-addseries/
27533 * Add a series from a button
27534 * @sample stock/members/chart-addseries/
27535 * Add a series in Highstock
27536 */
27537 addSeries: function(options, redraw, animation) {
27538 var series,
27539 chart = this;
27540
27541 if (options) {
27542 redraw = pick(redraw, true); // defaults to true
27543
27544 fireEvent(chart, 'addSeries', {
27545 options: options
27546 }, function() {
27547 series = chart.initSeries(options);
27548
27549 chart.isDirtyLegend = true; // the series array is out of sync with the display
27550 chart.linkSeries();
27551 if (redraw) {
27552 chart.redraw(animation);
27553 }
27554 });
27555 }
27556
27557 return series;
27558 },
27559
27560 /**
27561 * Add an axis to the chart after render time. Note that this method should
27562 * never be used when adding data synchronously at chart render time, as it
27563 * adds expense to the calculations and rendering. When adding data at the
27564 * same time as the chart is initialized, add the axis as a configuration
27565 * option instead.
27566 * @param {AxisOptions} options
27567 * The axis options.
27568 * @param {Boolean} [isX=false]
27569 * Whether it is an X axis or a value axis.
27570 * @param {Boolean} [redraw=true]
27571 * Whether to redraw the chart after adding.
27572 * @param {AnimationOptions} [animation=true]
27573 * Whether and how to apply animation in the redraw.
27574 *
27575 * @sample highcharts/members/chart-addaxis/ Add and remove axes
27576 *
27577 * @return {Axis}
27578 * The newly generated Axis object.
27579 */
27580 addAxis: function(options, isX, redraw, animation) {
27581 var key = isX ? 'xAxis' : 'yAxis',
27582 chartOptions = this.options,
27583 userOptions = merge(options, {
27584 index: this[key].length,
27585 isX: isX
27586 }),
27587 axis;
27588
27589 axis = new Axis(this, userOptions);
27590
27591 // Push the new axis options to the chart options
27592 chartOptions[key] = splat(chartOptions[key] || {});
27593 chartOptions[key].push(userOptions);
27594
27595 if (pick(redraw, true)) {
27596 this.redraw(animation);
27597 }
27598
27599 return axis;
27600 },
27601
27602 /**
27603 * Dim the chart and show a loading text or symbol. Options for the loading
27604 * screen are defined in {@link
27605 * https://api.highcharts.com/highcharts/loading|the loading options}.
27606 *
27607 * @param {String} str
27608 * An optional text to show in the loading label instead of the
27609 * default one. The default text is set in {@link
27610 * http://api.highcharts.com/highcharts/lang.loading|lang.loading}.
27611 *
27612 * @sample highcharts/members/chart-hideloading/
27613 * Show and hide loading from a button
27614 * @sample highcharts/members/chart-showloading/
27615 * Apply different text labels
27616 * @sample stock/members/chart-show-hide-loading/
27617 * Toggle loading in Highstock
27618 */
27619 showLoading: function(str) {
27620 var chart = this,
27621 options = chart.options,
27622 loadingDiv = chart.loadingDiv,
27623 loadingOptions = options.loading,
27624 setLoadingSize = function() {
27625 if (loadingDiv) {
27626 css(loadingDiv, {
27627 left: chart.plotLeft + 'px',
27628 top: chart.plotTop + 'px',
27629 width: chart.plotWidth + 'px',
27630 height: chart.plotHeight + 'px'
27631 });
27632 }
27633 };
27634
27635 // create the layer at the first call
27636 if (!loadingDiv) {
27637 chart.loadingDiv = loadingDiv = createElement('div', {
27638 className: 'highcharts-loading highcharts-loading-hidden'
27639 }, null, chart.container);
27640
27641 chart.loadingSpan = createElement(
27642 'span', {
27643 className: 'highcharts-loading-inner'
27644 },
27645 null,
27646 loadingDiv
27647 );
27648 addEvent(chart, 'redraw', setLoadingSize); // #1080
27649 }
27650
27651 loadingDiv.className = 'highcharts-loading';
27652
27653 // Update text
27654 chart.loadingSpan.innerHTML = str || options.lang.loading;
27655
27656
27657 // Update visuals
27658 css(loadingDiv, extend(loadingOptions.style, {
27659 zIndex: 10
27660 }));
27661 css(chart.loadingSpan, loadingOptions.labelStyle);
27662
27663 // Show it
27664 if (!chart.loadingShown) {
27665 css(loadingDiv, {
27666 opacity: 0,
27667 display: ''
27668 });
27669 animate(loadingDiv, {
27670 opacity: loadingOptions.style.opacity || 0.5
27671 }, {
27672 duration: loadingOptions.showDuration || 0
27673 });
27674 }
27675
27676
27677 chart.loadingShown = true;
27678 setLoadingSize();
27679 },
27680
27681 /**
27682 * Hide the loading layer.
27683 *
27684 * @see Highcharts.Chart#showLoading
27685 * @sample highcharts/members/chart-hideloading/
27686 * Show and hide loading from a button
27687 * @sample stock/members/chart-show-hide-loading/
27688 * Toggle loading in Highstock
27689 */
27690 hideLoading: function() {
27691 var options = this.options,
27692 loadingDiv = this.loadingDiv;
27693
27694 if (loadingDiv) {
27695 loadingDiv.className = 'highcharts-loading highcharts-loading-hidden';
27696
27697 animate(loadingDiv, {
27698 opacity: 0
27699 }, {
27700 duration: options.loading.hideDuration || 100,
27701 complete: function() {
27702 css(loadingDiv, {
27703 display: 'none'
27704 });
27705 }
27706 });
27707
27708 }
27709 this.loadingShown = false;
27710 },
27711
27712 /**
27713 * These properties cause isDirtyBox to be set to true when updating. Can be extended from plugins.
27714 */
27715 propsRequireDirtyBox: ['backgroundColor', 'borderColor', 'borderWidth', 'margin', 'marginTop', 'marginRight',
27716 'marginBottom', 'marginLeft', 'spacing', 'spacingTop', 'spacingRight', 'spacingBottom', 'spacingLeft',
27717 'borderRadius', 'plotBackgroundColor', 'plotBackgroundImage', 'plotBorderColor', 'plotBorderWidth',
27718 'plotShadow', 'shadow'
27719 ],
27720
27721 /**
27722 * These properties cause all series to be updated when updating. Can be
27723 * extended from plugins.
27724 */
27725 propsRequireUpdateSeries: ['chart.inverted', 'chart.polar',
27726 'chart.ignoreHiddenSeries', 'chart.type', 'colors', 'plotOptions',
27727 'tooltip'
27728 ],
27729
27730 /**
27731 * A generic function to update any element of the chart. Elements can be
27732 * enabled and disabled, moved, re-styled, re-formatted etc.
27733 *
27734 * A special case is configuration objects that take arrays, for example
27735 * {@link https://api.highcharts.com/highcharts/xAxis|xAxis},
27736 * {@link https://api.highcharts.com/highcharts/yAxis|yAxis} or
27737 * {@link https://api.highcharts.com/highcharts/series|series}. For these
27738 * collections, an `id` option is used to map the new option set to an
27739 * existing object. If an existing object of the same id is not found, the
27740 * corresponding item is updated. So for example, running `chart.update`
27741 * with a series item without an id, will cause the existing chart's series
27742 * with the same index in the series array to be updated. When the
27743 * `oneToOne` parameter is true, `chart.update` will also take care of
27744 * adding and removing items from the collection. Read more under the
27745 * parameter description below.
27746 *
27747 * See also the {@link https://api.highcharts.com/highcharts/responsive|
27748 * responsive option set}. Switching between `responsive.rules` basically
27749 * runs `chart.update` under the hood.
27750 *
27751 * @param {Options} options
27752 * A configuration object for the new chart options.
27753 * @param {Boolean} [redraw=true]
27754 * Whether to redraw the chart.
27755 * @param {Boolean} [oneToOne=false]
27756 * When `true`, the `series`, `xAxis` and `yAxis` collections will
27757 * be updated one to one, and items will be either added or removed
27758 * to match the new updated options. For example, if the chart has
27759 * two series and we call `chart.update` with a configuration
27760 * containing three series, one will be added. If we call
27761 * `chart.update` with one series, one will be removed. Setting an
27762 * empty `series` array will remove all series, but leaving out the
27763 * `series` property will leave all series untouched. If the series
27764 * have id's, the new series options will be matched by id, and the
27765 * remaining ones removed.
27766 *
27767 * @sample highcharts/members/chart-update/
27768 * Update chart geometry
27769 */
27770 update: function(options, redraw, oneToOne) {
27771 var chart = this,
27772 adders = {
27773 credits: 'addCredits',
27774 title: 'setTitle',
27775 subtitle: 'setSubtitle'
27776 },
27777 optionsChart = options.chart,
27778 updateAllAxes,
27779 updateAllSeries,
27780 newWidth,
27781 newHeight,
27782 itemsForRemoval = [];
27783
27784 // If the top-level chart option is present, some special updates are required
27785 if (optionsChart) {
27786 merge(true, chart.options.chart, optionsChart);
27787
27788 // Setter function
27789 if ('className' in optionsChart) {
27790 chart.setClassName(optionsChart.className);
27791 }
27792
27793 if ('inverted' in optionsChart || 'polar' in optionsChart) {
27794 // Parse options.chart.inverted and options.chart.polar together
27795 // with the available series.
27796 chart.propFromSeries();
27797 updateAllAxes = true;
27798 }
27799
27800 if ('alignTicks' in optionsChart) { // #6452
27801 updateAllAxes = true;
27802 }
27803
27804 objectEach(optionsChart, function(val, key) {
27805 if (inArray('chart.' + key, chart.propsRequireUpdateSeries) !== -1) {
27806 updateAllSeries = true;
27807 }
27808 // Only dirty box
27809 if (inArray(key, chart.propsRequireDirtyBox) !== -1) {
27810 chart.isDirtyBox = true;
27811 }
27812 });
27813
27814
27815 if ('style' in optionsChart) {
27816 chart.renderer.setStyle(optionsChart.style);
27817 }
27818
27819 }
27820
27821 // Moved up, because tooltip needs updated plotOptions (#6218)
27822
27823 if (options.colors) {
27824 this.options.colors = options.colors;
27825 }
27826
27827
27828 if (options.plotOptions) {
27829 merge(true, this.options.plotOptions, options.plotOptions);
27830 }
27831
27832 // Some option stuctures correspond one-to-one to chart objects that
27833 // have update methods, for example
27834 // options.credits => chart.credits
27835 // options.legend => chart.legend
27836 // options.title => chart.title
27837 // options.tooltip => chart.tooltip
27838 // options.subtitle => chart.subtitle
27839 // options.mapNavigation => chart.mapNavigation
27840 // options.navigator => chart.navigator
27841 // options.scrollbar => chart.scrollbar
27842 objectEach(options, function(val, key) {
27843 if (chart[key] && typeof chart[key].update === 'function') {
27844 chart[key].update(val, false);
27845
27846 // If a one-to-one object does not exist, look for an adder function
27847 } else if (typeof chart[adders[key]] === 'function') {
27848 chart[adders[key]](val);
27849 }
27850
27851 if (
27852 key !== 'chart' &&
27853 inArray(key, chart.propsRequireUpdateSeries) !== -1
27854 ) {
27855 updateAllSeries = true;
27856 }
27857 });
27858
27859 // Setters for collections. For axes and series, each item is referred
27860 // by an id. If the id is not found, it defaults to the corresponding
27861 // item in the collection, so setting one series without an id, will
27862 // update the first series in the chart. Setting two series without
27863 // an id will update the first and the second respectively (#6019)
27864 // chart.update and responsive.
27865 each([
27866 'xAxis',
27867 'yAxis',
27868 'zAxis',
27869 'series',
27870 'colorAxis',
27871 'pane'
27872 ], function(coll) {
27873 if (options[coll]) {
27874 each(splat(options[coll]), function(newOptions, i) {
27875 var item = (
27876 defined(newOptions.id) &&
27877 chart.get(newOptions.id)
27878 ) || chart[coll][i];
27879 if (item && item.coll === coll) {
27880 item.update(newOptions, false);
27881
27882 if (oneToOne) {
27883 item.touched = true;
27884 }
27885 }
27886
27887 // If oneToOne and no matching item is found, add one
27888 if (!item && oneToOne) {
27889 if (coll === 'series') {
27890 chart.addSeries(newOptions, false)
27891 .touched = true;
27892 } else if (coll === 'xAxis' || coll === 'yAxis') {
27893 chart.addAxis(newOptions, coll === 'xAxis', false)
27894 .touched = true;
27895 }
27896 }
27897
27898 });
27899
27900 // Add items for removal
27901 if (oneToOne) {
27902 each(chart[coll], function(item) {
27903 if (!item.touched) {
27904 itemsForRemoval.push(item);
27905 } else {
27906 delete item.touched;
27907 }
27908 });
27909 }
27910
27911
27912 }
27913 });
27914
27915 each(itemsForRemoval, function(item) {
27916 item.remove(false);
27917 });
27918
27919 if (updateAllAxes) {
27920 each(chart.axes, function(axis) {
27921 axis.update({}, false);
27922 });
27923 }
27924
27925 // Certain options require the whole series structure to be thrown away
27926 // and rebuilt
27927 if (updateAllSeries) {
27928 each(chart.series, function(series) {
27929 series.update({}, false);
27930 });
27931 }
27932
27933 // For loading, just update the options, do not redraw
27934 if (options.loading) {
27935 merge(true, chart.options.loading, options.loading);
27936 }
27937
27938 // Update size. Redraw is forced.
27939 newWidth = optionsChart && optionsChart.width;
27940 newHeight = optionsChart && optionsChart.height;
27941 if ((isNumber(newWidth) && newWidth !== chart.chartWidth) ||
27942 (isNumber(newHeight) && newHeight !== chart.chartHeight)) {
27943 chart.setSize(newWidth, newHeight);
27944 } else if (pick(redraw, true)) {
27945 chart.redraw();
27946 }
27947 },
27948
27949 /**
27950 * Shortcut to set the subtitle options. This can also be done from {@link
27951 * Chart#update} or {@link Chart#setTitle}.
27952 *
27953 * @param {SubtitleOptions} options
27954 * New subtitle options. The subtitle text itself is set by the
27955 * `options.text` property.
27956 */
27957 setSubtitle: function(options) {
27958 this.setTitle(undefined, options);
27959 }
27960
27961
27962 });
27963
27964 // extend the Point prototype for dynamic methods
27965 extend(Point.prototype, /** @lends Highcharts.Point.prototype */ {
27966 /**
27967 * Update point with new options (typically x/y data) and optionally redraw
27968 * the series.
27969 *
27970 * @param {Object} options
27971 * The point options. Point options are handled as described under
27972 * the `series.type.data` item for each series type. For example
27973 * for a line series, if options is a single number, the point will
27974 * be given that number as the main y value. If it is an array, it
27975 * will be interpreted as x and y values respectively. If it is an
27976 * object, advanced options are applied.
27977 * @param {Boolean} [redraw=true]
27978 * Whether to redraw the chart after the point is updated. If doing
27979 * more operations on the chart, it is best practice to set
27980 * `redraw` to false and call `chart.redraw()` after.
27981 * @param {AnimationOptions} [animation=true]
27982 * Whether to apply animation, and optionally animation
27983 * configuration.
27984 *
27985 * @sample highcharts/members/point-update-column/
27986 * Update column value
27987 * @sample highcharts/members/point-update-pie/
27988 * Update pie slice
27989 * @sample maps/members/point-update/
27990 * Update map area value in Highmaps
27991 */
27992 update: function(options, redraw, animation, runEvent) {
27993 var point = this,
27994 series = point.series,
27995 graphic = point.graphic,
27996 i,
27997 chart = series.chart,
27998 seriesOptions = series.options;
27999
28000 redraw = pick(redraw, true);
28001
28002 function update() {
28003
28004 point.applyOptions(options);
28005
28006 // Update visuals
28007 if (point.y === null && graphic) { // #4146
28008 point.graphic = graphic.destroy();
28009 }
28010 if (isObject(options, true)) {
28011 // Destroy so we can get new elements
28012 if (graphic && graphic.element) {
28013 // "null" is also a valid symbol
28014 if (options && options.marker && options.marker.symbol !== undefined) {
28015 point.graphic = graphic.destroy();
28016 }
28017 }
28018 if (options && options.dataLabels && point.dataLabel) { // #2468
28019 point.dataLabel = point.dataLabel.destroy();
28020 }
28021 if (point.connector) {
28022 point.connector = point.connector.destroy(); // #7243
28023 }
28024 }
28025
28026 // record changes in the parallel arrays
28027 i = point.index;
28028 series.updateParallelArrays(point, i);
28029
28030 // Record the options to options.data. If the old or the new config
28031 // is an object, use point options, otherwise use raw options
28032 // (#4701, #4916).
28033 seriesOptions.data[i] = (
28034 isObject(seriesOptions.data[i], true) ||
28035 isObject(options, true)
28036 ) ?
28037 point.options :
28038 options;
28039
28040 // redraw
28041 series.isDirty = series.isDirtyData = true;
28042 if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320
28043 chart.isDirtyBox = true;
28044 }
28045
28046 if (seriesOptions.legendType === 'point') { // #1831, #1885
28047 chart.isDirtyLegend = true;
28048 }
28049 if (redraw) {
28050 chart.redraw(animation);
28051 }
28052 }
28053
28054 // Fire the event with a default handler of doing the update
28055 if (runEvent === false) { // When called from setData
28056 update();
28057 } else {
28058 point.firePointEvent('update', {
28059 options: options
28060 }, update);
28061 }
28062 },
28063
28064 /**
28065 * Remove a point and optionally redraw the series and if necessary the axes
28066 * @param {Boolean} redraw
28067 * Whether to redraw the chart or wait for an explicit call. When
28068 * doing more operations on the chart, for example running
28069 * `point.remove()` in a loop, it is best practice to set `redraw`
28070 * to false and call `chart.redraw()` after.
28071 * @param {AnimationOptions} [animation=false]
28072 * Whether to apply animation, and optionally animation
28073 * configuration.
28074 *
28075 * @sample highcharts/plotoptions/series-point-events-remove/
28076 * Remove point and confirm
28077 * @sample highcharts/members/point-remove/
28078 * Remove pie slice
28079 * @sample maps/members/point-remove/
28080 * Remove selected points in Highmaps
28081 */
28082 remove: function(redraw, animation) {
28083 this.series.removePoint(inArray(this, this.series.data), redraw, animation);
28084 }
28085 });
28086
28087 // Extend the series prototype for dynamic methods
28088 extend(Series.prototype, /** @lends Series.prototype */ {
28089 /**
28090 * Add a point to the series after render time. The point can be added at
28091 * the end, or by giving it an X value, to the start or in the middle of the
28092 * series.
28093 *
28094 * @param {Number|Array|Object} options
28095 * The point options. If options is a single number, a point with
28096 * that y value is appended to the series.If it is an array, it will
28097 * be interpreted as x and y values respectively. If it is an
28098 * object, advanced options as outlined under `series.data` are
28099 * applied.
28100 * @param {Boolean} [redraw=true]
28101 * Whether to redraw the chart after the point is added. When adding
28102 * more than one point, it is highly recommended that the redraw
28103 * option be set to false, and instead {@link Chart#redraw}
28104 * is explicitly called after the adding of points is finished.
28105 * Otherwise, the chart will redraw after adding each point.
28106 * @param {Boolean} [shift=false]
28107 * If true, a point is shifted off the start of the series as one is
28108 * appended to the end.
28109 * @param {AnimationOptions} [animation]
28110 * Whether to apply animation, and optionally animation
28111 * configuration.
28112 *
28113 * @sample highcharts/members/series-addpoint-append/
28114 * Append point
28115 * @sample highcharts/members/series-addpoint-append-and-shift/
28116 * Append and shift
28117 * @sample highcharts/members/series-addpoint-x-and-y/
28118 * Both X and Y values given
28119 * @sample highcharts/members/series-addpoint-pie/
28120 * Append pie slice
28121 * @sample stock/members/series-addpoint/
28122 * Append 100 points in Highstock
28123 * @sample stock/members/series-addpoint-shift/
28124 * Append and shift in Highstock
28125 * @sample maps/members/series-addpoint/
28126 * Add a point in Highmaps
28127 */
28128 addPoint: function(options, redraw, shift, animation) {
28129 var series = this,
28130 seriesOptions = series.options,
28131 data = series.data,
28132 chart = series.chart,
28133 xAxis = series.xAxis,
28134 names = xAxis && xAxis.hasNames && xAxis.names,
28135 dataOptions = seriesOptions.data,
28136 point,
28137 isInTheMiddle,
28138 xData = series.xData,
28139 i,
28140 x;
28141
28142 // Optional redraw, defaults to true
28143 redraw = pick(redraw, true);
28144
28145 // Get options and push the point to xData, yData and series.options. In series.generatePoints
28146 // the Point instance will be created on demand and pushed to the series.data array.
28147 point = {
28148 series: series
28149 };
28150 series.pointClass.prototype.applyOptions.apply(point, [options]);
28151 x = point.x;
28152
28153 // Get the insertion point
28154 i = xData.length;
28155 if (series.requireSorting && x < xData[i - 1]) {
28156 isInTheMiddle = true;
28157 while (i && xData[i - 1] > x) {
28158 i--;
28159 }
28160 }
28161
28162 series.updateParallelArrays(point, 'splice', i, 0, 0); // insert undefined item
28163 series.updateParallelArrays(point, i); // update it
28164
28165 if (names && point.name) {
28166 names[x] = point.name;
28167 }
28168 dataOptions.splice(i, 0, options);
28169
28170 if (isInTheMiddle) {
28171 series.data.splice(i, 0, null);
28172 series.processData();
28173 }
28174
28175 // Generate points to be added to the legend (#1329)
28176 if (seriesOptions.legendType === 'point') {
28177 series.generatePoints();
28178 }
28179
28180 // Shift the first point off the parallel arrays
28181 if (shift) {
28182 if (data[0] && data[0].remove) {
28183 data[0].remove(false);
28184 } else {
28185 data.shift();
28186 series.updateParallelArrays(point, 'shift');
28187
28188 dataOptions.shift();
28189 }
28190 }
28191
28192 // redraw
28193 series.isDirty = true;
28194 series.isDirtyData = true;
28195
28196 if (redraw) {
28197 chart.redraw(animation); // Animation is set anyway on redraw, #5665
28198 }
28199 },
28200
28201 /**
28202 * Remove a point from the series. Unlike the {@link Highcharts.Point#remove}
28203 * method, this can also be done on a point that is not instanciated because
28204 * it is outside the view or subject to Highstock data grouping.
28205 *
28206 * @param {Number} i
28207 * The index of the point in the {@link Highcharts.Series.data|data}
28208 * array.
28209 * @param {Boolean} [redraw=true]
28210 * Whether to redraw the chart after the point is added. When
28211 * removing more than one point, it is highly recommended that the
28212 * `redraw` option be set to `false`, and instead {@link
28213 * Highcharts.Chart#redraw} is explicitly called after the adding of
28214 * points is finished.
28215 * @param {AnimationOptions} [animation]
28216 * Whether and optionally how the series should be animated.
28217 *
28218 * @sample highcharts/members/series-removepoint/
28219 * Remove cropped point
28220 */
28221 removePoint: function(i, redraw, animation) {
28222
28223 var series = this,
28224 data = series.data,
28225 point = data[i],
28226 points = series.points,
28227 chart = series.chart,
28228 remove = function() {
28229
28230 if (points && points.length === data.length) { // #4935
28231 points.splice(i, 1);
28232 }
28233 data.splice(i, 1);
28234 series.options.data.splice(i, 1);
28235 series.updateParallelArrays(point || {
28236 series: series
28237 }, 'splice', i, 1);
28238
28239 if (point) {
28240 point.destroy();
28241 }
28242
28243 // redraw
28244 series.isDirty = true;
28245 series.isDirtyData = true;
28246 if (redraw) {
28247 chart.redraw();
28248 }
28249 };
28250
28251 setAnimation(animation, chart);
28252 redraw = pick(redraw, true);
28253
28254 // Fire the event with a default handler of removing the point
28255 if (point) {
28256 point.firePointEvent('remove', null, remove);
28257 } else {
28258 remove();
28259 }
28260 },
28261
28262 /**
28263 * Remove a series and optionally redraw the chart.
28264 *
28265 * @param {Boolean} [redraw=true]
28266 * Whether to redraw the chart or wait for an explicit call to
28267 * {@link Highcharts.Chart#redraw}.
28268 * @param {AnimationOptions} [animation]
28269 * Whether to apply animation, and optionally animation
28270 * configuration
28271 * @param {Boolean} [withEvent=true]
28272 * Used internally, whether to fire the series `remove` event.
28273 *
28274 * @sample highcharts/members/series-remove/
28275 * Remove first series from a button
28276 */
28277 remove: function(redraw, animation, withEvent) {
28278 var series = this,
28279 chart = series.chart;
28280
28281 function remove() {
28282
28283 // Destroy elements
28284 series.destroy();
28285
28286 // Redraw
28287 chart.isDirtyLegend = chart.isDirtyBox = true;
28288 chart.linkSeries();
28289
28290 if (pick(redraw, true)) {
28291 chart.redraw(animation);
28292 }
28293 }
28294
28295 // Fire the event with a default handler of removing the point
28296 if (withEvent !== false) {
28297 fireEvent(series, 'remove', null, remove);
28298 } else {
28299 remove();
28300 }
28301 },
28302
28303 /**
28304 * Update the series with a new set of options. For a clean and precise
28305 * handling of new options, all methods and elements from the series are
28306 * removed, and it is initiated from scratch. Therefore, this method is more
28307 * performance expensive than some other utility methods like {@link
28308 * Series#setData} or {@link Series#setVisible}.
28309 *
28310 * @param {SeriesOptions} options
28311 * New options that will be merged with the series' existing
28312 * options.
28313 * @param {Boolean} [redraw=true]
28314 * Whether to redraw the chart after the series is altered. If doing
28315 * more operations on the chart, it is a good idea to set redraw to
28316 * false and call {@link Chart#redraw} after.
28317 *
28318 * @sample highcharts/members/series-update/
28319 * Updating series options
28320 * @sample maps/members/series-update/
28321 * Update series options in Highmaps
28322 */
28323 update: function(newOptions, redraw) {
28324 var series = this,
28325 chart = series.chart,
28326 // must use user options when changing type because series.options
28327 // is merged in with type specific plotOptions
28328 oldOptions = series.userOptions,
28329 oldType = series.oldType || series.type,
28330 newType = newOptions.type || oldOptions.type || chart.options.chart.type,
28331 proto = seriesTypes[oldType].prototype,
28332 n,
28333 preserveGroups = [
28334 'group',
28335 'markerGroup',
28336 'dataLabelsGroup'
28337 ],
28338 preserve = [
28339 'navigatorSeries',
28340 'baseSeries'
28341 ],
28342
28343 // Animation must be enabled when calling update before the initial
28344 // animation has first run. This happens when calling update
28345 // directly after chart initialization, or when applying responsive
28346 // rules (#6912).
28347 animation = series.finishedAnimating && {
28348 animation: false
28349 };
28350
28351 // Running Series.update to update the data only is an intuitive usage,
28352 // so we want to make sure that when used like this, we run the
28353 // cheaper setData function and allow animation instead of completely
28354 // recreating the series instance.
28355 if (Object.keys && Object.keys(newOptions).toString() === 'data') {
28356 return this.setData(newOptions.data, redraw);
28357 }
28358
28359 // If we're changing type or zIndex, create new groups (#3380, #3404)
28360 // Also create new groups for navigator series.
28361 if (
28362 (newType && newType !== oldType) ||
28363 newOptions.zIndex !== undefined
28364 ) {
28365 preserveGroups.length = 0;
28366 }
28367
28368 // Make sure preserved properties are not destroyed (#3094)
28369 preserve = preserveGroups.concat(preserve);
28370 each(preserve, function(prop) {
28371 preserve[prop] = series[prop];
28372 delete series[prop];
28373 });
28374
28375 // Do the merge, with some forced options
28376 newOptions = merge(oldOptions, animation, {
28377 index: series.index,
28378 pointStart: series.xData[0] // when updating after addPoint
28379 }, {
28380 data: series.options.data
28381 }, newOptions);
28382
28383 // Destroy the series and delete all properties. Reinsert all methods
28384 // and properties from the new type prototype (#2270, #3719)
28385 series.remove(false, null, false);
28386 for (n in proto) {
28387 series[n] = undefined;
28388 }
28389 extend(series, seriesTypes[newType || oldType].prototype);
28390
28391 // Re-register groups (#3094) and other preserved properties
28392 each(preserve, function(prop) {
28393 series[prop] = preserve[prop];
28394 });
28395
28396 series.init(chart, newOptions);
28397 series.oldType = oldType;
28398 chart.linkSeries(); // Links are lost in series.remove (#3028)
28399 if (pick(redraw, true)) {
28400 chart.redraw(false);
28401 }
28402 }
28403 });
28404
28405 // Extend the Axis.prototype for dynamic methods
28406 extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {
28407
28408 /**
28409 * Update an axis object with a new set of options. The options are merged
28410 * with the existing options, so only new or altered options need to be
28411 * specified.
28412 *
28413 * @param {Object} options
28414 * The new options that will be merged in with existing options on
28415 * the axis.
28416 * @sample highcharts/members/axis-update/ Axis update demo
28417 */
28418 update: function(options, redraw) {
28419 var chart = this.chart;
28420
28421 options = chart.options[this.coll][this.options.index] =
28422 merge(this.userOptions, options);
28423
28424 this.destroy(true);
28425
28426 this.init(chart, extend(options, {
28427 events: undefined
28428 }));
28429
28430 chart.isDirtyBox = true;
28431 if (pick(redraw, true)) {
28432 chart.redraw();
28433 }
28434 },
28435
28436 /**
28437 * Remove the axis from the chart.
28438 *
28439 * @param {Boolean} [redraw=true] Whether to redraw the chart following the
28440 * remove.
28441 *
28442 * @sample highcharts/members/chart-addaxis/ Add and remove axes
28443 */
28444 remove: function(redraw) {
28445 var chart = this.chart,
28446 key = this.coll, // xAxis or yAxis
28447 axisSeries = this.series,
28448 i = axisSeries.length;
28449
28450 // Remove associated series (#2687)
28451 while (i--) {
28452 if (axisSeries[i]) {
28453 axisSeries[i].remove(false);
28454 }
28455 }
28456
28457 // Remove the axis
28458 erase(chart.axes, this);
28459 erase(chart[key], this);
28460
28461 if (isArray(chart.options[key])) {
28462 chart.options[key].splice(this.options.index, 1);
28463 } else { // color axis, #6488
28464 delete chart.options[key];
28465 }
28466
28467 each(chart[key], function(axis, i) { // Re-index, #1706
28468 axis.options.index = i;
28469 });
28470 this.destroy();
28471 chart.isDirtyBox = true;
28472
28473 if (pick(redraw, true)) {
28474 chart.redraw();
28475 }
28476 },
28477
28478 /**
28479 * Update the axis title by options after render time.
28480 *
28481 * @param {TitleOptions} titleOptions
28482 * The additional title options.
28483 * @param {Boolean} [redraw=true]
28484 * Whether to redraw the chart after setting the title.
28485 * @sample highcharts/members/axis-settitle/ Set a new Y axis title
28486 */
28487 setTitle: function(titleOptions, redraw) {
28488 this.update({
28489 title: titleOptions
28490 }, redraw);
28491 },
28492
28493 /**
28494 * Set new axis categories and optionally redraw.
28495 * @param {Array.<String>} categories - The new categories.
28496 * @param {Boolean} [redraw=true] - Whether to redraw the chart.
28497 * @sample highcharts/members/axis-setcategories/ Set categories by click on
28498 * a button
28499 */
28500 setCategories: function(categories, redraw) {
28501 this.update({
28502 categories: categories
28503 }, redraw);
28504 }
28505
28506 });
28507
28508 }(Highcharts));
28509 (function(H) {
28510 /**
28511 * (c) 2010-2017 Torstein Honsi
28512 *
28513 * License: www.highcharts.com/license
28514 */
28515 var color = H.color,
28516 each = H.each,
28517 LegendSymbolMixin = H.LegendSymbolMixin,
28518 map = H.map,
28519 pick = H.pick,
28520 Series = H.Series,
28521 seriesType = H.seriesType;
28522
28523 /**
28524 * Area series type.
28525 * @constructor seriesTypes.area
28526 * @extends {Series}
28527 */
28528 /**
28529 * The area series type.
28530 * @extends {plotOptions.line}
28531 * @product highcharts highstock
28532 * @sample {highcharts} highcharts/demo/area-basic/
28533 * Area chart
28534 * @sample {highstock} stock/demo/area/
28535 * Area chart
28536 * @optionparent plotOptions.area
28537 */
28538 seriesType('area', 'line', {
28539
28540 /**
28541 * Fill color or gradient for the area. When `null`, the series' `color`
28542 * is used with the series' `fillOpacity`.
28543 *
28544 * @type {Color}
28545 * @see In styled mode, the fill color can be set with the `.highcharts-area` class name.
28546 * @sample {highcharts} highcharts/plotoptions/area-fillcolor-default/ Null by default
28547 * @sample {highcharts} highcharts/plotoptions/area-fillcolor-gradient/ Gradient
28548 * @default null
28549 * @product highcharts highstock
28550 * @apioption plotOptions.area.fillColor
28551 */
28552
28553 /**
28554 * Fill opacity for the area. When you set an explicit `fillColor`,
28555 * the `fillOpacity` is not applied. Instead, you should define the
28556 * opacity in the `fillColor` with an rgba color definition. The `fillOpacity`
28557 * setting, also the default setting, overrides the alpha component
28558 * of the `color` setting.
28559 *
28560 * @type {Number}
28561 * @see In styled mode, the fill opacity can be set with the `.highcharts-area` class name.
28562 * @sample {highcharts} highcharts/plotoptions/area-fillopacity/ Automatic fill color and fill opacity of 0.1
28563 * @default {highcharts} 0.75
28564 * @default {highstock} .75
28565 * @product highcharts highstock
28566 * @apioption plotOptions.area.fillOpacity
28567 */
28568
28569 /**
28570 * A separate color for the graph line. By default the line takes the
28571 * `color` of the series, but the lineColor setting allows setting a
28572 * separate color for the line without altering the `fillColor`.
28573 *
28574 * @type {Color}
28575 * @see In styled mode, the line stroke can be set with the `.highcharts-graph` class name.
28576 * @sample {highcharts} highcharts/plotoptions/area-linecolor/ Dark gray line
28577 * @default null
28578 * @product highcharts highstock
28579 * @apioption plotOptions.area.lineColor
28580 */
28581
28582 /**
28583 * A separate color for the negative part of the area.
28584 *
28585 * @type {Color}
28586 * @see [negativeColor](#plotOptions.area.negativeColor). In styled mode, a negative
28587 * color is set with the `.highcharts-negative` class name ([view live
28588 * demo](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/series-
28589 * negative-color/)).
28590 * @since 3.0
28591 * @product highcharts
28592 * @apioption plotOptions.area.negativeFillColor
28593 */
28594
28595 /**
28596 * When this is true, the series will not cause the Y axis to cross
28597 * the zero plane (or [threshold](#plotOptions.series.threshold) option)
28598 * unless the data actually crosses the plane.
28599 *
28600 * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
28601 * 3 will make the Y axis show negative values according to the `minPadding`
28602 * option. If `softThreshold` is `true`, the Y axis starts at 0.
28603 *
28604 * @type {Boolean}
28605 * @default false
28606 * @since 4.1.9
28607 * @product highcharts highstock
28608 */
28609 softThreshold: false,
28610
28611 /**
28612 * The Y axis value to serve as the base for the area, for distinguishing
28613 * between values above and below a threshold. If `null`, the area
28614 * behaves like a line series with fill between the graph and the Y
28615 * axis minimum.
28616 *
28617 * @type {Number}
28618 * @sample {highcharts} highcharts/plotoptions/area-threshold/ A threshold of 100
28619 * @default 0
28620 * @since 2.0
28621 * @product highcharts highstock
28622 */
28623 threshold: 0
28624
28625 /**
28626 * Whether the whole area or just the line should respond to mouseover
28627 * tooltips and other mouse or touch events.
28628 *
28629 * @type {Boolean}
28630 * @sample {highcharts} highcharts/plotoptions/area-trackbyarea/ Display the tooltip when the area is hovered
28631 * @sample {highstock} highcharts/plotoptions/area-trackbyarea/ Display the tooltip when the area is hovered
28632 * @default false
28633 * @since 1.1.6
28634 * @product highcharts highstock
28635 * @apioption plotOptions.area.trackByArea
28636 */
28637
28638
28639 }, /** @lends seriesTypes.area.prototype */ {
28640 singleStacks: false,
28641 /**
28642 * Return an array of stacked points, where null and missing points are replaced by
28643 * dummy points in order for gaps to be drawn correctly in stacks.
28644 */
28645 getStackPoints: function(points) {
28646 var series = this,
28647 segment = [],
28648 keys = [],
28649 xAxis = this.xAxis,
28650 yAxis = this.yAxis,
28651 stack = yAxis.stacks[this.stackKey],
28652 pointMap = {},
28653 seriesIndex = series.index,
28654 yAxisSeries = yAxis.series,
28655 seriesLength = yAxisSeries.length,
28656 visibleSeries,
28657 upOrDown = pick(yAxis.options.reversedStacks, true) ? 1 : -1,
28658 i;
28659
28660
28661 points = points || this.points;
28662
28663 if (this.options.stacking) {
28664
28665 for (i = 0; i < points.length; i++) {
28666 // Reset after point update (#7326)
28667 points[i].leftNull = points[i].rightNull = null;
28668
28669 // Create a map where we can quickly look up the points by their
28670 // X values.
28671 pointMap[points[i].x] = points[i];
28672 }
28673
28674 // Sort the keys (#1651)
28675 H.objectEach(stack, function(stackX, x) {
28676 if (stackX.total !== null) { // nulled after switching between grouping and not (#1651, #2336)
28677 keys.push(x);
28678 }
28679 });
28680 keys.sort(function(a, b) {
28681 return a - b;
28682 });
28683
28684 visibleSeries = map(yAxisSeries, function() {
28685 return this.visible;
28686 });
28687
28688 each(keys, function(x, idx) {
28689 var y = 0,
28690 stackPoint,
28691 stackedValues;
28692
28693 if (pointMap[x] && !pointMap[x].isNull) {
28694 segment.push(pointMap[x]);
28695
28696 // Find left and right cliff. -1 goes left, 1 goes right.
28697 each([-1, 1], function(direction) {
28698 var nullName = direction === 1 ? 'rightNull' : 'leftNull',
28699 cliffName = direction === 1 ? 'rightCliff' : 'leftCliff',
28700 cliff = 0,
28701 otherStack = stack[keys[idx + direction]];
28702
28703 // If there is a stack next to this one, to the left or to the right...
28704 if (otherStack) {
28705 i = seriesIndex;
28706 while (i >= 0 && i < seriesLength) { // Can go either up or down, depending on reversedStacks
28707 stackPoint = otherStack.points[i];
28708 if (!stackPoint) {
28709 // If the next point in this series is missing, mark the point
28710 // with point.leftNull or point.rightNull = true.
28711 if (i === seriesIndex) {
28712 pointMap[x][nullName] = true;
28713
28714 // If there are missing points in the next stack in any of the
28715 // series below this one, we need to substract the missing values
28716 // and add a hiatus to the left or right.
28717 } else if (visibleSeries[i]) {
28718 stackedValues = stack[x].points[i];
28719 if (stackedValues) {
28720 cliff -= stackedValues[1] - stackedValues[0];
28721 }
28722 }
28723 }
28724 // When reversedStacks is true, loop up, else loop down
28725 i += upOrDown;
28726 }
28727 }
28728 pointMap[x][cliffName] = cliff;
28729 });
28730
28731
28732 // There is no point for this X value in this series, so we
28733 // insert a dummy point in order for the areas to be drawn
28734 // correctly.
28735 } else {
28736
28737 // Loop down the stack to find the series below this one that has
28738 // a value (#1991)
28739 i = seriesIndex;
28740 while (i >= 0 && i < seriesLength) {
28741 stackPoint = stack[x].points[i];
28742 if (stackPoint) {
28743 y = stackPoint[1];
28744 break;
28745 }
28746 // When reversedStacks is true, loop up, else loop down
28747 i += upOrDown;
28748 }
28749 y = yAxis.translate(y, 0, 1, 0, 1); // #6272
28750 segment.push({
28751 isNull: true,
28752 plotX: xAxis.translate(x, 0, 0, 0, 1), // #6272
28753 x: x,
28754 plotY: y,
28755 yBottom: y
28756 });
28757 }
28758 });
28759
28760 }
28761
28762 return segment;
28763 },
28764
28765 getGraphPath: function(points) {
28766 var getGraphPath = Series.prototype.getGraphPath,
28767 graphPath,
28768 options = this.options,
28769 stacking = options.stacking,
28770 yAxis = this.yAxis,
28771 topPath,
28772 bottomPath,
28773 bottomPoints = [],
28774 graphPoints = [],
28775 seriesIndex = this.index,
28776 i,
28777 areaPath,
28778 plotX,
28779 stacks = yAxis.stacks[this.stackKey],
28780 threshold = options.threshold,
28781 translatedThreshold = yAxis.getThreshold(options.threshold),
28782 isNull,
28783 yBottom,
28784 connectNulls = options.connectNulls || stacking === 'percent',
28785 /**
28786 * To display null points in underlying stacked series, this series graph must be
28787 * broken, and the area also fall down to fill the gap left by the null point. #2069
28788 */
28789 addDummyPoints = function(i, otherI, side) {
28790 var point = points[i],
28791 stackedValues = stacking && stacks[point.x].points[seriesIndex],
28792 nullVal = point[side + 'Null'] || 0,
28793 cliffVal = point[side + 'Cliff'] || 0,
28794 top,
28795 bottom,
28796 isNull = true;
28797
28798 if (cliffVal || nullVal) {
28799
28800 top = (nullVal ? stackedValues[0] : stackedValues[1]) + cliffVal;
28801 bottom = stackedValues[0] + cliffVal;
28802 isNull = !!nullVal;
28803
28804 } else if (!stacking && points[otherI] && points[otherI].isNull) {
28805 top = bottom = threshold;
28806 }
28807
28808 // Add to the top and bottom line of the area
28809 if (top !== undefined) {
28810 graphPoints.push({
28811 plotX: plotX,
28812 plotY: top === null ? translatedThreshold : yAxis.getThreshold(top),
28813 isNull: isNull,
28814 isCliff: true
28815 });
28816 bottomPoints.push({
28817 plotX: plotX,
28818 plotY: bottom === null ? translatedThreshold : yAxis.getThreshold(bottom),
28819 doCurve: false // #1041, gaps in areaspline areas
28820 });
28821 }
28822 };
28823
28824 // Find what points to use
28825 points = points || this.points;
28826
28827 // Fill in missing points
28828 if (stacking) {
28829 points = this.getStackPoints(points);
28830 }
28831
28832 for (i = 0; i < points.length; i++) {
28833 isNull = points[i].isNull;
28834 plotX = pick(points[i].rectPlotX, points[i].plotX);
28835 yBottom = pick(points[i].yBottom, translatedThreshold);
28836
28837 if (!isNull || connectNulls) {
28838
28839 if (!connectNulls) {
28840 addDummyPoints(i, i - 1, 'left');
28841 }
28842
28843 if (!(isNull && !stacking && connectNulls)) { // Skip null point when stacking is false and connectNulls true
28844 graphPoints.push(points[i]);
28845 bottomPoints.push({
28846 x: i,
28847 plotX: plotX,
28848 plotY: yBottom
28849 });
28850 }
28851
28852 if (!connectNulls) {
28853 addDummyPoints(i, i + 1, 'right');
28854 }
28855 }
28856 }
28857
28858 topPath = getGraphPath.call(this, graphPoints, true, true);
28859
28860 bottomPoints.reversed = true;
28861 bottomPath = getGraphPath.call(this, bottomPoints, true, true);
28862 if (bottomPath.length) {
28863 bottomPath[0] = 'L';
28864 }
28865
28866 areaPath = topPath.concat(bottomPath);
28867 graphPath = getGraphPath.call(this, graphPoints, false, connectNulls); // TODO: don't set leftCliff and rightCliff when connectNulls?
28868
28869 areaPath.xMap = topPath.xMap;
28870 this.areaPath = areaPath;
28871
28872 return graphPath;
28873 },
28874
28875 /**
28876 * Draw the graph and the underlying area. This method calls the Series base
28877 * function and adds the area. The areaPath is calculated in the getSegmentPath
28878 * method called from Series.prototype.drawGraph.
28879 */
28880 drawGraph: function() {
28881
28882 // Define or reset areaPath
28883 this.areaPath = [];
28884
28885 // Call the base method
28886 Series.prototype.drawGraph.apply(this);
28887
28888 // Define local variables
28889 var series = this,
28890 areaPath = this.areaPath,
28891 options = this.options,
28892 zones = this.zones,
28893 props = [
28894 [
28895 'area',
28896 'highcharts-area',
28897
28898 this.color,
28899 options.fillColor
28900
28901 ]
28902 ]; // area name, main color, fill color
28903
28904 each(zones, function(zone, i) {
28905 props.push([
28906 'zone-area-' + i,
28907 'highcharts-area highcharts-zone-area-' + i + ' ' + zone.className,
28908
28909 zone.color || series.color,
28910 zone.fillColor || options.fillColor
28911
28912 ]);
28913 });
28914
28915 each(props, function(prop) {
28916 var areaKey = prop[0],
28917 area = series[areaKey];
28918
28919 // Create or update the area
28920 if (area) { // update
28921 area.endX = series.preventGraphAnimation ? null : areaPath.xMap;
28922 area.animate({
28923 d: areaPath
28924 });
28925
28926 } else { // create
28927 area = series[areaKey] = series.chart.renderer.path(areaPath)
28928 .addClass(prop[1])
28929 .attr({
28930
28931 fill: pick(
28932 prop[3],
28933 color(prop[2]).setOpacity(pick(options.fillOpacity, 0.75)).get()
28934 ),
28935
28936 zIndex: 0 // #1069
28937 }).add(series.group);
28938 area.isArea = true;
28939 }
28940 area.startX = areaPath.xMap;
28941 area.shiftUnit = options.step ? 2 : 1;
28942 });
28943 },
28944
28945 drawLegendSymbol: LegendSymbolMixin.drawRectangle
28946 });
28947
28948 /**
28949 * A `area` series. If the [type](#series.area.type) option is not
28950 * specified, it is inherited from [chart.type](#chart.type).
28951 *
28952 * For options that apply to multiple series, it is recommended to add
28953 * them to the [plotOptions.series](#plotOptions.series) options structure.
28954 * To apply to all series of this specific type, apply it to [plotOptions.
28955 * area](#plotOptions.area).
28956 *
28957 * @type {Object}
28958 * @extends series,plotOptions.area
28959 * @excluding dataParser,dataURL
28960 * @product highcharts highstock
28961 * @apioption series.area
28962 */
28963
28964 /**
28965 * An array of data points for the series. For the `area` series type,
28966 * points can be given in the following ways:
28967 *
28968 * 1. An array of numerical values. In this case, the numerical values
28969 * will be interpreted as `y` options. The `x` values will be automatically
28970 * calculated, either starting at 0 and incremented by 1, or from `pointStart`
28971 * and `pointInterval` given in the series options. If the axis has
28972 * categories, these will be used. Example:
28973 *
28974 * ```js
28975 * data: [0, 5, 3, 5]
28976 * ```
28977 *
28978 * 2. An array of arrays with 2 values. In this case, the values correspond
28979 * to `x,y`. If the first value is a string, it is applied as the name
28980 * of the point, and the `x` value is inferred.
28981 *
28982 * ```js
28983 * data: [
28984 * [0, 9],
28985 * [1, 7],
28986 * [2, 6]
28987 * ]
28988 * ```
28989 *
28990 * 3. An array of objects with named values. The objects are point
28991 * configuration objects as seen below. If the total number of data
28992 * points exceeds the series' [turboThreshold](#series.area.turboThreshold),
28993 * this option is not available.
28994 *
28995 * ```js
28996 * data: [{
28997 * x: 1,
28998 * y: 9,
28999 * name: "Point2",
29000 * color: "#00FF00"
29001 * }, {
29002 * x: 1,
29003 * y: 6,
29004 * name: "Point1",
29005 * color: "#FF00FF"
29006 * }]
29007 * ```
29008 *
29009 * @type {Array<Object|Array|Number>}
29010 * @extends series.line.data
29011 * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
29012 * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
29013 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
29014 * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
29015 * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
29016 * @product highcharts highstock
29017 * @apioption series.area.data
29018 */
29019
29020 }(Highcharts));
29021 (function(H) {
29022 /**
29023 * (c) 2010-2017 Torstein Honsi
29024 *
29025 * License: www.highcharts.com/license
29026 */
29027 var pick = H.pick,
29028 seriesType = H.seriesType;
29029
29030 /**
29031 * A spline series is a special type of line series, where the segments between
29032 * the data points are smoothed.
29033 *
29034 * @sample {highcharts} highcharts/demo/spline-irregular-time/ Spline chart
29035 * @sample {highstock} stock/demo/spline/ Spline chart
29036 *
29037 * @extends plotOptions.series
29038 * @excluding step
29039 * @product highcharts highstock
29040 * @apioption plotOptions.spline
29041 */
29042
29043 /**
29044 * Spline series type.
29045 * @constructor seriesTypes.spline
29046 * @extends {Series}
29047 */
29048 seriesType('spline', 'line', {}, /** @lends seriesTypes.spline.prototype */ {
29049 /**
29050 * Get the spline segment from a given point's previous neighbour to the
29051 * given point
29052 */
29053 getPointSpline: function(points, point, i) {
29054 var
29055 // 1 means control points midway between points, 2 means 1/3 from
29056 // the point, 3 is 1/4 etc
29057 smoothing = 1.5,
29058 denom = smoothing + 1,
29059 plotX = point.plotX,
29060 plotY = point.plotY,
29061 lastPoint = points[i - 1],
29062 nextPoint = points[i + 1],
29063 leftContX,
29064 leftContY,
29065 rightContX,
29066 rightContY,
29067 ret;
29068
29069 function doCurve(otherPoint) {
29070 return otherPoint &&
29071 !otherPoint.isNull &&
29072 otherPoint.doCurve !== false &&
29073 !point.isCliff; // #6387, area splines next to null
29074 }
29075
29076 // Find control points
29077 if (doCurve(lastPoint) && doCurve(nextPoint)) {
29078 var lastX = lastPoint.plotX,
29079 lastY = lastPoint.plotY,
29080 nextX = nextPoint.plotX,
29081 nextY = nextPoint.plotY,
29082 correction = 0;
29083
29084 leftContX = (smoothing * plotX + lastX) / denom;
29085 leftContY = (smoothing * plotY + lastY) / denom;
29086 rightContX = (smoothing * plotX + nextX) / denom;
29087 rightContY = (smoothing * plotY + nextY) / denom;
29088
29089 // Have the two control points make a straight line through main
29090 // point
29091 if (rightContX !== leftContX) { // #5016, division by zero
29092 correction = ((rightContY - leftContY) * (rightContX - plotX)) /
29093 (rightContX - leftContX) + plotY - rightContY;
29094 }
29095
29096 leftContY += correction;
29097 rightContY += correction;
29098
29099 // to prevent false extremes, check that control points are between
29100 // neighbouring points' y values
29101 if (leftContY > lastY && leftContY > plotY) {
29102 leftContY = Math.max(lastY, plotY);
29103 // mirror of left control point
29104 rightContY = 2 * plotY - leftContY;
29105 } else if (leftContY < lastY && leftContY < plotY) {
29106 leftContY = Math.min(lastY, plotY);
29107 rightContY = 2 * plotY - leftContY;
29108 }
29109 if (rightContY > nextY && rightContY > plotY) {
29110 rightContY = Math.max(nextY, plotY);
29111 leftContY = 2 * plotY - rightContY;
29112 } else if (rightContY < nextY && rightContY < plotY) {
29113 rightContY = Math.min(nextY, plotY);
29114 leftContY = 2 * plotY - rightContY;
29115 }
29116
29117 // record for drawing in next point
29118 point.rightContX = rightContX;
29119 point.rightContY = rightContY;
29120
29121
29122 }
29123
29124 // Visualize control points for debugging
29125 /*
29126 if (leftContX) {
29127 this.chart.renderer.circle(
29128 leftContX + this.chart.plotLeft,
29129 leftContY + this.chart.plotTop,
29130 2
29131 )
29132 .attr({
29133 stroke: 'red',
29134 'stroke-width': 2,
29135 fill: 'none',
29136 zIndex: 9
29137 })
29138 .add();
29139 this.chart.renderer.path(['M', leftContX + this.chart.plotLeft,
29140 leftContY + this.chart.plotTop,
29141 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
29142 .attr({
29143 stroke: 'red',
29144 'stroke-width': 2,
29145 zIndex: 9
29146 })
29147 .add();
29148 }
29149 if (rightContX) {
29150 this.chart.renderer.circle(
29151 rightContX + this.chart.plotLeft,
29152 rightContY + this.chart.plotTop,
29153 2
29154 )
29155 .attr({
29156 stroke: 'green',
29157 'stroke-width': 2,
29158 fill: 'none',
29159 zIndex: 9
29160 })
29161 .add();
29162 this.chart.renderer.path(['M', rightContX + this.chart.plotLeft,
29163 rightContY + this.chart.plotTop,
29164 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
29165 .attr({
29166 stroke: 'green',
29167 'stroke-width': 2,
29168 zIndex: 9
29169 })
29170 .add();
29171 }
29172 // */
29173 ret = [
29174 'C',
29175 pick(lastPoint.rightContX, lastPoint.plotX),
29176 pick(lastPoint.rightContY, lastPoint.plotY),
29177 pick(leftContX, plotX),
29178 pick(leftContY, plotY),
29179 plotX,
29180 plotY
29181 ];
29182 // reset for updating series later
29183 lastPoint.rightContX = lastPoint.rightContY = null;
29184 return ret;
29185 }
29186 });
29187
29188 /**
29189 * A `spline` series. If the [type](#series.spline.type) option is
29190 * not specified, it is inherited from [chart.type](#chart.type).
29191 *
29192 * For options that apply to multiple series, it is recommended to add
29193 * them to the [plotOptions.series](#plotOptions.series) options structure.
29194 * To apply to all series of this specific type, apply it to [plotOptions.
29195 * spline](#plotOptions.spline).
29196 *
29197 * @type {Object}
29198 * @extends series,plotOptions.spline
29199 * @excluding dataParser,dataURL
29200 * @product highcharts highstock
29201 * @apioption series.spline
29202 */
29203
29204 /**
29205 * An array of data points for the series. For the `spline` series type,
29206 * points can be given in the following ways:
29207 *
29208 * 1. An array of numerical values. In this case, the numerical values
29209 * will be interpreted as `y` options. The `x` values will be automatically
29210 * calculated, either starting at 0 and incremented by 1, or from `pointStart`
29211 * and `pointInterval` given in the series options. If the axis has
29212 * categories, these will be used. Example:
29213 *
29214 * ```js
29215 * data: [0, 5, 3, 5]
29216 * ```
29217 *
29218 * 2. An array of arrays with 2 values. In this case, the values correspond
29219 * to `x,y`. If the first value is a string, it is applied as the name
29220 * of the point, and the `x` value is inferred.
29221 *
29222 * ```js
29223 * data: [
29224 * [0, 9],
29225 * [1, 2],
29226 * [2, 8]
29227 * ]
29228 * ```
29229 *
29230 * 3. An array of objects with named values. The objects are point
29231 * configuration objects as seen below. If the total number of data
29232 * points exceeds the series' [turboThreshold](#series.spline.turboThreshold),
29233 * this option is not available.
29234 *
29235 * ```js
29236 * data: [{
29237 * x: 1,
29238 * y: 9,
29239 * name: "Point2",
29240 * color: "#00FF00"
29241 * }, {
29242 * x: 1,
29243 * y: 0,
29244 * name: "Point1",
29245 * color: "#FF00FF"
29246 * }]
29247 * ```
29248 *
29249 * @type {Array<Object|Array|Number>}
29250 * @extends series.line.data
29251 * @sample {highcharts} highcharts/chart/reflow-true/
29252 * Numerical values
29253 * @sample {highcharts} highcharts/series/data-array-of-arrays/
29254 * Arrays of numeric x and y
29255 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
29256 * Arrays of datetime x and y
29257 * @sample {highcharts} highcharts/series/data-array-of-name-value/
29258 * Arrays of point.name and y
29259 * @sample {highcharts} highcharts/series/data-array-of-objects/
29260 * Config objects
29261 * @product highcharts highstock
29262 * @apioption series.spline.data
29263 */
29264
29265 }(Highcharts));
29266 (function(H) {
29267 /**
29268 * (c) 2010-2017 Torstein Honsi
29269 *
29270 * License: www.highcharts.com/license
29271 */
29272 var areaProto = H.seriesTypes.area.prototype,
29273 defaultPlotOptions = H.defaultPlotOptions,
29274 LegendSymbolMixin = H.LegendSymbolMixin,
29275 seriesType = H.seriesType;
29276 /**
29277 * AreaSplineSeries object
29278 */
29279 /**
29280 * The area spline series is an area series where the graph between the points
29281 * is smoothed into a spline.
29282 *
29283 * @extends plotOptions.area
29284 * @excluding step
29285 * @sample {highcharts} highcharts/demo/areaspline/ Area spline chart
29286 * @sample {highstock} stock/demo/areaspline/ Area spline chart
29287 * @product highcharts highstock
29288 * @apioption plotOptions.areaspline
29289 */
29290 seriesType('areaspline', 'spline', defaultPlotOptions.area, {
29291 getStackPoints: areaProto.getStackPoints,
29292 getGraphPath: areaProto.getGraphPath,
29293 drawGraph: areaProto.drawGraph,
29294 drawLegendSymbol: LegendSymbolMixin.drawRectangle
29295 });
29296 /**
29297 * A `areaspline` series. If the [type](#series.areaspline.type) option
29298 * is not specified, it is inherited from [chart.type](#chart.type).
29299 *
29300 *
29301 * For options that apply to multiple series, it is recommended to add
29302 * them to the [plotOptions.series](#plotOptions.series) options structure.
29303 * To apply to all series of this specific type, apply it to [plotOptions.
29304 * areaspline](#plotOptions.areaspline).
29305 *
29306 * @type {Object}
29307 * @extends series,plotOptions.areaspline
29308 * @excluding dataParser,dataURL
29309 * @product highcharts highstock
29310 * @apioption series.areaspline
29311 */
29312
29313
29314 /**
29315 * An array of data points for the series. For the `areaspline` series
29316 * type, points can be given in the following ways:
29317 *
29318 * 1. An array of numerical values. In this case, the numerical values
29319 * will be interpreted as `y` options. The `x` values will be automatically
29320 * calculated, either starting at 0 and incremented by 1, or from `pointStart`
29321 * and `pointInterval` given in the series options. If the axis has
29322 * categories, these will be used. Example:
29323 *
29324 * ```js
29325 * data: [0, 5, 3, 5]
29326 * ```
29327 *
29328 * 2. An array of arrays with 2 values. In this case, the values correspond
29329 * to `x,y`. If the first value is a string, it is applied as the name
29330 * of the point, and the `x` value is inferred.
29331 *
29332 * ```js
29333 * data: [
29334 * [0, 10],
29335 * [1, 9],
29336 * [2, 3]
29337 * ]
29338 * ```
29339 *
29340 * 3. An array of objects with named values. The objects are point
29341 * configuration objects as seen below. If the total number of data
29342 * points exceeds the series' [turboThreshold](#series.areaspline.turboThreshold),
29343 * this option is not available.
29344 *
29345 * ```js
29346 * data: [{
29347 * x: 1,
29348 * y: 4,
29349 * name: "Point2",
29350 * color: "#00FF00"
29351 * }, {
29352 * x: 1,
29353 * y: 4,
29354 * name: "Point1",
29355 * color: "#FF00FF"
29356 * }]
29357 * ```
29358 *
29359 * @type {Array<Object|Array|Number>}
29360 * @extends series.line.data
29361 * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
29362 * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
29363 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
29364 * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
29365 * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
29366 * @product highcharts highstock
29367 * @apioption series.areaspline.data
29368 */
29369
29370
29371
29372 }(Highcharts));
29373 (function(H) {
29374 /**
29375 * (c) 2010-2017 Torstein Honsi
29376 *
29377 * License: www.highcharts.com/license
29378 */
29379 var animObject = H.animObject,
29380 color = H.color,
29381 each = H.each,
29382 extend = H.extend,
29383 isNumber = H.isNumber,
29384 LegendSymbolMixin = H.LegendSymbolMixin,
29385 merge = H.merge,
29386 noop = H.noop,
29387 pick = H.pick,
29388 Series = H.Series,
29389 seriesType = H.seriesType,
29390 svg = H.svg;
29391 /**
29392 * The column series type.
29393 *
29394 * @constructor seriesTypes.column
29395 * @augments Series
29396 */
29397
29398 /**
29399 * Column series display one column per value along an X axis.
29400 *
29401 * @sample {highcharts} highcharts/demo/column-basic/ Column chart
29402 * @sample {highstock} stock/demo/column/ Column chart
29403 *
29404 * @extends {plotOptions.line}
29405 * @product highcharts highstock
29406 * @excluding connectNulls,dashStyle,gapSize,gapUnit,linecap,lineWidth,marker,
29407 * connectEnds,step
29408 * @optionparent plotOptions.column
29409 */
29410 seriesType('column', 'line', {
29411
29412 /**
29413 * The corner radius of the border surrounding each column or bar.
29414 *
29415 * @type {Number}
29416 * @sample {highcharts} highcharts/plotoptions/column-borderradius/
29417 * Rounded columns
29418 * @default 0
29419 * @product highcharts highstock
29420 */
29421 borderRadius: 0,
29422
29423 /**
29424 * The width of the border surrounding each column or bar.
29425 *
29426 * In styled mode, the stroke width can be set with the `.highcharts-point`
29427 * rule.
29428 *
29429 * @type {Number}
29430 * @sample {highcharts} highcharts/plotoptions/column-borderwidth/
29431 * 2px black border
29432 * @default 1
29433 * @product highcharts highstock
29434 * @apioption plotOptions.column.borderWidth
29435 */
29436
29437 /**
29438 * When using automatic point colors pulled from the `options.colors`
29439 * collection, this option determines whether the chart should receive
29440 * one color per series or one color per point.
29441 *
29442 * @type {Boolean}
29443 * @see [series colors](#plotOptions.column.colors)
29444 * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-false/
29445 * False by default
29446 * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-true/
29447 * True
29448 * @default false
29449 * @since 2.0
29450 * @product highcharts highstock
29451 * @apioption plotOptions.column.colorByPoint
29452 */
29453
29454 /**
29455 * A series specific or series type specific color set to apply instead
29456 * of the global [colors](#colors) when [colorByPoint](#plotOptions.
29457 * column.colorByPoint) is true.
29458 *
29459 * @type {Array<Color>}
29460 * @since 3.0
29461 * @product highcharts highstock
29462 * @apioption plotOptions.column.colors
29463 */
29464
29465 /**
29466 * When true, each column edge is rounded to its nearest pixel in order
29467 * to render sharp on screen. In some cases, when there are a lot of
29468 * densely packed columns, this leads to visible difference in column
29469 * widths or distance between columns. In these cases, setting `crisp`
29470 * to `false` may look better, even though each column is rendered
29471 * blurry.
29472 *
29473 * @type {Boolean}
29474 * @sample {highcharts} highcharts/plotoptions/column-crisp-false/
29475 * Crisp is false
29476 * @default true
29477 * @since 5.0.10
29478 * @product highcharts highstock
29479 */
29480 crisp: true,
29481
29482 /**
29483 * Padding between each value groups, in x axis units.
29484 *
29485 * @type {Number}
29486 * @sample {highcharts} highcharts/plotoptions/column-grouppadding-default/
29487 * 0.2 by default
29488 * @sample {highcharts} highcharts/plotoptions/column-grouppadding-none/
29489 * No group padding - all columns are evenly spaced
29490 * @default 0.2
29491 * @product highcharts highstock
29492 */
29493 groupPadding: 0.2,
29494
29495 /**
29496 * Whether to group non-stacked columns or to let them render independent
29497 * of each other. Non-grouped columns will be laid out individually
29498 * and overlap each other.
29499 *
29500 * @type {Boolean}
29501 * @sample {highcharts} highcharts/plotoptions/column-grouping-false/
29502 * Grouping disabled
29503 * @sample {highstock} highcharts/plotoptions/column-grouping-false/
29504 * Grouping disabled
29505 * @default true
29506 * @since 2.3.0
29507 * @product highcharts highstock
29508 * @apioption plotOptions.column.grouping
29509 */
29510
29511 marker: null, // point options are specified in the base options
29512
29513 /**
29514 * The maximum allowed pixel width for a column, translated to the height
29515 * of a bar in a bar chart. This prevents the columns from becoming
29516 * too wide when there is a small number of points in the chart.
29517 *
29518 * @type {Number}
29519 * @see [pointWidth](#plotOptions.column.pointWidth)
29520 * @sample {highcharts} highcharts/plotoptions/column-maxpointwidth-20/
29521 * Limited to 50
29522 * @sample {highstock} highcharts/plotoptions/column-maxpointwidth-20/
29523 * Limited to 50
29524 * @default null
29525 * @since 4.1.8
29526 * @product highcharts highstock
29527 * @apioption plotOptions.column.maxPointWidth
29528 */
29529
29530 /**
29531 * Padding between each column or bar, in x axis units.
29532 *
29533 * @type {Number}
29534 * @sample {highcharts} highcharts/plotoptions/column-pointpadding-default/
29535 * 0.1 by default
29536 * @sample {highcharts} highcharts/plotoptions/column-pointpadding-025/
29537 * 0.25
29538 * @sample {highcharts} highcharts/plotoptions/column-pointpadding-none/
29539 * 0 for tightly packed columns
29540 * @default 0.1
29541 * @product highcharts highstock
29542 */
29543 pointPadding: 0.1,
29544
29545 /**
29546 * A pixel value specifying a fixed width for each column or bar. When
29547 * `null`, the width is calculated from the `pointPadding` and
29548 * `groupPadding`.
29549 *
29550 * @type {Number}
29551 * @see [maxPointWidth](#plotOptions.column.maxPointWidth)
29552 * @sample {highcharts} highcharts/plotoptions/column-pointwidth-20/
29553 * 20px wide columns regardless of chart width or the amount of data
29554 * points
29555 * @default null
29556 * @since 1.2.5
29557 * @product highcharts highstock
29558 * @apioption plotOptions.column.pointWidth
29559 */
29560
29561 /**
29562 * The minimal height for a column or width for a bar. By default,
29563 * 0 values are not shown. To visualize a 0 (or close to zero) point,
29564 * set the minimal point length to a pixel value like 3\. In stacked
29565 * column charts, minPointLength might not be respected for tightly
29566 * packed values.
29567 *
29568 * @type {Number}
29569 * @sample {highcharts} highcharts/plotoptions/column-minpointlength/
29570 * Zero base value
29571 * @sample {highcharts} highcharts/plotoptions/column-minpointlength-pos-and-neg/
29572 * Positive and negative close to zero values
29573 * @default 0
29574 * @product highcharts highstock
29575 */
29576 minPointLength: 0,
29577
29578 /**
29579 * When the series contains less points than the crop threshold, all
29580 * points are drawn, event if the points fall outside the visible plot
29581 * area at the current zoom. The advantage of drawing all points (including
29582 * markers and columns), is that animation is performed on updates.
29583 * On the other hand, when the series contains more points than the
29584 * crop threshold, the series data is cropped to only contain points
29585 * that fall within the plot area. The advantage of cropping away invisible
29586 * points is to increase performance on large series. .
29587 *
29588 * @type {Number}
29589 * @default 50
29590 * @product highcharts highstock
29591 */
29592 cropThreshold: 50,
29593
29594 /**
29595 * The X axis range that each point is valid for. This determines the
29596 * width of the column. On a categorized axis, the range will be 1
29597 * by default (one category unit). On linear and datetime axes, the
29598 * range will be computed as the distance between the two closest data
29599 * points.
29600 *
29601 * The default `null` means it is computed automatically, but this option
29602 * can be used to override the automatic value.
29603 *
29604 * @type {Number}
29605 * @sample {highcharts} highcharts/plotoptions/column-pointrange/
29606 * Set the point range to one day on a data set with one week
29607 * between the points
29608 * @default null
29609 * @since 2.3
29610 * @product highcharts highstock
29611 */
29612 pointRange: null,
29613
29614 states: {
29615
29616 /**
29617 * @extends plotOptions.series.states.hover
29618 * @excluding halo,lineWidth,lineWidthPlus,marker
29619 * @product highcharts highstock
29620 */
29621 hover: {
29622
29623 /**
29624 * @ignore-option
29625 */
29626 halo: false,
29627 /**
29628 * A specific border color for the hovered point. Defaults to
29629 * inherit the normal state border color.
29630 *
29631 * @type {Color}
29632 * @product highcharts
29633 * @apioption plotOptions.column.states.hover.borderColor
29634 */
29635
29636 /**
29637 * A specific color for the hovered point.
29638 *
29639 * @type {Color}
29640 * @default undefined
29641 * @product highcharts
29642 * @apioption plotOptions.column.states.hover.color
29643 */
29644
29645
29646
29647 /**
29648 * How much to brighten the point on interaction. Requires the main
29649 * color to be defined in hex or rgb(a) format.
29650 *
29651 * In styled mode, the hover brightening is by default replaced
29652 * with a fill-opacity set in the `.highcharts-point:hover` rule.
29653 *
29654 * @type {Number}
29655 * @sample {highcharts} highcharts/plotoptions/column-states-hover-brightness/
29656 * Brighten by 0.5
29657 * @default 0.1
29658 * @product highcharts highstock
29659 */
29660 brightness: 0.1
29661
29662
29663 },
29664
29665
29666 select: {
29667 color: '#cccccc',
29668 borderColor: '#000000'
29669 }
29670
29671 },
29672
29673 dataLabels: {
29674 align: null, // auto
29675 verticalAlign: null, // auto
29676 y: null
29677 },
29678
29679 /**
29680 * When this is true, the series will not cause the Y axis to cross
29681 * the zero plane (or [threshold](#plotOptions.series.threshold) option)
29682 * unless the data actually crosses the plane.
29683 *
29684 * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
29685 * 3 will make the Y axis show negative values according to the `minPadding`
29686 * option. If `softThreshold` is `true`, the Y axis starts at 0.
29687 *
29688 * @type {Boolean}
29689 * @default {highcharts} true
29690 * @default {highstock} false
29691 * @since 4.1.9
29692 * @product highcharts highstock
29693 */
29694 softThreshold: false,
29695
29696 // false doesn't work well: http://jsfiddle.net/highcharts/hz8fopan/14/
29697 /** @ignore */
29698 startFromThreshold: true,
29699
29700 stickyTracking: false,
29701
29702 tooltip: {
29703 distance: 6
29704 },
29705
29706 /**
29707 * The Y axis value to serve as the base for the columns, for distinguishing
29708 * between values above and below a threshold. If `null`, the columns
29709 * extend from the padding Y axis minimum.
29710 *
29711 * @type {Number}
29712 * @default 0
29713 * @since 2.0
29714 * @product highcharts
29715 */
29716 threshold: 0,
29717
29718
29719 /**
29720 * The color of the border surrounding each column or bar.
29721 *
29722 * In styled mode, the border stroke can be set with the `.highcharts-point`
29723 * rule.
29724 *
29725 * @type {Color}
29726 * @sample {highcharts} highcharts/plotoptions/column-bordercolor/
29727 * Dark gray border
29728 * @default #ffffff
29729 * @product highcharts highstock
29730 */
29731 borderColor: '#ffffff'
29732 // borderWidth: 1
29733
29734
29735 }, /** @lends seriesTypes.column.prototype */ {
29736 cropShoulder: 0,
29737 // When tooltip is not shared, this series (and derivatives) requires direct
29738 // touch/hover. KD-tree does not apply.
29739 directTouch: true,
29740 trackerGroups: ['group', 'dataLabelsGroup'],
29741 // use separate negative stacks, unlike area stacks where a negative point
29742 // is substracted from previous (#1910)
29743 negStacks: true,
29744
29745 /**
29746 * Initialize the series. Extends the basic Series.init method by
29747 * marking other series of the same type as dirty.
29748 *
29749 * @function #init
29750 * @memberOf seriesTypes.column
29751 *
29752 */
29753 init: function() {
29754 Series.prototype.init.apply(this, arguments);
29755
29756 var series = this,
29757 chart = series.chart;
29758
29759 // if the series is added dynamically, force redraw of other
29760 // series affected by a new column
29761 if (chart.hasRendered) {
29762 each(chart.series, function(otherSeries) {
29763 if (otherSeries.type === series.type) {
29764 otherSeries.isDirty = true;
29765 }
29766 });
29767 }
29768 },
29769
29770 /**
29771 * Return the width and x offset of the columns adjusted for grouping,
29772 * groupPadding, pointPadding, pointWidth etc.
29773 */
29774 getColumnMetrics: function() {
29775
29776 var series = this,
29777 options = series.options,
29778 xAxis = series.xAxis,
29779 yAxis = series.yAxis,
29780 reversedXAxis = xAxis.reversed,
29781 stackKey,
29782 stackGroups = {},
29783 columnCount = 0;
29784
29785 // Get the total number of column type series. This is called on every
29786 // series. Consider moving this logic to a chart.orderStacks() function
29787 // and call it on init, addSeries and removeSeries
29788 if (options.grouping === false) {
29789 columnCount = 1;
29790 } else {
29791 each(series.chart.series, function(otherSeries) {
29792 var otherOptions = otherSeries.options,
29793 otherYAxis = otherSeries.yAxis,
29794 columnIndex;
29795 if (
29796 otherSeries.type === series.type &&
29797 (
29798 otherSeries.visible ||
29799 !series.chart.options.chart.ignoreHiddenSeries
29800 ) &&
29801 yAxis.len === otherYAxis.len &&
29802 yAxis.pos === otherYAxis.pos
29803 ) { // #642, #2086
29804 if (otherOptions.stacking) {
29805 stackKey = otherSeries.stackKey;
29806 if (stackGroups[stackKey] === undefined) {
29807 stackGroups[stackKey] = columnCount++;
29808 }
29809 columnIndex = stackGroups[stackKey];
29810 } else if (otherOptions.grouping !== false) { // #1162
29811 columnIndex = columnCount++;
29812 }
29813 otherSeries.columnIndex = columnIndex;
29814 }
29815 });
29816 }
29817
29818 var categoryWidth = Math.min(
29819 Math.abs(xAxis.transA) * (
29820 xAxis.ordinalSlope ||
29821 options.pointRange ||
29822 xAxis.closestPointRange ||
29823 xAxis.tickInterval ||
29824 1
29825 ), // #2610
29826 xAxis.len // #1535
29827 ),
29828 groupPadding = categoryWidth * options.groupPadding,
29829 groupWidth = categoryWidth - 2 * groupPadding,
29830 pointOffsetWidth = groupWidth / (columnCount || 1),
29831 pointWidth = Math.min(
29832 options.maxPointWidth || xAxis.len,
29833 pick(
29834 options.pointWidth,
29835 pointOffsetWidth * (1 - 2 * options.pointPadding)
29836 )
29837 ),
29838 pointPadding = (pointOffsetWidth - pointWidth) / 2,
29839 // #1251, #3737
29840 colIndex = (series.columnIndex || 0) + (reversedXAxis ? 1 : 0),
29841 pointXOffset =
29842 pointPadding +
29843 (
29844 groupPadding +
29845 colIndex * pointOffsetWidth -
29846 (categoryWidth / 2)
29847 ) * (reversedXAxis ? -1 : 1);
29848
29849 // Save it for reading in linked series (Error bars particularly)
29850 series.columnMetrics = {
29851 width: pointWidth,
29852 offset: pointXOffset
29853 };
29854 return series.columnMetrics;
29855
29856 },
29857
29858 /**
29859 * Make the columns crisp. The edges are rounded to the nearest full pixel.
29860 */
29861 crispCol: function(x, y, w, h) {
29862 var chart = this.chart,
29863 borderWidth = this.borderWidth,
29864 xCrisp = -(borderWidth % 2 ? 0.5 : 0),
29865 yCrisp = borderWidth % 2 ? 0.5 : 1,
29866 right,
29867 bottom,
29868 fromTop;
29869
29870 if (chart.inverted && chart.renderer.isVML) {
29871 yCrisp += 1;
29872 }
29873
29874 // Horizontal. We need to first compute the exact right edge, then round
29875 // it and compute the width from there.
29876 if (this.options.crisp) {
29877 right = Math.round(x + w) + xCrisp;
29878 x = Math.round(x) + xCrisp;
29879 w = right - x;
29880 }
29881
29882 // Vertical
29883 bottom = Math.round(y + h) + yCrisp;
29884 fromTop = Math.abs(y) <= 0.5 && bottom > 0.5; // #4504, #4656
29885 y = Math.round(y) + yCrisp;
29886 h = bottom - y;
29887
29888 // Top edges are exceptions
29889 if (fromTop && h) { // #5146
29890 y -= 1;
29891 h += 1;
29892 }
29893
29894 return {
29895 x: x,
29896 y: y,
29897 width: w,
29898 height: h
29899 };
29900 },
29901
29902 /**
29903 * Translate each point to the plot area coordinate system and find shape
29904 * positions
29905 */
29906 translate: function() {
29907 var series = this,
29908 chart = series.chart,
29909 options = series.options,
29910 dense = series.dense =
29911 series.closestPointRange * series.xAxis.transA < 2,
29912 borderWidth = series.borderWidth = pick(
29913 options.borderWidth,
29914 dense ? 0 : 1 // #3635
29915 ),
29916 yAxis = series.yAxis,
29917 threshold = options.threshold,
29918 translatedThreshold = series.translatedThreshold =
29919 yAxis.getThreshold(threshold),
29920 minPointLength = pick(options.minPointLength, 5),
29921 metrics = series.getColumnMetrics(),
29922 pointWidth = metrics.width,
29923 // postprocessed for border width
29924 seriesBarW = series.barW =
29925 Math.max(pointWidth, 1 + 2 * borderWidth),
29926 pointXOffset = series.pointXOffset = metrics.offset;
29927
29928 if (chart.inverted) {
29929 translatedThreshold -= 0.5; // #3355
29930 }
29931
29932 // When the pointPadding is 0, we want the columns to be packed tightly,
29933 // so we allow individual columns to have individual sizes. When
29934 // pointPadding is greater, we strive for equal-width columns (#2694).
29935 if (options.pointPadding) {
29936 seriesBarW = Math.ceil(seriesBarW);
29937 }
29938
29939 Series.prototype.translate.apply(series);
29940
29941 // Record the new values
29942 each(series.points, function(point) {
29943 var yBottom = pick(point.yBottom, translatedThreshold),
29944 safeDistance = 999 + Math.abs(yBottom),
29945 plotY = Math.min(
29946 Math.max(-safeDistance, point.plotY),
29947 yAxis.len + safeDistance
29948 ), // Don't draw too far outside plot area (#1303, #2241, #4264)
29949 barX = point.plotX + pointXOffset,
29950 barW = seriesBarW,
29951 barY = Math.min(plotY, yBottom),
29952 up,
29953 barH = Math.max(plotY, yBottom) - barY;
29954
29955 // Handle options.minPointLength
29956 if (minPointLength && Math.abs(barH) < minPointLength) {
29957 barH = minPointLength;
29958 up = (!yAxis.reversed && !point.negative) ||
29959 (yAxis.reversed && point.negative);
29960
29961 // Reverse zeros if there's no positive value in the series
29962 // in visible range (#7046)
29963 if (
29964 point.y === threshold &&
29965 series.dataMax <= threshold &&
29966 yAxis.min < threshold // and if there's room for it (#7311)
29967 ) {
29968 up = !up;
29969 }
29970
29971 // If stacked...
29972 barY = Math.abs(barY - translatedThreshold) > minPointLength ?
29973 // ...keep position
29974 yBottom - minPointLength :
29975 // #1485, #4051
29976 translatedThreshold - (up ? minPointLength : 0);
29977 }
29978
29979 // Cache for access in polar
29980 point.barX = barX;
29981 point.pointWidth = pointWidth;
29982
29983 // Fix the tooltip on center of grouped columns (#1216, #424, #3648)
29984 point.tooltipPos = chart.inverted ? [
29985 yAxis.len + yAxis.pos - chart.plotLeft - plotY,
29986 series.xAxis.len - barX - barW / 2, barH
29987 ] : [barX + barW / 2, plotY + yAxis.pos - chart.plotTop, barH];
29988
29989 // Register shape type and arguments to be used in drawPoints
29990 point.shapeType = 'rect';
29991 point.shapeArgs = series.crispCol.apply(
29992 series,
29993 point.isNull ?
29994 // #3169, drilldown from null must have a position to work
29995 // from #6585, dataLabel should be placed on xAxis, not
29996 // floating in the middle of the chart
29997 [barX, translatedThreshold, barW, 0] : [barX, barY, barW, barH]
29998 );
29999 });
30000
30001 },
30002
30003 getSymbol: noop,
30004
30005 /**
30006 * Use a solid rectangle like the area series types
30007 */
30008 drawLegendSymbol: LegendSymbolMixin.drawRectangle,
30009
30010
30011 /**
30012 * Columns have no graph
30013 */
30014 drawGraph: function() {
30015 this.group[
30016 this.dense ? 'addClass' : 'removeClass'
30017 ]('highcharts-dense-data');
30018 },
30019
30020
30021 /**
30022 * Get presentational attributes
30023 */
30024 pointAttribs: function(point, state) {
30025 var options = this.options,
30026 stateOptions,
30027 ret,
30028 p2o = this.pointAttrToOptions || {},
30029 strokeOption = p2o.stroke || 'borderColor',
30030 strokeWidthOption = p2o['stroke-width'] || 'borderWidth',
30031 fill = (point && point.color) || this.color,
30032 stroke = (point && point[strokeOption]) || options[strokeOption] ||
30033 this.color || fill, // set to fill when borderColor null
30034 strokeWidth = (point && point[strokeWidthOption]) ||
30035 options[strokeWidthOption] || this[strokeWidthOption] || 0,
30036 dashstyle = options.dashStyle,
30037 zone,
30038 brightness;
30039
30040 // Handle zone colors
30041 if (point && this.zones.length) {
30042 zone = point.getZone();
30043 // When zones are present, don't use point.color (#4267). Changed
30044 // order (#6527)
30045 fill = point.options.color || (zone && zone.color) || this.color;
30046 }
30047
30048 // Select or hover states
30049 if (state) {
30050 stateOptions = merge(
30051 options.states[state],
30052 // #6401
30053 point.options.states && point.options.states[state] || {}
30054 );
30055 brightness = stateOptions.brightness;
30056 fill = stateOptions.color ||
30057 (
30058 brightness !== undefined &&
30059 color(fill).brighten(stateOptions.brightness).get()
30060 ) ||
30061 fill;
30062 stroke = stateOptions[strokeOption] || stroke;
30063 strokeWidth = stateOptions[strokeWidthOption] || strokeWidth;
30064 dashstyle = stateOptions.dashStyle || dashstyle;
30065 }
30066
30067 ret = {
30068 'fill': fill,
30069 'stroke': stroke,
30070 'stroke-width': strokeWidth
30071 };
30072
30073 if (dashstyle) {
30074 ret.dashstyle = dashstyle;
30075 }
30076
30077 return ret;
30078 },
30079
30080
30081 /**
30082 * Draw the columns. For bars, the series.group is rotated, so the same
30083 * coordinates apply for columns and bars. This method is inherited by
30084 * scatter series.
30085 */
30086 drawPoints: function() {
30087 var series = this,
30088 chart = this.chart,
30089 options = series.options,
30090 renderer = chart.renderer,
30091 animationLimit = options.animationLimit || 250,
30092 shapeArgs;
30093
30094 // draw the columns
30095 each(series.points, function(point) {
30096 var plotY = point.plotY,
30097 graphic = point.graphic;
30098
30099 if (isNumber(plotY) && point.y !== null) {
30100 shapeArgs = point.shapeArgs;
30101
30102 if (graphic) { // update
30103 graphic[
30104 chart.pointCount < animationLimit ? 'animate' : 'attr'
30105 ](
30106 merge(shapeArgs)
30107 );
30108
30109 } else {
30110 point.graphic = graphic =
30111 renderer[point.shapeType](shapeArgs)
30112 .add(point.group || series.group);
30113 }
30114
30115 // Border radius is not stylable (#6900)
30116 if (options.borderRadius) {
30117 graphic.attr({
30118 r: options.borderRadius
30119 });
30120 }
30121
30122
30123 // Presentational
30124 graphic
30125 .attr(series.pointAttribs(
30126 point,
30127 point.selected && 'select'
30128 ))
30129 .shadow(
30130 options.shadow,
30131 null,
30132 options.stacking && !options.borderRadius
30133 );
30134
30135
30136 graphic.addClass(point.getClassName(), true);
30137
30138
30139 } else if (graphic) {
30140 point.graphic = graphic.destroy(); // #1269
30141 }
30142 });
30143 },
30144
30145 /**
30146 * Animate the column heights one by one from zero
30147 * @param {Boolean} init Whether to initialize the animation or run it
30148 */
30149 animate: function(init) {
30150 var series = this,
30151 yAxis = this.yAxis,
30152 options = series.options,
30153 inverted = this.chart.inverted,
30154 attr = {},
30155 translateProp = inverted ? 'translateX' : 'translateY',
30156 translateStart,
30157 translatedThreshold;
30158
30159 if (svg) { // VML is too slow anyway
30160 if (init) {
30161 attr.scaleY = 0.001;
30162 translatedThreshold = Math.min(
30163 yAxis.pos + yAxis.len,
30164 Math.max(yAxis.pos, yAxis.toPixels(options.threshold))
30165 );
30166 if (inverted) {
30167 attr.translateX = translatedThreshold - yAxis.len;
30168 } else {
30169 attr.translateY = translatedThreshold;
30170 }
30171 series.group.attr(attr);
30172
30173 } else { // run the animation
30174 translateStart = series.group.attr(translateProp);
30175 series.group.animate({
30176 scaleY: 1
30177 },
30178 extend(animObject(series.options.animation), {
30179 // Do the scale synchronously to ensure smooth updating
30180 // (#5030, #7228)
30181 step: function(val, fx) {
30182
30183 attr[translateProp] =
30184 translateStart +
30185 fx.pos * (yAxis.pos - translateStart);
30186 series.group.attr(attr);
30187 }
30188 }));
30189
30190 // delete this function to allow it only once
30191 series.animate = null;
30192 }
30193 }
30194 },
30195
30196 /**
30197 * Remove this series from the chart
30198 */
30199 remove: function() {
30200 var series = this,
30201 chart = series.chart;
30202
30203 // column and bar series affects other series of the same type
30204 // as they are either stacked or grouped
30205 if (chart.hasRendered) {
30206 each(chart.series, function(otherSeries) {
30207 if (otherSeries.type === series.type) {
30208 otherSeries.isDirty = true;
30209 }
30210 });
30211 }
30212
30213 Series.prototype.remove.apply(series, arguments);
30214 }
30215 });
30216
30217
30218 /**
30219 * A `column` series. If the [type](#series.column.type) option is
30220 * not specified, it is inherited from [chart.type](#chart.type).
30221 *
30222 * For options that apply to multiple series, it is recommended to add
30223 * them to the [plotOptions.series](#plotOptions.series) options structure.
30224 * To apply to all series of this specific type, apply it to [plotOptions.
30225 * column](#plotOptions.column).
30226 *
30227 * @type {Object}
30228 * @extends series,plotOptions.column
30229 * @excluding dataParser,dataURL,marker
30230 * @product highcharts highstock
30231 * @apioption series.column
30232 */
30233
30234 /**
30235 * An array of data points for the series. For the `column` series type,
30236 * points can be given in the following ways:
30237 *
30238 * 1. An array of numerical values. In this case, the numerical values
30239 * will be interpreted as `y` options. The `x` values will be automatically
30240 * calculated, either starting at 0 and incremented by 1, or from `pointStart`
30241 * and `pointInterval` given in the series options. If the axis has
30242 * categories, these will be used. Example:
30243 *
30244 * ```js
30245 * data: [0, 5, 3, 5]
30246 * ```
30247 *
30248 * 2. An array of arrays with 2 values. In this case, the values correspond
30249 * to `x,y`. If the first value is a string, it is applied as the name
30250 * of the point, and the `x` value is inferred.
30251 *
30252 * ```js
30253 * data: [
30254 * [0, 6],
30255 * [1, 2],
30256 * [2, 6]
30257 * ]
30258 * ```
30259 *
30260 * 3. An array of objects with named values. The objects are point
30261 * configuration objects as seen below. If the total number of data
30262 * points exceeds the series' [turboThreshold](#series.column.turboThreshold),
30263 * this option is not available.
30264 *
30265 * ```js
30266 * data: [{
30267 * x: 1,
30268 * y: 9,
30269 * name: "Point2",
30270 * color: "#00FF00"
30271 * }, {
30272 * x: 1,
30273 * y: 6,
30274 * name: "Point1",
30275 * color: "#FF00FF"
30276 * }]
30277 * ```
30278 *
30279 * @type {Array<Object|Array|Number>}
30280 * @extends series.line.data
30281 * @excluding marker
30282 * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
30283 * @sample {highcharts} highcharts/series/data-array-of-arrays/
30284 * Arrays of numeric x and y
30285 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
30286 * Arrays of datetime x and y
30287 * @sample {highcharts} highcharts/series/data-array-of-name-value/
30288 * Arrays of point.name and y
30289 * @sample {highcharts} highcharts/series/data-array-of-objects/
30290 * Config objects
30291 * @product highcharts highstock
30292 * @apioption series.column.data
30293 */
30294
30295
30296 }(Highcharts));
30297 (function(H) {
30298 /**
30299 * (c) 2010-2017 Torstein Honsi
30300 *
30301 * License: www.highcharts.com/license
30302 */
30303
30304 var seriesType = H.seriesType;
30305
30306 /**
30307 * The Bar series class
30308 */
30309 seriesType('bar', 'column', null, {
30310 inverted: true
30311 });
30312 /**
30313 * A bar series is a special type of column series where the columns are
30314 * horizontal.
30315 *
30316 * @sample highcharts/demo/bar-basic/ Bar chart
30317 * @extends {plotOptions.column}
30318 * @product highcharts
30319 * @optionparent plotOptions.bar
30320 */
30321
30322
30323 /**
30324 * A `bar` series. If the [type](#series.bar.type) option is not specified,
30325 * it is inherited from [chart.type](#chart.type).
30326 *
30327 * For options that apply to multiple series, it is recommended to add
30328 * them to the [plotOptions.series](#plotOptions.series) options structure.
30329 * To apply to all series of this specific type, apply it to [plotOptions.
30330 * bar](#plotOptions.bar).
30331 *
30332 * @type {Object}
30333 * @extends series,plotOptions.bar
30334 * @excluding dataParser,dataURL
30335 * @product highcharts
30336 * @apioption series.bar
30337 */
30338
30339 /**
30340 * An array of data points for the series. For the `bar` series type,
30341 * points can be given in the following ways:
30342 *
30343 * 1. An array of numerical values. In this case, the numerical values
30344 * will be interpreted as `y` options. The `x` values will be automatically
30345 * calculated, either starting at 0 and incremented by 1, or from `pointStart`
30346 * and `pointInterval` given in the series options. If the axis has
30347 * categories, these will be used. Example:
30348 *
30349 * ```js
30350 * data: [0, 5, 3, 5]
30351 * ```
30352 *
30353 * 2. An array of arrays with 2 values. In this case, the values correspond
30354 * to `x,y`. If the first value is a string, it is applied as the name
30355 * of the point, and the `x` value is inferred.
30356 *
30357 * ```js
30358 * data: [
30359 * [0, 5],
30360 * [1, 10],
30361 * [2, 3]
30362 * ]
30363 * ```
30364 *
30365 * 3. An array of objects with named values. The objects are point
30366 * configuration objects as seen below. If the total number of data
30367 * points exceeds the series' [turboThreshold](#series.bar.turboThreshold),
30368 * this option is not available.
30369 *
30370 * ```js
30371 * data: [{
30372 * x: 1,
30373 * y: 1,
30374 * name: "Point2",
30375 * color: "#00FF00"
30376 * }, {
30377 * x: 1,
30378 * y: 10,
30379 * name: "Point1",
30380 * color: "#FF00FF"
30381 * }]
30382 * ```
30383 *
30384 * @type {Array<Object|Array|Number>}
30385 * @extends series.column.data
30386 * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
30387 * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
30388 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
30389 * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
30390 * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
30391 * @product highcharts
30392 * @apioption series.bar.data
30393 */
30394
30395 /**
30396 * Alignment of the data label relative to the data point.
30397 *
30398 * @type {String}
30399 * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/
30400 * Data labels inside the bar
30401 * @default left
30402 * @product highcharts
30403 * @apioption plotOptions.bar.dataLabels.align
30404 */
30405
30406 /**
30407 * The x position of the data label relative to the data point.
30408 *
30409 * @type {Number}
30410 * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/
30411 * Data labels inside the bar
30412 * @default 5
30413 * @product highcharts
30414 * @apioption plotOptions.bar.dataLabels.x
30415 */
30416
30417 }(Highcharts));
30418 (function(H) {
30419 /**
30420 * (c) 2010-2017 Torstein Honsi
30421 *
30422 * License: www.highcharts.com/license
30423 */
30424 var Series = H.Series,
30425 seriesType = H.seriesType;
30426
30427 /**
30428 * A scatter plot uses cartesian coordinates to display values for two variables
30429 * for a set of data.
30430 *
30431 * @sample {highcharts} highcharts/demo/scatter/ Scatter plot
30432 *
30433 * @extends {plotOptions.line}
30434 * @product highcharts highstock
30435 * @optionparent plotOptions.scatter
30436 */
30437 seriesType('scatter', 'line', {
30438
30439 /**
30440 * The width of the line connecting the data points.
30441 *
30442 * @type {Number}
30443 * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-none/
30444 * 0 by default
30445 * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-1/
30446 * 1px
30447 * @default 0
30448 * @product highcharts highstock
30449 */
30450 lineWidth: 0,
30451
30452 findNearestPointBy: 'xy',
30453 marker: {
30454 enabled: true // Overrides auto-enabling in line series (#3647)
30455 },
30456
30457 /**
30458 * Sticky tracking of mouse events. When true, the `mouseOut` event
30459 * on a series isn't triggered until the mouse moves over another series,
30460 * or out of the plot area. When false, the `mouseOut` event on a series
30461 * is triggered when the mouse leaves the area around the series' graph
30462 * or markers. This also implies the tooltip. When `stickyTracking`
30463 * is false and `tooltip.shared` is false, the tooltip will be hidden
30464 * when moving the mouse between series.
30465 *
30466 * @type {Boolean}
30467 * @default false
30468 * @product highcharts highstock
30469 * @apioption plotOptions.scatter.stickyTracking
30470 */
30471
30472 /**
30473 * A configuration object for the tooltip rendering of each single
30474 * series. Properties are inherited from <a class="internal">#tooltip</a>.
30475 * Overridable properties are `headerFormat`, `pointFormat`, `yDecimals`,
30476 * `xDateFormat`, `yPrefix` and `ySuffix`. Unlike other series, in
30477 * a scatter plot the series.name by default shows in the headerFormat
30478 * and point.x and point.y in the pointFormat.
30479 *
30480 * @product highcharts highstock
30481 */
30482 tooltip: {
30483
30484 headerFormat: '<span style="color:{point.color}">\u25CF</span> ' +
30485 '<span style="font-size: 0.85em"> {series.name}</span><br/>',
30486
30487
30488 pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>'
30489 }
30490
30491 // Prototype members
30492 }, {
30493 sorted: false,
30494 requireSorting: false,
30495 noSharedTooltip: true,
30496 trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],
30497 takeOrdinalPosition: false, // #2342
30498 drawGraph: function() {
30499 if (this.options.lineWidth) {
30500 Series.prototype.drawGraph.call(this);
30501 }
30502 }
30503 });
30504
30505 /**
30506 * A `scatter` series. If the [type](#series.scatter.type) option is
30507 * not specified, it is inherited from [chart.type](#chart.type).
30508 *
30509 * For options that apply to multiple series, it is recommended to add
30510 * them to the [plotOptions.series](#plotOptions.series) options structure.
30511 * To apply to all series of this specific type, apply it to [plotOptions.
30512 * scatter](#plotOptions.scatter).
30513 *
30514 * @type {Object}
30515 * @extends series,plotOptions.scatter
30516 * @excluding dataParser,dataURL,stack
30517 * @product highcharts highstock
30518 * @apioption series.scatter
30519 */
30520
30521 /**
30522 * An array of data points for the series. For the `scatter` series
30523 * type, points can be given in the following ways:
30524 *
30525 * 1. An array of numerical values. In this case, the numerical values
30526 * will be interpreted as `y` options. The `x` values will be automatically
30527 * calculated, either starting at 0 and incremented by 1, or from `pointStart`
30528 * and `pointInterval` given in the series options. If the axis has
30529 * categories, these will be used. Example:
30530 *
30531 * ```js
30532 * data: [0, 5, 3, 5]
30533 * ```
30534 *
30535 * 2. An array of arrays with 2 values. In this case, the values correspond
30536 * to `x,y`. If the first value is a string, it is applied as the name
30537 * of the point, and the `x` value is inferred.
30538 *
30539 * ```js
30540 * data: [
30541 * [0, 0],
30542 * [1, 8],
30543 * [2, 9]
30544 * ]
30545 * ```
30546 *
30547 * 3. An array of objects with named values. The objects are point
30548 * configuration objects as seen below. If the total number of data
30549 * points exceeds the series' [turboThreshold](#series.scatter.turboThreshold),
30550 * this option is not available.
30551 *
30552 * ```js
30553 * data: [{
30554 * x: 1,
30555 * y: 2,
30556 * name: "Point2",
30557 * color: "#00FF00"
30558 * }, {
30559 * x: 1,
30560 * y: 4,
30561 * name: "Point1",
30562 * color: "#FF00FF"
30563 * }]
30564 * ```
30565 *
30566 * @type {Array<Object|Array|Number>}
30567 * @extends series.line.data
30568 * @sample {highcharts} highcharts/chart/reflow-true/
30569 * Numerical values
30570 * @sample {highcharts} highcharts/series/data-array-of-arrays/
30571 * Arrays of numeric x and y
30572 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
30573 * Arrays of datetime x and y
30574 * @sample {highcharts} highcharts/series/data-array-of-name-value/
30575 * Arrays of point.name and y
30576 * @sample {highcharts} highcharts/series/data-array-of-objects/
30577 * Config objects
30578 * @product highcharts highstock
30579 * @apioption series.scatter.data
30580 */
30581
30582
30583 }(Highcharts));
30584 (function(H) {
30585 /**
30586 * (c) 2010-2017 Torstein Honsi
30587 *
30588 * License: www.highcharts.com/license
30589 */
30590 var deg2rad = H.deg2rad,
30591 isNumber = H.isNumber,
30592 pick = H.pick,
30593 relativeLength = H.relativeLength;
30594 H.CenteredSeriesMixin = {
30595 /**
30596 * Get the center of the pie based on the size and center options relative to the
30597 * plot area. Borrowed by the polar and gauge series types.
30598 */
30599 getCenter: function() {
30600
30601 var options = this.options,
30602 chart = this.chart,
30603 slicingRoom = 2 * (options.slicedOffset || 0),
30604 handleSlicingRoom,
30605 plotWidth = chart.plotWidth - 2 * slicingRoom,
30606 plotHeight = chart.plotHeight - 2 * slicingRoom,
30607 centerOption = options.center,
30608 positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0],
30609 smallestSize = Math.min(plotWidth, plotHeight),
30610 i,
30611 value;
30612
30613 for (i = 0; i < 4; ++i) {
30614 value = positions[i];
30615 handleSlicingRoom = i < 2 || (i === 2 && /%$/.test(value));
30616
30617 // i == 0: centerX, relative to width
30618 // i == 1: centerY, relative to height
30619 // i == 2: size, relative to smallestSize
30620 // i == 3: innerSize, relative to size
30621 positions[i] = relativeLength(value, [plotWidth, plotHeight, smallestSize, positions[2]][i]) +
30622 (handleSlicingRoom ? slicingRoom : 0);
30623
30624 }
30625 // innerSize cannot be larger than size (#3632)
30626 if (positions[3] > positions[2]) {
30627 positions[3] = positions[2];
30628 }
30629 return positions;
30630 },
30631 /**
30632 * getStartAndEndRadians - Calculates start and end angles in radians.
30633 * Used in series types such as pie and sunburst.
30634 *
30635 * @param {Number} start Start angle in degrees.
30636 * @param {Number} end Start angle in degrees.
30637 * @return {object} Returns an object containing start and end angles as
30638 * radians.
30639 */
30640 getStartAndEndRadians: function getStartAndEndRadians(start, end) {
30641 var startAngle = isNumber(start) ? start : 0, // must be a number
30642 endAngle = (
30643 (
30644 isNumber(end) && // must be a number
30645 end > startAngle && // must be larger than the start angle
30646 // difference must be less than 360 degrees
30647 (end - startAngle) < 360
30648 ) ?
30649 end :
30650 startAngle + 360
30651 ),
30652 correction = -90;
30653 return {
30654 start: deg2rad * (startAngle + correction),
30655 end: deg2rad * (endAngle + correction)
30656 };
30657 }
30658 };
30659
30660 }(Highcharts));
30661 (function(H) {
30662 /**
30663 * (c) 2010-2017 Torstein Honsi
30664 *
30665 * License: www.highcharts.com/license
30666 */
30667 var addEvent = H.addEvent,
30668 CenteredSeriesMixin = H.CenteredSeriesMixin,
30669 defined = H.defined,
30670 each = H.each,
30671 extend = H.extend,
30672 getStartAndEndRadians = CenteredSeriesMixin.getStartAndEndRadians,
30673 inArray = H.inArray,
30674 LegendSymbolMixin = H.LegendSymbolMixin,
30675 noop = H.noop,
30676 pick = H.pick,
30677 Point = H.Point,
30678 Series = H.Series,
30679 seriesType = H.seriesType,
30680 seriesTypes = H.seriesTypes,
30681 setAnimation = H.setAnimation;
30682
30683 /**
30684 * The pie series type.
30685 *
30686 * @constructor seriesTypes.pie
30687 * @augments Series
30688 */
30689
30690 /**
30691 * A pie chart is a circular graphic which is divided into slices to illustrate
30692 * numerical proportion.
30693 *
30694 * @sample highcharts/demo/pie-basic/ Pie chart
30695 *
30696 * @extends {plotOptions.line}
30697 * @excluding animationLimit,boostThreshold,connectEnds,connectNulls,
30698 * cropThreshold,dashStyle,findNearestPointBy,getExtremesFromAll,
30699 * lineWidth,marker,negativeColor,pointInterval,pointIntervalUnit,
30700 * pointPlacement,pointStart,softThreshold,stacking,step,threshold,
30701 * turboThreshold,zoneAxis,zones
30702 * @product highcharts
30703 * @optionparent plotOptions.pie
30704 */
30705 seriesType('pie', 'line', {
30706
30707 /**
30708 * The center of the pie chart relative to the plot area. Can be percentages
30709 * or pixel values. The default behaviour (as of 3.0) is to center
30710 * the pie so that all slices and data labels are within the plot area.
30711 * As a consequence, the pie may actually jump around in a chart with
30712 * dynamic values, as the data labels move. In that case, the center
30713 * should be explicitly set, for example to `["50%", "50%"]`.
30714 *
30715 * @type {Array<String|Number>}
30716 * @sample {highcharts} highcharts/plotoptions/pie-center/ Centered at 100, 100
30717 * @default [null, null]
30718 * @product highcharts
30719 */
30720 center: [null, null],
30721
30722 clip: false,
30723
30724 /** @ignore */
30725 colorByPoint: true, // always true for pies
30726
30727 /**
30728 * A series specific or series type specific color set to use instead
30729 * of the global [colors](#colors).
30730 *
30731 * @type {Array<Color>}
30732 * @sample {highcharts} highcharts/demo/pie-monochrome/ Set default colors for all pies
30733 * @since 3.0
30734 * @product highcharts
30735 * @apioption plotOptions.pie.colors
30736 */
30737
30738 /**
30739 * @extends plotOptions.series.dataLabels
30740 * @excluding align,allowOverlap,staggerLines,step
30741 * @product highcharts
30742 */
30743 dataLabels: {
30744 /**
30745 * The color of the line connecting the data label to the pie slice.
30746 * The default color is the same as the point's color.
30747 *
30748 * In styled mode, the connector stroke is given in the
30749 * `.highcharts-data-label-connector` class.
30750 *
30751 * @type {String}
30752 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorcolor/ Blue connectors
30753 * @sample {highcharts} highcharts/css/pie-point/ Styled connectors
30754 * @default {point.color}
30755 * @since 2.1
30756 * @product highcharts
30757 * @apioption plotOptions.pie.dataLabels.connectorColor
30758 */
30759
30760 /**
30761 * The distance from the data label to the connector.
30762 *
30763 * @type {Number}
30764 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorpadding/ No padding
30765 * @default 5
30766 * @since 2.1
30767 * @product highcharts
30768 * @apioption plotOptions.pie.dataLabels.connectorPadding
30769 */
30770
30771 /**
30772 * The width of the line connecting the data label to the pie slice.
30773 *
30774 *
30775 * In styled mode, the connector stroke width is given in the
30776 * `.highcharts-data-label-connector` class.
30777 *
30778 * @type {Number}
30779 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorwidth-disabled/ Disable the connector
30780 * @sample {highcharts} highcharts/css/pie-point/ Styled connectors
30781 * @default 1
30782 * @since 2.1
30783 * @product highcharts
30784 * @apioption plotOptions.pie.dataLabels.connectorWidth
30785 */
30786
30787 /**
30788 * The distance of the data label from the pie's edge. Negative numbers
30789 * put the data label on top of the pie slices. Connectors are only
30790 * shown for data labels outside the pie.
30791 *
30792 * @type {Number}
30793 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-distance/ Data labels on top of the pie
30794 * @default 30
30795 * @since 2.1
30796 * @product highcharts
30797 */
30798 distance: 30,
30799
30800 /**
30801 * Enable or disable the data labels.
30802 *
30803 * @type {Boolean}
30804 * @since 2.1
30805 * @product highcharts
30806 */
30807 enabled: true,
30808
30809 formatter: function() { // #2945
30810 return this.point.isNull ? undefined : this.point.name;
30811 },
30812
30813 /**
30814 * Whether to render the connector as a soft arc or a line with sharp
30815 * break.
30816 *
30817 * @type {Number}
30818 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-true/ Soft
30819 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-false/ Non soft
30820 * @since 2.1.7
30821 * @product highcharts
30822 * @apioption plotOptions.pie.dataLabels.softConnector
30823 */
30824
30825 x: 0
30826 // y: 0
30827 },
30828
30829 /**
30830 * The end angle of the pie in degrees where 0 is top and 90 is right.
30831 * Defaults to `startAngle` plus 360.
30832 *
30833 * @type {Number}
30834 * @sample {highcharts} highcharts/demo/pie-semi-circle/ Semi-circle donut
30835 * @default null
30836 * @since 1.3.6
30837 * @product highcharts
30838 * @apioption plotOptions.pie.endAngle
30839 */
30840
30841 /**
30842 * Equivalent to [chart.ignoreHiddenSeries](#chart.ignoreHiddenSeries),
30843 * this option tells whether the series shall be redrawn as if the
30844 * hidden point were `null`.
30845 *
30846 * The default value changed from `false` to `true` with Highcharts
30847 * 3.0.
30848 *
30849 * @type {Boolean}
30850 * @sample {highcharts} highcharts/plotoptions/pie-ignorehiddenpoint/ True, the hiddden point is ignored
30851 * @default true
30852 * @since 2.3.0
30853 * @product highcharts
30854 */
30855 ignoreHiddenPoint: true,
30856
30857 /**
30858 * The size of the inner diameter for the pie. A size greater than 0
30859 * renders a donut chart. Can be a percentage or pixel value. Percentages
30860 * are relative to the pie size. Pixel values are given as integers.
30861 *
30862 *
30863 * Note: in Highcharts < 4.1.2, the percentage was relative to the plot
30864 * area, not the pie size.
30865 *
30866 * @type {String|Number}
30867 * @sample {highcharts} highcharts/plotoptions/pie-innersize-80px/ 80px inner size
30868 * @sample {highcharts} highcharts/plotoptions/pie-innersize-50percent/ 50% of the plot area
30869 * @sample {highcharts} highcharts/demo/3d-pie-donut/ 3D donut
30870 * @default 0
30871 * @since 2.0
30872 * @product highcharts
30873 * @apioption plotOptions.pie.innerSize
30874 */
30875
30876 /** @ignore */
30877 legendType: 'point',
30878
30879 /** @ignore */
30880 marker: null, // point options are specified in the base options
30881
30882 /**
30883 * The minimum size for a pie in response to auto margins. The pie will
30884 * try to shrink to make room for data labels in side the plot area,
30885 * but only to this size.
30886 *
30887 * @type {Number}
30888 * @default 80
30889 * @since 3.0
30890 * @product highcharts
30891 * @apioption plotOptions.pie.minSize
30892 */
30893
30894 /**
30895 * The diameter of the pie relative to the plot area. Can be a percentage
30896 * or pixel value. Pixel values are given as integers. The default
30897 * behaviour (as of 3.0) is to scale to the plot area and give room
30898 * for data labels within the plot area. As a consequence, the size
30899 * of the pie may vary when points are updated and data labels more
30900 * around. In that case it is best to set a fixed value, for example
30901 * `"75%"`.
30902 *
30903 * @type {String|Number}
30904 * @sample {highcharts} highcharts/plotoptions/pie-size/ Smaller pie
30905 * @default
30906 * @product highcharts
30907 */
30908 size: null,
30909
30910 /**
30911 * Whether to display this particular series or series type in the
30912 * legend. Since 2.1, pies are not shown in the legend by default.
30913 *
30914 * @type {Boolean}
30915 * @sample {highcharts} highcharts/plotoptions/series-showinlegend/ One series in the legend, one hidden
30916 * @product highcharts
30917 */
30918 showInLegend: false,
30919
30920 /**
30921 * If a point is sliced, moved out from the center, how many pixels
30922 * should it be moved?.
30923 *
30924 * @type {Number}
30925 * @sample {highcharts} highcharts/plotoptions/pie-slicedoffset-20/ 20px offset
30926 * @default 10
30927 * @product highcharts
30928 */
30929 slicedOffset: 10,
30930
30931 /**
30932 * The start angle of the pie slices in degrees where 0 is top and 90
30933 * right.
30934 *
30935 * @type {Number}
30936 * @sample {highcharts} highcharts/plotoptions/pie-startangle-90/ Start from right
30937 * @default 0
30938 * @since 2.3.4
30939 * @product highcharts
30940 * @apioption plotOptions.pie.startAngle
30941 */
30942
30943 /**
30944 * Sticky tracking of mouse events. When true, the `mouseOut` event
30945 * on a series isn't triggered until the mouse moves over another series,
30946 * or out of the plot area. When false, the `mouseOut` event on a
30947 * series is triggered when the mouse leaves the area around the series'
30948 * graph or markers. This also implies the tooltip. When `stickyTracking`
30949 * is false and `tooltip.shared` is false, the tooltip will be hidden
30950 * when moving the mouse between series.
30951 *
30952 * @product highcharts
30953 */
30954 stickyTracking: false,
30955
30956 tooltip: {
30957 followPointer: true
30958 },
30959
30960
30961 /**
30962 * The color of the border surrounding each slice. When `null`, the
30963 * border takes the same color as the slice fill. This can be used
30964 * together with a `borderWidth` to fill drawing gaps created by antialiazing
30965 * artefacts in borderless pies.
30966 *
30967 * In styled mode, the border stroke is given in the `.highcharts-point` class.
30968 *
30969 * @type {Color}
30970 * @sample {highcharts} highcharts/plotoptions/pie-bordercolor-black/ Black border
30971 * @default #ffffff
30972 * @product highcharts
30973 */
30974 borderColor: '#ffffff',
30975
30976 /**
30977 * The width of the border surrounding each slice.
30978 *
30979 * When setting the border width to 0, there may be small gaps between
30980 * the slices due to SVG antialiasing artefacts. To work around this,
30981 * keep the border width at 0.5 or 1, but set the `borderColor` to
30982 * `null` instead.
30983 *
30984 * In styled mode, the border stroke width is given in the `.highcharts-point` class.
30985 *
30986 * @type {Number}
30987 * @sample {highcharts} highcharts/plotoptions/pie-borderwidth/ 3px border
30988 * @default 1
30989 * @product highcharts
30990 */
30991 borderWidth: 1,
30992
30993 states: {
30994
30995 /**
30996 * @extends plotOptions.series.states.hover
30997 * @product highcharts
30998 */
30999 hover: {
31000
31001 /**
31002 * How much to brighten the point on interaction. Requires the main
31003 * color to be defined in hex or rgb(a) format.
31004 *
31005 * In styled mode, the hover brightness is by default replaced
31006 * by a fill-opacity given in the `.highcharts-point-hover` class.
31007 *
31008 * @type {Number}
31009 * @sample {highcharts} highcharts/plotoptions/pie-states-hover-brightness/ Brightened by 0.5
31010 * @default 0.1
31011 * @product highcharts
31012 */
31013 brightness: 0.1,
31014
31015 shadow: false
31016 }
31017 }
31018
31019
31020 }, /** @lends seriesTypes.pie.prototype */ {
31021 isCartesian: false,
31022 requireSorting: false,
31023 directTouch: true,
31024 noSharedTooltip: true,
31025 trackerGroups: ['group', 'dataLabelsGroup'],
31026 axisTypes: [],
31027 pointAttribs: seriesTypes.column.prototype.pointAttribs,
31028 /**
31029 * Animate the pies in
31030 */
31031 animate: function(init) {
31032 var series = this,
31033 points = series.points,
31034 startAngleRad = series.startAngleRad;
31035
31036 if (!init) {
31037 each(points, function(point) {
31038 var graphic = point.graphic,
31039 args = point.shapeArgs;
31040
31041 if (graphic) {
31042 // start values
31043 graphic.attr({
31044 r: point.startR || (series.center[3] / 2), // animate from inner radius (#779)
31045 start: startAngleRad,
31046 end: startAngleRad
31047 });
31048
31049 // animate
31050 graphic.animate({
31051 r: args.r,
31052 start: args.start,
31053 end: args.end
31054 }, series.options.animation);
31055 }
31056 });
31057
31058 // delete this function to allow it only once
31059 series.animate = null;
31060 }
31061 },
31062
31063 /**
31064 * Recompute total chart sum and update percentages of points.
31065 */
31066 updateTotals: function() {
31067 var i,
31068 total = 0,
31069 points = this.points,
31070 len = points.length,
31071 point,
31072 ignoreHiddenPoint = this.options.ignoreHiddenPoint;
31073
31074 // Get the total sum
31075 for (i = 0; i < len; i++) {
31076 point = points[i];
31077 total += (ignoreHiddenPoint && !point.visible) ?
31078 0 :
31079 point.isNull ? 0 : point.y;
31080 }
31081 this.total = total;
31082
31083 // Set each point's properties
31084 for (i = 0; i < len; i++) {
31085 point = points[i];
31086 point.percentage = (total > 0 && (point.visible || !ignoreHiddenPoint)) ? point.y / total * 100 : 0;
31087 point.total = total;
31088 }
31089 },
31090
31091 /**
31092 * Extend the generatePoints method by adding total and percentage properties to each point
31093 */
31094 generatePoints: function() {
31095 Series.prototype.generatePoints.call(this);
31096 this.updateTotals();
31097 },
31098
31099 /**
31100 * Do translation for pie slices
31101 */
31102 translate: function(positions) {
31103 this.generatePoints();
31104
31105 var series = this,
31106 cumulative = 0,
31107 precision = 1000, // issue #172
31108 options = series.options,
31109 slicedOffset = options.slicedOffset,
31110 connectorOffset = slicedOffset + (options.borderWidth || 0),
31111 finalConnectorOffset,
31112 start,
31113 end,
31114 angle,
31115 radians = getStartAndEndRadians(options.startAngle, options.endAngle),
31116 startAngleRad = series.startAngleRad = radians.start,
31117 endAngleRad = series.endAngleRad = radians.end,
31118 circ = endAngleRad - startAngleRad, // 2 * Math.PI,
31119 points = series.points,
31120 radiusX, // the x component of the radius vector for a given point
31121 radiusY,
31122 labelDistance = options.dataLabels.distance,
31123 ignoreHiddenPoint = options.ignoreHiddenPoint,
31124 i,
31125 len = points.length,
31126 point;
31127
31128 // Get positions - either an integer or a percentage string must be given.
31129 // If positions are passed as a parameter, we're in a recursive loop for adjusting
31130 // space for data labels.
31131 if (!positions) {
31132 series.center = positions = series.getCenter();
31133 }
31134
31135 // Utility for getting the x value from a given y, used for anticollision
31136 // logic in data labels.
31137 // Added point for using specific points' label distance.
31138 series.getX = function(y, left, point) {
31139 angle = Math.asin(Math.min((y - positions[1]) / (positions[2] / 2 + point.labelDistance), 1));
31140 return positions[0] +
31141 (left ? -1 : 1) *
31142 (Math.cos(angle) * (positions[2] / 2 + point.labelDistance));
31143 };
31144
31145 // Calculate the geometry for each point
31146 for (i = 0; i < len; i++) {
31147
31148 point = points[i];
31149
31150 // Used for distance calculation for specific point.
31151 point.labelDistance = pick(
31152 point.options.dataLabels && point.options.dataLabels.distance,
31153 labelDistance
31154 );
31155
31156 // Saved for later dataLabels distance calculation.
31157 series.maxLabelDistance = Math.max(series.maxLabelDistance || 0, point.labelDistance);
31158
31159 // set start and end angle
31160 start = startAngleRad + (cumulative * circ);
31161 if (!ignoreHiddenPoint || point.visible) {
31162 cumulative += point.percentage / 100;
31163 }
31164 end = startAngleRad + (cumulative * circ);
31165
31166 // set the shape
31167 point.shapeType = 'arc';
31168 point.shapeArgs = {
31169 x: positions[0],
31170 y: positions[1],
31171 r: positions[2] / 2,
31172 innerR: positions[3] / 2,
31173 start: Math.round(start * precision) / precision,
31174 end: Math.round(end * precision) / precision
31175 };
31176
31177 // The angle must stay within -90 and 270 (#2645)
31178 angle = (end + start) / 2;
31179 if (angle > 1.5 * Math.PI) {
31180 angle -= 2 * Math.PI;
31181 } else if (angle < -Math.PI / 2) {
31182 angle += 2 * Math.PI;
31183 }
31184
31185 // Center for the sliced out slice
31186 point.slicedTranslation = {
31187 translateX: Math.round(Math.cos(angle) * slicedOffset),
31188 translateY: Math.round(Math.sin(angle) * slicedOffset)
31189 };
31190
31191 // set the anchor point for tooltips
31192 radiusX = Math.cos(angle) * positions[2] / 2;
31193 radiusY = Math.sin(angle) * positions[2] / 2;
31194 point.tooltipPos = [
31195 positions[0] + radiusX * 0.7,
31196 positions[1] + radiusY * 0.7
31197 ];
31198
31199 point.half = angle < -Math.PI / 2 || angle > Math.PI / 2 ? 1 : 0;
31200 point.angle = angle;
31201
31202 // Set the anchor point for data labels. Use point.labelDistance
31203 // instead of labelDistance // #1174
31204 // finalConnectorOffset - not override connectorOffset value.
31205 finalConnectorOffset = Math.min(connectorOffset, point.labelDistance / 5); // #1678
31206 point.labelPos = [
31207 positions[0] + radiusX + Math.cos(angle) * point.labelDistance, // first break of connector
31208 positions[1] + radiusY + Math.sin(angle) * point.labelDistance, // a/a
31209 positions[0] + radiusX + Math.cos(angle) * finalConnectorOffset, // second break, right outside pie
31210 positions[1] + radiusY + Math.sin(angle) * finalConnectorOffset, // a/a
31211 positions[0] + radiusX, // landing point for connector
31212 positions[1] + radiusY, // a/a
31213 point.labelDistance < 0 ? // alignment
31214 'center' :
31215 point.half ? 'right' : 'left', // alignment
31216 angle // center angle
31217 ];
31218
31219 }
31220 },
31221
31222 drawGraph: null,
31223
31224 /**
31225 * Draw the data points
31226 */
31227 drawPoints: function() {
31228 var series = this,
31229 chart = series.chart,
31230 renderer = chart.renderer,
31231 groupTranslation,
31232 graphic,
31233 pointAttr,
31234 shapeArgs;
31235
31236
31237 var shadow = series.options.shadow;
31238 if (shadow && !series.shadowGroup) {
31239 series.shadowGroup = renderer.g('shadow')
31240 .add(series.group);
31241 }
31242
31243
31244 // draw the slices
31245 each(series.points, function(point) {
31246 graphic = point.graphic;
31247 if (!point.isNull) {
31248 shapeArgs = point.shapeArgs;
31249
31250
31251 // If the point is sliced, use special translation, else use
31252 // plot area traslation
31253 groupTranslation = point.getTranslate();
31254
31255
31256 // Put the shadow behind all points
31257 var shadowGroup = point.shadowGroup;
31258 if (shadow && !shadowGroup) {
31259 shadowGroup = point.shadowGroup = renderer.g('shadow')
31260 .add(series.shadowGroup);
31261 }
31262
31263 if (shadowGroup) {
31264 shadowGroup.attr(groupTranslation);
31265 }
31266 pointAttr = series.pointAttribs(point, point.selected && 'select');
31267
31268
31269 // Draw the slice
31270 if (graphic) {
31271 graphic
31272 .setRadialReference(series.center)
31273
31274 .attr(pointAttr)
31275
31276 .animate(extend(shapeArgs, groupTranslation));
31277 } else {
31278
31279 point.graphic = graphic = renderer[point.shapeType](shapeArgs)
31280 .setRadialReference(series.center)
31281 .attr(groupTranslation)
31282 .add(series.group);
31283
31284 if (!point.visible) {
31285 graphic.attr({
31286 visibility: 'hidden'
31287 });
31288 }
31289
31290
31291 graphic
31292 .attr(pointAttr)
31293 .attr({
31294 'stroke-linejoin': 'round'
31295 })
31296 .shadow(shadow, shadowGroup);
31297
31298 }
31299
31300 graphic.addClass(point.getClassName());
31301
31302 } else if (graphic) {
31303 point.graphic = graphic.destroy();
31304 }
31305 });
31306
31307 },
31308
31309
31310 searchPoint: noop,
31311
31312 /**
31313 * Utility for sorting data labels
31314 */
31315 sortByAngle: function(points, sign) {
31316 points.sort(function(a, b) {
31317 return a.angle !== undefined && (b.angle - a.angle) * sign;
31318 });
31319 },
31320
31321 /**
31322 * Use a simple symbol from LegendSymbolMixin
31323 */
31324 drawLegendSymbol: LegendSymbolMixin.drawRectangle,
31325
31326 /**
31327 * Use the getCenter method from drawLegendSymbol
31328 */
31329 getCenter: CenteredSeriesMixin.getCenter,
31330
31331 /**
31332 * Pies don't have point marker symbols
31333 */
31334 getSymbol: noop
31335
31336
31337 /**
31338 * @constructor seriesTypes.pie.prototype.pointClass
31339 * @extends {Point}
31340 */
31341 }, /** @lends seriesTypes.pie.prototype.pointClass.prototype */ {
31342 /**
31343 * Initiate the pie slice
31344 */
31345 init: function() {
31346
31347 Point.prototype.init.apply(this, arguments);
31348
31349 var point = this,
31350 toggleSlice;
31351
31352 point.name = pick(point.name, 'Slice');
31353
31354 // add event listener for select
31355 toggleSlice = function(e) {
31356 point.slice(e.type === 'select');
31357 };
31358 addEvent(point, 'select', toggleSlice);
31359 addEvent(point, 'unselect', toggleSlice);
31360
31361 return point;
31362 },
31363
31364 /**
31365 * Negative points are not valid (#1530, #3623, #5322)
31366 */
31367 isValid: function() {
31368 return H.isNumber(this.y, true) && this.y >= 0;
31369 },
31370
31371 /**
31372 * Toggle the visibility of the pie slice
31373 * @param {Boolean} vis Whether to show the slice or not. If undefined, the
31374 * visibility is toggled
31375 */
31376 setVisible: function(vis, redraw) {
31377 var point = this,
31378 series = point.series,
31379 chart = series.chart,
31380 ignoreHiddenPoint = series.options.ignoreHiddenPoint;
31381
31382 redraw = pick(redraw, ignoreHiddenPoint);
31383
31384 if (vis !== point.visible) {
31385
31386 // If called without an argument, toggle visibility
31387 point.visible = point.options.visible = vis = vis === undefined ? !point.visible : vis;
31388 series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
31389
31390 // Show and hide associated elements. This is performed regardless of redraw or not,
31391 // because chart.redraw only handles full series.
31392 each(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function(key) {
31393 if (point[key]) {
31394 point[key][vis ? 'show' : 'hide'](true);
31395 }
31396 });
31397
31398 if (point.legendItem) {
31399 chart.legend.colorizeItem(point, vis);
31400 }
31401
31402 // #4170, hide halo after hiding point
31403 if (!vis && point.state === 'hover') {
31404 point.setState('');
31405 }
31406
31407 // Handle ignore hidden slices
31408 if (ignoreHiddenPoint) {
31409 series.isDirty = true;
31410 }
31411
31412 if (redraw) {
31413 chart.redraw();
31414 }
31415 }
31416 },
31417
31418 /**
31419 * Set or toggle whether the slice is cut out from the pie
31420 * @param {Boolean} sliced When undefined, the slice state is toggled
31421 * @param {Boolean} redraw Whether to redraw the chart. True by default.
31422 */
31423 slice: function(sliced, redraw, animation) {
31424 var point = this,
31425 series = point.series,
31426 chart = series.chart;
31427
31428 setAnimation(animation, chart);
31429
31430 // redraw is true by default
31431 redraw = pick(redraw, true);
31432
31433 // if called without an argument, toggle
31434 point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced;
31435 series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
31436
31437 point.graphic.animate(this.getTranslate());
31438
31439
31440 if (point.shadowGroup) {
31441 point.shadowGroup.animate(this.getTranslate());
31442 }
31443
31444 },
31445
31446 getTranslate: function() {
31447 return this.sliced ? this.slicedTranslation : {
31448 translateX: 0,
31449 translateY: 0
31450 };
31451 },
31452
31453 haloPath: function(size) {
31454 var shapeArgs = this.shapeArgs;
31455
31456 return this.sliced || !this.visible ? [] :
31457 this.series.chart.renderer.symbols.arc(
31458 shapeArgs.x,
31459 shapeArgs.y,
31460 shapeArgs.r + size,
31461 shapeArgs.r + size, {
31462 innerR: this.shapeArgs.r,
31463 start: shapeArgs.start,
31464 end: shapeArgs.end
31465 }
31466 );
31467 }
31468 });
31469
31470 /**
31471 * A `pie` series. If the [type](#series.pie.type) option is not specified,
31472 * it is inherited from [chart.type](#chart.type).
31473 *
31474 * For options that apply to multiple series, it is recommended to add
31475 * them to the [plotOptions.series](#plotOptions.series) options structure.
31476 * To apply to all series of this specific type, apply it to [plotOptions.
31477 * pie](#plotOptions.pie).
31478 *
31479 * @type {Object}
31480 * @extends series,plotOptions.pie
31481 * @excluding dataParser,dataURL,stack,xAxis,yAxis
31482 * @product highcharts
31483 * @apioption series.pie
31484 */
31485
31486 /**
31487 * An array of data points for the series. For the `pie` series type,
31488 * points can be given in the following ways:
31489 *
31490 * 1. An array of numerical values. In this case, the numerical values
31491 * will be interpreted as `y` options. Example:
31492 *
31493 * ```js
31494 * data: [0, 5, 3, 5]
31495 * ```
31496 *
31497 * 2. An array of objects with named values. The objects are point
31498 * configuration objects as seen below. If the total number of data
31499 * points exceeds the series' [turboThreshold](#series.pie.turboThreshold),
31500 * this option is not available.
31501 *
31502 * ```js
31503 * data: [{
31504 * y: 1,
31505 * name: "Point2",
31506 * color: "#00FF00"
31507 * }, {
31508 * y: 7,
31509 * name: "Point1",
31510 * color: "#FF00FF"
31511 * }]</pre>
31512 *
31513 * @type {Array<Object|Number>}
31514 * @extends series.line.data
31515 * @excluding marker,x
31516 * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
31517 * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
31518 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
31519 * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
31520 * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
31521 * @product highcharts
31522 * @apioption series.pie.data
31523 */
31524
31525 /**
31526 * The sequential index of the data point in the legend.
31527 *
31528 * @type {Number}
31529 * @product highcharts
31530 * @apioption series.pie.data.legendIndex
31531 */
31532
31533 /**
31534 * Whether to display a slice offset from the center.
31535 *
31536 * @type {Boolean}
31537 * @sample {highcharts} highcharts/point/sliced/ One sliced point
31538 * @product highcharts
31539 * @apioption series.pie.data.sliced
31540 */
31541
31542 /**
31543 * Fires when the checkbox next to the point name in the legend is clicked.
31544 * One parameter, event, is passed to the function. The state of the
31545 * checkbox is found by event.checked. The checked item is found by
31546 * event.item. Return false to prevent the default action which is to
31547 * toggle the select state of the series.
31548 *
31549 * @type {Function}
31550 * @context Point
31551 * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/
31552 * Alert checkbox status
31553 * @since 1.2.0
31554 * @product highcharts
31555 * @apioption plotOptions.pie.events.checkboxClick
31556 */
31557
31558 /**
31559 * Not applicable to pies, as the legend item is per point. See point.
31560 * events.
31561 *
31562 * @type {Function}
31563 * @since 1.2.0
31564 * @product highcharts
31565 * @apioption plotOptions.pie.events.legendItemClick
31566 */
31567
31568 /**
31569 * Fires when the legend item belonging to the pie point (slice) is
31570 * clicked. The `this` keyword refers to the point itself. One parameter,
31571 * `event`, is passed to the function, containing common event information. The
31572 * default action is to toggle the visibility of the point. This can be
31573 * prevented by calling `event.preventDefault()`.
31574 *
31575 * @type {Function}
31576 * @sample {highcharts} highcharts/plotoptions/pie-point-events-legenditemclick/
31577 * Confirm toggle visibility
31578 * @since 1.2.0
31579 * @product highcharts
31580 * @apioption plotOptions.pie.point.events.legendItemClick
31581 */
31582
31583 }(Highcharts));
31584 (function(H) {
31585 /**
31586 * (c) 2010-2017 Torstein Honsi
31587 *
31588 * License: www.highcharts.com/license
31589 */
31590 var addEvent = H.addEvent,
31591 arrayMax = H.arrayMax,
31592 defined = H.defined,
31593 each = H.each,
31594 extend = H.extend,
31595 format = H.format,
31596 map = H.map,
31597 merge = H.merge,
31598 noop = H.noop,
31599 pick = H.pick,
31600 relativeLength = H.relativeLength,
31601 Series = H.Series,
31602 seriesTypes = H.seriesTypes,
31603 stableSort = H.stableSort;
31604
31605 /* eslint max-len: ["warn", 80, 4] */
31606 /**
31607 * General distribution algorithm for distributing labels of differing size
31608 * along a confined length in two dimensions. The algorithm takes an array of
31609 * objects containing a size, a target and a rank. It will place the labels as
31610 * close as possible to their targets, skipping the lowest ranked labels if
31611 * necessary.
31612 */
31613 H.distribute = function(boxes, len) {
31614
31615 var i,
31616 overlapping = true,
31617 origBoxes = boxes, // Original array will be altered with added .pos
31618 restBoxes = [], // The outranked overshoot
31619 box,
31620 target,
31621 total = 0;
31622
31623 function sortByTarget(a, b) {
31624 return a.target - b.target;
31625 }
31626
31627 // If the total size exceeds the len, remove those boxes with the lowest
31628 // rank
31629 i = boxes.length;
31630 while (i--) {
31631 total += boxes[i].size;
31632 }
31633
31634 // Sort by rank, then slice away overshoot
31635 if (total > len) {
31636 stableSort(boxes, function(a, b) {
31637 return (b.rank || 0) - (a.rank || 0);
31638 });
31639 i = 0;
31640 total = 0;
31641 while (total <= len) {
31642 total += boxes[i].size;
31643 i++;
31644 }
31645 restBoxes = boxes.splice(i - 1, boxes.length);
31646 }
31647
31648 // Order by target
31649 stableSort(boxes, sortByTarget);
31650
31651
31652 // So far we have been mutating the original array. Now
31653 // create a copy with target arrays
31654 boxes = map(boxes, function(box) {
31655 return {
31656 size: box.size,
31657 targets: [box.target],
31658 align: pick(box.align, 0.5)
31659 };
31660 });
31661
31662 while (overlapping) {
31663 // Initial positions: target centered in box
31664 i = boxes.length;
31665 while (i--) {
31666 box = boxes[i];
31667 // Composite box, average of targets
31668 target = (
31669 Math.min.apply(0, box.targets) +
31670 Math.max.apply(0, box.targets)
31671 ) / 2;
31672 box.pos = Math.min(
31673 Math.max(0, target - box.size * box.align),
31674 len - box.size
31675 );
31676 }
31677
31678 // Detect overlap and join boxes
31679 i = boxes.length;
31680 overlapping = false;
31681 while (i--) {
31682 // Overlap
31683 if (i > 0 && boxes[i - 1].pos + boxes[i - 1].size > boxes[i].pos) {
31684 // Add this size to the previous box
31685 boxes[i - 1].size += boxes[i].size;
31686 boxes[i - 1].targets = boxes[i - 1]
31687 .targets
31688 .concat(boxes[i].targets);
31689 boxes[i - 1].align = 0.5;
31690
31691 // Overlapping right, push left
31692 if (boxes[i - 1].pos + boxes[i - 1].size > len) {
31693 boxes[i - 1].pos = len - boxes[i - 1].size;
31694 }
31695 boxes.splice(i, 1); // Remove this item
31696 overlapping = true;
31697 }
31698 }
31699 }
31700
31701 // Now the composite boxes are placed, we need to put the original boxes
31702 // within them
31703 i = 0;
31704 each(boxes, function(box) {
31705 var posInCompositeBox = 0;
31706 each(box.targets, function() {
31707 origBoxes[i].pos = box.pos + posInCompositeBox;
31708 posInCompositeBox += origBoxes[i].size;
31709 i++;
31710 });
31711 });
31712
31713 // Add the rest (hidden) boxes and sort by target
31714 origBoxes.push.apply(origBoxes, restBoxes);
31715 stableSort(origBoxes, sortByTarget);
31716 };
31717
31718
31719 /**
31720 * Draw the data labels
31721 */
31722 Series.prototype.drawDataLabels = function() {
31723 var series = this,
31724 seriesOptions = series.options,
31725 options = seriesOptions.dataLabels,
31726 points = series.points,
31727 pointOptions,
31728 generalOptions,
31729 hasRendered = series.hasRendered || 0,
31730 str,
31731 dataLabelsGroup,
31732 defer = pick(options.defer, !!seriesOptions.animation),
31733 renderer = series.chart.renderer;
31734
31735 /*
31736 * Handle the dataLabels.filter option.
31737 */
31738 function applyFilter(point, options) {
31739 var filter = options.filter,
31740 op,
31741 prop,
31742 val;
31743 if (filter) {
31744 op = filter.operator;
31745 prop = point[filter.property];
31746 val = filter.value;
31747 if (
31748 (op === '>' && prop > val) ||
31749 (op === '<' && prop < val) ||
31750 (op === '>=' && prop >= val) ||
31751 (op === '<=' && prop <= val) ||
31752 (op === '==' && prop == val) || // eslint-disable-line eqeqeq
31753 (op === '===' && prop === val)
31754 ) {
31755 return true;
31756 }
31757 return false;
31758 }
31759 return true;
31760 }
31761
31762 if (options.enabled || series._hasPointLabels) {
31763
31764 // Process default alignment of data labels for columns
31765 if (series.dlProcessOptions) {
31766 series.dlProcessOptions(options);
31767 }
31768
31769 // Create a separate group for the data labels to avoid rotation
31770 dataLabelsGroup = series.plotGroup(
31771 'dataLabelsGroup',
31772 'data-labels',
31773 defer && !hasRendered ? 'hidden' : 'visible', // #5133
31774 options.zIndex || 6
31775 );
31776
31777 if (defer) {
31778 dataLabelsGroup.attr({
31779 opacity: +hasRendered
31780 }); // #3300
31781 if (!hasRendered) {
31782 addEvent(series, 'afterAnimate', function() {
31783 if (series.visible) { // #2597, #3023, #3024
31784 dataLabelsGroup.show(true);
31785 }
31786 dataLabelsGroup[
31787 seriesOptions.animation ? 'animate' : 'attr'
31788 ]({
31789 opacity: 1
31790 }, {
31791 duration: 200
31792 });
31793 });
31794 }
31795 }
31796
31797 // Make the labels for each point
31798 generalOptions = options;
31799 each(points, function(point) {
31800 var enabled,
31801 dataLabel = point.dataLabel,
31802 labelConfig,
31803 attr,
31804 rotation,
31805 connector = point.connector,
31806 isNew = !dataLabel,
31807 style,
31808 formatString;
31809
31810 // Determine if each data label is enabled
31811 // @note dataLabelAttribs (like pointAttribs) would eradicate
31812 // the need for dlOptions, and simplify the section below.
31813 pointOptions = point.dlOptions || // dlOptions is used in treemaps
31814 (point.options && point.options.dataLabels);
31815 enabled = pick(
31816 pointOptions && pointOptions.enabled,
31817 generalOptions.enabled
31818 ) && !point.isNull; // #2282, #4641, #7112
31819
31820 if (enabled) {
31821 enabled = applyFilter(point, pointOptions || options) === true;
31822 }
31823
31824 if (enabled) {
31825 // Create individual options structure that can be extended
31826 // without affecting others
31827 options = merge(generalOptions, pointOptions);
31828 labelConfig = point.getLabelConfig();
31829 formatString = (
31830 options[point.formatPrefix + 'Format'] ||
31831 options.format
31832 );
31833
31834 str = defined(formatString) ?
31835 format(formatString, labelConfig) :
31836 (
31837 options[point.formatPrefix + 'Formatter'] ||
31838 options.formatter
31839 ).call(labelConfig, options);
31840
31841 style = options.style;
31842 rotation = options.rotation;
31843
31844 // Determine the color
31845 style.color = pick(
31846 options.color,
31847 style.color,
31848 series.color,
31849 '#000000'
31850 );
31851 // Get automated contrast color
31852 if (style.color === 'contrast') {
31853 point.contrastColor =
31854 renderer.getContrast(point.color || series.color);
31855 style.color = options.inside ||
31856 pick(point.labelDistance, options.distance) < 0 ||
31857 !!seriesOptions.stacking ?
31858 point.contrastColor :
31859 '#000000';
31860 }
31861 if (seriesOptions.cursor) {
31862 style.cursor = seriesOptions.cursor;
31863 }
31864
31865
31866 attr = {
31867
31868 fill: options.backgroundColor,
31869 stroke: options.borderColor,
31870 'stroke-width': options.borderWidth,
31871
31872 r: options.borderRadius || 0,
31873 rotation: rotation,
31874 padding: options.padding,
31875 zIndex: 1
31876 };
31877
31878 // Remove unused attributes (#947)
31879 H.objectEach(attr, function(val, name) {
31880 if (val === undefined) {
31881 delete attr[name];
31882 }
31883 });
31884 }
31885 // If the point is outside the plot area, destroy it. #678, #820
31886 if (dataLabel && (!enabled || !defined(str))) {
31887 point.dataLabel = dataLabel = dataLabel.destroy();
31888 if (connector) {
31889 point.connector = connector.destroy();
31890 }
31891 // Individual labels are disabled if the are explicitly disabled
31892 // in the point options, or if they fall outside the plot area.
31893 } else if (enabled && defined(str)) {
31894 // create new label
31895 if (!dataLabel) {
31896 dataLabel = point.dataLabel = renderer[
31897 rotation ? 'text' : 'label' // labels don't rotate
31898 ](
31899 str,
31900 0, -9999,
31901 options.shape,
31902 null,
31903 null,
31904 options.useHTML,
31905 null,
31906 'data-label'
31907 );
31908 dataLabel.addClass(
31909 'highcharts-data-label-color-' + point.colorIndex +
31910 ' ' + (options.className || '') +
31911 (options.useHTML ? 'highcharts-tracker' : '') // #3398
31912 );
31913 } else {
31914 attr.text = str;
31915 }
31916 dataLabel.attr(attr);
31917
31918 // Styles must be applied before add in order to read text
31919 // bounding box
31920 dataLabel.css(style).shadow(options.shadow);
31921
31922
31923 if (!dataLabel.added) {
31924 dataLabel.add(dataLabelsGroup);
31925 }
31926 // Now the data label is created and placed at 0,0, so we need
31927 // to align it
31928 series.alignDataLabel(point, dataLabel, options, null, isNew);
31929 }
31930 });
31931 }
31932 };
31933
31934 /**
31935 * Align each individual data label
31936 */
31937 Series.prototype.alignDataLabel = function(
31938 point,
31939 dataLabel,
31940 options,
31941 alignTo,
31942 isNew
31943 ) {
31944 var chart = this.chart,
31945 inverted = chart.inverted,
31946 plotX = pick(point.dlBox && point.dlBox.centerX, point.plotX, -9999),
31947 plotY = pick(point.plotY, -9999),
31948 bBox = dataLabel.getBBox(),
31949 fontSize,
31950 baseline,
31951 rotation = options.rotation,
31952 normRotation,
31953 negRotation,
31954 align = options.align,
31955 rotCorr, // rotation correction
31956 // Math.round for rounding errors (#2683), alignTo to allow column
31957 // labels (#2700)
31958 visible =
31959 this.visible &&
31960 (
31961 point.series.forceDL ||
31962 chart.isInsidePlot(plotX, Math.round(plotY), inverted) ||
31963 (
31964 alignTo && chart.isInsidePlot(
31965 plotX,
31966 inverted ?
31967 alignTo.x + 1 :
31968 alignTo.y + alignTo.height - 1,
31969 inverted
31970 )
31971 )
31972 ),
31973 alignAttr, // the final position;
31974 justify = pick(options.overflow, 'justify') === 'justify';
31975
31976 if (visible) {
31977
31978
31979 fontSize = options.style.fontSize;
31980
31981
31982 baseline = chart.renderer.fontMetrics(fontSize, dataLabel).b;
31983
31984 // The alignment box is a singular point
31985 alignTo = extend({
31986 x: inverted ? this.yAxis.len - plotY : plotX,
31987 y: Math.round(inverted ? this.xAxis.len - plotX : plotY),
31988 width: 0,
31989 height: 0
31990 }, alignTo);
31991
31992 // Add the text size for alignment calculation
31993 extend(options, {
31994 width: bBox.width,
31995 height: bBox.height
31996 });
31997
31998 // Allow a hook for changing alignment in the last moment, then do the
31999 // alignment
32000 if (rotation) {
32001 justify = false; // Not supported for rotated text
32002 rotCorr = chart.renderer.rotCorr(baseline, rotation); // #3723
32003 alignAttr = {
32004 x: alignTo.x + options.x + alignTo.width / 2 + rotCorr.x,
32005 y: (
32006 alignTo.y +
32007 options.y + {
32008 top: 0,
32009 middle: 0.5,
32010 bottom: 1
32011 }[options.verticalAlign] *
32012 alignTo.height
32013 )
32014 };
32015 dataLabel[isNew ? 'attr' : 'animate'](alignAttr)
32016 .attr({ // #3003
32017 align: align
32018 });
32019
32020 // Compensate for the rotated label sticking out on the sides
32021 normRotation = (rotation + 720) % 360;
32022 negRotation = normRotation > 180 && normRotation < 360;
32023
32024 if (align === 'left') {
32025 alignAttr.y -= negRotation ? bBox.height : 0;
32026 } else if (align === 'center') {
32027 alignAttr.x -= bBox.width / 2;
32028 alignAttr.y -= bBox.height / 2;
32029 } else if (align === 'right') {
32030 alignAttr.x -= bBox.width;
32031 alignAttr.y -= negRotation ? 0 : bBox.height;
32032 }
32033
32034
32035 } else {
32036 dataLabel.align(options, null, alignTo);
32037 alignAttr = dataLabel.alignAttr;
32038 }
32039
32040 // Handle justify or crop
32041 if (justify) {
32042 point.isLabelJustified = this.justifyDataLabel(
32043 dataLabel,
32044 options,
32045 alignAttr,
32046 bBox,
32047 alignTo,
32048 isNew
32049 );
32050
32051 // Now check that the data label is within the plot area
32052 } else if (pick(options.crop, true)) {
32053 visible =
32054 chart.isInsidePlot(
32055 alignAttr.x,
32056 alignAttr.y
32057 ) &&
32058 chart.isInsidePlot(
32059 alignAttr.x + bBox.width,
32060 alignAttr.y + bBox.height
32061 );
32062 }
32063
32064 // When we're using a shape, make it possible with a connector or an
32065 // arrow pointing to thie point
32066 if (options.shape && !rotation) {
32067 dataLabel[isNew ? 'attr' : 'animate']({
32068 anchorX: inverted ? chart.plotWidth - point.plotY : point.plotX,
32069 anchorY: inverted ? chart.plotHeight - point.plotX : point.plotY
32070 });
32071 }
32072 }
32073
32074 // Show or hide based on the final aligned position
32075 if (!visible) {
32076 dataLabel.attr({
32077 y: -9999
32078 });
32079 dataLabel.placed = false; // don't animate back in
32080 }
32081
32082 };
32083
32084 /**
32085 * If data labels fall partly outside the plot area, align them back in, in a
32086 * way that doesn't hide the point.
32087 */
32088 Series.prototype.justifyDataLabel = function(
32089 dataLabel,
32090 options,
32091 alignAttr,
32092 bBox,
32093 alignTo,
32094 isNew
32095 ) {
32096 var chart = this.chart,
32097 align = options.align,
32098 verticalAlign = options.verticalAlign,
32099 off,
32100 justified,
32101 padding = dataLabel.box ? 0 : (dataLabel.padding || 0);
32102
32103 // Off left
32104 off = alignAttr.x + padding;
32105 if (off < 0) {
32106 if (align === 'right') {
32107 options.align = 'left';
32108 } else {
32109 options.x = -off;
32110 }
32111 justified = true;
32112 }
32113
32114 // Off right
32115 off = alignAttr.x + bBox.width - padding;
32116 if (off > chart.plotWidth) {
32117 if (align === 'left') {
32118 options.align = 'right';
32119 } else {
32120 options.x = chart.plotWidth - off;
32121 }
32122 justified = true;
32123 }
32124
32125 // Off top
32126 off = alignAttr.y + padding;
32127 if (off < 0) {
32128 if (verticalAlign === 'bottom') {
32129 options.verticalAlign = 'top';
32130 } else {
32131 options.y = -off;
32132 }
32133 justified = true;
32134 }
32135
32136 // Off bottom
32137 off = alignAttr.y + bBox.height - padding;
32138 if (off > chart.plotHeight) {
32139 if (verticalAlign === 'top') {
32140 options.verticalAlign = 'bottom';
32141 } else {
32142 options.y = chart.plotHeight - off;
32143 }
32144 justified = true;
32145 }
32146
32147 if (justified) {
32148 dataLabel.placed = !isNew;
32149 dataLabel.align(options, null, alignTo);
32150 }
32151
32152 return justified;
32153 };
32154
32155 /**
32156 * Override the base drawDataLabels method by pie specific functionality
32157 */
32158 if (seriesTypes.pie) {
32159 seriesTypes.pie.prototype.drawDataLabels = function() {
32160 var series = this,
32161 data = series.data,
32162 point,
32163 chart = series.chart,
32164 options = series.options.dataLabels,
32165 connectorPadding = pick(options.connectorPadding, 10),
32166 connectorWidth = pick(options.connectorWidth, 1),
32167 plotWidth = chart.plotWidth,
32168 plotHeight = chart.plotHeight,
32169 connector,
32170 seriesCenter = series.center,
32171 radius = seriesCenter[2] / 2,
32172 centerY = seriesCenter[1],
32173 dataLabel,
32174 dataLabelWidth,
32175 labelPos,
32176 labelHeight,
32177 // divide the points into right and left halves for anti collision
32178 halves = [
32179 [], // right
32180 [] // left
32181 ],
32182 x,
32183 y,
32184 visibility,
32185 j,
32186 overflow = [0, 0, 0, 0]; // top, right, bottom, left
32187
32188 // get out if not enabled
32189 if (!series.visible || (!options.enabled && !series._hasPointLabels)) {
32190 return;
32191 }
32192
32193 // Reset all labels that have been shortened
32194 each(data, function(point) {
32195 if (point.dataLabel && point.visible && point.dataLabel.shortened) {
32196 point.dataLabel
32197 .attr({
32198 width: 'auto'
32199 }).css({
32200 width: 'auto',
32201 textOverflow: 'clip'
32202 });
32203 point.dataLabel.shortened = false;
32204 }
32205 });
32206
32207
32208 // run parent method
32209 Series.prototype.drawDataLabels.apply(series);
32210
32211 each(data, function(point) {
32212 if (point.dataLabel && point.visible) { // #407, #2510
32213
32214 // Arrange points for detection collision
32215 halves[point.half].push(point);
32216
32217 // Reset positions (#4905)
32218 point.dataLabel._pos = null;
32219 }
32220 });
32221
32222 /* Loop over the points in each half, starting from the top and bottom
32223 * of the pie to detect overlapping labels.
32224 */
32225 each(halves, function(points, i) {
32226
32227 var top,
32228 bottom,
32229 length = points.length,
32230 positions = [],
32231 naturalY,
32232 sideOverflow,
32233 positionsIndex, // Point index in positions array.
32234 size;
32235
32236 if (!length) {
32237 return;
32238 }
32239
32240 // Sort by angle
32241 series.sortByAngle(points, i - 0.5);
32242 // Only do anti-collision when we have dataLabels outside the pie
32243 // and have connectors. (#856)
32244 if (series.maxLabelDistance > 0) {
32245 top = Math.max(
32246 0,
32247 centerY - radius - series.maxLabelDistance
32248 );
32249 bottom = Math.min(
32250 centerY + radius + series.maxLabelDistance,
32251 chart.plotHeight
32252 );
32253 each(points, function(point) {
32254 // check if specific points' label is outside the pie
32255 if (point.labelDistance > 0 && point.dataLabel) {
32256 // point.top depends on point.labelDistance value
32257 // Used for calculation of y value in getX method
32258 point.top = Math.max(
32259 0,
32260 centerY - radius - point.labelDistance
32261 );
32262 point.bottom = Math.min(
32263 centerY + radius + point.labelDistance,
32264 chart.plotHeight
32265 );
32266 size = point.dataLabel.getBBox().height || 21;
32267
32268 // point.positionsIndex is needed for getting index of
32269 // parameter related to specific point inside positions
32270 // array - not every point is in positions array.
32271 point.positionsIndex = positions.push({
32272 target: point.labelPos[1] - point.top + size / 2,
32273 size: size,
32274 rank: point.y
32275 }) - 1;
32276 }
32277 });
32278 H.distribute(positions, bottom + size - top);
32279 }
32280
32281 // Now the used slots are sorted, fill them up sequentially
32282 for (j = 0; j < length; j++) {
32283
32284 point = points[j];
32285 positionsIndex = point.positionsIndex;
32286 labelPos = point.labelPos;
32287 dataLabel = point.dataLabel;
32288 visibility = point.visible === false ? 'hidden' : 'inherit';
32289 naturalY = labelPos[1];
32290 y = naturalY;
32291
32292 if (positions && defined(positions[positionsIndex])) {
32293 if (positions[positionsIndex].pos === undefined) {
32294 visibility = 'hidden';
32295 } else {
32296 labelHeight = positions[positionsIndex].size;
32297 y = point.top + positions[positionsIndex].pos;
32298 }
32299 }
32300
32301 // It is needed to delete point.positionIndex for
32302 // dynamically added points etc.
32303
32304 delete point.positionIndex;
32305
32306 // get the x - use the natural x position for labels near the
32307 // top and bottom, to prevent the top and botton slice
32308 // connectors from touching each other on either side
32309 if (options.justify) {
32310 x = seriesCenter[0] +
32311 (i ? -1 : 1) * (radius + point.labelDistance);
32312 } else {
32313 x = series.getX(
32314 y < point.top + 2 || y > point.bottom - 2 ?
32315 naturalY :
32316 y,
32317 i,
32318 point
32319 );
32320 }
32321
32322
32323 // Record the placement and visibility
32324 dataLabel._attr = {
32325 visibility: visibility,
32326 align: labelPos[6]
32327 };
32328 dataLabel._pos = {
32329 x: (
32330 x +
32331 options.x +
32332 ({
32333 left: connectorPadding,
32334 right: -connectorPadding
32335 }[labelPos[6]] || 0)
32336 ),
32337
32338 // 10 is for the baseline (label vs text)
32339 y: y + options.y - 10
32340 };
32341 labelPos.x = x;
32342 labelPos.y = y;
32343
32344
32345 // Detect overflowing data labels
32346 if (pick(options.crop, true)) {
32347 dataLabelWidth = dataLabel.getBBox().width;
32348
32349 sideOverflow = null;
32350 // Overflow left
32351 if (x - dataLabelWidth < connectorPadding) {
32352 sideOverflow = Math.round(
32353 dataLabelWidth - x + connectorPadding
32354 );
32355 overflow[3] = Math.max(sideOverflow, overflow[3]);
32356
32357 // Overflow right
32358 } else if (
32359 x + dataLabelWidth >
32360 plotWidth - connectorPadding
32361 ) {
32362 sideOverflow = Math.round(
32363 x + dataLabelWidth - plotWidth + connectorPadding
32364 );
32365 overflow[1] = Math.max(sideOverflow, overflow[1]);
32366 }
32367
32368 // Overflow top
32369 if (y - labelHeight / 2 < 0) {
32370 overflow[0] = Math.max(
32371 Math.round(-y + labelHeight / 2),
32372 overflow[0]
32373 );
32374
32375 // Overflow left
32376 } else if (y + labelHeight / 2 > plotHeight) {
32377 overflow[2] = Math.max(
32378 Math.round(y + labelHeight / 2 - plotHeight),
32379 overflow[2]
32380 );
32381 }
32382 dataLabel.sideOverflow = sideOverflow;
32383 }
32384 } // for each point
32385 }); // for each half
32386
32387 // Do not apply the final placement and draw the connectors until we
32388 // have verified that labels are not spilling over.
32389 if (
32390 arrayMax(overflow) === 0 ||
32391 this.verifyDataLabelOverflow(overflow)
32392 ) {
32393
32394 // Place the labels in the final position
32395 this.placeDataLabels();
32396
32397 // Draw the connectors
32398 if (connectorWidth) {
32399 each(this.points, function(point) {
32400 var isNew;
32401
32402 connector = point.connector;
32403 dataLabel = point.dataLabel;
32404
32405 if (
32406 dataLabel &&
32407 dataLabel._pos &&
32408 point.visible &&
32409 point.labelDistance > 0
32410 ) {
32411 visibility = dataLabel._attr.visibility;
32412
32413 isNew = !connector;
32414
32415 if (isNew) {
32416 point.connector = connector = chart.renderer.path()
32417 .addClass('highcharts-data-label-connector ' +
32418 ' highcharts-color-' + point.colorIndex)
32419 .add(series.dataLabelsGroup);
32420
32421
32422 connector.attr({
32423 'stroke-width': connectorWidth,
32424 'stroke': (
32425 options.connectorColor ||
32426 point.color ||
32427 '#666666'
32428 )
32429 });
32430
32431 }
32432 connector[isNew ? 'attr' : 'animate']({
32433 d: series.connectorPath(point.labelPos)
32434 });
32435 connector.attr('visibility', visibility);
32436
32437 } else if (connector) {
32438 point.connector = connector.destroy();
32439 }
32440 });
32441 }
32442 }
32443 };
32444
32445 /**
32446 * Extendable method for getting the path of the connector between the data
32447 * label and the pie slice.
32448 */
32449 seriesTypes.pie.prototype.connectorPath = function(labelPos) {
32450 var x = labelPos.x,
32451 y = labelPos.y;
32452 return pick(this.options.dataLabels.softConnector, true) ? [
32453 'M',
32454 // end of the string at the label
32455 x + (labelPos[6] === 'left' ? 5 : -5), y,
32456 'C',
32457 x, y, // first break, next to the label
32458 2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],
32459 labelPos[2], labelPos[3], // second break
32460 'L',
32461 labelPos[4], labelPos[5] // base
32462 ] : [
32463 'M',
32464 // end of the string at the label
32465 x + (labelPos[6] === 'left' ? 5 : -5), y,
32466 'L',
32467 labelPos[2], labelPos[3], // second break
32468 'L',
32469 labelPos[4], labelPos[5] // base
32470 ];
32471 };
32472
32473 /**
32474 * Perform the final placement of the data labels after we have verified
32475 * that they fall within the plot area.
32476 */
32477 seriesTypes.pie.prototype.placeDataLabels = function() {
32478 each(this.points, function(point) {
32479 var dataLabel = point.dataLabel,
32480 _pos;
32481 if (dataLabel && point.visible) {
32482 _pos = dataLabel._pos;
32483 if (_pos) {
32484
32485 // Shorten data labels with ellipsis if they still overflow
32486 // after the pie has reached minSize (#223).
32487 if (dataLabel.sideOverflow) {
32488 dataLabel._attr.width =
32489 dataLabel.getBBox().width - dataLabel.sideOverflow;
32490 dataLabel.css({
32491 width: dataLabel._attr.width + 'px',
32492 textOverflow: 'ellipsis'
32493 });
32494 dataLabel.shortened = true;
32495 }
32496
32497 dataLabel.attr(dataLabel._attr);
32498 dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos);
32499 dataLabel.moved = true;
32500 } else if (dataLabel) {
32501 dataLabel.attr({
32502 y: -9999
32503 });
32504 }
32505 }
32506 }, this);
32507 };
32508
32509 seriesTypes.pie.prototype.alignDataLabel = noop;
32510
32511 /**
32512 * Verify whether the data labels are allowed to draw, or we should run more
32513 * translation and data label positioning to keep them inside the plot area.
32514 * Returns true when data labels are ready to draw.
32515 */
32516 seriesTypes.pie.prototype.verifyDataLabelOverflow = function(overflow) {
32517
32518 var center = this.center,
32519 options = this.options,
32520 centerOption = options.center,
32521 minSize = options.minSize || 80,
32522 newSize = minSize,
32523 // If a size is set, return true and don't try to shrink the pie
32524 // to fit the labels.
32525 ret = options.size !== null;
32526
32527 if (!ret) {
32528 // Handle horizontal size and center
32529 if (centerOption[0] !== null) { // Fixed center
32530 newSize = Math.max(center[2] -
32531 Math.max(overflow[1], overflow[3]), minSize);
32532
32533 } else { // Auto center
32534 newSize = Math.max(
32535 // horizontal overflow
32536 center[2] - overflow[1] - overflow[3],
32537 minSize
32538 );
32539 // horizontal center
32540 center[0] += (overflow[3] - overflow[1]) / 2;
32541 }
32542
32543 // Handle vertical size and center
32544 if (centerOption[1] !== null) { // Fixed center
32545 newSize = Math.max(Math.min(newSize, center[2] -
32546 Math.max(overflow[0], overflow[2])), minSize);
32547
32548 } else { // Auto center
32549 newSize = Math.max(
32550 Math.min(
32551 newSize,
32552 // vertical overflow
32553 center[2] - overflow[0] - overflow[2]
32554 ),
32555 minSize
32556 );
32557 // vertical center
32558 center[1] += (overflow[0] - overflow[2]) / 2;
32559 }
32560
32561 // If the size must be decreased, we need to run translate and
32562 // drawDataLabels again
32563 if (newSize < center[2]) {
32564 center[2] = newSize;
32565 center[3] = Math.min( // #3632
32566 relativeLength(options.innerSize || 0, newSize),
32567 newSize
32568 );
32569 this.translate(center);
32570
32571 if (this.drawDataLabels) {
32572 this.drawDataLabels();
32573 }
32574 // Else, return true to indicate that the pie and its labels is
32575 // within the plot area
32576 } else {
32577 ret = true;
32578 }
32579 }
32580 return ret;
32581 };
32582 }
32583
32584 if (seriesTypes.column) {
32585
32586 /**
32587 * Override the basic data label alignment by adjusting for the position of
32588 * the column
32589 */
32590 seriesTypes.column.prototype.alignDataLabel = function(
32591 point,
32592 dataLabel,
32593 options,
32594 alignTo,
32595 isNew
32596 ) {
32597 var inverted = this.chart.inverted,
32598 series = point.series,
32599 // data label box for alignment
32600 dlBox = point.dlBox || point.shapeArgs,
32601 below = pick(
32602 point.below, // range series
32603 point.plotY > pick(this.translatedThreshold, series.yAxis.len)
32604 ),
32605 // draw it inside the box?
32606 inside = pick(options.inside, !!this.options.stacking),
32607 overshoot;
32608
32609 // Align to the column itself, or the top of it
32610 if (dlBox) { // Area range uses this method but not alignTo
32611 alignTo = merge(dlBox);
32612
32613 if (alignTo.y < 0) {
32614 alignTo.height += alignTo.y;
32615 alignTo.y = 0;
32616 }
32617 overshoot = alignTo.y + alignTo.height - series.yAxis.len;
32618 if (overshoot > 0) {
32619 alignTo.height -= overshoot;
32620 }
32621
32622 if (inverted) {
32623 alignTo = {
32624 x: series.yAxis.len - alignTo.y - alignTo.height,
32625 y: series.xAxis.len - alignTo.x - alignTo.width,
32626 width: alignTo.height,
32627 height: alignTo.width
32628 };
32629 }
32630
32631 // Compute the alignment box
32632 if (!inside) {
32633 if (inverted) {
32634 alignTo.x += below ? 0 : alignTo.width;
32635 alignTo.width = 0;
32636 } else {
32637 alignTo.y += below ? alignTo.height : 0;
32638 alignTo.height = 0;
32639 }
32640 }
32641 }
32642
32643
32644 // When alignment is undefined (typically columns and bars), display the
32645 // individual point below or above the point depending on the threshold
32646 options.align = pick(
32647 options.align, !inverted || inside ? 'center' : below ? 'right' : 'left'
32648 );
32649 options.verticalAlign = pick(
32650 options.verticalAlign,
32651 inverted || inside ? 'middle' : below ? 'top' : 'bottom'
32652 );
32653
32654 // Call the parent method
32655 Series.prototype.alignDataLabel.call(
32656 this,
32657 point,
32658 dataLabel,
32659 options,
32660 alignTo,
32661 isNew
32662 );
32663
32664 // If label was justified and we have contrast, set it:
32665 if (point.isLabelJustified && point.contrastColor) {
32666 point.dataLabel.css({
32667 color: point.contrastColor
32668 });
32669 }
32670 };
32671 }
32672
32673 }(Highcharts));
32674 (function(H) {
32675 /**
32676 * (c) 2009-2017 Torstein Honsi
32677 *
32678 * License: www.highcharts.com/license
32679 */
32680 /**
32681 * Highcharts module to hide overlapping data labels. This module is included in
32682 * Highcharts.
32683 */
32684 var Chart = H.Chart,
32685 each = H.each,
32686 objectEach = H.objectEach,
32687 pick = H.pick,
32688 addEvent = H.addEvent;
32689
32690 // Collect potensial overlapping data labels. Stack labels probably don't need
32691 // to be considered because they are usually accompanied by data labels that lie
32692 // inside the columns.
32693 addEvent(Chart.prototype, 'render', function collectAndHide() {
32694 var labels = [];
32695
32696 // Consider external label collectors
32697 each(this.labelCollectors || [], function(collector) {
32698 labels = labels.concat(collector());
32699 });
32700
32701 each(this.yAxis || [], function(yAxis) {
32702 if (
32703 yAxis.options.stackLabels &&
32704 !yAxis.options.stackLabels.allowOverlap
32705 ) {
32706 objectEach(yAxis.stacks, function(stack) {
32707 objectEach(stack, function(stackItem) {
32708 labels.push(stackItem.label);
32709 });
32710 });
32711 }
32712 });
32713
32714 each(this.series || [], function(series) {
32715 var dlOptions = series.options.dataLabels,
32716 // Range series have two collections
32717 collections = series.dataLabelCollections || ['dataLabel'];
32718
32719 if (
32720 (dlOptions.enabled || series._hasPointLabels) &&
32721 !dlOptions.allowOverlap &&
32722 series.visible
32723 ) { // #3866
32724 each(collections, function(coll) {
32725 each(series.points, function(point) {
32726 if (point[coll]) {
32727 point[coll].labelrank = pick(
32728 point.labelrank,
32729 point.shapeArgs && point.shapeArgs.height
32730 ); // #4118
32731 labels.push(point[coll]);
32732 }
32733 });
32734 });
32735 }
32736 });
32737 this.hideOverlappingLabels(labels);
32738 });
32739
32740 /**
32741 * Hide overlapping labels. Labels are moved and faded in and out on zoom to
32742 * provide a smooth visual imression.
32743 */
32744 Chart.prototype.hideOverlappingLabels = function(labels) {
32745
32746 var len = labels.length,
32747 label,
32748 i,
32749 j,
32750 label1,
32751 label2,
32752 isIntersecting,
32753 pos1,
32754 pos2,
32755 parent1,
32756 parent2,
32757 padding,
32758 bBox,
32759 intersectRect = function(x1, y1, w1, h1, x2, y2, w2, h2) {
32760 return !(
32761 x2 > x1 + w1 ||
32762 x2 + w2 < x1 ||
32763 y2 > y1 + h1 ||
32764 y2 + h2 < y1
32765 );
32766 };
32767
32768 for (i = 0; i < len; i++) {
32769 label = labels[i];
32770 if (label) {
32771
32772 // Mark with initial opacity
32773 label.oldOpacity = label.opacity;
32774 label.newOpacity = 1;
32775
32776 // Get width and height if pure text nodes (stack labels)
32777 if (!label.width) {
32778 bBox = label.getBBox();
32779 label.width = bBox.width;
32780 label.height = bBox.height;
32781 }
32782 }
32783 }
32784
32785 // Prevent a situation in a gradually rising slope, that each label will
32786 // hide the previous one because the previous one always has lower rank.
32787 labels.sort(function(a, b) {
32788 return (b.labelrank || 0) - (a.labelrank || 0);
32789 });
32790
32791 // Detect overlapping labels
32792 for (i = 0; i < len; i++) {
32793 label1 = labels[i];
32794
32795 for (j = i + 1; j < len; ++j) {
32796 label2 = labels[j];
32797 if (
32798 label1 && label2 &&
32799 label1 !== label2 && // #6465, polar chart with connectEnds
32800 label1.placed && label2.placed &&
32801 label1.newOpacity !== 0 && label2.newOpacity !== 0
32802 ) {
32803 pos1 = label1.alignAttr;
32804 pos2 = label2.alignAttr;
32805 // Different panes have different positions
32806 parent1 = label1.parentGroup;
32807 parent2 = label2.parentGroup;
32808 // Substract the padding if no background or border (#4333)
32809 padding = 2 * (label1.box ? 0 : (label1.padding || 0));
32810 isIntersecting = intersectRect(
32811 pos1.x + parent1.translateX,
32812 pos1.y + parent1.translateY,
32813 label1.width - padding,
32814 label1.height - padding,
32815 pos2.x + parent2.translateX,
32816 pos2.y + parent2.translateY,
32817 label2.width - padding,
32818 label2.height - padding
32819 );
32820
32821 if (isIntersecting) {
32822 (label1.labelrank < label2.labelrank ? label1 : label2)
32823 .newOpacity = 0;
32824 }
32825 }
32826 }
32827 }
32828
32829 // Hide or show
32830 each(labels, function(label) {
32831 var complete,
32832 newOpacity;
32833
32834 if (label) {
32835 newOpacity = label.newOpacity;
32836
32837 if (label.oldOpacity !== newOpacity && label.placed) {
32838
32839 // Make sure the label is completely hidden to avoid catching
32840 // clicks (#4362)
32841 if (newOpacity) {
32842 label.show(true);
32843 } else {
32844 complete = function() {
32845 label.hide();
32846 };
32847 }
32848
32849 // Animate or set the opacity
32850 label.alignAttr.opacity = newOpacity;
32851 label[label.isOld ? 'animate' : 'attr'](
32852 label.alignAttr,
32853 null,
32854 complete
32855 );
32856
32857 }
32858 label.isOld = true;
32859 }
32860 });
32861 };
32862
32863 }(Highcharts));
32864 (function(H) {
32865 /**
32866 * (c) 2010-2017 Torstein Honsi
32867 *
32868 * License: www.highcharts.com/license
32869 */
32870 var addEvent = H.addEvent,
32871 Chart = H.Chart,
32872 createElement = H.createElement,
32873 css = H.css,
32874 defaultOptions = H.defaultOptions,
32875 defaultPlotOptions = H.defaultPlotOptions,
32876 each = H.each,
32877 extend = H.extend,
32878 fireEvent = H.fireEvent,
32879 hasTouch = H.hasTouch,
32880 inArray = H.inArray,
32881 isObject = H.isObject,
32882 Legend = H.Legend,
32883 merge = H.merge,
32884 pick = H.pick,
32885 Point = H.Point,
32886 Series = H.Series,
32887 seriesTypes = H.seriesTypes,
32888 svg = H.svg,
32889 TrackerMixin;
32890
32891 /**
32892 * TrackerMixin for points and graphs.
32893 */
32894 TrackerMixin = H.TrackerMixin = {
32895
32896 /**
32897 * Draw the tracker for a point.
32898 */
32899 drawTrackerPoint: function() {
32900 var series = this,
32901 chart = series.chart,
32902 pointer = chart.pointer,
32903 onMouseOver = function(e) {
32904 var point = pointer.getPointFromEvent(e);
32905 // undefined on graph in scatterchart
32906 if (point !== undefined) {
32907 pointer.isDirectTouch = true;
32908 point.onMouseOver(e);
32909 }
32910 };
32911
32912 // Add reference to the point
32913 each(series.points, function(point) {
32914 if (point.graphic) {
32915 point.graphic.element.point = point;
32916 }
32917 if (point.dataLabel) {
32918 if (point.dataLabel.div) {
32919 point.dataLabel.div.point = point;
32920 } else {
32921 point.dataLabel.element.point = point;
32922 }
32923 }
32924 });
32925
32926 // Add the event listeners, we need to do this only once
32927 if (!series._hasTracking) {
32928 each(series.trackerGroups, function(key) {
32929 if (series[key]) { // we don't always have dataLabelsGroup
32930 series[key]
32931 .addClass('highcharts-tracker')
32932 .on('mouseover', onMouseOver)
32933 .on('mouseout', function(e) {
32934 pointer.onTrackerMouseOut(e);
32935 });
32936 if (hasTouch) {
32937 series[key].on('touchstart', onMouseOver);
32938 }
32939
32940
32941 if (series.options.cursor) {
32942 series[key]
32943 .css(css)
32944 .css({
32945 cursor: series.options.cursor
32946 });
32947 }
32948
32949 }
32950 });
32951 series._hasTracking = true;
32952 }
32953 },
32954
32955 /**
32956 * Draw the tracker object that sits above all data labels and markers to
32957 * track mouse events on the graph or points. For the line type charts
32958 * the tracker uses the same graphPath, but with a greater stroke width
32959 * for better control.
32960 */
32961 drawTrackerGraph: function() {
32962 var series = this,
32963 options = series.options,
32964 trackByArea = options.trackByArea,
32965 trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath),
32966 trackerPathLength = trackerPath.length,
32967 chart = series.chart,
32968 pointer = chart.pointer,
32969 renderer = chart.renderer,
32970 snap = chart.options.tooltip.snap,
32971 tracker = series.tracker,
32972 i,
32973 onMouseOver = function() {
32974 if (chart.hoverSeries !== series) {
32975 series.onMouseOver();
32976 }
32977 },
32978 /*
32979 * Empirical lowest possible opacities for TRACKER_FILL for an element to stay invisible but clickable
32980 * IE6: 0.002
32981 * IE7: 0.002
32982 * IE8: 0.002
32983 * IE9: 0.00000000001 (unlimited)
32984 * IE10: 0.0001 (exporting only)
32985 * FF: 0.00000000001 (unlimited)
32986 * Chrome: 0.000001
32987 * Safari: 0.000001
32988 * Opera: 0.00000000001 (unlimited)
32989 */
32990 TRACKER_FILL = 'rgba(192,192,192,' + (svg ? 0.0001 : 0.002) + ')';
32991
32992 // Extend end points. A better way would be to use round linecaps,
32993 // but those are not clickable in VML.
32994 if (trackerPathLength && !trackByArea) {
32995 i = trackerPathLength + 1;
32996 while (i--) {
32997 if (trackerPath[i] === 'M') { // extend left side
32998 trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], 'L');
32999 }
33000 if ((i && trackerPath[i] === 'M') || i === trackerPathLength) { // extend right side
33001 trackerPath.splice(i, 0, 'L', trackerPath[i - 2] + snap, trackerPath[i - 1]);
33002 }
33003 }
33004 }
33005
33006 // draw the tracker
33007 if (tracker) {
33008 tracker.attr({
33009 d: trackerPath
33010 });
33011 } else if (series.graph) { // create
33012
33013 series.tracker = renderer.path(trackerPath)
33014 .attr({
33015 'stroke-linejoin': 'round', // #1225
33016 visibility: series.visible ? 'visible' : 'hidden',
33017 stroke: TRACKER_FILL,
33018 fill: trackByArea ? TRACKER_FILL : 'none',
33019 'stroke-width': series.graph.strokeWidth() + (trackByArea ? 0 : 2 * snap),
33020 zIndex: 2
33021 })
33022 .add(series.group);
33023
33024 // The tracker is added to the series group, which is clipped, but is covered
33025 // by the marker group. So the marker group also needs to capture events.
33026 each([series.tracker, series.markerGroup], function(tracker) {
33027 tracker.addClass('highcharts-tracker')
33028 .on('mouseover', onMouseOver)
33029 .on('mouseout', function(e) {
33030 pointer.onTrackerMouseOut(e);
33031 });
33032
33033
33034 if (options.cursor) {
33035 tracker.css({
33036 cursor: options.cursor
33037 });
33038 }
33039
33040
33041 if (hasTouch) {
33042 tracker.on('touchstart', onMouseOver);
33043 }
33044 });
33045 }
33046 }
33047 };
33048 /* End TrackerMixin */
33049
33050
33051 /**
33052 * Add tracking event listener to the series group, so the point graphics
33053 * themselves act as trackers
33054 */
33055
33056 if (seriesTypes.column) {
33057 seriesTypes.column.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
33058 }
33059
33060 if (seriesTypes.pie) {
33061 seriesTypes.pie.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
33062 }
33063
33064 if (seriesTypes.scatter) {
33065 seriesTypes.scatter.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
33066 }
33067
33068 /*
33069 * Extend Legend for item events
33070 */
33071 extend(Legend.prototype, {
33072
33073 setItemEvents: function(item, legendItem, useHTML) {
33074 var legend = this,
33075 boxWrapper = legend.chart.renderer.boxWrapper,
33076 activeClass = 'highcharts-legend-' + (item.series ? 'point' : 'series') + '-active';
33077
33078 // Set the events on the item group, or in case of useHTML, the item itself (#1249)
33079 (useHTML ? legendItem : item.legendGroup).on('mouseover', function() {
33080 item.setState('hover');
33081
33082 // A CSS class to dim or hide other than the hovered series
33083 boxWrapper.addClass(activeClass);
33084
33085
33086 legendItem.css(legend.options.itemHoverStyle);
33087
33088 })
33089 .on('mouseout', function() {
33090
33091 legendItem.css(merge(item.visible ? legend.itemStyle : legend.itemHiddenStyle));
33092
33093
33094 // A CSS class to dim or hide other than the hovered series
33095 boxWrapper.removeClass(activeClass);
33096
33097 item.setState();
33098 })
33099 .on('click', function(event) {
33100 var strLegendItemClick = 'legendItemClick',
33101 fnLegendItemClick = function() {
33102 if (item.setVisible) {
33103 item.setVisible();
33104 }
33105 };
33106
33107 // Pass over the click/touch event. #4.
33108 event = {
33109 browserEvent: event
33110 };
33111
33112 // click the name or symbol
33113 if (item.firePointEvent) { // point
33114 item.firePointEvent(strLegendItemClick, event, fnLegendItemClick);
33115 } else {
33116 fireEvent(item, strLegendItemClick, event, fnLegendItemClick);
33117 }
33118 });
33119 },
33120
33121 createCheckboxForItem: function(item) {
33122 var legend = this;
33123
33124 item.checkbox = createElement('input', {
33125 type: 'checkbox',
33126 checked: item.selected,
33127 defaultChecked: item.selected // required by IE7
33128 }, legend.options.itemCheckboxStyle, legend.chart.container);
33129
33130 addEvent(item.checkbox, 'click', function(event) {
33131 var target = event.target;
33132 fireEvent(
33133 item.series || item,
33134 'checkboxClick', { // #3712
33135 checked: target.checked,
33136 item: item
33137 },
33138 function() {
33139 item.select();
33140 }
33141 );
33142 });
33143 }
33144 });
33145
33146
33147
33148 // Add pointer cursor to legend itemstyle in defaultOptions
33149 defaultOptions.legend.itemStyle.cursor = 'pointer';
33150
33151
33152
33153 /*
33154 * Extend the Chart object with interaction
33155 */
33156
33157 extend(Chart.prototype, /** @lends Chart.prototype */ {
33158 /**
33159 * Display the zoom button.
33160 *
33161 * @private
33162 */
33163 showResetZoom: function() {
33164 var chart = this,
33165 lang = defaultOptions.lang,
33166 btnOptions = chart.options.chart.resetZoomButton,
33167 theme = btnOptions.theme,
33168 states = theme.states,
33169 alignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox';
33170
33171 function zoomOut() {
33172 chart.zoomOut();
33173 }
33174
33175 this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, zoomOut, theme, states && states.hover)
33176 .attr({
33177 align: btnOptions.position.align,
33178 title: lang.resetZoomTitle
33179 })
33180 .addClass('highcharts-reset-zoom')
33181 .add()
33182 .align(btnOptions.position, false, alignTo);
33183
33184 },
33185
33186 /**
33187 * Zoom out to 1:1.
33188 *
33189 * @private
33190 */
33191 zoomOut: function() {
33192 var chart = this;
33193 fireEvent(chart, 'selection', {
33194 resetSelection: true
33195 }, function() {
33196 chart.zoom();
33197 });
33198 },
33199
33200 /**
33201 * Zoom into a given portion of the chart given by axis coordinates.
33202 * @param {Object} event
33203 *
33204 * @private
33205 */
33206 zoom: function(event) {
33207 var chart = this,
33208 hasZoomed,
33209 pointer = chart.pointer,
33210 displayButton = false,
33211 resetZoomButton;
33212
33213 // If zoom is called with no arguments, reset the axes
33214 if (!event || event.resetSelection) {
33215 each(chart.axes, function(axis) {
33216 hasZoomed = axis.zoom();
33217 });
33218 pointer.initiated = false; // #6804
33219
33220 } else { // else, zoom in on all axes
33221 each(event.xAxis.concat(event.yAxis), function(axisData) {
33222 var axis = axisData.axis,
33223 isXAxis = axis.isXAxis;
33224
33225 // don't zoom more than minRange
33226 if (pointer[isXAxis ? 'zoomX' : 'zoomY']) {
33227 hasZoomed = axis.zoom(axisData.min, axisData.max);
33228 if (axis.displayBtn) {
33229 displayButton = true;
33230 }
33231 }
33232 });
33233 }
33234
33235 // Show or hide the Reset zoom button
33236 resetZoomButton = chart.resetZoomButton;
33237 if (displayButton && !resetZoomButton) {
33238 chart.showResetZoom();
33239 } else if (!displayButton && isObject(resetZoomButton)) {
33240 chart.resetZoomButton = resetZoomButton.destroy();
33241 }
33242
33243
33244 // Redraw
33245 if (hasZoomed) {
33246 chart.redraw(
33247 pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation
33248 );
33249 }
33250 },
33251
33252 /**
33253 * Pan the chart by dragging the mouse across the pane. This function is
33254 * called on mouse move, and the distance to pan is computed from chartX
33255 * compared to the first chartX position in the dragging operation.
33256 *
33257 * @private
33258 */
33259 pan: function(e, panning) {
33260
33261 var chart = this,
33262 hoverPoints = chart.hoverPoints,
33263 doRedraw;
33264
33265 // remove active points for shared tooltip
33266 if (hoverPoints) {
33267 each(hoverPoints, function(point) {
33268 point.setState();
33269 });
33270 }
33271
33272 each(panning === 'xy' ? [1, 0] : [1], function(isX) { // xy is used in maps
33273 var axis = chart[isX ? 'xAxis' : 'yAxis'][0],
33274 horiz = axis.horiz,
33275 mousePos = e[horiz ? 'chartX' : 'chartY'],
33276 mouseDown = horiz ? 'mouseDownX' : 'mouseDownY',
33277 startPos = chart[mouseDown],
33278 halfPointRange = (axis.pointRange || 0) / 2,
33279 extremes = axis.getExtremes(),
33280 panMin = axis.toValue(startPos - mousePos, true) +
33281 halfPointRange,
33282 panMax = axis.toValue(startPos + axis.len - mousePos, true) -
33283 halfPointRange,
33284 flipped = panMax < panMin,
33285 newMin = flipped ? panMax : panMin,
33286 newMax = flipped ? panMin : panMax,
33287 paddedMin = Math.min(
33288 extremes.dataMin,
33289 axis.toValue(
33290 axis.toPixels(extremes.min) - axis.minPixelPadding
33291 )
33292 ),
33293 paddedMax = Math.max(
33294 extremes.dataMax,
33295 axis.toValue(
33296 axis.toPixels(extremes.max) + axis.minPixelPadding
33297 )
33298 ),
33299 spill;
33300
33301 // If the new range spills over, either to the min or max, adjust
33302 // the new range.
33303 spill = paddedMin - newMin;
33304 if (spill > 0) {
33305 newMax += spill;
33306 newMin = paddedMin;
33307 }
33308 spill = newMax - paddedMax;
33309 if (spill > 0) {
33310 newMax = paddedMax;
33311 newMin -= spill;
33312 }
33313
33314 // Set new extremes if they are actually new
33315 if (axis.series.length && newMin !== extremes.min && newMax !== extremes.max) {
33316 axis.setExtremes(
33317 newMin,
33318 newMax,
33319 false,
33320 false, {
33321 trigger: 'pan'
33322 }
33323 );
33324 doRedraw = true;
33325 }
33326
33327 chart[mouseDown] = mousePos; // set new reference for next run
33328 });
33329
33330 if (doRedraw) {
33331 chart.redraw(false);
33332 }
33333 css(chart.container, {
33334 cursor: 'move'
33335 });
33336 }
33337 });
33338
33339 /*
33340 * Extend the Point object with interaction
33341 */
33342 extend(Point.prototype, /** @lends Highcharts.Point.prototype */ {
33343 /**
33344 * Toggle the selection status of a point.
33345 * @param {Boolean} [selected]
33346 * When `true`, the point is selected. When `false`, the point is
33347 * unselected. When `null` or `undefined`, the selection state is
33348 * toggled.
33349 * @param {Boolean} [accumulate=false]
33350 * When `true`, the selection is added to other selected points.
33351 * When `false`, other selected points are deselected. Internally in
33352 * Highcharts, when {@link http://api.highcharts.com/highcharts/plotOptions.series.allowPointSelect|allowPointSelect}
33353 * is `true`, selected points are accumulated on Control, Shift or
33354 * Cmd clicking the point.
33355 *
33356 * @see Highcharts.Chart#getSelectedPoints
33357 *
33358 * @sample highcharts/members/point-select/
33359 * Select a point from a button
33360 * @sample highcharts/chart/events-selection-points/
33361 * Select a range of points through a drag selection
33362 * @sample maps/series/data-id/
33363 * Select a point in Highmaps
33364 */
33365 select: function(selected, accumulate) {
33366 var point = this,
33367 series = point.series,
33368 chart = series.chart;
33369
33370 selected = pick(selected, !point.selected);
33371
33372 // fire the event with the default handler
33373 point.firePointEvent(selected ? 'select' : 'unselect', {
33374 accumulate: accumulate
33375 }, function() {
33376
33377 /**
33378 * Whether the point is selected or not.
33379 * @see Point#select
33380 * @see Chart#getSelectedPoints
33381 * @memberof Point
33382 * @name selected
33383 * @type {Boolean}
33384 */
33385 point.selected = point.options.selected = selected;
33386 series.options.data[inArray(point, series.data)] = point.options;
33387
33388 point.setState(selected && 'select');
33389
33390 // unselect all other points unless Ctrl or Cmd + click
33391 if (!accumulate) {
33392 each(chart.getSelectedPoints(), function(loopPoint) {
33393 if (loopPoint.selected && loopPoint !== point) {
33394 loopPoint.selected = loopPoint.options.selected = false;
33395 series.options.data[inArray(loopPoint, series.data)] = loopPoint.options;
33396 loopPoint.setState('');
33397 loopPoint.firePointEvent('unselect');
33398 }
33399 });
33400 }
33401 });
33402 },
33403
33404 /**
33405 * Runs on mouse over the point. Called internally from mouse and touch
33406 * events.
33407 *
33408 * @param {Object} e The event arguments
33409 */
33410 onMouseOver: function(e) {
33411 var point = this,
33412 series = point.series,
33413 chart = series.chart,
33414 pointer = chart.pointer;
33415 e = e ?
33416 pointer.normalize(e) :
33417 // In cases where onMouseOver is called directly without an event
33418 pointer.getChartCoordinatesFromPoint(point, chart.inverted);
33419 pointer.runPointActions(e, point);
33420 },
33421
33422 /**
33423 * Runs on mouse out from the point. Called internally from mouse and touch
33424 * events.
33425 */
33426 onMouseOut: function() {
33427 var point = this,
33428 chart = point.series.chart;
33429 point.firePointEvent('mouseOut');
33430 each(chart.hoverPoints || [], function(p) {
33431 p.setState();
33432 });
33433 chart.hoverPoints = chart.hoverPoint = null;
33434 },
33435
33436 /**
33437 * Import events from the series' and point's options. Only do it on
33438 * demand, to save processing time on hovering.
33439 *
33440 * @private
33441 */
33442 importEvents: function() {
33443 if (!this.hasImportedEvents) {
33444 var point = this,
33445 options = merge(point.series.options.point, point.options),
33446 events = options.events;
33447
33448 point.events = events;
33449
33450 H.objectEach(events, function(event, eventType) {
33451 addEvent(point, eventType, event);
33452 });
33453 this.hasImportedEvents = true;
33454
33455 }
33456 },
33457
33458 /**
33459 * Set the point's state.
33460 * @param {String} [state]
33461 * The new state, can be one of `''` (an empty string), `hover` or
33462 * `select`.
33463 */
33464 setState: function(state, move) {
33465 var point = this,
33466 plotX = Math.floor(point.plotX), // #4586
33467 plotY = point.plotY,
33468 series = point.series,
33469 stateOptions = series.options.states[state] || {},
33470 markerOptions = defaultPlotOptions[series.type].marker &&
33471 series.options.marker,
33472 normalDisabled = markerOptions && markerOptions.enabled === false,
33473 markerStateOptions = (markerOptions && markerOptions.states &&
33474 markerOptions.states[state]) || {},
33475 stateDisabled = markerStateOptions.enabled === false,
33476 stateMarkerGraphic = series.stateMarkerGraphic,
33477 pointMarker = point.marker || {},
33478 chart = series.chart,
33479 halo = series.halo,
33480 haloOptions,
33481 markerAttribs,
33482 hasMarkers = markerOptions && series.markerAttribs,
33483 newSymbol;
33484
33485 state = state || ''; // empty string
33486
33487 if (
33488 // already has this state
33489 (state === point.state && !move) ||
33490
33491 // selected points don't respond to hover
33492 (point.selected && state !== 'select') ||
33493
33494 // series' state options is disabled
33495 (stateOptions.enabled === false) ||
33496
33497 // general point marker's state options is disabled
33498 (state && (
33499 stateDisabled ||
33500 (normalDisabled && markerStateOptions.enabled === false)
33501 )) ||
33502
33503 // individual point marker's state options is disabled
33504 (
33505 state &&
33506 pointMarker.states &&
33507 pointMarker.states[state] &&
33508 pointMarker.states[state].enabled === false
33509 ) // #1610
33510
33511 ) {
33512 return;
33513 }
33514
33515 if (hasMarkers) {
33516 markerAttribs = series.markerAttribs(point, state);
33517 }
33518
33519 // Apply hover styles to the existing point
33520 if (point.graphic) {
33521
33522 if (point.state) {
33523 point.graphic.removeClass('highcharts-point-' + point.state);
33524 }
33525 if (state) {
33526 point.graphic.addClass('highcharts-point-' + state);
33527 }
33528
33529
33530 point.graphic.animate(
33531 series.pointAttribs(point, state),
33532 pick(
33533 chart.options.chart.animation,
33534 stateOptions.animation
33535 )
33536 );
33537
33538
33539 if (markerAttribs) {
33540 point.graphic.animate(
33541 markerAttribs,
33542 pick(
33543 chart.options.chart.animation, // Turn off globally
33544 markerStateOptions.animation,
33545 markerOptions.animation
33546 )
33547 );
33548 }
33549
33550 // Zooming in from a range with no markers to a range with markers
33551 if (stateMarkerGraphic) {
33552 stateMarkerGraphic.hide();
33553 }
33554 } else {
33555 // if a graphic is not applied to each point in the normal state, create a shared
33556 // graphic for the hover state
33557 if (state && markerStateOptions) {
33558 newSymbol = pointMarker.symbol || series.symbol;
33559
33560 // If the point has another symbol than the previous one, throw away the
33561 // state marker graphic and force a new one (#1459)
33562 if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) {
33563 stateMarkerGraphic = stateMarkerGraphic.destroy();
33564 }
33565
33566 // Add a new state marker graphic
33567 if (!stateMarkerGraphic) {
33568 if (newSymbol) {
33569 series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
33570 newSymbol,
33571 markerAttribs.x,
33572 markerAttribs.y,
33573 markerAttribs.width,
33574 markerAttribs.height
33575 )
33576 .add(series.markerGroup);
33577 stateMarkerGraphic.currentSymbol = newSymbol;
33578 }
33579
33580 // Move the existing graphic
33581 } else {
33582 stateMarkerGraphic[move ? 'animate' : 'attr']({ // #1054
33583 x: markerAttribs.x,
33584 y: markerAttribs.y
33585 });
33586 }
33587
33588 if (stateMarkerGraphic) {
33589 stateMarkerGraphic.attr(series.pointAttribs(point, state));
33590 }
33591
33592 }
33593
33594 if (stateMarkerGraphic) {
33595 stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY, chart.inverted) ? 'show' : 'hide'](); // #2450
33596 stateMarkerGraphic.element.point = point; // #4310
33597 }
33598 }
33599
33600 // Show me your halo
33601 haloOptions = stateOptions.halo;
33602 if (haloOptions && haloOptions.size) {
33603 if (!halo) {
33604 series.halo = halo = chart.renderer.path()
33605 // #5818, #5903, #6705
33606 .add((point.graphic || stateMarkerGraphic).parentGroup);
33607 }
33608 halo[move ? 'animate' : 'attr']({
33609 d: point.haloPath(haloOptions.size)
33610 });
33611 halo.attr({
33612 'class': 'highcharts-halo highcharts-color-' +
33613 pick(point.colorIndex, series.colorIndex)
33614 });
33615 halo.point = point; // #6055
33616
33617
33618 halo.attr(extend({
33619 'fill': point.color || series.color,
33620 'fill-opacity': haloOptions.opacity,
33621 'zIndex': -1 // #4929, IE8 added halo above everything
33622 }, haloOptions.attributes));
33623
33624
33625 } else if (halo && halo.point && halo.point.haloPath) {
33626 // Animate back to 0 on the current halo point (#6055)
33627 halo.animate({
33628 d: halo.point.haloPath(0)
33629 });
33630 }
33631
33632 point.state = state;
33633 },
33634
33635 /**
33636 * Get the path definition for the halo, which is usually a shadow-like
33637 * circle around the currently hovered point.
33638 * @param {Number} size
33639 * The radius of the circular halo.
33640 * @return {Array} The path definition
33641 */
33642 haloPath: function(size) {
33643 var series = this.series,
33644 chart = series.chart;
33645
33646 return chart.renderer.symbols.circle(
33647 Math.floor(this.plotX) - size,
33648 this.plotY - size,
33649 size * 2,
33650 size * 2
33651 );
33652 }
33653 });
33654
33655 /*
33656 * Extend the Series object with interaction
33657 */
33658
33659 extend(Series.prototype, /** @lends Highcharts.Series.prototype */ {
33660 /**
33661 * Runs on mouse over the series graphical items.
33662 */
33663 onMouseOver: function() {
33664 var series = this,
33665 chart = series.chart,
33666 hoverSeries = chart.hoverSeries;
33667
33668 // set normal state to previous series
33669 if (hoverSeries && hoverSeries !== series) {
33670 hoverSeries.onMouseOut();
33671 }
33672
33673 // trigger the event, but to save processing time,
33674 // only if defined
33675 if (series.options.events.mouseOver) {
33676 fireEvent(series, 'mouseOver');
33677 }
33678
33679 // hover this
33680 series.setState('hover');
33681 chart.hoverSeries = series;
33682 },
33683
33684 /**
33685 * Runs on mouse out of the series graphical items.
33686 */
33687 onMouseOut: function() {
33688 // trigger the event only if listeners exist
33689 var series = this,
33690 options = series.options,
33691 chart = series.chart,
33692 tooltip = chart.tooltip,
33693 hoverPoint = chart.hoverPoint;
33694
33695 chart.hoverSeries = null; // #182, set to null before the mouseOut event fires
33696
33697 // trigger mouse out on the point, which must be in this series
33698 if (hoverPoint) {
33699 hoverPoint.onMouseOut();
33700 }
33701
33702 // fire the mouse out event
33703 if (series && options.events.mouseOut) {
33704 fireEvent(series, 'mouseOut');
33705 }
33706
33707
33708 // hide the tooltip
33709 if (tooltip && !series.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) {
33710 tooltip.hide();
33711 }
33712
33713 // set normal state
33714 series.setState();
33715 },
33716
33717 /**
33718 * Set the state of the series. Called internally on mouse interaction and
33719 * select operations, but it can also be called directly to visually
33720 * highlight a series.
33721 *
33722 * @param {String} [state]
33723 * Can be either `hover`, `select` or undefined to set to normal
33724 * state.
33725 */
33726 setState: function(state) {
33727 var series = this,
33728 options = series.options,
33729 graph = series.graph,
33730 stateOptions = options.states,
33731 lineWidth = options.lineWidth,
33732 attribs,
33733 i = 0;
33734
33735 state = state || '';
33736
33737 if (series.state !== state) {
33738
33739 // Toggle class names
33740 each([
33741 series.group,
33742 series.markerGroup,
33743 series.dataLabelsGroup
33744 ], function(group) {
33745 if (group) {
33746 // Old state
33747 if (series.state) {
33748 group.removeClass('highcharts-series-' + series.state);
33749 }
33750 // New state
33751 if (state) {
33752 group.addClass('highcharts-series-' + state);
33753 }
33754 }
33755 });
33756
33757 series.state = state;
33758
33759
33760
33761 if (stateOptions[state] && stateOptions[state].enabled === false) {
33762 return;
33763 }
33764
33765 if (state) {
33766 lineWidth = stateOptions[state].lineWidth || lineWidth + (stateOptions[state].lineWidthPlus || 0); // #4035
33767 }
33768
33769 if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
33770 attribs = {
33771 'stroke-width': lineWidth
33772 };
33773
33774 // Animate the graph stroke-width. By default a quick animation
33775 // to hover, slower to un-hover.
33776 graph.animate(
33777 attribs,
33778 pick(
33779 series.chart.options.chart.animation,
33780 stateOptions[state] && stateOptions[state].animation
33781 )
33782 );
33783 while (series['zone-graph-' + i]) {
33784 series['zone-graph-' + i].attr(attribs);
33785 i = i + 1;
33786 }
33787 }
33788
33789 }
33790 },
33791
33792 /**
33793 * Show or hide the series.
33794 *
33795 * @param {Boolean} [visible]
33796 * True to show the series, false to hide. If undefined, the
33797 * visibility is toggled.
33798 * @param {Boolean} [redraw=true]
33799 * Whether to redraw the chart after the series is altered. If doing
33800 * more operations on the chart, it is a good idea to set redraw to
33801 * false and call {@link Chart#redraw|chart.redraw()} after.
33802 */
33803 setVisible: function(vis, redraw) {
33804 var series = this,
33805 chart = series.chart,
33806 legendItem = series.legendItem,
33807 showOrHide,
33808 ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
33809 oldVisibility = series.visible;
33810
33811 // if called without an argument, toggle visibility
33812 series.visible = vis = series.options.visible = series.userOptions.visible = vis === undefined ? !oldVisibility : vis; // #5618
33813 showOrHide = vis ? 'show' : 'hide';
33814
33815 // show or hide elements
33816 each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker', 'tt'], function(key) {
33817 if (series[key]) {
33818 series[key][showOrHide]();
33819 }
33820 });
33821
33822
33823 // hide tooltip (#1361)
33824 if (chart.hoverSeries === series || (chart.hoverPoint && chart.hoverPoint.series) === series) {
33825 series.onMouseOut();
33826 }
33827
33828
33829 if (legendItem) {
33830 chart.legend.colorizeItem(series, vis);
33831 }
33832
33833
33834 // rescale or adapt to resized chart
33835 series.isDirty = true;
33836 // in a stack, all other series are affected
33837 if (series.options.stacking) {
33838 each(chart.series, function(otherSeries) {
33839 if (otherSeries.options.stacking && otherSeries.visible) {
33840 otherSeries.isDirty = true;
33841 }
33842 });
33843 }
33844
33845 // show or hide linked series
33846 each(series.linkedSeries, function(otherSeries) {
33847 otherSeries.setVisible(vis, false);
33848 });
33849
33850 if (ignoreHiddenSeries) {
33851 chart.isDirtyBox = true;
33852 }
33853 if (redraw !== false) {
33854 chart.redraw();
33855 }
33856
33857 fireEvent(series, showOrHide);
33858 },
33859
33860 /**
33861 * Show the series if hidden.
33862 *
33863 * @sample highcharts/members/series-hide/
33864 * Toggle visibility from a button
33865 */
33866 show: function() {
33867 this.setVisible(true);
33868 },
33869
33870 /**
33871 * Hide the series if visible. If the {@link
33872 * https://api.highcharts.com/highcharts/chart.ignoreHiddenSeries|
33873 * chart.ignoreHiddenSeries} option is true, the chart is redrawn without
33874 * this series.
33875 *
33876 * @sample highcharts/members/series-hide/
33877 * Toggle visibility from a button
33878 */
33879 hide: function() {
33880 this.setVisible(false);
33881 },
33882
33883
33884 /**
33885 * Select or unselect the series. This means its {@link
33886 * Highcharts.Series.selected|selected} property is set, the checkbox in the
33887 * legend is toggled and when selected, the series is returned by the
33888 * {@link Highcharts.Chart#getSelectedSeries} function.
33889 *
33890 * @param {Boolean} [selected]
33891 * True to select the series, false to unselect. If undefined, the
33892 * selection state is toggled.
33893 *
33894 * @sample highcharts/members/series-select/
33895 * Select a series from a button
33896 */
33897 select: function(selected) {
33898 var series = this;
33899
33900 series.selected = selected = (selected === undefined) ?
33901 !series.selected :
33902 selected;
33903
33904 if (series.checkbox) {
33905 series.checkbox.checked = selected;
33906 }
33907
33908 fireEvent(series, selected ? 'select' : 'unselect');
33909 },
33910
33911 drawTracker: TrackerMixin.drawTrackerGraph
33912 });
33913
33914 }(Highcharts));
33915 (function(H) {
33916 /**
33917 * (c) 2010-2017 Torstein Honsi
33918 *
33919 * License: www.highcharts.com/license
33920 */
33921 var Chart = H.Chart,
33922 each = H.each,
33923 inArray = H.inArray,
33924 isArray = H.isArray,
33925 isObject = H.isObject,
33926 pick = H.pick,
33927 splat = H.splat;
33928
33929
33930 /**
33931 * Allows setting a set of rules to apply for different screen or chart
33932 * sizes. Each rule specifies additional chart options.
33933 *
33934 * @sample {highstock} stock/demo/responsive/ Stock chart
33935 * @sample highcharts/responsive/axis/ Axis
33936 * @sample highcharts/responsive/legend/ Legend
33937 * @sample highcharts/responsive/classname/ Class name
33938 * @since 5.0.0
33939 * @apioption responsive
33940 */
33941
33942 /**
33943 * A set of rules for responsive settings. The rules are executed from
33944 * the top down.
33945 *
33946 * @type {Array<Object>}
33947 * @sample {highcharts} highcharts/responsive/axis/ Axis changes
33948 * @sample {highstock} highcharts/responsive/axis/ Axis changes
33949 * @sample {highmaps} highcharts/responsive/axis/ Axis changes
33950 * @since 5.0.0
33951 * @apioption responsive.rules
33952 */
33953
33954 /**
33955 * A full set of chart options to apply as overrides to the general
33956 * chart options. The chart options are applied when the given rule
33957 * is active.
33958 *
33959 * A special case is configuration objects that take arrays, for example
33960 * [xAxis](#xAxis), [yAxis](#yAxis) or [series](#series). For these
33961 * collections, an `id` option is used to map the new option set to
33962 * an existing object. If an existing object of the same id is not found,
33963 * the item of the same indexupdated. So for example, setting `chartOptions`
33964 * with two series items without an `id`, will cause the existing chart's
33965 * two series to be updated with respective options.
33966 *
33967 * @type {Object}
33968 * @sample {highstock} stock/demo/responsive/ Stock chart
33969 * @sample highcharts/responsive/axis/ Axis
33970 * @sample highcharts/responsive/legend/ Legend
33971 * @sample highcharts/responsive/classname/ Class name
33972 * @since 5.0.0
33973 * @apioption responsive.rules.chartOptions
33974 */
33975
33976 /**
33977 * Under which conditions the rule applies.
33978 *
33979 * @type {Object}
33980 * @since 5.0.0
33981 * @apioption responsive.rules.condition
33982 */
33983
33984 /**
33985 * A callback function to gain complete control on when the responsive
33986 * rule applies. Return `true` if it applies. This opens for checking
33987 * against other metrics than the chart size, or example the document
33988 * size or other elements.
33989 *
33990 * @type {Function}
33991 * @context Chart
33992 * @since 5.0.0
33993 * @apioption responsive.rules.condition.callback
33994 */
33995
33996 /**
33997 * The responsive rule applies if the chart height is less than this.
33998 *
33999 * @type {Number}
34000 * @since 5.0.0
34001 * @apioption responsive.rules.condition.maxHeight
34002 */
34003
34004 /**
34005 * The responsive rule applies if the chart width is less than this.
34006 *
34007 * @type {Number}
34008 * @sample highcharts/responsive/axis/ Max width is 500
34009 * @since 5.0.0
34010 * @apioption responsive.rules.condition.maxWidth
34011 */
34012
34013 /**
34014 * The responsive rule applies if the chart height is greater than this.
34015 *
34016 * @type {Number}
34017 * @default 0
34018 * @since 5.0.0
34019 * @apioption responsive.rules.condition.minHeight
34020 */
34021
34022 /**
34023 * The responsive rule applies if the chart width is greater than this.
34024 *
34025 * @type {Number}
34026 * @default 0
34027 * @since 5.0.0
34028 * @apioption responsive.rules.condition.minWidth
34029 */
34030
34031 /**
34032 * Update the chart based on the current chart/document size and options for
34033 * responsiveness.
34034 */
34035 Chart.prototype.setResponsive = function(redraw) {
34036 var options = this.options.responsive,
34037 ruleIds = [],
34038 currentResponsive = this.currentResponsive,
34039 currentRuleIds;
34040
34041 if (options && options.rules) {
34042 each(options.rules, function(rule) {
34043 if (rule._id === undefined) {
34044 rule._id = H.uniqueKey();
34045 }
34046
34047 this.matchResponsiveRule(rule, ruleIds, redraw);
34048 }, this);
34049 }
34050
34051 // Merge matching rules
34052 var mergedOptions = H.merge.apply(0, H.map(ruleIds, function(ruleId) {
34053 return H.find(options.rules, function(rule) {
34054 return rule._id === ruleId;
34055 }).chartOptions;
34056 }));
34057
34058 // Stringified key for the rules that currently apply.
34059 ruleIds = ruleIds.toString() || undefined;
34060 currentRuleIds = currentResponsive && currentResponsive.ruleIds;
34061
34062
34063 // Changes in what rules apply
34064 if (ruleIds !== currentRuleIds) {
34065
34066 // Undo previous rules. Before we apply a new set of rules, we need to
34067 // roll back completely to base options (#6291).
34068 if (currentResponsive) {
34069 this.update(currentResponsive.undoOptions, redraw);
34070 }
34071
34072 if (ruleIds) {
34073 // Get undo-options for matching rules
34074 this.currentResponsive = {
34075 ruleIds: ruleIds,
34076 mergedOptions: mergedOptions,
34077 undoOptions: this.currentOptions(mergedOptions)
34078 };
34079
34080 this.update(mergedOptions, redraw);
34081
34082 } else {
34083 this.currentResponsive = undefined;
34084 }
34085 }
34086 };
34087
34088 /**
34089 * Handle a single responsiveness rule
34090 */
34091 Chart.prototype.matchResponsiveRule = function(rule, matches) {
34092 var condition = rule.condition,
34093 fn = condition.callback || function() {
34094 return this.chartWidth <= pick(condition.maxWidth, Number.MAX_VALUE) &&
34095 this.chartHeight <= pick(condition.maxHeight, Number.MAX_VALUE) &&
34096 this.chartWidth >= pick(condition.minWidth, 0) &&
34097 this.chartHeight >= pick(condition.minHeight, 0);
34098 };
34099
34100 if (fn.call(this)) {
34101 matches.push(rule._id);
34102 }
34103
34104 };
34105
34106 /**
34107 * Get the current values for a given set of options. Used before we update
34108 * the chart with a new responsiveness rule.
34109 * TODO: Restore axis options (by id?)
34110 */
34111 Chart.prototype.currentOptions = function(options) {
34112
34113 var ret = {};
34114
34115 /**
34116 * Recurse over a set of options and its current values,
34117 * and store the current values in the ret object.
34118 */
34119 function getCurrent(options, curr, ret, depth) {
34120 var i;
34121 H.objectEach(options, function(val, key) {
34122 if (!depth && inArray(key, ['series', 'xAxis', 'yAxis']) > -1) {
34123 val = splat(val);
34124
34125 ret[key] = [];
34126
34127 // Iterate over collections like series, xAxis or yAxis and map
34128 // the items by index.
34129 for (i = 0; i < val.length; i++) {
34130 if (curr[key][i]) { // Item exists in current data (#6347)
34131 ret[key][i] = {};
34132 getCurrent(
34133 val[i],
34134 curr[key][i],
34135 ret[key][i],
34136 depth + 1
34137 );
34138 }
34139 }
34140 } else if (isObject(val)) {
34141 ret[key] = isArray(val) ? [] : {};
34142 getCurrent(val, curr[key] || {}, ret[key], depth + 1);
34143 } else {
34144 ret[key] = curr[key] || null;
34145 }
34146 });
34147 }
34148
34149 getCurrent(options, this.options, ret, 0);
34150 return ret;
34151 };
34152
34153 }(Highcharts));
34154 return Highcharts
34155}));