UNPKG

109 kBJavaScriptView Raw
1/**
2 * @license Highcharts JS v5.0.0 (2016-09-29)
3 *
4 * (c) 2009-2016 Torstein Honsi
5 *
6 * License: www.highcharts.com/license
7 */
8(function(factory) {
9 if (typeof module === 'object' && module.exports) {
10 module.exports = factory;
11 } else {
12 factory(Highcharts);
13 }
14}(function(Highcharts) {
15 (function(H) {
16 /**
17 * (c) 2010-2016 Torstein Honsi
18 *
19 * License: www.highcharts.com/license
20 */
21 'use strict';
22 var each = H.each,
23 extend = H.extend,
24 merge = H.merge,
25 splat = H.splat;
26 /**
27 * The Pane object allows options that are common to a set of X and Y axes.
28 *
29 * In the future, this can be extended to basic Highcharts and Highstock.
30 */
31 function Pane(options, chart, firstAxis) {
32 this.init(options, chart, firstAxis);
33 }
34
35 // Extend the Pane prototype
36 extend(Pane.prototype, {
37
38 /**
39 * Initiate the Pane object
40 */
41 init: function(options, chart, firstAxis) {
42 var pane = this,
43 backgroundOption,
44 defaultOptions = pane.defaultOptions;
45
46 pane.chart = chart;
47
48 // Set options. Angular charts have a default background (#3318)
49 pane.options = options = merge(defaultOptions, chart.angular ? {
50 background: {}
51 } : undefined, options);
52
53 backgroundOption = options.background;
54
55 // To avoid having weighty logic to place, update and remove the backgrounds,
56 // push them to the first axis' plot bands and borrow the existing logic there.
57 if (backgroundOption) {
58 each([].concat(splat(backgroundOption)).reverse(), function(config) {
59 var mConfig,
60 axisUserOptions = firstAxis.userOptions;
61 mConfig = merge(pane.defaultBackgroundOptions, config);
62
63
64 if (config.backgroundColor) {
65 mConfig.backgroundColor = config.backgroundColor;
66 }
67 mConfig.color = mConfig.backgroundColor; // due to naming in plotBands
68
69
70 firstAxis.options.plotBands.unshift(mConfig);
71 axisUserOptions.plotBands = axisUserOptions.plotBands || []; // #3176
72 if (axisUserOptions.plotBands !== firstAxis.options.plotBands) {
73 axisUserOptions.plotBands.unshift(mConfig);
74 }
75 });
76 }
77 },
78
79 /**
80 * The default options object
81 */
82 defaultOptions: {
83 // background: {conditional},
84 center: ['50%', '50%'],
85 size: '85%',
86 startAngle: 0
87 //endAngle: startAngle + 360
88 },
89
90 /**
91 * The default background options
92 */
93 defaultBackgroundOptions: {
94 className: 'highcharts-pane',
95 shape: 'circle',
96
97 borderWidth: 1,
98 borderColor: '#cccccc',
99 backgroundColor: {
100 linearGradient: {
101 x1: 0,
102 y1: 0,
103 x2: 0,
104 y2: 1
105 },
106 stops: [
107 [0, '#ffffff'],
108 [1, '#e6e6e6']
109 ]
110 },
111
112 from: -Number.MAX_VALUE, // corrected to axis min
113 innerRadius: 0,
114 to: Number.MAX_VALUE, // corrected to axis max
115 outerRadius: '105%'
116 }
117
118 });
119
120 H.Pane = Pane;
121
122 }(Highcharts));
123 (function(H) {
124 /**
125 * (c) 2010-2016 Torstein Honsi
126 *
127 * License: www.highcharts.com/license
128 */
129 'use strict';
130 var Axis = H.Axis,
131 CenteredSeriesMixin = H.CenteredSeriesMixin,
132 each = H.each,
133 extend = H.extend,
134 map = H.map,
135 merge = H.merge,
136 noop = H.noop,
137 Pane = H.Pane,
138 pick = H.pick,
139 pInt = H.pInt,
140 Tick = H.Tick,
141 splat = H.splat,
142 wrap = H.wrap,
143
144
145 hiddenAxisMixin, // @todo Extract this to a new file
146 radialAxisMixin, // @todo Extract this to a new file
147 axisProto = Axis.prototype,
148 tickProto = Tick.prototype;
149
150 /**
151 * Augmented methods for the x axis in order to hide it completely, used for the X axis in gauges
152 */
153 hiddenAxisMixin = {
154 getOffset: noop,
155 redraw: function() {
156 this.isDirty = false; // prevent setting Y axis dirty
157 },
158 render: function() {
159 this.isDirty = false; // prevent setting Y axis dirty
160 },
161 setScale: noop,
162 setCategories: noop,
163 setTitle: noop
164 };
165
166 /**
167 * Augmented methods for the value axis
168 */
169 radialAxisMixin = {
170
171 /**
172 * The default options extend defaultYAxisOptions
173 */
174 defaultRadialGaugeOptions: {
175 labels: {
176 align: 'center',
177 x: 0,
178 y: null // auto
179 },
180 minorGridLineWidth: 0,
181 minorTickInterval: 'auto',
182 minorTickLength: 10,
183 minorTickPosition: 'inside',
184 minorTickWidth: 1,
185 tickLength: 10,
186 tickPosition: 'inside',
187 tickWidth: 2,
188 title: {
189 rotation: 0
190 },
191 zIndex: 2 // behind dials, points in the series group
192 },
193
194 // Circular axis around the perimeter of a polar chart
195 defaultRadialXOptions: {
196 gridLineWidth: 1, // spokes
197 labels: {
198 align: null, // auto
199 distance: 15,
200 x: 0,
201 y: null // auto
202 },
203 maxPadding: 0,
204 minPadding: 0,
205 showLastLabel: false,
206 tickLength: 0
207 },
208
209 // Radial axis, like a spoke in a polar chart
210 defaultRadialYOptions: {
211 gridLineInterpolation: 'circle',
212 labels: {
213 align: 'right',
214 x: -3,
215 y: -2
216 },
217 showLastLabel: false,
218 title: {
219 x: 4,
220 text: null,
221 rotation: 90
222 }
223 },
224
225 /**
226 * Merge and set options
227 */
228 setOptions: function(userOptions) {
229
230 var options = this.options = merge(
231 this.defaultOptions,
232 this.defaultRadialOptions,
233 userOptions
234 );
235
236 // Make sure the plotBands array is instanciated for each Axis (#2649)
237 if (!options.plotBands) {
238 options.plotBands = [];
239 }
240
241 },
242
243 /**
244 * Wrap the getOffset method to return zero offset for title or labels in a radial
245 * axis
246 */
247 getOffset: function() {
248 // Call the Axis prototype method (the method we're in now is on the instance)
249 axisProto.getOffset.call(this);
250
251 // Title or label offsets are not counted
252 this.chart.axisOffset[this.side] = 0;
253
254 // Set the center array
255 this.center = this.pane.center = CenteredSeriesMixin.getCenter.call(this.pane);
256 },
257
258
259 /**
260 * Get the path for the axis line. This method is also referenced in the getPlotLinePath
261 * method.
262 */
263 getLinePath: function(lineWidth, radius) {
264 var center = this.center,
265 end,
266 chart = this.chart,
267 r = pick(radius, center[2] / 2 - this.offset),
268 path;
269
270 if (this.isCircular || radius !== undefined) {
271 path = this.chart.renderer.symbols.arc(
272 this.left + center[0],
273 this.top + center[1],
274 r,
275 r, {
276 start: this.startAngleRad,
277 end: this.endAngleRad,
278 open: true,
279 innerR: 0
280 }
281 );
282 } else {
283 end = this.postTranslate(this.angleRad, r);
284 path = ['M', center[0] + chart.plotLeft, center[1] + chart.plotTop, 'L', end.x, end.y];
285 }
286 return path;
287 },
288
289 /**
290 * Override setAxisTranslation by setting the translation to the difference
291 * in rotation. This allows the translate method to return angle for
292 * any given value.
293 */
294 setAxisTranslation: function() {
295
296 // Call uber method
297 axisProto.setAxisTranslation.call(this);
298
299 // Set transA and minPixelPadding
300 if (this.center) { // it's not defined the first time
301 if (this.isCircular) {
302
303 this.transA = (this.endAngleRad - this.startAngleRad) /
304 ((this.max - this.min) || 1);
305
306
307 } else {
308 this.transA = (this.center[2] / 2) / ((this.max - this.min) || 1);
309 }
310
311 if (this.isXAxis) {
312 this.minPixelPadding = this.transA * this.minPointOffset;
313 } else {
314 // This is a workaround for regression #2593, but categories still don't position correctly.
315 this.minPixelPadding = 0;
316 }
317 }
318 },
319
320 /**
321 * In case of auto connect, add one closestPointRange to the max value right before
322 * tickPositions are computed, so that ticks will extend passed the real max.
323 */
324 beforeSetTickPositions: function() {
325 // If autoConnect is true, polygonal grid lines are connected, and one closestPointRange
326 // is added to the X axis to prevent the last point from overlapping the first.
327 this.autoConnect = this.isCircular && pick(this.userMax, this.options.max) === undefined &&
328 this.endAngleRad - this.startAngleRad === 2 * Math.PI;
329
330 if (this.autoConnect) {
331 this.max += (this.categories && 1) || this.pointRange || this.closestPointRange || 0; // #1197, #2260
332 }
333 },
334
335 /**
336 * Override the setAxisSize method to use the arc's circumference as length. This
337 * allows tickPixelInterval to apply to pixel lengths along the perimeter
338 */
339 setAxisSize: function() {
340
341 axisProto.setAxisSize.call(this);
342
343 if (this.isRadial) {
344
345 // Set the center array
346 this.center = this.pane.center = CenteredSeriesMixin.getCenter.call(this.pane);
347
348 // The sector is used in Axis.translate to compute the translation of reversed axis points (#2570)
349 if (this.isCircular) {
350 this.sector = this.endAngleRad - this.startAngleRad;
351 }
352
353 // Axis len is used to lay out the ticks
354 this.len = this.width = this.height = this.center[2] * pick(this.sector, 1) / 2;
355
356
357 }
358 },
359
360 /**
361 * Returns the x, y coordinate of a point given by a value and a pixel distance
362 * from center
363 */
364 getPosition: function(value, length) {
365 return this.postTranslate(
366 this.isCircular ? this.translate(value) : this.angleRad, // #2848
367 pick(this.isCircular ? length : this.translate(value), this.center[2] / 2) - this.offset
368 );
369 },
370
371 /**
372 * Translate from intermediate plotX (angle), plotY (axis.len - radius) to final chart coordinates.
373 */
374 postTranslate: function(angle, radius) {
375
376 var chart = this.chart,
377 center = this.center;
378
379 angle = this.startAngleRad + angle;
380
381 return {
382 x: chart.plotLeft + center[0] + Math.cos(angle) * radius,
383 y: chart.plotTop + center[1] + Math.sin(angle) * radius
384 };
385
386 },
387
388 /**
389 * Find the path for plot bands along the radial axis
390 */
391 getPlotBandPath: function(from, to, options) {
392 var center = this.center,
393 startAngleRad = this.startAngleRad,
394 fullRadius = center[2] / 2,
395 radii = [
396 pick(options.outerRadius, '100%'),
397 options.innerRadius,
398 pick(options.thickness, 10)
399 ],
400 offset = Math.min(this.offset, 0),
401 percentRegex = /%$/,
402 start,
403 end,
404 open,
405 isCircular = this.isCircular, // X axis in a polar chart
406 ret;
407
408 // Polygonal plot bands
409 if (this.options.gridLineInterpolation === 'polygon') {
410 ret = this.getPlotLinePath(from).concat(this.getPlotLinePath(to, true));
411
412 // Circular grid bands
413 } else {
414
415 // Keep within bounds
416 from = Math.max(from, this.min);
417 to = Math.min(to, this.max);
418
419 // Plot bands on Y axis (radial axis) - inner and outer radius depend on to and from
420 if (!isCircular) {
421 radii[0] = this.translate(from);
422 radii[1] = this.translate(to);
423 }
424
425 // Convert percentages to pixel values
426 radii = map(radii, function(radius) {
427 if (percentRegex.test(radius)) {
428 radius = (pInt(radius, 10) * fullRadius) / 100;
429 }
430 return radius;
431 });
432
433 // Handle full circle
434 if (options.shape === 'circle' || !isCircular) {
435 start = -Math.PI / 2;
436 end = Math.PI * 1.5;
437 open = true;
438 } else {
439 start = startAngleRad + this.translate(from);
440 end = startAngleRad + this.translate(to);
441 }
442
443 radii[0] -= offset; // #5283
444 radii[2] -= offset; // #5283
445
446 ret = this.chart.renderer.symbols.arc(
447 this.left + center[0],
448 this.top + center[1],
449 radii[0],
450 radii[0], {
451 start: Math.min(start, end), // Math is for reversed yAxis (#3606)
452 end: Math.max(start, end),
453 innerR: pick(radii[1], radii[0] - radii[2]),
454 open: open
455 }
456 );
457 }
458
459 return ret;
460 },
461
462 /**
463 * Find the path for plot lines perpendicular to the radial axis.
464 */
465 getPlotLinePath: function(value, reverse) {
466 var axis = this,
467 center = axis.center,
468 chart = axis.chart,
469 end = axis.getPosition(value),
470 xAxis,
471 xy,
472 tickPositions,
473 ret;
474
475 // Spokes
476 if (axis.isCircular) {
477 ret = ['M', center[0] + chart.plotLeft, center[1] + chart.plotTop, 'L', end.x, end.y];
478
479 // Concentric circles
480 } else if (axis.options.gridLineInterpolation === 'circle') {
481 value = axis.translate(value);
482 if (value) { // a value of 0 is in the center
483 ret = axis.getLinePath(0, value);
484 }
485 // Concentric polygons
486 } else {
487 // Find the X axis in the same pane
488 each(chart.xAxis, function(a) {
489 if (a.pane === axis.pane) {
490 xAxis = a;
491 }
492 });
493 ret = [];
494 value = axis.translate(value);
495 tickPositions = xAxis.tickPositions;
496 if (xAxis.autoConnect) {
497 tickPositions = tickPositions.concat([tickPositions[0]]);
498 }
499 // Reverse the positions for concatenation of polygonal plot bands
500 if (reverse) {
501 tickPositions = [].concat(tickPositions).reverse();
502 }
503
504 each(tickPositions, function(pos, i) {
505 xy = xAxis.getPosition(pos, value);
506 ret.push(i ? 'L' : 'M', xy.x, xy.y);
507 });
508
509 }
510 return ret;
511 },
512
513 /**
514 * Find the position for the axis title, by default inside the gauge
515 */
516 getTitlePosition: function() {
517 var center = this.center,
518 chart = this.chart,
519 titleOptions = this.options.title;
520
521 return {
522 x: chart.plotLeft + center[0] + (titleOptions.x || 0),
523 y: chart.plotTop + center[1] - ({
524 high: 0.5,
525 middle: 0.25,
526 low: 0
527 }[titleOptions.align] *
528 center[2]) + (titleOptions.y || 0)
529 };
530 }
531
532 };
533
534 /**
535 * Override axisProto.init to mix in special axis instance functions and function overrides
536 */
537 wrap(axisProto, 'init', function(proceed, chart, userOptions) {
538 var axis = this,
539 angular = chart.angular,
540 polar = chart.polar,
541 isX = userOptions.isX,
542 isHidden = angular && isX,
543 isCircular,
544 options,
545 chartOptions = chart.options,
546 paneIndex = userOptions.pane || 0,
547 pane,
548 paneOptions;
549
550 // Before prototype.init
551 if (angular) {
552 extend(this, isHidden ? hiddenAxisMixin : radialAxisMixin);
553 isCircular = !isX;
554 if (isCircular) {
555 this.defaultRadialOptions = this.defaultRadialGaugeOptions;
556 }
557
558 } else if (polar) {
559 extend(this, radialAxisMixin);
560 isCircular = isX;
561 this.defaultRadialOptions = isX ? this.defaultRadialXOptions : merge(this.defaultYAxisOptions, this.defaultRadialYOptions);
562
563 }
564
565 // Disable certain features on angular and polar axes
566 if (angular || polar) {
567 this.isRadial = true;
568 chart.inverted = false;
569 chartOptions.chart.zoomType = null;
570 } else {
571 this.isRadial = false;
572 }
573
574 // Run prototype.init
575 proceed.call(this, chart, userOptions);
576
577 if (!isHidden && (angular || polar)) {
578 options = this.options;
579
580 // Create the pane and set the pane options.
581 if (!chart.panes) {
582 chart.panes = [];
583 }
584 this.pane = pane = chart.panes[paneIndex] = chart.panes[paneIndex] || new Pane(
585 splat(chartOptions.pane)[paneIndex],
586 chart,
587 axis
588 );
589 paneOptions = pane.options;
590
591 // Start and end angle options are
592 // given in degrees relative to top, while internal computations are
593 // in radians relative to right (like SVG).
594 this.angleRad = (options.angle || 0) * Math.PI / 180; // Y axis in polar charts
595 this.startAngleRad = (paneOptions.startAngle - 90) * Math.PI / 180; // Gauges
596 this.endAngleRad = (pick(paneOptions.endAngle, paneOptions.startAngle + 360) - 90) * Math.PI / 180; // Gauges
597 this.offset = options.offset || 0;
598
599 this.isCircular = isCircular;
600
601 }
602
603 });
604
605 /**
606 * Wrap auto label align to avoid setting axis-wide rotation on radial axes (#4920)
607 * @param {Function} proceed
608 * @returns {String} Alignment
609 */
610 wrap(axisProto, 'autoLabelAlign', function(proceed) {
611 if (!this.isRadial) {
612 return proceed.apply(this, [].slice.call(arguments, 1));
613 } // else return undefined
614 });
615
616 /**
617 * Add special cases within the Tick class' methods for radial axes.
618 */
619 wrap(tickProto, 'getPosition', function(proceed, horiz, pos, tickmarkOffset, old) {
620 var axis = this.axis;
621
622 return axis.getPosition ?
623 axis.getPosition(pos) :
624 proceed.call(this, horiz, pos, tickmarkOffset, old);
625 });
626
627 /**
628 * Wrap the getLabelPosition function to find the center position of the label
629 * based on the distance option
630 */
631 wrap(tickProto, 'getLabelPosition', function(proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
632 var axis = this.axis,
633 optionsY = labelOptions.y,
634 ret,
635 centerSlot = 20, // 20 degrees to each side at the top and bottom
636 align = labelOptions.align,
637 angle = ((axis.translate(this.pos) + axis.startAngleRad + Math.PI / 2) / Math.PI * 180) % 360;
638
639 if (axis.isRadial) { // Both X and Y axes in a polar chart
640 ret = axis.getPosition(this.pos, (axis.center[2] / 2) + pick(labelOptions.distance, -25));
641
642 // Automatically rotated
643 if (labelOptions.rotation === 'auto') {
644 label.attr({
645 rotation: angle
646 });
647
648 // Vertically centered
649 } else if (optionsY === null) {
650 optionsY = axis.chart.renderer.fontMetrics(label.styles.fontSize).b - label.getBBox().height / 2;
651 }
652
653 // Automatic alignment
654 if (align === null) {
655 if (axis.isCircular) { // Y axis
656 if (this.label.getBBox().width > axis.len * axis.tickInterval / (axis.max - axis.min)) { // #3506
657 centerSlot = 0;
658 }
659 if (angle > centerSlot && angle < 180 - centerSlot) {
660 align = 'left'; // right hemisphere
661 } else if (angle > 180 + centerSlot && angle < 360 - centerSlot) {
662 align = 'right'; // left hemisphere
663 } else {
664 align = 'center'; // top or bottom
665 }
666 } else {
667 align = 'center';
668 }
669 label.attr({
670 align: align
671 });
672 }
673
674 ret.x += labelOptions.x;
675 ret.y += optionsY;
676
677 } else {
678 ret = proceed.call(this, x, y, label, horiz, labelOptions, tickmarkOffset, index, step);
679 }
680 return ret;
681 });
682
683 /**
684 * Wrap the getMarkPath function to return the path of the radial marker
685 */
686 wrap(tickProto, 'getMarkPath', function(proceed, x, y, tickLength, tickWidth, horiz, renderer) {
687 var axis = this.axis,
688 endPoint,
689 ret;
690
691 if (axis.isRadial) {
692 endPoint = axis.getPosition(this.pos, axis.center[2] / 2 + tickLength);
693 ret = [
694 'M',
695 x,
696 y,
697 'L',
698 endPoint.x,
699 endPoint.y
700 ];
701 } else {
702 ret = proceed.call(this, x, y, tickLength, tickWidth, horiz, renderer);
703 }
704 return ret;
705 });
706
707 }(Highcharts));
708 (function(H) {
709 /**
710 * (c) 2010-2016 Torstein Honsi
711 *
712 * License: www.highcharts.com/license
713 */
714 'use strict';
715 var each = H.each,
716 noop = H.noop,
717 pick = H.pick,
718 Series = H.Series,
719 seriesType = H.seriesType,
720 seriesTypes = H.seriesTypes;
721 /*
722 * The arearangeseries series type
723 */
724 seriesType('arearange', 'area', {
725
726 lineWidth: 1,
727
728 marker: null,
729 threshold: null,
730 tooltip: {
731
732 pointFormat: '<span style="color:{series.color}">\u25CF</span> {series.name}: <b>{point.low}</b> - <b>{point.high}</b><br/>' // eslint-disable-line no-dupe-keys
733
734 },
735 trackByArea: true,
736 dataLabels: {
737 align: null,
738 verticalAlign: null,
739 xLow: 0,
740 xHigh: 0,
741 yLow: 0,
742 yHigh: 0
743 },
744 states: {
745 hover: {
746 halo: false
747 }
748 }
749
750 // Prototype members
751 }, {
752 pointArrayMap: ['low', 'high'],
753 dataLabelCollections: ['dataLabel', 'dataLabelUpper'],
754 toYData: function(point) {
755 return [point.low, point.high];
756 },
757 pointValKey: 'low',
758 deferTranslatePolar: true,
759
760 /**
761 * Translate a point's plotHigh from the internal angle and radius measures to
762 * true plotHigh coordinates. This is an addition of the toXY method found in
763 * Polar.js, because it runs too early for arearanges to be considered (#3419).
764 */
765 highToXY: function(point) {
766 // Find the polar plotX and plotY
767 var chart = this.chart,
768 xy = this.xAxis.postTranslate(point.rectPlotX, this.yAxis.len - point.plotHigh);
769 point.plotHighX = xy.x - chart.plotLeft;
770 point.plotHigh = xy.y - chart.plotTop;
771 },
772
773 /**
774 * Translate data points from raw values x and y to plotX and plotY
775 */
776 translate: function() {
777 var series = this,
778 yAxis = series.yAxis;
779
780 seriesTypes.area.prototype.translate.apply(series);
781
782 // Set plotLow and plotHigh
783 each(series.points, function(point) {
784
785 var low = point.low,
786 high = point.high,
787 plotY = point.plotY;
788
789 if (high === null || low === null) {
790 point.isNull = true;
791 } else {
792 point.plotLow = plotY;
793 point.plotHigh = yAxis.translate(high, 0, 1, 0, 1);
794 }
795 });
796
797 // Postprocess plotHigh
798 if (this.chart.polar) {
799 each(this.points, function(point) {
800 series.highToXY(point);
801 });
802 }
803 },
804
805 /**
806 * Extend the line series' getSegmentPath method by applying the segment
807 * path to both lower and higher values of the range
808 */
809 getGraphPath: function(points) {
810
811 var highPoints = [],
812 highAreaPoints = [],
813 i,
814 getGraphPath = seriesTypes.area.prototype.getGraphPath,
815 point,
816 pointShim,
817 linePath,
818 lowerPath,
819 options = this.options,
820 step = options.step,
821 higherPath,
822 higherAreaPath;
823
824 points = points || this.points;
825 i = points.length;
826
827 // Create the top line and the top part of the area fill. The area fill compensates for
828 // null points by drawing down to the lower graph, moving across the null gap and
829 // starting again at the lower graph.
830 i = points.length;
831 while (i--) {
832 point = points[i];
833
834 if (!point.isNull && !options.connectEnds && (!points[i + 1] || points[i + 1].isNull)) {
835 highAreaPoints.push({
836 plotX: point.plotX,
837 plotY: point.plotY,
838 doCurve: false // #5186, gaps in areasplinerange fill
839 });
840 }
841
842 pointShim = {
843 polarPlotY: point.polarPlotY,
844 rectPlotX: point.rectPlotX,
845 yBottom: point.yBottom,
846 plotX: pick(point.plotHighX, point.plotX), // plotHighX is for polar charts
847 plotY: point.plotHigh,
848 isNull: point.isNull
849 };
850 highAreaPoints.push(pointShim);
851 highPoints.push(pointShim);
852 if (!point.isNull && !options.connectEnds && (!points[i - 1] || points[i - 1].isNull)) {
853 highAreaPoints.push({
854 plotX: point.plotX,
855 plotY: point.plotY,
856 doCurve: false // #5186, gaps in areasplinerange fill
857 });
858 }
859 }
860
861 // Get the paths
862 lowerPath = getGraphPath.call(this, points);
863 if (step) {
864 if (step === true) {
865 step = 'left';
866 }
867 options.step = {
868 left: 'right',
869 center: 'center',
870 right: 'left'
871 }[step]; // swap for reading in getGraphPath
872 }
873 higherPath = getGraphPath.call(this, highPoints);
874 higherAreaPath = getGraphPath.call(this, highAreaPoints);
875 options.step = step;
876
877 // Create a line on both top and bottom of the range
878 linePath = [].concat(lowerPath, higherPath);
879
880 // For the area path, we need to change the 'move' statement into 'lineTo' or 'curveTo'
881 if (!this.chart.polar && higherAreaPath[0] === 'M') {
882 higherAreaPath[0] = 'L'; // this probably doesn't work for spline
883 }
884
885 this.graphPath = linePath;
886 this.areaPath = this.areaPath.concat(lowerPath, higherAreaPath);
887
888 // Prepare for sideways animation
889 linePath.isArea = true;
890 linePath.xMap = lowerPath.xMap;
891 this.areaPath.xMap = lowerPath.xMap;
892
893 return linePath;
894 },
895
896 /**
897 * Extend the basic drawDataLabels method by running it for both lower and higher
898 * values.
899 */
900 drawDataLabels: function() {
901
902 var data = this.data,
903 length = data.length,
904 i,
905 originalDataLabels = [],
906 seriesProto = Series.prototype,
907 dataLabelOptions = this.options.dataLabels,
908 align = dataLabelOptions.align,
909 verticalAlign = dataLabelOptions.verticalAlign,
910 inside = dataLabelOptions.inside,
911 point,
912 up,
913 inverted = this.chart.inverted;
914
915 if (dataLabelOptions.enabled || this._hasPointLabels) {
916
917 // Step 1: set preliminary values for plotY and dataLabel and draw the upper labels
918 i = length;
919 while (i--) {
920 point = data[i];
921 if (point) {
922 up = inside ? point.plotHigh < point.plotLow : point.plotHigh > point.plotLow;
923
924 // Set preliminary values
925 point.y = point.high;
926 point._plotY = point.plotY;
927 point.plotY = point.plotHigh;
928
929 // Store original data labels and set preliminary label objects to be picked up
930 // in the uber method
931 originalDataLabels[i] = point.dataLabel;
932 point.dataLabel = point.dataLabelUpper;
933
934 // Set the default offset
935 point.below = up;
936 if (inverted) {
937 if (!align) {
938 dataLabelOptions.align = up ? 'right' : 'left';
939 }
940 } else {
941 if (!verticalAlign) {
942 dataLabelOptions.verticalAlign = up ? 'top' : 'bottom';
943 }
944 }
945
946 dataLabelOptions.x = dataLabelOptions.xHigh;
947 dataLabelOptions.y = dataLabelOptions.yHigh;
948 }
949 }
950
951 if (seriesProto.drawDataLabels) {
952 seriesProto.drawDataLabels.apply(this, arguments); // #1209
953 }
954
955 // Step 2: reorganize and handle data labels for the lower values
956 i = length;
957 while (i--) {
958 point = data[i];
959 if (point) {
960 up = inside ? point.plotHigh < point.plotLow : point.plotHigh > point.plotLow;
961
962 // Move the generated labels from step 1, and reassign the original data labels
963 point.dataLabelUpper = point.dataLabel;
964 point.dataLabel = originalDataLabels[i];
965
966 // Reset values
967 point.y = point.low;
968 point.plotY = point._plotY;
969
970 // Set the default offset
971 point.below = !up;
972 if (inverted) {
973 if (!align) {
974 dataLabelOptions.align = up ? 'left' : 'right';
975 }
976 } else {
977 if (!verticalAlign) {
978 dataLabelOptions.verticalAlign = up ? 'bottom' : 'top';
979 }
980
981 }
982
983 dataLabelOptions.x = dataLabelOptions.xLow;
984 dataLabelOptions.y = dataLabelOptions.yLow;
985 }
986 }
987 if (seriesProto.drawDataLabels) {
988 seriesProto.drawDataLabels.apply(this, arguments);
989 }
990 }
991
992 dataLabelOptions.align = align;
993 dataLabelOptions.verticalAlign = verticalAlign;
994 },
995
996 alignDataLabel: function() {
997 seriesTypes.column.prototype.alignDataLabel.apply(this, arguments);
998 },
999
1000 setStackedPoints: noop,
1001
1002 getSymbol: noop,
1003
1004 drawPoints: noop
1005 });
1006
1007 }(Highcharts));
1008 (function(H) {
1009 /**
1010 * (c) 2010-2016 Torstein Honsi
1011 *
1012 * License: www.highcharts.com/license
1013 */
1014 'use strict';
1015 var seriesType = H.seriesType,
1016 seriesTypes = H.seriesTypes;
1017 /**
1018 * The areasplinerange series type
1019 */
1020 seriesType('areasplinerange', 'arearange', null, {
1021 getPointSpline: seriesTypes.spline.prototype.getPointSpline
1022 });
1023
1024 }(Highcharts));
1025 (function(H) {
1026 /**
1027 * (c) 2010-2016 Torstein Honsi
1028 *
1029 * License: www.highcharts.com/license
1030 */
1031 'use strict';
1032 var defaultPlotOptions = H.defaultPlotOptions,
1033 each = H.each,
1034 merge = H.merge,
1035 noop = H.noop,
1036 pick = H.pick,
1037 seriesType = H.seriesType,
1038 seriesTypes = H.seriesTypes;
1039
1040 var colProto = seriesTypes.column.prototype;
1041
1042 /**
1043 * The ColumnRangeSeries class
1044 */
1045 seriesType('columnrange', 'arearange', merge(defaultPlotOptions.column, defaultPlotOptions.arearange, {
1046 lineWidth: 1,
1047 pointRange: null
1048
1049 // Prototype members
1050 }), {
1051 /**
1052 * Translate data points from raw values x and y to plotX and plotY
1053 */
1054 translate: function() {
1055 var series = this,
1056 yAxis = series.yAxis,
1057 xAxis = series.xAxis,
1058 startAngleRad = xAxis.startAngleRad,
1059 start,
1060 chart = series.chart,
1061 isRadial = series.xAxis.isRadial,
1062 plotHigh;
1063
1064 colProto.translate.apply(series);
1065
1066 // Set plotLow and plotHigh
1067 each(series.points, function(point) {
1068 var shapeArgs = point.shapeArgs,
1069 minPointLength = series.options.minPointLength,
1070 heightDifference,
1071 height,
1072 y;
1073
1074 point.plotHigh = plotHigh = yAxis.translate(point.high, 0, 1, 0, 1);
1075 point.plotLow = point.plotY;
1076
1077 // adjust shape
1078 y = plotHigh;
1079 height = pick(point.rectPlotY, point.plotY) - plotHigh;
1080
1081 // Adjust for minPointLength
1082 if (Math.abs(height) < minPointLength) {
1083 heightDifference = (minPointLength - height);
1084 height += heightDifference;
1085 y -= heightDifference / 2;
1086
1087 // Adjust for negative ranges or reversed Y axis (#1457)
1088 } else if (height < 0) {
1089 height *= -1;
1090 y -= height;
1091 }
1092
1093 if (isRadial) {
1094
1095 start = point.barX + startAngleRad;
1096 point.shapeType = 'path';
1097 point.shapeArgs = {
1098 d: series.polarArc(y + height, y, start, start + point.pointWidth)
1099 };
1100 } else {
1101 shapeArgs.height = height;
1102 shapeArgs.y = y;
1103
1104 point.tooltipPos = chart.inverted ? [
1105 yAxis.len + yAxis.pos - chart.plotLeft - y - height / 2,
1106 xAxis.len + xAxis.pos - chart.plotTop - shapeArgs.x - shapeArgs.width / 2,
1107 height
1108 ] : [
1109 xAxis.left - chart.plotLeft + shapeArgs.x + shapeArgs.width / 2,
1110 yAxis.pos - chart.plotTop + y + height / 2,
1111 height
1112 ]; // don't inherit from column tooltip position - #3372
1113 }
1114 });
1115 },
1116 directTouch: true,
1117 trackerGroups: ['group', 'dataLabelsGroup'],
1118 drawGraph: noop,
1119 crispCol: colProto.crispCol,
1120 drawPoints: colProto.drawPoints,
1121 drawTracker: colProto.drawTracker,
1122 getColumnMetrics: colProto.getColumnMetrics,
1123 animate: function() {
1124 return colProto.animate.apply(this, arguments);
1125 },
1126 polarArc: function() {
1127 return colProto.polarArc.apply(this, arguments);
1128 },
1129 pointAttribs: colProto.pointAttribs
1130 });
1131
1132 }(Highcharts));
1133 (function(H) {
1134 /**
1135 * (c) 2010-2016 Torstein Honsi
1136 *
1137 * License: www.highcharts.com/license
1138 */
1139 'use strict';
1140 var each = H.each,
1141 isNumber = H.isNumber,
1142 merge = H.merge,
1143 noop = H.noop,
1144 pick = H.pick,
1145 pInt = H.pInt,
1146 Series = H.Series,
1147 seriesType = H.seriesType,
1148 TrackerMixin = H.TrackerMixin;
1149 /*
1150 * The GaugeSeries class
1151 */
1152 seriesType('gauge', 'line', {
1153 dataLabels: {
1154 enabled: true,
1155 defer: false,
1156 y: 15,
1157 borderRadius: 3,
1158 crop: false,
1159 verticalAlign: 'top',
1160 zIndex: 2,
1161
1162 // Presentational
1163 borderWidth: 1,
1164 borderColor: '#cccccc'
1165
1166 },
1167 dial: {
1168 // radius: '80%',
1169 // baseWidth: 3,
1170 // topWidth: 1,
1171 // baseLength: '70%' // of radius
1172 // rearLength: '10%'
1173
1174 // backgroundColor: '#000000',
1175 // borderColor: '#cccccc',
1176 // borderWidth: 0
1177
1178
1179 },
1180 pivot: {
1181 //radius: 5,
1182
1183 //borderWidth: 0
1184 //borderColor: '#cccccc',
1185 //backgroundColor: '#000000'
1186
1187 },
1188 tooltip: {
1189 headerFormat: ''
1190 },
1191 showInLegend: false
1192
1193 // Prototype members
1194 }, {
1195 // chart.angular will be set to true when a gauge series is present, and this will
1196 // be used on the axes
1197 angular: true,
1198 directTouch: true, // #5063
1199 drawGraph: noop,
1200 fixedBox: true,
1201 forceDL: true,
1202 noSharedTooltip: true,
1203 trackerGroups: ['group', 'dataLabelsGroup'],
1204
1205 /**
1206 * Calculate paths etc
1207 */
1208 translate: function() {
1209
1210 var series = this,
1211 yAxis = series.yAxis,
1212 options = series.options,
1213 center = yAxis.center;
1214
1215 series.generatePoints();
1216
1217 each(series.points, function(point) {
1218
1219 var dialOptions = merge(options.dial, point.dial),
1220 radius = (pInt(pick(dialOptions.radius, 80)) * center[2]) / 200,
1221 baseLength = (pInt(pick(dialOptions.baseLength, 70)) * radius) / 100,
1222 rearLength = (pInt(pick(dialOptions.rearLength, 10)) * radius) / 100,
1223 baseWidth = dialOptions.baseWidth || 3,
1224 topWidth = dialOptions.topWidth || 1,
1225 overshoot = options.overshoot,
1226 rotation = yAxis.startAngleRad + yAxis.translate(point.y, null, null, null, true);
1227
1228 // Handle the wrap and overshoot options
1229 if (isNumber(overshoot)) {
1230 overshoot = overshoot / 180 * Math.PI;
1231 rotation = Math.max(yAxis.startAngleRad - overshoot, Math.min(yAxis.endAngleRad + overshoot, rotation));
1232
1233 } else if (options.wrap === false) {
1234 rotation = Math.max(yAxis.startAngleRad, Math.min(yAxis.endAngleRad, rotation));
1235 }
1236
1237 rotation = rotation * 180 / Math.PI;
1238
1239 point.shapeType = 'path';
1240 point.shapeArgs = {
1241 d: dialOptions.path || [
1242 'M', -rearLength, -baseWidth / 2,
1243 'L',
1244 baseLength, -baseWidth / 2,
1245 radius, -topWidth / 2,
1246 radius, topWidth / 2,
1247 baseLength, baseWidth / 2, -rearLength, baseWidth / 2,
1248 'z'
1249 ],
1250 translateX: center[0],
1251 translateY: center[1],
1252 rotation: rotation
1253 };
1254
1255 // Positions for data label
1256 point.plotX = center[0];
1257 point.plotY = center[1];
1258 });
1259 },
1260
1261 /**
1262 * Draw the points where each point is one needle
1263 */
1264 drawPoints: function() {
1265
1266 var series = this,
1267 center = series.yAxis.center,
1268 pivot = series.pivot,
1269 options = series.options,
1270 pivotOptions = options.pivot,
1271 renderer = series.chart.renderer;
1272
1273 each(series.points, function(point) {
1274
1275 var graphic = point.graphic,
1276 shapeArgs = point.shapeArgs,
1277 d = shapeArgs.d,
1278 dialOptions = merge(options.dial, point.dial); // #1233
1279
1280 if (graphic) {
1281 graphic.animate(shapeArgs);
1282 shapeArgs.d = d; // animate alters it
1283 } else {
1284 point.graphic = renderer[point.shapeType](shapeArgs)
1285 .attr({
1286 rotation: shapeArgs.rotation, // required by VML when animation is false
1287 zIndex: 1
1288 })
1289 .addClass('highcharts-dial')
1290 .add(series.group);
1291
1292
1293 // Presentational attributes
1294 point.graphic.attr({
1295 stroke: dialOptions.borderColor || 'none',
1296 'stroke-width': dialOptions.borderWidth || 0,
1297 fill: dialOptions.backgroundColor || '#000000'
1298 });
1299
1300 }
1301 });
1302
1303 // Add or move the pivot
1304 if (pivot) {
1305 pivot.animate({ // #1235
1306 translateX: center[0],
1307 translateY: center[1]
1308 });
1309 } else {
1310 series.pivot = renderer.circle(0, 0, pick(pivotOptions.radius, 5))
1311 .attr({
1312 zIndex: 2
1313 })
1314 .addClass('highcharts-pivot')
1315 .translate(center[0], center[1])
1316 .add(series.group);
1317
1318
1319 // Presentational attributes
1320 series.pivot.attr({
1321 'stroke-width': pivotOptions.borderWidth || 0,
1322 stroke: pivotOptions.borderColor || '#cccccc',
1323 fill: pivotOptions.backgroundColor || '#000000'
1324 });
1325
1326 }
1327 },
1328
1329 /**
1330 * Animate the arrow up from startAngle
1331 */
1332 animate: function(init) {
1333 var series = this;
1334
1335 if (!init) {
1336 each(series.points, function(point) {
1337 var graphic = point.graphic;
1338
1339 if (graphic) {
1340 // start value
1341 graphic.attr({
1342 rotation: series.yAxis.startAngleRad * 180 / Math.PI
1343 });
1344
1345 // animate
1346 graphic.animate({
1347 rotation: point.shapeArgs.rotation
1348 }, series.options.animation);
1349 }
1350 });
1351
1352 // delete this function to allow it only once
1353 series.animate = null;
1354 }
1355 },
1356
1357 render: function() {
1358 this.group = this.plotGroup(
1359 'group',
1360 'series',
1361 this.visible ? 'visible' : 'hidden',
1362 this.options.zIndex,
1363 this.chart.seriesGroup
1364 );
1365 Series.prototype.render.call(this);
1366 this.group.clip(this.chart.clipRect);
1367 },
1368
1369 /**
1370 * Extend the basic setData method by running processData and generatePoints immediately,
1371 * in order to access the points from the legend.
1372 */
1373 setData: function(data, redraw) {
1374 Series.prototype.setData.call(this, data, false);
1375 this.processData();
1376 this.generatePoints();
1377 if (pick(redraw, true)) {
1378 this.chart.redraw();
1379 }
1380 },
1381
1382 /**
1383 * If the tracking module is loaded, add the point tracker
1384 */
1385 drawTracker: TrackerMixin && TrackerMixin.drawTrackerPoint
1386
1387 // Point members
1388 }, {
1389 /**
1390 * Don't do any hover colors or anything
1391 */
1392 setState: function(state) {
1393 this.state = state;
1394 }
1395 });
1396
1397 }(Highcharts));
1398 (function(H) {
1399 /**
1400 * (c) 2010-2016 Torstein Honsi
1401 *
1402 * License: www.highcharts.com/license
1403 */
1404 'use strict';
1405 var each = H.each,
1406 noop = H.noop,
1407 pick = H.pick,
1408 seriesType = H.seriesType,
1409 seriesTypes = H.seriesTypes;
1410
1411 /* ****************************************************************************
1412 * Start Box plot series code *
1413 *****************************************************************************/
1414 seriesType('boxplot', 'column', {
1415 threshold: null,
1416 tooltip: {
1417
1418 pointFormat: '<span style="color:{point.color}">\u25CF</span> <b> {series.name}</b><br/>' + // eslint-disable-line no-dupe-keys
1419 'Maximum: {point.high}<br/>' +
1420 'Upper quartile: {point.q3}<br/>' +
1421 'Median: {point.median}<br/>' +
1422 'Lower quartile: {point.q1}<br/>' +
1423 'Minimum: {point.low}<br/>'
1424
1425 },
1426 whiskerLength: '50%',
1427
1428 fillColor: '#ffffff',
1429 lineWidth: 1,
1430 //medianColor: null,
1431 medianWidth: 2,
1432 states: {
1433 hover: {
1434 brightness: -0.3
1435 }
1436 },
1437 //stemColor: null,
1438 //stemDashStyle: 'solid'
1439 //stemWidth: null,
1440
1441 //whiskerColor: null,
1442 whiskerWidth: 2
1443
1444
1445 // Prototype members
1446 }, {
1447 pointArrayMap: ['low', 'q1', 'median', 'q3', 'high'], // array point configs are mapped to this
1448 toYData: function(point) { // return a plain array for speedy calculation
1449 return [point.low, point.q1, point.median, point.q3, point.high];
1450 },
1451 pointValKey: 'high', // defines the top of the tracker
1452
1453
1454 /**
1455 * Get presentational attributes
1456 */
1457 pointAttribs: function(point) {
1458 var options = this.options,
1459 color = (point && point.color) || this.color;
1460
1461 return {
1462 'fill': options.fillColor || color,
1463 'stroke': options.lineColor || color,
1464 'stroke-width': options.lineWidth || 0
1465 };
1466 },
1467
1468
1469 /**
1470 * Disable data labels for box plot
1471 */
1472 drawDataLabels: noop,
1473
1474 /**
1475 * Translate data points from raw values x and y to plotX and plotY
1476 */
1477 translate: function() {
1478 var series = this,
1479 yAxis = series.yAxis,
1480 pointArrayMap = series.pointArrayMap;
1481
1482 seriesTypes.column.prototype.translate.apply(series);
1483
1484 // do the translation on each point dimension
1485 each(series.points, function(point) {
1486 each(pointArrayMap, function(key) {
1487 if (point[key] !== null) {
1488 point[key + 'Plot'] = yAxis.translate(point[key], 0, 1, 0, 1);
1489 }
1490 });
1491 });
1492 },
1493
1494 /**
1495 * Draw the data points
1496 */
1497 drawPoints: function() {
1498 var series = this, //state = series.state,
1499 points = series.points,
1500 options = series.options,
1501 chart = series.chart,
1502 renderer = chart.renderer,
1503 q1Plot,
1504 q3Plot,
1505 highPlot,
1506 lowPlot,
1507 medianPlot,
1508 medianPath,
1509 crispCorr,
1510 crispX = 0,
1511 boxPath,
1512 width,
1513 left,
1514 right,
1515 halfWidth,
1516 doQuartiles = series.doQuartiles !== false, // error bar inherits this series type but doesn't do quartiles
1517 pointWiskerLength,
1518 whiskerLength = series.options.whiskerLength;
1519
1520
1521 each(points, function(point) {
1522
1523 var graphic = point.graphic,
1524 verb = graphic ? 'animate' : 'attr',
1525 shapeArgs = point.shapeArgs; // the box
1526
1527
1528 var boxAttr,
1529 stemAttr = {},
1530 whiskersAttr = {},
1531 medianAttr = {},
1532 color = point.color || series.color;
1533
1534
1535 if (point.plotY !== undefined) {
1536
1537 // crisp vector coordinates
1538 width = shapeArgs.width;
1539 left = Math.floor(shapeArgs.x);
1540 right = left + width;
1541 halfWidth = Math.round(width / 2);
1542 q1Plot = Math.floor(doQuartiles ? point.q1Plot : point.lowPlot);
1543 q3Plot = Math.floor(doQuartiles ? point.q3Plot : point.lowPlot);
1544 highPlot = Math.floor(point.highPlot);
1545 lowPlot = Math.floor(point.lowPlot);
1546
1547 if (!graphic) {
1548 point.graphic = graphic = renderer.g('point')
1549 .add(series.group);
1550
1551 point.stem = renderer.path()
1552 .addClass('highcharts-boxplot-stem')
1553 .add(graphic);
1554
1555 if (whiskerLength) {
1556 point.whiskers = renderer.path()
1557 .addClass('highcharts-boxplot-whisker')
1558 .add(graphic);
1559 }
1560 if (doQuartiles) {
1561 point.box = renderer.path(boxPath)
1562 .addClass('highcharts-boxplot-box')
1563 .add(graphic);
1564 }
1565 point.medianShape = renderer.path(medianPath)
1566 .addClass('highcharts-boxplot-median')
1567 .add(graphic);
1568
1569
1570
1571
1572 // Stem attributes
1573 stemAttr.stroke = point.stemColor || options.stemColor || color;
1574 stemAttr['stroke-width'] = pick(point.stemWidth, options.stemWidth, options.lineWidth);
1575 stemAttr.dashstyle = point.stemDashStyle || options.stemDashStyle;
1576 point.stem.attr(stemAttr);
1577
1578 // Whiskers attributes
1579 if (whiskerLength) {
1580 whiskersAttr.stroke = point.whiskerColor || options.whiskerColor || color;
1581 whiskersAttr['stroke-width'] = pick(point.whiskerWidth, options.whiskerWidth, options.lineWidth);
1582 point.whiskers.attr(whiskersAttr);
1583 }
1584
1585 if (doQuartiles) {
1586 boxAttr = series.pointAttribs(point);
1587 point.box.attr(boxAttr);
1588 }
1589
1590
1591 // Median attributes
1592 medianAttr.stroke = point.medianColor || options.medianColor || color;
1593 medianAttr['stroke-width'] = pick(point.medianWidth, options.medianWidth, options.lineWidth);
1594 point.medianShape.attr(medianAttr);
1595
1596
1597 }
1598
1599
1600
1601 // The stem
1602 crispCorr = (point.stem.strokeWidth() % 2) / 2;
1603 crispX = left + halfWidth + crispCorr;
1604 point.stem[verb]({
1605 d: [
1606 // stem up
1607 'M',
1608 crispX, q3Plot,
1609 'L',
1610 crispX, highPlot,
1611
1612 // stem down
1613 'M',
1614 crispX, q1Plot,
1615 'L',
1616 crispX, lowPlot
1617 ]
1618 });
1619
1620 // The box
1621 if (doQuartiles) {
1622 crispCorr = (point.box.strokeWidth() % 2) / 2;
1623 q1Plot = Math.floor(q1Plot) + crispCorr;
1624 q3Plot = Math.floor(q3Plot) + crispCorr;
1625 left += crispCorr;
1626 right += crispCorr;
1627 point.box[verb]({
1628 d: [
1629 'M',
1630 left, q3Plot,
1631 'L',
1632 left, q1Plot,
1633 'L',
1634 right, q1Plot,
1635 'L',
1636 right, q3Plot,
1637 'L',
1638 left, q3Plot,
1639 'z'
1640 ]
1641 });
1642 }
1643
1644 // The whiskers
1645 if (whiskerLength) {
1646 crispCorr = (point.whiskers.strokeWidth() % 2) / 2;
1647 highPlot = highPlot + crispCorr;
1648 lowPlot = lowPlot + crispCorr;
1649 pointWiskerLength = (/%$/).test(whiskerLength) ? halfWidth * parseFloat(whiskerLength) / 100 : whiskerLength / 2;
1650 point.whiskers[verb]({
1651 d: [
1652 // High whisker
1653 'M',
1654 crispX - pointWiskerLength,
1655 highPlot,
1656 'L',
1657 crispX + pointWiskerLength,
1658 highPlot,
1659
1660 // Low whisker
1661 'M',
1662 crispX - pointWiskerLength,
1663 lowPlot,
1664 'L',
1665 crispX + pointWiskerLength,
1666 lowPlot
1667 ]
1668 });
1669 }
1670
1671 // The median
1672 medianPlot = Math.round(point.medianPlot);
1673 crispCorr = (point.medianShape.strokeWidth() % 2) / 2;
1674 medianPlot = medianPlot + crispCorr;
1675
1676 point.medianShape[verb]({
1677 d: [
1678 'M',
1679 left,
1680 medianPlot,
1681 'L',
1682 right,
1683 medianPlot
1684 ]
1685 });
1686 }
1687 });
1688
1689 },
1690 setStackedPoints: noop // #3890
1691
1692
1693 });
1694
1695 /* ****************************************************************************
1696 * End Box plot series code *
1697 *****************************************************************************/
1698
1699 }(Highcharts));
1700 (function(H) {
1701 /**
1702 * (c) 2010-2016 Torstein Honsi
1703 *
1704 * License: www.highcharts.com/license
1705 */
1706 'use strict';
1707 var each = H.each,
1708 noop = H.noop,
1709 seriesType = H.seriesType,
1710 seriesTypes = H.seriesTypes;
1711
1712
1713 /* ****************************************************************************
1714 * Start error bar series code *
1715 *****************************************************************************/
1716 seriesType('errorbar', 'boxplot', {
1717
1718 color: '#000000',
1719
1720 grouping: false,
1721 linkedTo: ':previous',
1722 tooltip: {
1723 pointFormat: '<span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.low}</b> - <b>{point.high}</b><br/>'
1724 },
1725 whiskerWidth: null
1726
1727 // Prototype members
1728 }, {
1729 type: 'errorbar',
1730 pointArrayMap: ['low', 'high'], // array point configs are mapped to this
1731 toYData: function(point) { // return a plain array for speedy calculation
1732 return [point.low, point.high];
1733 },
1734 pointValKey: 'high', // defines the top of the tracker
1735 doQuartiles: false,
1736 drawDataLabels: seriesTypes.arearange ? function() {
1737 var valKey = this.pointValKey;
1738 seriesTypes.arearange.prototype.drawDataLabels.call(this);
1739 // Arearange drawDataLabels does not reset point.y to high, but to low after drawing. #4133
1740 each(this.data, function(point) {
1741 point.y = point[valKey];
1742 });
1743 } : noop,
1744
1745 /**
1746 * Get the width and X offset, either on top of the linked series column
1747 * or standalone
1748 */
1749 getColumnMetrics: function() {
1750 return (this.linkedParent && this.linkedParent.columnMetrics) ||
1751 seriesTypes.column.prototype.getColumnMetrics.call(this);
1752 }
1753 });
1754
1755 /* ****************************************************************************
1756 * End error bar series code *
1757 *****************************************************************************/
1758
1759 }(Highcharts));
1760 (function(H) {
1761 /**
1762 * (c) 2010-2016 Torstein Honsi
1763 *
1764 * License: www.highcharts.com/license
1765 */
1766 'use strict';
1767 var correctFloat = H.correctFloat,
1768 isNumber = H.isNumber,
1769 noop = H.noop,
1770 pick = H.pick,
1771 Point = H.Point,
1772 Series = H.Series,
1773 seriesType = H.seriesType,
1774 seriesTypes = H.seriesTypes;
1775
1776 /* ****************************************************************************
1777 * Start Waterfall series code *
1778 *****************************************************************************/
1779 seriesType('waterfall', 'column', {
1780 dataLabels: {
1781 inside: true
1782 },
1783
1784 lineWidth: 1,
1785 lineColor: '#333333',
1786 dashStyle: 'dot',
1787 borderColor: '#333333',
1788 states: {
1789 hover: {
1790 lineWidthPlus: 0 // #3126
1791 }
1792 }
1793
1794
1795 // Prototype members
1796 }, {
1797 pointValKey: 'y',
1798
1799 /**
1800 * Translate data points from raw values
1801 */
1802 translate: function() {
1803 var series = this,
1804 options = series.options,
1805 yAxis = series.yAxis,
1806 len,
1807 i,
1808 points,
1809 point,
1810 shapeArgs,
1811 stack,
1812 y,
1813 yValue,
1814 previousY,
1815 previousIntermediate,
1816 range,
1817 minPointLength = pick(options.minPointLength, 5),
1818 threshold = options.threshold,
1819 stacking = options.stacking,
1820 tooltipY;
1821
1822 // run column series translate
1823 seriesTypes.column.prototype.translate.apply(this);
1824 series.minPointLengthOffset = 0;
1825
1826 previousY = previousIntermediate = threshold;
1827 points = series.points;
1828
1829 for (i = 0, len = points.length; i < len; i++) {
1830 // cache current point object
1831 point = points[i];
1832 yValue = this.processedYData[i];
1833 shapeArgs = point.shapeArgs;
1834
1835 // get current stack
1836 stack = stacking && yAxis.stacks[(series.negStacks && yValue < threshold ? '-' : '') + series.stackKey];
1837 range = stack ?
1838 stack[point.x].points[series.index + ',' + i] : [0, yValue];
1839
1840 // override point value for sums
1841 // #3710 Update point does not propagate to sum
1842 if (point.isSum) {
1843 point.y = correctFloat(yValue);
1844 } else if (point.isIntermediateSum) {
1845 point.y = correctFloat(yValue - previousIntermediate); // #3840
1846 }
1847 // up points
1848 y = Math.max(previousY, previousY + point.y) + range[0];
1849 shapeArgs.y = yAxis.toPixels(y, true);
1850
1851
1852 // sum points
1853 if (point.isSum) {
1854 shapeArgs.y = yAxis.toPixels(range[1], true);
1855 shapeArgs.height = Math.min(yAxis.toPixels(range[0], true), yAxis.len) - shapeArgs.y + series.minPointLengthOffset; // #4256
1856
1857 } else if (point.isIntermediateSum) {
1858 shapeArgs.y = yAxis.toPixels(range[1], true);
1859 shapeArgs.height = Math.min(yAxis.toPixels(previousIntermediate, true), yAxis.len) - shapeArgs.y + series.minPointLengthOffset;
1860 previousIntermediate = range[1];
1861
1862 // If it's not the sum point, update previous stack end position and get
1863 // shape height (#3886)
1864 } else {
1865 shapeArgs.height = yValue > 0 ?
1866 yAxis.toPixels(previousY, true) - shapeArgs.y :
1867 yAxis.toPixels(previousY, true) - yAxis.toPixels(previousY - yValue, true);
1868 previousY += yValue;
1869 }
1870 // #3952 Negative sum or intermediate sum not rendered correctly
1871 if (shapeArgs.height < 0) {
1872 shapeArgs.y += shapeArgs.height;
1873 shapeArgs.height *= -1;
1874 }
1875
1876 point.plotY = shapeArgs.y = Math.round(shapeArgs.y) - (series.borderWidth % 2) / 2;
1877 shapeArgs.height = Math.max(Math.round(shapeArgs.height), 0.001); // #3151
1878 point.yBottom = shapeArgs.y + shapeArgs.height;
1879
1880 if (shapeArgs.height <= minPointLength) {
1881 shapeArgs.height = minPointLength;
1882 series.minPointLengthOffset += minPointLength;
1883 }
1884
1885 shapeArgs.y -= series.minPointLengthOffset;
1886
1887 // Correct tooltip placement (#3014)
1888 tooltipY = point.plotY + (point.negative ? shapeArgs.height : 0) - series.minPointLengthOffset;
1889 if (series.chart.inverted) {
1890 point.tooltipPos[0] = yAxis.len - tooltipY;
1891 } else {
1892 point.tooltipPos[1] = tooltipY;
1893 }
1894
1895 }
1896 },
1897
1898 /**
1899 * Call default processData then override yData to reflect waterfall's extremes on yAxis
1900 */
1901 processData: function(force) {
1902 var series = this,
1903 options = series.options,
1904 yData = series.yData,
1905 points = series.options.data, // #3710 Update point does not propagate to sum
1906 point,
1907 dataLength = yData.length,
1908 threshold = options.threshold || 0,
1909 subSum,
1910 sum,
1911 dataMin,
1912 dataMax,
1913 y,
1914 i;
1915
1916 sum = subSum = dataMin = dataMax = threshold;
1917
1918 for (i = 0; i < dataLength; i++) {
1919 y = yData[i];
1920 point = points && points[i] ? points[i] : {};
1921
1922 if (y === 'sum' || point.isSum) {
1923 yData[i] = correctFloat(sum);
1924 } else if (y === 'intermediateSum' || point.isIntermediateSum) {
1925 yData[i] = correctFloat(subSum);
1926 } else {
1927 sum += y;
1928 subSum += y;
1929 }
1930 dataMin = Math.min(sum, dataMin);
1931 dataMax = Math.max(sum, dataMax);
1932 }
1933
1934 Series.prototype.processData.call(this, force);
1935
1936 // Record extremes
1937 series.dataMin = dataMin;
1938 series.dataMax = dataMax;
1939 },
1940
1941 /**
1942 * Return y value or string if point is sum
1943 */
1944 toYData: function(pt) {
1945 if (pt.isSum) {
1946 return (pt.x === 0 ? null : 'sum'); //#3245 Error when first element is Sum or Intermediate Sum
1947 }
1948 if (pt.isIntermediateSum) {
1949 return (pt.x === 0 ? null : 'intermediateSum'); //#3245
1950 }
1951 return pt.y;
1952 },
1953
1954
1955 /**
1956 * Postprocess mapping between options and SVG attributes
1957 */
1958 pointAttribs: function(point, state) {
1959
1960 var upColor = this.options.upColor,
1961 attr;
1962
1963 // Set or reset up color (#3710, update to negative)
1964 if (upColor && !point.options.color) {
1965 point.color = point.y > 0 ? upColor : null;
1966 }
1967
1968 attr = seriesTypes.column.prototype.pointAttribs.call(this, point, state);
1969
1970 // The dashStyle option in waterfall applies to the graph, not
1971 // the points
1972 delete attr.dashstyle;
1973
1974 return attr;
1975 },
1976
1977
1978 /**
1979 * Return an empty path initially, because we need to know the stroke-width in order
1980 * to set the final path.
1981 */
1982 getGraphPath: function() {
1983 return ['M', 0, 0];
1984 },
1985
1986 /**
1987 * Draw columns' connector lines
1988 */
1989 getCrispPath: function() {
1990
1991 var data = this.data,
1992 length = data.length,
1993 lineWidth = this.graph.strokeWidth() + this.borderWidth,
1994 normalizer = Math.round(lineWidth) % 2 / 2,
1995 path = [],
1996 prevArgs,
1997 pointArgs,
1998 i,
1999 d;
2000
2001 for (i = 1; i < length; i++) {
2002 pointArgs = data[i].shapeArgs;
2003 prevArgs = data[i - 1].shapeArgs;
2004
2005 d = [
2006 'M',
2007 prevArgs.x + prevArgs.width, prevArgs.y + normalizer,
2008 'L',
2009 pointArgs.x, prevArgs.y + normalizer
2010 ];
2011
2012 if (data[i - 1].y < 0) {
2013 d[2] += prevArgs.height;
2014 d[5] += prevArgs.height;
2015 }
2016
2017 path = path.concat(d);
2018 }
2019
2020 return path;
2021 },
2022
2023 /**
2024 * The graph is initally drawn with an empty definition, then updated with
2025 * crisp rendering.
2026 */
2027 drawGraph: function() {
2028 Series.prototype.drawGraph.call(this);
2029 this.graph.attr({
2030 d: this.getCrispPath()
2031 });
2032 },
2033
2034 /**
2035 * Extremes are recorded in processData
2036 */
2037 getExtremes: noop
2038
2039 // Point members
2040 }, {
2041 getClassName: function() {
2042 var className = Point.prototype.getClassName.call(this);
2043
2044 if (this.isSum) {
2045 className += ' highcharts-sum';
2046 } else if (this.isIntermediateSum) {
2047 className += ' highcharts-intermediate-sum';
2048 }
2049 return className;
2050 },
2051 /**
2052 * Pass the null test in ColumnSeries.translate.
2053 */
2054 isValid: function() {
2055 return isNumber(this.y, true) || this.isSum || this.isIntermediateSum;
2056 }
2057
2058 });
2059
2060 /* ****************************************************************************
2061 * End Waterfall series code *
2062 *****************************************************************************/
2063
2064 }(Highcharts));
2065 (function(H) {
2066 /**
2067 * (c) 2010-2016 Torstein Honsi
2068 *
2069 * License: www.highcharts.com/license
2070 */
2071 'use strict';
2072 var LegendSymbolMixin = H.LegendSymbolMixin,
2073 noop = H.noop,
2074 Series = H.Series,
2075 seriesType = H.seriesType,
2076 seriesTypes = H.seriesTypes;
2077 /**
2078 * The polygon series prototype
2079 */
2080 seriesType('polygon', 'scatter', {
2081 marker: {
2082 enabled: false,
2083 states: {
2084 hover: {
2085 enabled: false
2086 }
2087 }
2088 },
2089 stickyTracking: false,
2090 tooltip: {
2091 followPointer: true,
2092 pointFormat: ''
2093 },
2094 trackByArea: true
2095
2096 // Prototype members
2097 }, {
2098 type: 'polygon',
2099 getGraphPath: function() {
2100
2101 var graphPath = Series.prototype.getGraphPath.call(this),
2102 i = graphPath.length + 1;
2103
2104 // Close all segments
2105 while (i--) {
2106 if ((i === graphPath.length || graphPath[i] === 'M') && i > 0) {
2107 graphPath.splice(i, 0, 'z');
2108 }
2109 }
2110 this.areaPath = graphPath;
2111 return graphPath;
2112 },
2113 drawGraph: function() {
2114
2115 this.options.fillColor = this.color; // Hack into the fill logic in area.drawGraph
2116
2117 seriesTypes.area.prototype.drawGraph.call(this);
2118 },
2119 drawLegendSymbol: LegendSymbolMixin.drawRectangle,
2120 drawTracker: Series.prototype.drawTracker,
2121 setStackedPoints: noop // No stacking points on polygons (#5310)
2122 });
2123
2124 }(Highcharts));
2125 (function(H) {
2126 /**
2127 * (c) 2010-2016 Torstein Honsi
2128 *
2129 * License: www.highcharts.com/license
2130 */
2131 'use strict';
2132 var arrayMax = H.arrayMax,
2133 arrayMin = H.arrayMin,
2134 Axis = H.Axis,
2135 color = H.color,
2136 each = H.each,
2137 isNumber = H.isNumber,
2138 noop = H.noop,
2139 pick = H.pick,
2140 pInt = H.pInt,
2141 Point = H.Point,
2142 Series = H.Series,
2143 seriesType = H.seriesType,
2144 seriesTypes = H.seriesTypes;
2145
2146 /* ****************************************************************************
2147 * Start Bubble series code *
2148 *****************************************************************************/
2149
2150 seriesType('bubble', 'scatter', {
2151 dataLabels: {
2152 formatter: function() { // #2945
2153 return this.point.z;
2154 },
2155 inside: true,
2156 verticalAlign: 'middle'
2157 },
2158 // displayNegative: true,
2159 marker: {
2160
2161 // fillOpacity: 0.5,
2162 lineColor: null, // inherit from series.color
2163 lineWidth: 1,
2164
2165 // Avoid offset in Point.setState
2166 radius: null,
2167 states: {
2168 hover: {
2169 radiusPlus: 0
2170 }
2171 }
2172 },
2173 minSize: 8,
2174 maxSize: '20%',
2175 // negativeColor: null,
2176 // sizeBy: 'area'
2177 softThreshold: false,
2178 states: {
2179 hover: {
2180 halo: {
2181 size: 5
2182 }
2183 }
2184 },
2185 tooltip: {
2186 pointFormat: '({point.x}, {point.y}), Size: {point.z}'
2187 },
2188 turboThreshold: 0,
2189 zThreshold: 0,
2190 zoneAxis: 'z'
2191
2192 // Prototype members
2193 }, {
2194 pointArrayMap: ['y', 'z'],
2195 parallelArrays: ['x', 'y', 'z'],
2196 trackerGroups: ['group', 'dataLabelsGroup'],
2197 bubblePadding: true,
2198 zoneAxis: 'z',
2199
2200
2201 pointAttribs: function(point, state) {
2202 var markerOptions = this.options.marker,
2203 fillOpacity = pick(markerOptions.fillOpacity, 0.5),
2204 attr = Series.prototype.pointAttribs.call(this, point, state);
2205
2206 if (fillOpacity !== 1) {
2207 attr.fill = color(attr.fill).setOpacity(fillOpacity).get('rgba');
2208 }
2209
2210 return attr;
2211 },
2212
2213
2214 /**
2215 * Get the radius for each point based on the minSize, maxSize and each point's Z value. This
2216 * must be done prior to Series.translate because the axis needs to add padding in
2217 * accordance with the point sizes.
2218 */
2219 getRadii: function(zMin, zMax, minSize, maxSize) {
2220 var len,
2221 i,
2222 pos,
2223 zData = this.zData,
2224 radii = [],
2225 options = this.options,
2226 sizeByArea = options.sizeBy !== 'width',
2227 zThreshold = options.zThreshold,
2228 zRange = zMax - zMin,
2229 value,
2230 radius;
2231
2232 // Set the shape type and arguments to be picked up in drawPoints
2233 for (i = 0, len = zData.length; i < len; i++) {
2234
2235 value = zData[i];
2236
2237 // When sizing by threshold, the absolute value of z determines the size
2238 // of the bubble.
2239 if (options.sizeByAbsoluteValue && value !== null) {
2240 value = Math.abs(value - zThreshold);
2241 zMax = Math.max(zMax - zThreshold, Math.abs(zMin - zThreshold));
2242 zMin = 0;
2243 }
2244
2245 if (value === null) {
2246 radius = null;
2247 // Issue #4419 - if value is less than zMin, push a radius that's always smaller than the minimum size
2248 } else if (value < zMin) {
2249 radius = minSize / 2 - 1;
2250 } else {
2251 // Relative size, a number between 0 and 1
2252 pos = zRange > 0 ? (value - zMin) / zRange : 0.5;
2253
2254 if (sizeByArea && pos >= 0) {
2255 pos = Math.sqrt(pos);
2256 }
2257 radius = Math.ceil(minSize + pos * (maxSize - minSize)) / 2;
2258 }
2259 radii.push(radius);
2260 }
2261 this.radii = radii;
2262 },
2263
2264 /**
2265 * Perform animation on the bubbles
2266 */
2267 animate: function(init) {
2268 var animation = this.options.animation;
2269
2270 if (!init) { // run the animation
2271 each(this.points, function(point) {
2272 var graphic = point.graphic,
2273 shapeArgs = point.shapeArgs;
2274
2275 if (graphic && shapeArgs) {
2276 // start values
2277 graphic.attr('r', 1);
2278
2279 // animate
2280 graphic.animate({
2281 r: shapeArgs.r
2282 }, animation);
2283 }
2284 });
2285
2286 // delete this function to allow it only once
2287 this.animate = null;
2288 }
2289 },
2290
2291 /**
2292 * Extend the base translate method to handle bubble size
2293 */
2294 translate: function() {
2295
2296 var i,
2297 data = this.data,
2298 point,
2299 radius,
2300 radii = this.radii;
2301
2302 // Run the parent method
2303 seriesTypes.scatter.prototype.translate.call(this);
2304
2305 // Set the shape type and arguments to be picked up in drawPoints
2306 i = data.length;
2307
2308 while (i--) {
2309 point = data[i];
2310 radius = radii ? radii[i] : 0; // #1737
2311
2312 if (isNumber(radius) && radius >= this.minPxSize / 2) {
2313 // Shape arguments
2314 point.shapeType = 'circle';
2315 point.shapeArgs = {
2316 x: point.plotX,
2317 y: point.plotY,
2318 r: radius
2319 };
2320
2321 // Alignment box for the data label
2322 point.dlBox = {
2323 x: point.plotX - radius,
2324 y: point.plotY - radius,
2325 width: 2 * radius,
2326 height: 2 * radius
2327 };
2328 } else { // below zThreshold
2329 point.shapeArgs = point.plotY = point.dlBox = undefined; // #1691
2330 }
2331 }
2332 },
2333
2334 /**
2335 * Get the series' symbol in the legend
2336 *
2337 * @param {Object} legend The legend object
2338 * @param {Object} item The series (this) or point
2339 */
2340 drawLegendSymbol: function(legend, item) {
2341 var renderer = this.chart.renderer,
2342 radius = renderer.fontMetrics(legend.itemStyle.fontSize).f / 2;
2343
2344 item.legendSymbol = renderer.circle(
2345 radius,
2346 legend.baseline - radius,
2347 radius
2348 ).attr({
2349 zIndex: 3
2350 }).add(item.legendGroup);
2351 item.legendSymbol.isMarker = true;
2352
2353 },
2354
2355 drawPoints: seriesTypes.column.prototype.drawPoints,
2356 alignDataLabel: seriesTypes.column.prototype.alignDataLabel,
2357 buildKDTree: noop,
2358 applyZones: noop
2359
2360 // Point class
2361 }, {
2362 haloPath: function() {
2363 return Point.prototype.haloPath.call(this, this.shapeArgs.r + this.series.options.states.hover.halo.size);
2364 },
2365 ttBelow: false
2366 });
2367
2368 /**
2369 * Add logic to pad each axis with the amount of pixels
2370 * necessary to avoid the bubbles to overflow.
2371 */
2372 Axis.prototype.beforePadding = function() {
2373 var axis = this,
2374 axisLength = this.len,
2375 chart = this.chart,
2376 pxMin = 0,
2377 pxMax = axisLength,
2378 isXAxis = this.isXAxis,
2379 dataKey = isXAxis ? 'xData' : 'yData',
2380 min = this.min,
2381 extremes = {},
2382 smallestSize = Math.min(chart.plotWidth, chart.plotHeight),
2383 zMin = Number.MAX_VALUE,
2384 zMax = -Number.MAX_VALUE,
2385 range = this.max - min,
2386 transA = axisLength / range,
2387 activeSeries = [];
2388
2389 // Handle padding on the second pass, or on redraw
2390 each(this.series, function(series) {
2391
2392 var seriesOptions = series.options,
2393 zData;
2394
2395 if (series.bubblePadding && (series.visible || !chart.options.chart.ignoreHiddenSeries)) {
2396
2397 // Correction for #1673
2398 axis.allowZoomOutside = true;
2399
2400 // Cache it
2401 activeSeries.push(series);
2402
2403 if (isXAxis) { // because X axis is evaluated first
2404
2405 // For each series, translate the size extremes to pixel values
2406 each(['minSize', 'maxSize'], function(prop) {
2407 var length = seriesOptions[prop],
2408 isPercent = /%$/.test(length);
2409
2410 length = pInt(length);
2411 extremes[prop] = isPercent ?
2412 smallestSize * length / 100 :
2413 length;
2414
2415 });
2416 series.minPxSize = extremes.minSize;
2417 series.maxPxSize = extremes.maxSize;
2418
2419 // Find the min and max Z
2420 zData = series.zData;
2421 if (zData.length) { // #1735
2422 zMin = pick(seriesOptions.zMin, Math.min(
2423 zMin,
2424 Math.max(
2425 arrayMin(zData),
2426 seriesOptions.displayNegative === false ? seriesOptions.zThreshold : -Number.MAX_VALUE
2427 )
2428 ));
2429 zMax = pick(seriesOptions.zMax, Math.max(zMax, arrayMax(zData)));
2430 }
2431 }
2432 }
2433 });
2434
2435 each(activeSeries, function(series) {
2436
2437 var data = series[dataKey],
2438 i = data.length,
2439 radius;
2440
2441 if (isXAxis) {
2442 series.getRadii(zMin, zMax, series.minPxSize, series.maxPxSize);
2443 }
2444
2445 if (range > 0) {
2446 while (i--) {
2447 if (isNumber(data[i]) && axis.dataMin <= data[i] && data[i] <= axis.dataMax) {
2448 radius = series.radii[i];
2449 pxMin = Math.min(((data[i] - min) * transA) - radius, pxMin);
2450 pxMax = Math.max(((data[i] - min) * transA) + radius, pxMax);
2451 }
2452 }
2453 }
2454 });
2455
2456 if (activeSeries.length && range > 0 && !this.isLog) {
2457 pxMax -= axisLength;
2458 transA *= (axisLength + pxMin - pxMax) / axisLength;
2459 each([
2460 ['min', 'userMin', pxMin],
2461 ['max', 'userMax', pxMax]
2462 ], function(keys) {
2463 if (pick(axis.options[keys[0]], axis[keys[1]]) === undefined) {
2464 axis[keys[0]] += keys[2] / transA;
2465 }
2466 });
2467 }
2468 };
2469
2470 /* ****************************************************************************
2471 * End Bubble series code *
2472 *****************************************************************************/
2473
2474 }(Highcharts));
2475 (function(H) {
2476 /**
2477 * (c) 2010-2016 Torstein Honsi
2478 *
2479 * License: www.highcharts.com/license
2480 */
2481 'use strict';
2482
2483 /**
2484 * Extensions for polar charts. Additionally, much of the geometry required for polar charts is
2485 * gathered in RadialAxes.js.
2486 *
2487 */
2488
2489 var each = H.each,
2490 pick = H.pick,
2491 Pointer = H.Pointer,
2492 Series = H.Series,
2493 seriesTypes = H.seriesTypes,
2494 wrap = H.wrap,
2495
2496 seriesProto = Series.prototype,
2497 pointerProto = Pointer.prototype,
2498 colProto;
2499
2500 /**
2501 * Search a k-d tree by the point angle, used for shared tooltips in polar charts
2502 */
2503 seriesProto.searchPointByAngle = function(e) {
2504 var series = this,
2505 chart = series.chart,
2506 xAxis = series.xAxis,
2507 center = xAxis.pane.center,
2508 plotX = e.chartX - center[0] - chart.plotLeft,
2509 plotY = e.chartY - center[1] - chart.plotTop;
2510
2511 return this.searchKDTree({
2512 clientX: 180 + (Math.atan2(plotX, plotY) * (-180 / Math.PI))
2513 });
2514
2515 };
2516
2517 /**
2518 * Wrap the buildKDTree function so that it searches by angle (clientX) in case of shared tooltip,
2519 * and by two dimensional distance in case of non-shared.
2520 */
2521 wrap(seriesProto, 'buildKDTree', function(proceed) {
2522 if (this.chart.polar) {
2523 if (this.kdByAngle) {
2524 this.searchPoint = this.searchPointByAngle;
2525 } else {
2526 this.kdDimensions = 2;
2527 }
2528 }
2529 proceed.apply(this);
2530 });
2531
2532 /**
2533 * Translate a point's plotX and plotY from the internal angle and radius measures to
2534 * true plotX, plotY coordinates
2535 */
2536 seriesProto.toXY = function(point) {
2537 var xy,
2538 chart = this.chart,
2539 plotX = point.plotX,
2540 plotY = point.plotY,
2541 clientX;
2542
2543 // Save rectangular plotX, plotY for later computation
2544 point.rectPlotX = plotX;
2545 point.rectPlotY = plotY;
2546
2547 // Find the polar plotX and plotY
2548 xy = this.xAxis.postTranslate(point.plotX, this.yAxis.len - plotY);
2549 point.plotX = point.polarPlotX = xy.x - chart.plotLeft;
2550 point.plotY = point.polarPlotY = xy.y - chart.plotTop;
2551
2552 // If shared tooltip, record the angle in degrees in order to align X points. Otherwise,
2553 // use a standard k-d tree to get the nearest point in two dimensions.
2554 if (this.kdByAngle) {
2555 clientX = ((plotX / Math.PI * 180) + this.xAxis.pane.options.startAngle) % 360;
2556 if (clientX < 0) { // #2665
2557 clientX += 360;
2558 }
2559 point.clientX = clientX;
2560 } else {
2561 point.clientX = point.plotX;
2562 }
2563 };
2564
2565 if (seriesTypes.spline) {
2566 /**
2567 * Overridden method for calculating a spline from one point to the next
2568 */
2569 wrap(seriesTypes.spline.prototype, 'getPointSpline', function(proceed, segment, point, i) {
2570
2571 var ret,
2572 smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc;
2573 denom = smoothing + 1,
2574 plotX,
2575 plotY,
2576 lastPoint,
2577 nextPoint,
2578 lastX,
2579 lastY,
2580 nextX,
2581 nextY,
2582 leftContX,
2583 leftContY,
2584 rightContX,
2585 rightContY,
2586 distanceLeftControlPoint,
2587 distanceRightControlPoint,
2588 leftContAngle,
2589 rightContAngle,
2590 jointAngle;
2591
2592
2593 if (this.chart.polar) {
2594
2595 plotX = point.plotX;
2596 plotY = point.plotY;
2597 lastPoint = segment[i - 1];
2598 nextPoint = segment[i + 1];
2599
2600 // Connect ends
2601 if (this.connectEnds) {
2602 if (!lastPoint) {
2603 lastPoint = segment[segment.length - 2]; // not the last but the second last, because the segment is already connected
2604 }
2605 if (!nextPoint) {
2606 nextPoint = segment[1];
2607 }
2608 }
2609
2610 // find control points
2611 if (lastPoint && nextPoint) {
2612
2613 lastX = lastPoint.plotX;
2614 lastY = lastPoint.plotY;
2615 nextX = nextPoint.plotX;
2616 nextY = nextPoint.plotY;
2617 leftContX = (smoothing * plotX + lastX) / denom;
2618 leftContY = (smoothing * plotY + lastY) / denom;
2619 rightContX = (smoothing * plotX + nextX) / denom;
2620 rightContY = (smoothing * plotY + nextY) / denom;
2621 distanceLeftControlPoint = Math.sqrt(Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2));
2622 distanceRightControlPoint = Math.sqrt(Math.pow(rightContX - plotX, 2) + Math.pow(rightContY - plotY, 2));
2623 leftContAngle = Math.atan2(leftContY - plotY, leftContX - plotX);
2624 rightContAngle = Math.atan2(rightContY - plotY, rightContX - plotX);
2625 jointAngle = (Math.PI / 2) + ((leftContAngle + rightContAngle) / 2);
2626
2627
2628 // Ensure the right direction, jointAngle should be in the same quadrant as leftContAngle
2629 if (Math.abs(leftContAngle - jointAngle) > Math.PI / 2) {
2630 jointAngle -= Math.PI;
2631 }
2632
2633 // Find the corrected control points for a spline straight through the point
2634 leftContX = plotX + Math.cos(jointAngle) * distanceLeftControlPoint;
2635 leftContY = plotY + Math.sin(jointAngle) * distanceLeftControlPoint;
2636 rightContX = plotX + Math.cos(Math.PI + jointAngle) * distanceRightControlPoint;
2637 rightContY = plotY + Math.sin(Math.PI + jointAngle) * distanceRightControlPoint;
2638
2639 // Record for drawing in next point
2640 point.rightContX = rightContX;
2641 point.rightContY = rightContY;
2642
2643 }
2644
2645
2646 // moveTo or lineTo
2647 if (!i) {
2648 ret = ['M', plotX, plotY];
2649 } else { // curve from last point to this
2650 ret = [
2651 'C',
2652 lastPoint.rightContX || lastPoint.plotX,
2653 lastPoint.rightContY || lastPoint.plotY,
2654 leftContX || plotX,
2655 leftContY || plotY,
2656 plotX,
2657 plotY
2658 ];
2659 lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
2660 }
2661
2662
2663 } else {
2664 ret = proceed.call(this, segment, point, i);
2665 }
2666 return ret;
2667 });
2668 }
2669
2670 /**
2671 * Extend translate. The plotX and plotY values are computed as if the polar chart were a
2672 * cartesian plane, where plotX denotes the angle in radians and (yAxis.len - plotY) is the pixel distance from
2673 * center.
2674 */
2675 wrap(seriesProto, 'translate', function(proceed) {
2676 var chart = this.chart,
2677 points,
2678 i;
2679
2680 // Run uber method
2681 proceed.call(this);
2682
2683 // Postprocess plot coordinates
2684 if (chart.polar) {
2685 this.kdByAngle = chart.tooltip && chart.tooltip.shared;
2686
2687 if (!this.preventPostTranslate) {
2688 points = this.points;
2689 i = points.length;
2690
2691 while (i--) {
2692 // Translate plotX, plotY from angle and radius to true plot coordinates
2693 this.toXY(points[i]);
2694 }
2695 }
2696 }
2697 });
2698
2699 /**
2700 * Extend getSegmentPath to allow connecting ends across 0 to provide a closed circle in
2701 * line-like series.
2702 */
2703 wrap(seriesProto, 'getGraphPath', function(proceed, points) {
2704 var series = this,
2705 i,
2706 firstValid;
2707
2708 // Connect the path
2709 if (this.chart.polar) {
2710 points = points || this.points;
2711
2712 // Append first valid point in order to connect the ends
2713 for (i = 0; i < points.length; i++) {
2714 if (!points[i].isNull) {
2715 firstValid = i;
2716 break;
2717 }
2718 }
2719 if (this.options.connectEnds !== false && firstValid !== undefined) {
2720 this.connectEnds = true; // re-used in splines
2721 points.splice(points.length, 0, points[firstValid]);
2722 }
2723
2724 // For area charts, pseudo points are added to the graph, now we need to translate these
2725 each(points, function(point) {
2726 if (point.polarPlotY === undefined) {
2727 series.toXY(point);
2728 }
2729 });
2730 }
2731
2732 // Run uber method
2733 return proceed.apply(this, [].slice.call(arguments, 1));
2734
2735 });
2736
2737
2738 function polarAnimate(proceed, init) {
2739 var chart = this.chart,
2740 animation = this.options.animation,
2741 group = this.group,
2742 markerGroup = this.markerGroup,
2743 center = this.xAxis.center,
2744 plotLeft = chart.plotLeft,
2745 plotTop = chart.plotTop,
2746 attribs;
2747
2748 // Specific animation for polar charts
2749 if (chart.polar) {
2750
2751 // Enable animation on polar charts only in SVG. In VML, the scaling is different, plus animation
2752 // would be so slow it would't matter.
2753 if (chart.renderer.isSVG) {
2754
2755 if (animation === true) {
2756 animation = {};
2757 }
2758
2759 // Initialize the animation
2760 if (init) {
2761
2762 // Scale down the group and place it in the center
2763 attribs = {
2764 translateX: center[0] + plotLeft,
2765 translateY: center[1] + plotTop,
2766 scaleX: 0.001, // #1499
2767 scaleY: 0.001
2768 };
2769
2770 group.attr(attribs);
2771 if (markerGroup) {
2772 //markerGroup.attrSetters = group.attrSetters;
2773 markerGroup.attr(attribs);
2774 }
2775
2776 // Run the animation
2777 } else {
2778 attribs = {
2779 translateX: plotLeft,
2780 translateY: plotTop,
2781 scaleX: 1,
2782 scaleY: 1
2783 };
2784 group.animate(attribs, animation);
2785 if (markerGroup) {
2786 markerGroup.animate(attribs, animation);
2787 }
2788
2789 // Delete this function to allow it only once
2790 this.animate = null;
2791 }
2792 }
2793
2794 // For non-polar charts, revert to the basic animation
2795 } else {
2796 proceed.call(this, init);
2797 }
2798 }
2799
2800 // Define the animate method for regular series
2801 wrap(seriesProto, 'animate', polarAnimate);
2802
2803
2804 if (seriesTypes.column) {
2805
2806 colProto = seriesTypes.column.prototype;
2807
2808 colProto.polarArc = function(low, high, start, end) {
2809 var center = this.xAxis.center,
2810 len = this.yAxis.len;
2811
2812 return this.chart.renderer.symbols.arc(
2813 center[0],
2814 center[1],
2815 len - high,
2816 null, {
2817 start: start,
2818 end: end,
2819 innerR: len - pick(low, len)
2820 }
2821 );
2822 };
2823
2824 /**
2825 * Define the animate method for columnseries
2826 */
2827 wrap(colProto, 'animate', polarAnimate);
2828
2829
2830 /**
2831 * Extend the column prototype's translate method
2832 */
2833 wrap(colProto, 'translate', function(proceed) {
2834
2835 var xAxis = this.xAxis,
2836 startAngleRad = xAxis.startAngleRad,
2837 start,
2838 points,
2839 point,
2840 i;
2841
2842 this.preventPostTranslate = true;
2843
2844 // Run uber method
2845 proceed.call(this);
2846
2847 // Postprocess plot coordinates
2848 if (xAxis.isRadial) {
2849 points = this.points;
2850 i = points.length;
2851 while (i--) {
2852 point = points[i];
2853 start = point.barX + startAngleRad;
2854 point.shapeType = 'path';
2855 point.shapeArgs = {
2856 d: this.polarArc(point.yBottom, point.plotY, start, start + point.pointWidth)
2857 };
2858 // Provide correct plotX, plotY for tooltip
2859 this.toXY(point);
2860 point.tooltipPos = [point.plotX, point.plotY];
2861 point.ttBelow = point.plotY > xAxis.center[1];
2862 }
2863 }
2864 });
2865
2866
2867 /**
2868 * Align column data labels outside the columns. #1199.
2869 */
2870 wrap(colProto, 'alignDataLabel', function(proceed, point, dataLabel, options, alignTo, isNew) {
2871
2872 if (this.chart.polar) {
2873 var angle = point.rectPlotX / Math.PI * 180,
2874 align,
2875 verticalAlign;
2876
2877 // Align nicely outside the perimeter of the columns
2878 if (options.align === null) {
2879 if (angle > 20 && angle < 160) {
2880 align = 'left'; // right hemisphere
2881 } else if (angle > 200 && angle < 340) {
2882 align = 'right'; // left hemisphere
2883 } else {
2884 align = 'center'; // top or bottom
2885 }
2886 options.align = align;
2887 }
2888 if (options.verticalAlign === null) {
2889 if (angle < 45 || angle > 315) {
2890 verticalAlign = 'bottom'; // top part
2891 } else if (angle > 135 && angle < 225) {
2892 verticalAlign = 'top'; // bottom part
2893 } else {
2894 verticalAlign = 'middle'; // left or right
2895 }
2896 options.verticalAlign = verticalAlign;
2897 }
2898
2899 seriesProto.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);
2900 } else {
2901 proceed.call(this, point, dataLabel, options, alignTo, isNew);
2902 }
2903
2904 });
2905 }
2906
2907 /**
2908 * Extend getCoordinates to prepare for polar axis values
2909 */
2910 wrap(pointerProto, 'getCoordinates', function(proceed, e) {
2911 var chart = this.chart,
2912 ret = {
2913 xAxis: [],
2914 yAxis: []
2915 };
2916
2917 if (chart.polar) {
2918
2919 each(chart.axes, function(axis) {
2920 var isXAxis = axis.isXAxis,
2921 center = axis.center,
2922 x = e.chartX - center[0] - chart.plotLeft,
2923 y = e.chartY - center[1] - chart.plotTop;
2924
2925 ret[isXAxis ? 'xAxis' : 'yAxis'].push({
2926 axis: axis,
2927 value: axis.translate(
2928 isXAxis ?
2929 Math.PI - Math.atan2(x, y) : // angle
2930 Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), // distance from center
2931 true
2932 )
2933 });
2934 });
2935
2936 } else {
2937 ret = proceed.call(this, e);
2938 }
2939
2940 return ret;
2941 });
2942
2943 }(Highcharts));
2944}));