UNPKG

110 kBJavaScriptView Raw
1/**
2 * @license Highcharts JS v5.0.5 (2016-11-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 hasModifyValue = !!series.modifyValue;
780
781 seriesTypes.area.prototype.translate.apply(series);
782
783 // Set plotLow and plotHigh
784 each(series.points, function(point) {
785
786 var low = point.low,
787 high = point.high,
788 plotY = point.plotY;
789
790 if (high === null || low === null) {
791 point.isNull = true;
792 } else {
793 point.plotLow = plotY;
794 point.plotHigh = yAxis.translate(
795 hasModifyValue ? series.modifyValue(high, point) : high,
796 0,
797 1,
798 0,
799 1
800 );
801 if (hasModifyValue) {
802 point.yBottom = point.plotHigh;
803 }
804 }
805 });
806
807 // Postprocess plotHigh
808 if (this.chart.polar) {
809 each(this.points, function(point) {
810 series.highToXY(point);
811 });
812 }
813 },
814
815 /**
816 * Extend the line series' getSegmentPath method by applying the segment
817 * path to both lower and higher values of the range
818 */
819 getGraphPath: function(points) {
820
821 var highPoints = [],
822 highAreaPoints = [],
823 i,
824 getGraphPath = seriesTypes.area.prototype.getGraphPath,
825 point,
826 pointShim,
827 linePath,
828 lowerPath,
829 options = this.options,
830 step = options.step,
831 higherPath,
832 higherAreaPath;
833
834 points = points || this.points;
835 i = points.length;
836
837 // Create the top line and the top part of the area fill. The area fill compensates for
838 // null points by drawing down to the lower graph, moving across the null gap and
839 // starting again at the lower graph.
840 i = points.length;
841 while (i--) {
842 point = points[i];
843
844 if (!point.isNull && !options.connectEnds && (!points[i + 1] || points[i + 1].isNull)) {
845 highAreaPoints.push({
846 plotX: point.plotX,
847 plotY: point.plotY,
848 doCurve: false // #5186, gaps in areasplinerange fill
849 });
850 }
851
852 pointShim = {
853 polarPlotY: point.polarPlotY,
854 rectPlotX: point.rectPlotX,
855 yBottom: point.yBottom,
856 plotX: pick(point.plotHighX, point.plotX), // plotHighX is for polar charts
857 plotY: point.plotHigh,
858 isNull: point.isNull
859 };
860 highAreaPoints.push(pointShim);
861 highPoints.push(pointShim);
862 if (!point.isNull && !options.connectEnds && (!points[i - 1] || points[i - 1].isNull)) {
863 highAreaPoints.push({
864 plotX: point.plotX,
865 plotY: point.plotY,
866 doCurve: false // #5186, gaps in areasplinerange fill
867 });
868 }
869 }
870
871 // Get the paths
872 lowerPath = getGraphPath.call(this, points);
873 if (step) {
874 if (step === true) {
875 step = 'left';
876 }
877 options.step = {
878 left: 'right',
879 center: 'center',
880 right: 'left'
881 }[step]; // swap for reading in getGraphPath
882 }
883 higherPath = getGraphPath.call(this, highPoints);
884 higherAreaPath = getGraphPath.call(this, highAreaPoints);
885 options.step = step;
886
887 // Create a line on both top and bottom of the range
888 linePath = [].concat(lowerPath, higherPath);
889
890 // For the area path, we need to change the 'move' statement into 'lineTo' or 'curveTo'
891 if (!this.chart.polar && higherAreaPath[0] === 'M') {
892 higherAreaPath[0] = 'L'; // this probably doesn't work for spline
893 }
894
895 this.graphPath = linePath;
896 this.areaPath = this.areaPath.concat(lowerPath, higherAreaPath);
897
898 // Prepare for sideways animation
899 linePath.isArea = true;
900 linePath.xMap = lowerPath.xMap;
901 this.areaPath.xMap = lowerPath.xMap;
902
903 return linePath;
904 },
905
906 /**
907 * Extend the basic drawDataLabels method by running it for both lower and higher
908 * values.
909 */
910 drawDataLabels: function() {
911
912 var data = this.data,
913 length = data.length,
914 i,
915 originalDataLabels = [],
916 seriesProto = Series.prototype,
917 dataLabelOptions = this.options.dataLabels,
918 align = dataLabelOptions.align,
919 verticalAlign = dataLabelOptions.verticalAlign,
920 inside = dataLabelOptions.inside,
921 point,
922 up,
923 inverted = this.chart.inverted;
924
925 if (dataLabelOptions.enabled || this._hasPointLabels) {
926
927 // Step 1: set preliminary values for plotY and dataLabel and draw the upper labels
928 i = length;
929 while (i--) {
930 point = data[i];
931 if (point) {
932 up = inside ? point.plotHigh < point.plotLow : point.plotHigh > point.plotLow;
933
934 // Set preliminary values
935 point.y = point.high;
936 point._plotY = point.plotY;
937 point.plotY = point.plotHigh;
938
939 // Store original data labels and set preliminary label objects to be picked up
940 // in the uber method
941 originalDataLabels[i] = point.dataLabel;
942 point.dataLabel = point.dataLabelUpper;
943
944 // Set the default offset
945 point.below = up;
946 if (inverted) {
947 if (!align) {
948 dataLabelOptions.align = up ? 'right' : 'left';
949 }
950 } else {
951 if (!verticalAlign) {
952 dataLabelOptions.verticalAlign = up ? 'top' : 'bottom';
953 }
954 }
955
956 dataLabelOptions.x = dataLabelOptions.xHigh;
957 dataLabelOptions.y = dataLabelOptions.yHigh;
958 }
959 }
960
961 if (seriesProto.drawDataLabels) {
962 seriesProto.drawDataLabels.apply(this, arguments); // #1209
963 }
964
965 // Step 2: reorganize and handle data labels for the lower values
966 i = length;
967 while (i--) {
968 point = data[i];
969 if (point) {
970 up = inside ? point.plotHigh < point.plotLow : point.plotHigh > point.plotLow;
971
972 // Move the generated labels from step 1, and reassign the original data labels
973 point.dataLabelUpper = point.dataLabel;
974 point.dataLabel = originalDataLabels[i];
975
976 // Reset values
977 point.y = point.low;
978 point.plotY = point._plotY;
979
980 // Set the default offset
981 point.below = !up;
982 if (inverted) {
983 if (!align) {
984 dataLabelOptions.align = up ? 'left' : 'right';
985 }
986 } else {
987 if (!verticalAlign) {
988 dataLabelOptions.verticalAlign = up ? 'bottom' : 'top';
989 }
990
991 }
992
993 dataLabelOptions.x = dataLabelOptions.xLow;
994 dataLabelOptions.y = dataLabelOptions.yLow;
995 }
996 }
997 if (seriesProto.drawDataLabels) {
998 seriesProto.drawDataLabels.apply(this, arguments);
999 }
1000 }
1001
1002 dataLabelOptions.align = align;
1003 dataLabelOptions.verticalAlign = verticalAlign;
1004 },
1005
1006 alignDataLabel: function() {
1007 seriesTypes.column.prototype.alignDataLabel.apply(this, arguments);
1008 },
1009
1010 setStackedPoints: noop,
1011
1012 getSymbol: noop,
1013
1014 drawPoints: noop
1015 });
1016
1017 }(Highcharts));
1018 (function(H) {
1019 /**
1020 * (c) 2010-2016 Torstein Honsi
1021 *
1022 * License: www.highcharts.com/license
1023 */
1024 'use strict';
1025
1026 var seriesType = H.seriesType,
1027 seriesTypes = H.seriesTypes;
1028
1029 /**
1030 * The areasplinerange series type
1031 */
1032 seriesType('areasplinerange', 'arearange', null, {
1033 getPointSpline: seriesTypes.spline.prototype.getPointSpline
1034 });
1035
1036 }(Highcharts));
1037 (function(H) {
1038 /**
1039 * (c) 2010-2016 Torstein Honsi
1040 *
1041 * License: www.highcharts.com/license
1042 */
1043 'use strict';
1044 var defaultPlotOptions = H.defaultPlotOptions,
1045 each = H.each,
1046 merge = H.merge,
1047 noop = H.noop,
1048 pick = H.pick,
1049 seriesType = H.seriesType,
1050 seriesTypes = H.seriesTypes;
1051
1052 var colProto = seriesTypes.column.prototype;
1053
1054 /**
1055 * The ColumnRangeSeries class
1056 */
1057 seriesType('columnrange', 'arearange', merge(defaultPlotOptions.column, defaultPlotOptions.arearange, {
1058 lineWidth: 1,
1059 pointRange: null
1060
1061 // Prototype members
1062 }), {
1063 /**
1064 * Translate data points from raw values x and y to plotX and plotY
1065 */
1066 translate: function() {
1067 var series = this,
1068 yAxis = series.yAxis,
1069 xAxis = series.xAxis,
1070 startAngleRad = xAxis.startAngleRad,
1071 start,
1072 chart = series.chart,
1073 isRadial = series.xAxis.isRadial,
1074 plotHigh;
1075
1076 colProto.translate.apply(series);
1077
1078 // Set plotLow and plotHigh
1079 each(series.points, function(point) {
1080 var shapeArgs = point.shapeArgs,
1081 minPointLength = series.options.minPointLength,
1082 heightDifference,
1083 height,
1084 y;
1085
1086 point.plotHigh = plotHigh = yAxis.translate(point.high, 0, 1, 0, 1);
1087 point.plotLow = point.plotY;
1088
1089 // adjust shape
1090 y = plotHigh;
1091 height = pick(point.rectPlotY, point.plotY) - plotHigh;
1092
1093 // Adjust for minPointLength
1094 if (Math.abs(height) < minPointLength) {
1095 heightDifference = (minPointLength - height);
1096 height += heightDifference;
1097 y -= heightDifference / 2;
1098
1099 // Adjust for negative ranges or reversed Y axis (#1457)
1100 } else if (height < 0) {
1101 height *= -1;
1102 y -= height;
1103 }
1104
1105 if (isRadial) {
1106
1107 start = point.barX + startAngleRad;
1108 point.shapeType = 'path';
1109 point.shapeArgs = {
1110 d: series.polarArc(y + height, y, start, start + point.pointWidth)
1111 };
1112 } else {
1113 shapeArgs.height = height;
1114 shapeArgs.y = y;
1115
1116 point.tooltipPos = chart.inverted ? [
1117 yAxis.len + yAxis.pos - chart.plotLeft - y - height / 2,
1118 xAxis.len + xAxis.pos - chart.plotTop - shapeArgs.x - shapeArgs.width / 2,
1119 height
1120 ] : [
1121 xAxis.left - chart.plotLeft + shapeArgs.x + shapeArgs.width / 2,
1122 yAxis.pos - chart.plotTop + y + height / 2,
1123 height
1124 ]; // don't inherit from column tooltip position - #3372
1125 }
1126 });
1127 },
1128 directTouch: true,
1129 trackerGroups: ['group', 'dataLabelsGroup'],
1130 drawGraph: noop,
1131 crispCol: colProto.crispCol,
1132 drawPoints: colProto.drawPoints,
1133 drawTracker: colProto.drawTracker,
1134 getColumnMetrics: colProto.getColumnMetrics,
1135 animate: function() {
1136 return colProto.animate.apply(this, arguments);
1137 },
1138 polarArc: function() {
1139 return colProto.polarArc.apply(this, arguments);
1140 },
1141 pointAttribs: colProto.pointAttribs
1142 });
1143
1144 }(Highcharts));
1145 (function(H) {
1146 /**
1147 * (c) 2010-2016 Torstein Honsi
1148 *
1149 * License: www.highcharts.com/license
1150 */
1151 'use strict';
1152 var each = H.each,
1153 isNumber = H.isNumber,
1154 merge = H.merge,
1155 noop = H.noop,
1156 pick = H.pick,
1157 pInt = H.pInt,
1158 Series = H.Series,
1159 seriesType = H.seriesType,
1160 TrackerMixin = H.TrackerMixin;
1161 /*
1162 * The GaugeSeries class
1163 */
1164 seriesType('gauge', 'line', {
1165 dataLabels: {
1166 enabled: true,
1167 defer: false,
1168 y: 15,
1169 borderRadius: 3,
1170 crop: false,
1171 verticalAlign: 'top',
1172 zIndex: 2,
1173
1174 // Presentational
1175 borderWidth: 1,
1176 borderColor: '#cccccc'
1177
1178 },
1179 dial: {
1180 // radius: '80%',
1181 // baseWidth: 3,
1182 // topWidth: 1,
1183 // baseLength: '70%' // of radius
1184 // rearLength: '10%'
1185
1186 // backgroundColor: '#000000',
1187 // borderColor: '#cccccc',
1188 // borderWidth: 0
1189
1190
1191 },
1192 pivot: {
1193 //radius: 5,
1194
1195 //borderWidth: 0
1196 //borderColor: '#cccccc',
1197 //backgroundColor: '#000000'
1198
1199 },
1200 tooltip: {
1201 headerFormat: ''
1202 },
1203 showInLegend: false
1204
1205 // Prototype members
1206 }, {
1207 // chart.angular will be set to true when a gauge series is present, and this will
1208 // be used on the axes
1209 angular: true,
1210 directTouch: true, // #5063
1211 drawGraph: noop,
1212 fixedBox: true,
1213 forceDL: true,
1214 noSharedTooltip: true,
1215 trackerGroups: ['group', 'dataLabelsGroup'],
1216
1217 /**
1218 * Calculate paths etc
1219 */
1220 translate: function() {
1221
1222 var series = this,
1223 yAxis = series.yAxis,
1224 options = series.options,
1225 center = yAxis.center;
1226
1227 series.generatePoints();
1228
1229 each(series.points, function(point) {
1230
1231 var dialOptions = merge(options.dial, point.dial),
1232 radius = (pInt(pick(dialOptions.radius, 80)) * center[2]) / 200,
1233 baseLength = (pInt(pick(dialOptions.baseLength, 70)) * radius) / 100,
1234 rearLength = (pInt(pick(dialOptions.rearLength, 10)) * radius) / 100,
1235 baseWidth = dialOptions.baseWidth || 3,
1236 topWidth = dialOptions.topWidth || 1,
1237 overshoot = options.overshoot,
1238 rotation = yAxis.startAngleRad + yAxis.translate(point.y, null, null, null, true);
1239
1240 // Handle the wrap and overshoot options
1241 if (isNumber(overshoot)) {
1242 overshoot = overshoot / 180 * Math.PI;
1243 rotation = Math.max(yAxis.startAngleRad - overshoot, Math.min(yAxis.endAngleRad + overshoot, rotation));
1244
1245 } else if (options.wrap === false) {
1246 rotation = Math.max(yAxis.startAngleRad, Math.min(yAxis.endAngleRad, rotation));
1247 }
1248
1249 rotation = rotation * 180 / Math.PI;
1250
1251 point.shapeType = 'path';
1252 point.shapeArgs = {
1253 d: dialOptions.path || [
1254 'M', -rearLength, -baseWidth / 2,
1255 'L',
1256 baseLength, -baseWidth / 2,
1257 radius, -topWidth / 2,
1258 radius, topWidth / 2,
1259 baseLength, baseWidth / 2, -rearLength, baseWidth / 2,
1260 'z'
1261 ],
1262 translateX: center[0],
1263 translateY: center[1],
1264 rotation: rotation
1265 };
1266
1267 // Positions for data label
1268 point.plotX = center[0];
1269 point.plotY = center[1];
1270 });
1271 },
1272
1273 /**
1274 * Draw the points where each point is one needle
1275 */
1276 drawPoints: function() {
1277
1278 var series = this,
1279 center = series.yAxis.center,
1280 pivot = series.pivot,
1281 options = series.options,
1282 pivotOptions = options.pivot,
1283 renderer = series.chart.renderer;
1284
1285 each(series.points, function(point) {
1286
1287 var graphic = point.graphic,
1288 shapeArgs = point.shapeArgs,
1289 d = shapeArgs.d,
1290 dialOptions = merge(options.dial, point.dial); // #1233
1291
1292 if (graphic) {
1293 graphic.animate(shapeArgs);
1294 shapeArgs.d = d; // animate alters it
1295 } else {
1296 point.graphic = renderer[point.shapeType](shapeArgs)
1297 .attr({
1298 rotation: shapeArgs.rotation, // required by VML when animation is false
1299 zIndex: 1
1300 })
1301 .addClass('highcharts-dial')
1302 .add(series.group);
1303
1304
1305 // Presentational attributes
1306 point.graphic.attr({
1307 stroke: dialOptions.borderColor || 'none',
1308 'stroke-width': dialOptions.borderWidth || 0,
1309 fill: dialOptions.backgroundColor || '#000000'
1310 });
1311
1312 }
1313 });
1314
1315 // Add or move the pivot
1316 if (pivot) {
1317 pivot.animate({ // #1235
1318 translateX: center[0],
1319 translateY: center[1]
1320 });
1321 } else {
1322 series.pivot = renderer.circle(0, 0, pick(pivotOptions.radius, 5))
1323 .attr({
1324 zIndex: 2
1325 })
1326 .addClass('highcharts-pivot')
1327 .translate(center[0], center[1])
1328 .add(series.group);
1329
1330
1331 // Presentational attributes
1332 series.pivot.attr({
1333 'stroke-width': pivotOptions.borderWidth || 0,
1334 stroke: pivotOptions.borderColor || '#cccccc',
1335 fill: pivotOptions.backgroundColor || '#000000'
1336 });
1337
1338 }
1339 },
1340
1341 /**
1342 * Animate the arrow up from startAngle
1343 */
1344 animate: function(init) {
1345 var series = this;
1346
1347 if (!init) {
1348 each(series.points, function(point) {
1349 var graphic = point.graphic;
1350
1351 if (graphic) {
1352 // start value
1353 graphic.attr({
1354 rotation: series.yAxis.startAngleRad * 180 / Math.PI
1355 });
1356
1357 // animate
1358 graphic.animate({
1359 rotation: point.shapeArgs.rotation
1360 }, series.options.animation);
1361 }
1362 });
1363
1364 // delete this function to allow it only once
1365 series.animate = null;
1366 }
1367 },
1368
1369 render: function() {
1370 this.group = this.plotGroup(
1371 'group',
1372 'series',
1373 this.visible ? 'visible' : 'hidden',
1374 this.options.zIndex,
1375 this.chart.seriesGroup
1376 );
1377 Series.prototype.render.call(this);
1378 this.group.clip(this.chart.clipRect);
1379 },
1380
1381 /**
1382 * Extend the basic setData method by running processData and generatePoints immediately,
1383 * in order to access the points from the legend.
1384 */
1385 setData: function(data, redraw) {
1386 Series.prototype.setData.call(this, data, false);
1387 this.processData();
1388 this.generatePoints();
1389 if (pick(redraw, true)) {
1390 this.chart.redraw();
1391 }
1392 },
1393
1394 /**
1395 * If the tracking module is loaded, add the point tracker
1396 */
1397 drawTracker: TrackerMixin && TrackerMixin.drawTrackerPoint
1398
1399 // Point members
1400 }, {
1401 /**
1402 * Don't do any hover colors or anything
1403 */
1404 setState: function(state) {
1405 this.state = state;
1406 }
1407 });
1408
1409 }(Highcharts));
1410 (function(H) {
1411 /**
1412 * (c) 2010-2016 Torstein Honsi
1413 *
1414 * License: www.highcharts.com/license
1415 */
1416 'use strict';
1417 var each = H.each,
1418 noop = H.noop,
1419 pick = H.pick,
1420 seriesType = H.seriesType,
1421 seriesTypes = H.seriesTypes;
1422
1423 /**
1424 * The boxplot series type.
1425 *
1426 * @constructor seriesTypes.boxplot
1427 * @augments seriesTypes.column
1428 */
1429 seriesType('boxplot', 'column', {
1430 threshold: null,
1431 tooltip: {
1432
1433 pointFormat: '<span style="color:{point.color}">\u25CF</span> <b> {series.name}</b><br/>' + // eslint-disable-line no-dupe-keys
1434 'Maximum: {point.high}<br/>' +
1435 'Upper quartile: {point.q3}<br/>' +
1436 'Median: {point.median}<br/>' +
1437 'Lower quartile: {point.q1}<br/>' +
1438 'Minimum: {point.low}<br/>'
1439
1440 },
1441 whiskerLength: '50%',
1442
1443 fillColor: '#ffffff',
1444 lineWidth: 1,
1445 //medianColor: null,
1446 medianWidth: 2,
1447 states: {
1448 hover: {
1449 brightness: -0.3
1450 }
1451 },
1452 //stemColor: null,
1453 //stemDashStyle: 'solid'
1454 //stemWidth: null,
1455
1456 //whiskerColor: null,
1457 whiskerWidth: 2
1458
1459
1460 }, /** @lends seriesTypes.boxplot */ {
1461 pointArrayMap: ['low', 'q1', 'median', 'q3', 'high'], // array point configs are mapped to this
1462 toYData: function(point) { // return a plain array for speedy calculation
1463 return [point.low, point.q1, point.median, point.q3, point.high];
1464 },
1465 pointValKey: 'high', // defines the top of the tracker
1466
1467
1468 /**
1469 * Get presentational attributes
1470 */
1471 pointAttribs: function(point) {
1472 var options = this.options,
1473 color = (point && point.color) || this.color;
1474
1475 return {
1476 'fill': point.fillColor || options.fillColor || color,
1477 'stroke': options.lineColor || color,
1478 'stroke-width': options.lineWidth || 0
1479 };
1480 },
1481
1482
1483 /**
1484 * Disable data labels for box plot
1485 */
1486 drawDataLabels: noop,
1487
1488 /**
1489 * Translate data points from raw values x and y to plotX and plotY
1490 */
1491 translate: function() {
1492 var series = this,
1493 yAxis = series.yAxis,
1494 pointArrayMap = series.pointArrayMap;
1495
1496 seriesTypes.column.prototype.translate.apply(series);
1497
1498 // do the translation on each point dimension
1499 each(series.points, function(point) {
1500 each(pointArrayMap, function(key) {
1501 if (point[key] !== null) {
1502 point[key + 'Plot'] = yAxis.translate(point[key], 0, 1, 0, 1);
1503 }
1504 });
1505 });
1506 },
1507
1508 /**
1509 * Draw the data points
1510 */
1511 drawPoints: function() {
1512 var series = this, //state = series.state,
1513 points = series.points,
1514 options = series.options,
1515 chart = series.chart,
1516 renderer = chart.renderer,
1517 q1Plot,
1518 q3Plot,
1519 highPlot,
1520 lowPlot,
1521 medianPlot,
1522 medianPath,
1523 crispCorr,
1524 crispX = 0,
1525 boxPath,
1526 width,
1527 left,
1528 right,
1529 halfWidth,
1530 doQuartiles = series.doQuartiles !== false, // error bar inherits this series type but doesn't do quartiles
1531 pointWiskerLength,
1532 whiskerLength = series.options.whiskerLength;
1533
1534
1535 each(points, function(point) {
1536
1537 var graphic = point.graphic,
1538 verb = graphic ? 'animate' : 'attr',
1539 shapeArgs = point.shapeArgs; // the box
1540
1541
1542 var boxAttr,
1543 stemAttr = {},
1544 whiskersAttr = {},
1545 medianAttr = {},
1546 color = point.color || series.color;
1547
1548
1549 if (point.plotY !== undefined) {
1550
1551 // crisp vector coordinates
1552 width = shapeArgs.width;
1553 left = Math.floor(shapeArgs.x);
1554 right = left + width;
1555 halfWidth = Math.round(width / 2);
1556 q1Plot = Math.floor(doQuartiles ? point.q1Plot : point.lowPlot);
1557 q3Plot = Math.floor(doQuartiles ? point.q3Plot : point.lowPlot);
1558 highPlot = Math.floor(point.highPlot);
1559 lowPlot = Math.floor(point.lowPlot);
1560
1561 if (!graphic) {
1562 point.graphic = graphic = renderer.g('point')
1563 .add(series.group);
1564
1565 point.stem = renderer.path()
1566 .addClass('highcharts-boxplot-stem')
1567 .add(graphic);
1568
1569 if (whiskerLength) {
1570 point.whiskers = renderer.path()
1571 .addClass('highcharts-boxplot-whisker')
1572 .add(graphic);
1573 }
1574 if (doQuartiles) {
1575 point.box = renderer.path(boxPath)
1576 .addClass('highcharts-boxplot-box')
1577 .add(graphic);
1578 }
1579 point.medianShape = renderer.path(medianPath)
1580 .addClass('highcharts-boxplot-median')
1581 .add(graphic);
1582
1583
1584
1585
1586 // Stem attributes
1587 stemAttr.stroke = point.stemColor || options.stemColor || color;
1588 stemAttr['stroke-width'] = pick(point.stemWidth, options.stemWidth, options.lineWidth);
1589 stemAttr.dashstyle = point.stemDashStyle || options.stemDashStyle;
1590 point.stem.attr(stemAttr);
1591
1592 // Whiskers attributes
1593 if (whiskerLength) {
1594 whiskersAttr.stroke = point.whiskerColor || options.whiskerColor || color;
1595 whiskersAttr['stroke-width'] = pick(point.whiskerWidth, options.whiskerWidth, options.lineWidth);
1596 point.whiskers.attr(whiskersAttr);
1597 }
1598
1599 if (doQuartiles) {
1600 boxAttr = series.pointAttribs(point);
1601 point.box.attr(boxAttr);
1602 }
1603
1604
1605 // Median attributes
1606 medianAttr.stroke = point.medianColor || options.medianColor || color;
1607 medianAttr['stroke-width'] = pick(point.medianWidth, options.medianWidth, options.lineWidth);
1608 point.medianShape.attr(medianAttr);
1609
1610
1611 }
1612
1613
1614
1615 // The stem
1616 crispCorr = (point.stem.strokeWidth() % 2) / 2;
1617 crispX = left + halfWidth + crispCorr;
1618 point.stem[verb]({
1619 d: [
1620 // stem up
1621 'M',
1622 crispX, q3Plot,
1623 'L',
1624 crispX, highPlot,
1625
1626 // stem down
1627 'M',
1628 crispX, q1Plot,
1629 'L',
1630 crispX, lowPlot
1631 ]
1632 });
1633
1634 // The box
1635 if (doQuartiles) {
1636 crispCorr = (point.box.strokeWidth() % 2) / 2;
1637 q1Plot = Math.floor(q1Plot) + crispCorr;
1638 q3Plot = Math.floor(q3Plot) + crispCorr;
1639 left += crispCorr;
1640 right += crispCorr;
1641 point.box[verb]({
1642 d: [
1643 'M',
1644 left, q3Plot,
1645 'L',
1646 left, q1Plot,
1647 'L',
1648 right, q1Plot,
1649 'L',
1650 right, q3Plot,
1651 'L',
1652 left, q3Plot,
1653 'z'
1654 ]
1655 });
1656 }
1657
1658 // The whiskers
1659 if (whiskerLength) {
1660 crispCorr = (point.whiskers.strokeWidth() % 2) / 2;
1661 highPlot = highPlot + crispCorr;
1662 lowPlot = lowPlot + crispCorr;
1663 pointWiskerLength = (/%$/).test(whiskerLength) ? halfWidth * parseFloat(whiskerLength) / 100 : whiskerLength / 2;
1664 point.whiskers[verb]({
1665 d: [
1666 // High whisker
1667 'M',
1668 crispX - pointWiskerLength,
1669 highPlot,
1670 'L',
1671 crispX + pointWiskerLength,
1672 highPlot,
1673
1674 // Low whisker
1675 'M',
1676 crispX - pointWiskerLength,
1677 lowPlot,
1678 'L',
1679 crispX + pointWiskerLength,
1680 lowPlot
1681 ]
1682 });
1683 }
1684
1685 // The median
1686 medianPlot = Math.round(point.medianPlot);
1687 crispCorr = (point.medianShape.strokeWidth() % 2) / 2;
1688 medianPlot = medianPlot + crispCorr;
1689
1690 point.medianShape[verb]({
1691 d: [
1692 'M',
1693 left,
1694 medianPlot,
1695 'L',
1696 right,
1697 medianPlot
1698 ]
1699 });
1700 }
1701 });
1702
1703 },
1704 setStackedPoints: noop // #3890
1705
1706
1707 });
1708
1709 /* ****************************************************************************
1710 * End Box plot series code *
1711 *****************************************************************************/
1712
1713 }(Highcharts));
1714 (function(H) {
1715 /**
1716 * (c) 2010-2016 Torstein Honsi
1717 *
1718 * License: www.highcharts.com/license
1719 */
1720 'use strict';
1721 var each = H.each,
1722 noop = H.noop,
1723 seriesType = H.seriesType,
1724 seriesTypes = H.seriesTypes;
1725
1726
1727 /* ****************************************************************************
1728 * Start error bar series code *
1729 *****************************************************************************/
1730 seriesType('errorbar', 'boxplot', {
1731
1732 color: '#000000',
1733
1734 grouping: false,
1735 linkedTo: ':previous',
1736 tooltip: {
1737 pointFormat: '<span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.low}</b> - <b>{point.high}</b><br/>'
1738 },
1739 whiskerWidth: null
1740
1741 // Prototype members
1742 }, {
1743 type: 'errorbar',
1744 pointArrayMap: ['low', 'high'], // array point configs are mapped to this
1745 toYData: function(point) { // return a plain array for speedy calculation
1746 return [point.low, point.high];
1747 },
1748 pointValKey: 'high', // defines the top of the tracker
1749 doQuartiles: false,
1750 drawDataLabels: seriesTypes.arearange ? function() {
1751 var valKey = this.pointValKey;
1752 seriesTypes.arearange.prototype.drawDataLabels.call(this);
1753 // Arearange drawDataLabels does not reset point.y to high, but to low after drawing. #4133
1754 each(this.data, function(point) {
1755 point.y = point[valKey];
1756 });
1757 } : noop,
1758
1759 /**
1760 * Get the width and X offset, either on top of the linked series column
1761 * or standalone
1762 */
1763 getColumnMetrics: function() {
1764 return (this.linkedParent && this.linkedParent.columnMetrics) ||
1765 seriesTypes.column.prototype.getColumnMetrics.call(this);
1766 }
1767 });
1768
1769 /* ****************************************************************************
1770 * End error bar series code *
1771 *****************************************************************************/
1772
1773 }(Highcharts));
1774 (function(H) {
1775 /**
1776 * (c) 2010-2016 Torstein Honsi
1777 *
1778 * License: www.highcharts.com/license
1779 */
1780 'use strict';
1781 var correctFloat = H.correctFloat,
1782 isNumber = H.isNumber,
1783 noop = H.noop,
1784 pick = H.pick,
1785 Point = H.Point,
1786 Series = H.Series,
1787 seriesType = H.seriesType,
1788 seriesTypes = H.seriesTypes;
1789
1790 /* ****************************************************************************
1791 * Start Waterfall series code *
1792 *****************************************************************************/
1793 seriesType('waterfall', 'column', {
1794 dataLabels: {
1795 inside: true
1796 },
1797
1798 lineWidth: 1,
1799 lineColor: '#333333',
1800 dashStyle: 'dot',
1801 borderColor: '#333333',
1802 states: {
1803 hover: {
1804 lineWidthPlus: 0 // #3126
1805 }
1806 }
1807
1808
1809 // Prototype members
1810 }, {
1811 pointValKey: 'y',
1812
1813 /**
1814 * Translate data points from raw values
1815 */
1816 translate: function() {
1817 var series = this,
1818 options = series.options,
1819 yAxis = series.yAxis,
1820 len,
1821 i,
1822 points,
1823 point,
1824 shapeArgs,
1825 stack,
1826 y,
1827 yValue,
1828 previousY,
1829 previousIntermediate,
1830 range,
1831 minPointLength = pick(options.minPointLength, 5),
1832 threshold = options.threshold,
1833 stacking = options.stacking,
1834 // Separate offsets for negative and positive columns:
1835 positiveOffset = 0,
1836 negativeOffset = 0,
1837 tooltipY;
1838
1839 // run column series translate
1840 seriesTypes.column.prototype.translate.apply(this);
1841
1842 previousY = previousIntermediate = threshold;
1843 points = series.points;
1844
1845 for (i = 0, len = points.length; i < len; i++) {
1846 // cache current point object
1847 point = points[i];
1848 yValue = this.processedYData[i];
1849 shapeArgs = point.shapeArgs;
1850
1851 // get current stack
1852 stack = stacking && yAxis.stacks[(series.negStacks && yValue < threshold ? '-' : '') + series.stackKey];
1853 range = stack ?
1854 stack[point.x].points[series.index + ',' + i] : [0, yValue];
1855
1856 // override point value for sums
1857 // #3710 Update point does not propagate to sum
1858 if (point.isSum) {
1859 point.y = correctFloat(yValue);
1860 } else if (point.isIntermediateSum) {
1861 point.y = correctFloat(yValue - previousIntermediate); // #3840
1862 }
1863 // up points
1864 y = Math.max(previousY, previousY + point.y) + range[0];
1865 shapeArgs.y = yAxis.toPixels(y, true);
1866
1867
1868 // sum points
1869 if (point.isSum) {
1870 shapeArgs.y = yAxis.toPixels(range[1], true);
1871 shapeArgs.height = Math.min(yAxis.toPixels(range[0], true), yAxis.len) -
1872 shapeArgs.y + positiveOffset + negativeOffset; // #4256
1873
1874 } else if (point.isIntermediateSum) {
1875 shapeArgs.y = yAxis.toPixels(range[1], true);
1876 shapeArgs.height = Math.min(yAxis.toPixels(previousIntermediate, true), yAxis.len) -
1877 shapeArgs.y + positiveOffset + negativeOffset;
1878 previousIntermediate = range[1];
1879
1880 // If it's not the sum point, update previous stack end position and get
1881 // shape height (#3886)
1882 } else {
1883 shapeArgs.height = yValue > 0 ?
1884 yAxis.toPixels(previousY, true) - shapeArgs.y :
1885 yAxis.toPixels(previousY, true) - yAxis.toPixels(previousY - yValue, true);
1886 previousY += yValue;
1887 }
1888 // #3952 Negative sum or intermediate sum not rendered correctly
1889 if (shapeArgs.height < 0) {
1890 shapeArgs.y += shapeArgs.height;
1891 shapeArgs.height *= -1;
1892 }
1893
1894 point.plotY = shapeArgs.y = Math.round(shapeArgs.y) - (series.borderWidth % 2) / 2;
1895 shapeArgs.height = Math.max(Math.round(shapeArgs.height), 0.001); // #3151
1896 point.yBottom = shapeArgs.y + shapeArgs.height;
1897
1898 // Before minPointLength, apply negative offset:
1899 shapeArgs.y -= negativeOffset;
1900
1901 if (shapeArgs.height <= minPointLength) {
1902 shapeArgs.height = minPointLength;
1903 if (point.y < 0) {
1904 negativeOffset -= minPointLength;
1905 } else {
1906 positiveOffset += minPointLength;
1907 }
1908 }
1909
1910 // After minPointLength is updated, apply positive offset:
1911 shapeArgs.y -= positiveOffset;
1912
1913 // Correct tooltip placement (#3014)
1914 tooltipY = point.plotY - negativeOffset - positiveOffset +
1915 (point.negative && negativeOffset >= 0 ? shapeArgs.height : 0);
1916 if (series.chart.inverted) {
1917 point.tooltipPos[0] = yAxis.len - tooltipY;
1918 } else {
1919 point.tooltipPos[1] = tooltipY;
1920 }
1921 }
1922 },
1923
1924 /**
1925 * Call default processData then override yData to reflect waterfall's extremes on yAxis
1926 */
1927 processData: function(force) {
1928 var series = this,
1929 options = series.options,
1930 yData = series.yData,
1931 points = series.options.data, // #3710 Update point does not propagate to sum
1932 point,
1933 dataLength = yData.length,
1934 threshold = options.threshold || 0,
1935 subSum,
1936 sum,
1937 dataMin,
1938 dataMax,
1939 y,
1940 i;
1941
1942 sum = subSum = dataMin = dataMax = threshold;
1943
1944 for (i = 0; i < dataLength; i++) {
1945 y = yData[i];
1946 point = points && points[i] ? points[i] : {};
1947
1948 if (y === 'sum' || point.isSum) {
1949 yData[i] = correctFloat(sum);
1950 } else if (y === 'intermediateSum' || point.isIntermediateSum) {
1951 yData[i] = correctFloat(subSum);
1952 } else {
1953 sum += y;
1954 subSum += y;
1955 }
1956 dataMin = Math.min(sum, dataMin);
1957 dataMax = Math.max(sum, dataMax);
1958 }
1959
1960 Series.prototype.processData.call(this, force);
1961
1962 // Record extremes
1963 series.dataMin = dataMin;
1964 series.dataMax = dataMax;
1965 },
1966
1967 /**
1968 * Return y value or string if point is sum
1969 */
1970 toYData: function(pt) {
1971 if (pt.isSum) {
1972 return (pt.x === 0 ? null : 'sum'); //#3245 Error when first element is Sum or Intermediate Sum
1973 }
1974 if (pt.isIntermediateSum) {
1975 return (pt.x === 0 ? null : 'intermediateSum'); //#3245
1976 }
1977 return pt.y;
1978 },
1979
1980
1981 /**
1982 * Postprocess mapping between options and SVG attributes
1983 */
1984 pointAttribs: function(point, state) {
1985
1986 var upColor = this.options.upColor,
1987 attr;
1988
1989 // Set or reset up color (#3710, update to negative)
1990 if (upColor && !point.options.color) {
1991 point.color = point.y > 0 ? upColor : null;
1992 }
1993
1994 attr = seriesTypes.column.prototype.pointAttribs.call(this, point, state);
1995
1996 // The dashStyle option in waterfall applies to the graph, not
1997 // the points
1998 delete attr.dashstyle;
1999
2000 return attr;
2001 },
2002
2003
2004 /**
2005 * Return an empty path initially, because we need to know the stroke-width in order
2006 * to set the final path.
2007 */
2008 getGraphPath: function() {
2009 return ['M', 0, 0];
2010 },
2011
2012 /**
2013 * Draw columns' connector lines
2014 */
2015 getCrispPath: function() {
2016
2017 var data = this.data,
2018 length = data.length,
2019 lineWidth = this.graph.strokeWidth() + this.borderWidth,
2020 normalizer = Math.round(lineWidth) % 2 / 2,
2021 path = [],
2022 prevArgs,
2023 pointArgs,
2024 i,
2025 d;
2026
2027 for (i = 1; i < length; i++) {
2028 pointArgs = data[i].shapeArgs;
2029 prevArgs = data[i - 1].shapeArgs;
2030
2031 d = [
2032 'M',
2033 prevArgs.x + prevArgs.width, prevArgs.y + normalizer,
2034 'L',
2035 pointArgs.x, prevArgs.y + normalizer
2036 ];
2037
2038 if (data[i - 1].y < 0) {
2039 d[2] += prevArgs.height;
2040 d[5] += prevArgs.height;
2041 }
2042
2043 path = path.concat(d);
2044 }
2045
2046 return path;
2047 },
2048
2049 /**
2050 * The graph is initally drawn with an empty definition, then updated with
2051 * crisp rendering.
2052 */
2053 drawGraph: function() {
2054 Series.prototype.drawGraph.call(this);
2055 this.graph.attr({
2056 d: this.getCrispPath()
2057 });
2058 },
2059
2060 /**
2061 * Extremes are recorded in processData
2062 */
2063 getExtremes: noop
2064
2065 // Point members
2066 }, {
2067 getClassName: function() {
2068 var className = Point.prototype.getClassName.call(this);
2069
2070 if (this.isSum) {
2071 className += ' highcharts-sum';
2072 } else if (this.isIntermediateSum) {
2073 className += ' highcharts-intermediate-sum';
2074 }
2075 return className;
2076 },
2077 /**
2078 * Pass the null test in ColumnSeries.translate.
2079 */
2080 isValid: function() {
2081 return isNumber(this.y, true) || this.isSum || this.isIntermediateSum;
2082 }
2083
2084 });
2085
2086 /* ****************************************************************************
2087 * End Waterfall series code *
2088 *****************************************************************************/
2089
2090 }(Highcharts));
2091 (function(H) {
2092 /**
2093 * (c) 2010-2016 Torstein Honsi
2094 *
2095 * License: www.highcharts.com/license
2096 */
2097 'use strict';
2098 var LegendSymbolMixin = H.LegendSymbolMixin,
2099 noop = H.noop,
2100 Series = H.Series,
2101 seriesType = H.seriesType,
2102 seriesTypes = H.seriesTypes;
2103 /**
2104 * The polygon series prototype
2105 */
2106 seriesType('polygon', 'scatter', {
2107 marker: {
2108 enabled: false,
2109 states: {
2110 hover: {
2111 enabled: false
2112 }
2113 }
2114 },
2115 stickyTracking: false,
2116 tooltip: {
2117 followPointer: true,
2118 pointFormat: ''
2119 },
2120 trackByArea: true
2121
2122 // Prototype members
2123 }, {
2124 type: 'polygon',
2125 getGraphPath: function() {
2126
2127 var graphPath = Series.prototype.getGraphPath.call(this),
2128 i = graphPath.length + 1;
2129
2130 // Close all segments
2131 while (i--) {
2132 if ((i === graphPath.length || graphPath[i] === 'M') && i > 0) {
2133 graphPath.splice(i, 0, 'z');
2134 }
2135 }
2136 this.areaPath = graphPath;
2137 return graphPath;
2138 },
2139 drawGraph: function() {
2140
2141 this.options.fillColor = this.color; // Hack into the fill logic in area.drawGraph
2142
2143 seriesTypes.area.prototype.drawGraph.call(this);
2144 },
2145 drawLegendSymbol: LegendSymbolMixin.drawRectangle,
2146 drawTracker: Series.prototype.drawTracker,
2147 setStackedPoints: noop // No stacking points on polygons (#5310)
2148 });
2149
2150 }(Highcharts));
2151 (function(H) {
2152 /**
2153 * (c) 2010-2016 Torstein Honsi
2154 *
2155 * License: www.highcharts.com/license
2156 */
2157 'use strict';
2158 var arrayMax = H.arrayMax,
2159 arrayMin = H.arrayMin,
2160 Axis = H.Axis,
2161 color = H.color,
2162 each = H.each,
2163 isNumber = H.isNumber,
2164 noop = H.noop,
2165 pick = H.pick,
2166 pInt = H.pInt,
2167 Point = H.Point,
2168 Series = H.Series,
2169 seriesType = H.seriesType,
2170 seriesTypes = H.seriesTypes;
2171
2172 /* ****************************************************************************
2173 * Start Bubble series code *
2174 *****************************************************************************/
2175
2176 seriesType('bubble', 'scatter', {
2177 dataLabels: {
2178 formatter: function() { // #2945
2179 return this.point.z;
2180 },
2181 inside: true,
2182 verticalAlign: 'middle'
2183 },
2184 // displayNegative: true,
2185 marker: {
2186
2187 // fillOpacity: 0.5,
2188 lineColor: null, // inherit from series.color
2189 lineWidth: 1,
2190
2191 // Avoid offset in Point.setState
2192 radius: null,
2193 states: {
2194 hover: {
2195 radiusPlus: 0
2196 }
2197 }
2198 },
2199 minSize: 8,
2200 maxSize: '20%',
2201 // negativeColor: null,
2202 // sizeBy: 'area'
2203 softThreshold: false,
2204 states: {
2205 hover: {
2206 halo: {
2207 size: 5
2208 }
2209 }
2210 },
2211 tooltip: {
2212 pointFormat: '({point.x}, {point.y}), Size: {point.z}'
2213 },
2214 turboThreshold: 0,
2215 zThreshold: 0,
2216 zoneAxis: 'z'
2217
2218 // Prototype members
2219 }, {
2220 pointArrayMap: ['y', 'z'],
2221 parallelArrays: ['x', 'y', 'z'],
2222 trackerGroups: ['group', 'dataLabelsGroup'],
2223 bubblePadding: true,
2224 zoneAxis: 'z',
2225 markerAttribs: noop,
2226
2227
2228 pointAttribs: function(point, state) {
2229 var markerOptions = this.options.marker,
2230 fillOpacity = pick(markerOptions.fillOpacity, 0.5),
2231 attr = Series.prototype.pointAttribs.call(this, point, state);
2232
2233 if (fillOpacity !== 1) {
2234 attr.fill = color(attr.fill).setOpacity(fillOpacity).get('rgba');
2235 }
2236
2237 return attr;
2238 },
2239
2240
2241 /**
2242 * Get the radius for each point based on the minSize, maxSize and each point's Z value. This
2243 * must be done prior to Series.translate because the axis needs to add padding in
2244 * accordance with the point sizes.
2245 */
2246 getRadii: function(zMin, zMax, minSize, maxSize) {
2247 var len,
2248 i,
2249 pos,
2250 zData = this.zData,
2251 radii = [],
2252 options = this.options,
2253 sizeByArea = options.sizeBy !== 'width',
2254 zThreshold = options.zThreshold,
2255 zRange = zMax - zMin,
2256 value,
2257 radius;
2258
2259 // Set the shape type and arguments to be picked up in drawPoints
2260 for (i = 0, len = zData.length; i < len; i++) {
2261
2262 value = zData[i];
2263
2264 // When sizing by threshold, the absolute value of z determines the size
2265 // of the bubble.
2266 if (options.sizeByAbsoluteValue && value !== null) {
2267 value = Math.abs(value - zThreshold);
2268 zMax = Math.max(zMax - zThreshold, Math.abs(zMin - zThreshold));
2269 zMin = 0;
2270 }
2271
2272 if (value === null) {
2273 radius = null;
2274 // Issue #4419 - if value is less than zMin, push a radius that's always smaller than the minimum size
2275 } else if (value < zMin) {
2276 radius = minSize / 2 - 1;
2277 } else {
2278 // Relative size, a number between 0 and 1
2279 pos = zRange > 0 ? (value - zMin) / zRange : 0.5;
2280
2281 if (sizeByArea && pos >= 0) {
2282 pos = Math.sqrt(pos);
2283 }
2284 radius = Math.ceil(minSize + pos * (maxSize - minSize)) / 2;
2285 }
2286 radii.push(radius);
2287 }
2288 this.radii = radii;
2289 },
2290
2291 /**
2292 * Perform animation on the bubbles
2293 */
2294 animate: function(init) {
2295 var animation = this.options.animation;
2296
2297 if (!init) { // run the animation
2298 each(this.points, function(point) {
2299 var graphic = point.graphic,
2300 shapeArgs = point.shapeArgs;
2301
2302 if (graphic && shapeArgs) {
2303 // start values
2304 graphic.attr('r', 1);
2305
2306 // animate
2307 graphic.animate({
2308 r: shapeArgs.r
2309 }, animation);
2310 }
2311 });
2312
2313 // delete this function to allow it only once
2314 this.animate = null;
2315 }
2316 },
2317
2318 /**
2319 * Extend the base translate method to handle bubble size
2320 */
2321 translate: function() {
2322
2323 var i,
2324 data = this.data,
2325 point,
2326 radius,
2327 radii = this.radii;
2328
2329 // Run the parent method
2330 seriesTypes.scatter.prototype.translate.call(this);
2331
2332 // Set the shape type and arguments to be picked up in drawPoints
2333 i = data.length;
2334
2335 while (i--) {
2336 point = data[i];
2337 radius = radii ? radii[i] : 0; // #1737
2338
2339 if (isNumber(radius) && radius >= this.minPxSize / 2) {
2340 // Shape arguments
2341 point.shapeType = 'circle';
2342 point.shapeArgs = {
2343 x: point.plotX,
2344 y: point.plotY,
2345 r: radius
2346 };
2347
2348 // Alignment box for the data label
2349 point.dlBox = {
2350 x: point.plotX - radius,
2351 y: point.plotY - radius,
2352 width: 2 * radius,
2353 height: 2 * radius
2354 };
2355 } else { // below zThreshold
2356 point.shapeArgs = point.plotY = point.dlBox = undefined; // #1691
2357 }
2358 }
2359 },
2360
2361 /**
2362 * Get the series' symbol in the legend
2363 *
2364 * @param {Object} legend The legend object
2365 * @param {Object} item The series (this) or point
2366 */
2367 drawLegendSymbol: function(legend, item) {
2368 var renderer = this.chart.renderer,
2369 radius = renderer.fontMetrics(
2370 legend.itemStyle && legend.itemStyle.fontSize,
2371 item.legendItem
2372 ).f / 2;
2373
2374 item.legendSymbol = renderer.circle(
2375 radius,
2376 legend.baseline - radius,
2377 radius
2378 ).attr({
2379 zIndex: 3
2380 }).add(item.legendGroup);
2381 item.legendSymbol.isMarker = true;
2382
2383 },
2384
2385 drawPoints: seriesTypes.column.prototype.drawPoints,
2386 alignDataLabel: seriesTypes.column.prototype.alignDataLabel,
2387 buildKDTree: noop,
2388 applyZones: noop
2389
2390 // Point class
2391 }, {
2392 haloPath: function(size) {
2393 return Point.prototype.haloPath.call(
2394 this,
2395 this.shapeArgs.r + size
2396 );
2397 },
2398 ttBelow: false
2399 });
2400
2401 /**
2402 * Add logic to pad each axis with the amount of pixels
2403 * necessary to avoid the bubbles to overflow.
2404 */
2405 Axis.prototype.beforePadding = function() {
2406 var axis = this,
2407 axisLength = this.len,
2408 chart = this.chart,
2409 pxMin = 0,
2410 pxMax = axisLength,
2411 isXAxis = this.isXAxis,
2412 dataKey = isXAxis ? 'xData' : 'yData',
2413 min = this.min,
2414 extremes = {},
2415 smallestSize = Math.min(chart.plotWidth, chart.plotHeight),
2416 zMin = Number.MAX_VALUE,
2417 zMax = -Number.MAX_VALUE,
2418 range = this.max - min,
2419 transA = axisLength / range,
2420 activeSeries = [];
2421
2422 // Handle padding on the second pass, or on redraw
2423 each(this.series, function(series) {
2424
2425 var seriesOptions = series.options,
2426 zData;
2427
2428 if (series.bubblePadding && (series.visible || !chart.options.chart.ignoreHiddenSeries)) {
2429
2430 // Correction for #1673
2431 axis.allowZoomOutside = true;
2432
2433 // Cache it
2434 activeSeries.push(series);
2435
2436 if (isXAxis) { // because X axis is evaluated first
2437
2438 // For each series, translate the size extremes to pixel values
2439 each(['minSize', 'maxSize'], function(prop) {
2440 var length = seriesOptions[prop],
2441 isPercent = /%$/.test(length);
2442
2443 length = pInt(length);
2444 extremes[prop] = isPercent ?
2445 smallestSize * length / 100 :
2446 length;
2447
2448 });
2449 series.minPxSize = extremes.minSize;
2450 // Prioritize min size if conflict to make sure bubbles are
2451 // always visible. #5873
2452 series.maxPxSize = Math.max(extremes.maxSize, extremes.minSize);
2453
2454 // Find the min and max Z
2455 zData = series.zData;
2456 if (zData.length) { // #1735
2457 zMin = pick(seriesOptions.zMin, Math.min(
2458 zMin,
2459 Math.max(
2460 arrayMin(zData),
2461 seriesOptions.displayNegative === false ? seriesOptions.zThreshold : -Number.MAX_VALUE
2462 )
2463 ));
2464 zMax = pick(seriesOptions.zMax, Math.max(zMax, arrayMax(zData)));
2465 }
2466 }
2467 }
2468 });
2469
2470 each(activeSeries, function(series) {
2471
2472 var data = series[dataKey],
2473 i = data.length,
2474 radius;
2475
2476 if (isXAxis) {
2477 series.getRadii(zMin, zMax, series.minPxSize, series.maxPxSize);
2478 }
2479
2480 if (range > 0) {
2481 while (i--) {
2482 if (isNumber(data[i]) && axis.dataMin <= data[i] && data[i] <= axis.dataMax) {
2483 radius = series.radii[i];
2484 pxMin = Math.min(((data[i] - min) * transA) - radius, pxMin);
2485 pxMax = Math.max(((data[i] - min) * transA) + radius, pxMax);
2486 }
2487 }
2488 }
2489 });
2490
2491 if (activeSeries.length && range > 0 && !this.isLog) {
2492 pxMax -= axisLength;
2493 transA *= (axisLength + pxMin - pxMax) / axisLength;
2494 each([
2495 ['min', 'userMin', pxMin],
2496 ['max', 'userMax', pxMax]
2497 ], function(keys) {
2498 if (pick(axis.options[keys[0]], axis[keys[1]]) === undefined) {
2499 axis[keys[0]] += keys[2] / transA;
2500 }
2501 });
2502 }
2503 };
2504
2505 /* ****************************************************************************
2506 * End Bubble series code *
2507 *****************************************************************************/
2508
2509 }(Highcharts));
2510 (function(H) {
2511 /**
2512 * (c) 2010-2016 Torstein Honsi
2513 *
2514 * License: www.highcharts.com/license
2515 */
2516 'use strict';
2517
2518 /**
2519 * Extensions for polar charts. Additionally, much of the geometry required for polar charts is
2520 * gathered in RadialAxes.js.
2521 *
2522 */
2523
2524 var each = H.each,
2525 pick = H.pick,
2526 Pointer = H.Pointer,
2527 Series = H.Series,
2528 seriesTypes = H.seriesTypes,
2529 wrap = H.wrap,
2530
2531 seriesProto = Series.prototype,
2532 pointerProto = Pointer.prototype,
2533 colProto;
2534
2535 /**
2536 * Search a k-d tree by the point angle, used for shared tooltips in polar charts
2537 */
2538 seriesProto.searchPointByAngle = function(e) {
2539 var series = this,
2540 chart = series.chart,
2541 xAxis = series.xAxis,
2542 center = xAxis.pane.center,
2543 plotX = e.chartX - center[0] - chart.plotLeft,
2544 plotY = e.chartY - center[1] - chart.plotTop;
2545
2546 return this.searchKDTree({
2547 clientX: 180 + (Math.atan2(plotX, plotY) * (-180 / Math.PI))
2548 });
2549
2550 };
2551
2552 /**
2553 * Wrap the buildKDTree function so that it searches by angle (clientX) in case of shared tooltip,
2554 * and by two dimensional distance in case of non-shared.
2555 */
2556 wrap(seriesProto, 'buildKDTree', function(proceed) {
2557 if (this.chart.polar) {
2558 if (this.kdByAngle) {
2559 this.searchPoint = this.searchPointByAngle;
2560 } else {
2561 this.kdDimensions = 2;
2562 }
2563 }
2564 proceed.apply(this);
2565 });
2566
2567 /**
2568 * Translate a point's plotX and plotY from the internal angle and radius measures to
2569 * true plotX, plotY coordinates
2570 */
2571 seriesProto.toXY = function(point) {
2572 var xy,
2573 chart = this.chart,
2574 plotX = point.plotX,
2575 plotY = point.plotY,
2576 clientX;
2577
2578 // Save rectangular plotX, plotY for later computation
2579 point.rectPlotX = plotX;
2580 point.rectPlotY = plotY;
2581
2582 // Find the polar plotX and plotY
2583 xy = this.xAxis.postTranslate(point.plotX, this.yAxis.len - plotY);
2584 point.plotX = point.polarPlotX = xy.x - chart.plotLeft;
2585 point.plotY = point.polarPlotY = xy.y - chart.plotTop;
2586
2587 // If shared tooltip, record the angle in degrees in order to align X points. Otherwise,
2588 // use a standard k-d tree to get the nearest point in two dimensions.
2589 if (this.kdByAngle) {
2590 clientX = ((plotX / Math.PI * 180) + this.xAxis.pane.options.startAngle) % 360;
2591 if (clientX < 0) { // #2665
2592 clientX += 360;
2593 }
2594 point.clientX = clientX;
2595 } else {
2596 point.clientX = point.plotX;
2597 }
2598 };
2599
2600 if (seriesTypes.spline) {
2601 /**
2602 * Overridden method for calculating a spline from one point to the next
2603 */
2604 wrap(seriesTypes.spline.prototype, 'getPointSpline', function(proceed, segment, point, i) {
2605
2606 var ret,
2607 smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc;
2608 denom = smoothing + 1,
2609 plotX,
2610 plotY,
2611 lastPoint,
2612 nextPoint,
2613 lastX,
2614 lastY,
2615 nextX,
2616 nextY,
2617 leftContX,
2618 leftContY,
2619 rightContX,
2620 rightContY,
2621 distanceLeftControlPoint,
2622 distanceRightControlPoint,
2623 leftContAngle,
2624 rightContAngle,
2625 jointAngle;
2626
2627
2628 if (this.chart.polar) {
2629
2630 plotX = point.plotX;
2631 plotY = point.plotY;
2632 lastPoint = segment[i - 1];
2633 nextPoint = segment[i + 1];
2634
2635 // Connect ends
2636 if (this.connectEnds) {
2637 if (!lastPoint) {
2638 lastPoint = segment[segment.length - 2]; // not the last but the second last, because the segment is already connected
2639 }
2640 if (!nextPoint) {
2641 nextPoint = segment[1];
2642 }
2643 }
2644
2645 // find control points
2646 if (lastPoint && nextPoint) {
2647
2648 lastX = lastPoint.plotX;
2649 lastY = lastPoint.plotY;
2650 nextX = nextPoint.plotX;
2651 nextY = nextPoint.plotY;
2652 leftContX = (smoothing * plotX + lastX) / denom;
2653 leftContY = (smoothing * plotY + lastY) / denom;
2654 rightContX = (smoothing * plotX + nextX) / denom;
2655 rightContY = (smoothing * plotY + nextY) / denom;
2656 distanceLeftControlPoint = Math.sqrt(Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2));
2657 distanceRightControlPoint = Math.sqrt(Math.pow(rightContX - plotX, 2) + Math.pow(rightContY - plotY, 2));
2658 leftContAngle = Math.atan2(leftContY - plotY, leftContX - plotX);
2659 rightContAngle = Math.atan2(rightContY - plotY, rightContX - plotX);
2660 jointAngle = (Math.PI / 2) + ((leftContAngle + rightContAngle) / 2);
2661
2662
2663 // Ensure the right direction, jointAngle should be in the same quadrant as leftContAngle
2664 if (Math.abs(leftContAngle - jointAngle) > Math.PI / 2) {
2665 jointAngle -= Math.PI;
2666 }
2667
2668 // Find the corrected control points for a spline straight through the point
2669 leftContX = plotX + Math.cos(jointAngle) * distanceLeftControlPoint;
2670 leftContY = plotY + Math.sin(jointAngle) * distanceLeftControlPoint;
2671 rightContX = plotX + Math.cos(Math.PI + jointAngle) * distanceRightControlPoint;
2672 rightContY = plotY + Math.sin(Math.PI + jointAngle) * distanceRightControlPoint;
2673
2674 // Record for drawing in next point
2675 point.rightContX = rightContX;
2676 point.rightContY = rightContY;
2677
2678 }
2679
2680
2681 // moveTo or lineTo
2682 if (!i) {
2683 ret = ['M', plotX, plotY];
2684 } else { // curve from last point to this
2685 ret = [
2686 'C',
2687 lastPoint.rightContX || lastPoint.plotX,
2688 lastPoint.rightContY || lastPoint.plotY,
2689 leftContX || plotX,
2690 leftContY || plotY,
2691 plotX,
2692 plotY
2693 ];
2694 lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
2695 }
2696
2697
2698 } else {
2699 ret = proceed.call(this, segment, point, i);
2700 }
2701 return ret;
2702 });
2703 }
2704
2705 /**
2706 * Extend translate. The plotX and plotY values are computed as if the polar chart were a
2707 * cartesian plane, where plotX denotes the angle in radians and (yAxis.len - plotY) is the pixel distance from
2708 * center.
2709 */
2710 wrap(seriesProto, 'translate', function(proceed) {
2711 var chart = this.chart,
2712 points,
2713 i;
2714
2715 // Run uber method
2716 proceed.call(this);
2717
2718 // Postprocess plot coordinates
2719 if (chart.polar) {
2720 this.kdByAngle = chart.tooltip && chart.tooltip.shared;
2721
2722 if (!this.preventPostTranslate) {
2723 points = this.points;
2724 i = points.length;
2725
2726 while (i--) {
2727 // Translate plotX, plotY from angle and radius to true plot coordinates
2728 this.toXY(points[i]);
2729 }
2730 }
2731 }
2732 });
2733
2734 /**
2735 * Extend getSegmentPath to allow connecting ends across 0 to provide a closed circle in
2736 * line-like series.
2737 */
2738 wrap(seriesProto, 'getGraphPath', function(proceed, points) {
2739 var series = this,
2740 i,
2741 firstValid;
2742
2743 // Connect the path
2744 if (this.chart.polar) {
2745 points = points || this.points;
2746
2747 // Append first valid point in order to connect the ends
2748 for (i = 0; i < points.length; i++) {
2749 if (!points[i].isNull) {
2750 firstValid = i;
2751 break;
2752 }
2753 }
2754 if (this.options.connectEnds !== false && firstValid !== undefined) {
2755 this.connectEnds = true; // re-used in splines
2756 points.splice(points.length, 0, points[firstValid]);
2757 }
2758
2759 // For area charts, pseudo points are added to the graph, now we need to translate these
2760 each(points, function(point) {
2761 if (point.polarPlotY === undefined) {
2762 series.toXY(point);
2763 }
2764 });
2765 }
2766
2767 // Run uber method
2768 return proceed.apply(this, [].slice.call(arguments, 1));
2769
2770 });
2771
2772
2773 function polarAnimate(proceed, init) {
2774 var chart = this.chart,
2775 animation = this.options.animation,
2776 group = this.group,
2777 markerGroup = this.markerGroup,
2778 center = this.xAxis.center,
2779 plotLeft = chart.plotLeft,
2780 plotTop = chart.plotTop,
2781 attribs;
2782
2783 // Specific animation for polar charts
2784 if (chart.polar) {
2785
2786 // Enable animation on polar charts only in SVG. In VML, the scaling is different, plus animation
2787 // would be so slow it would't matter.
2788 if (chart.renderer.isSVG) {
2789
2790 if (animation === true) {
2791 animation = {};
2792 }
2793
2794 // Initialize the animation
2795 if (init) {
2796
2797 // Scale down the group and place it in the center
2798 attribs = {
2799 translateX: center[0] + plotLeft,
2800 translateY: center[1] + plotTop,
2801 scaleX: 0.001, // #1499
2802 scaleY: 0.001
2803 };
2804
2805 group.attr(attribs);
2806 if (markerGroup) {
2807 //markerGroup.attrSetters = group.attrSetters;
2808 markerGroup.attr(attribs);
2809 }
2810
2811 // Run the animation
2812 } else {
2813 attribs = {
2814 translateX: plotLeft,
2815 translateY: plotTop,
2816 scaleX: 1,
2817 scaleY: 1
2818 };
2819 group.animate(attribs, animation);
2820 if (markerGroup) {
2821 markerGroup.animate(attribs, animation);
2822 }
2823
2824 // Delete this function to allow it only once
2825 this.animate = null;
2826 }
2827 }
2828
2829 // For non-polar charts, revert to the basic animation
2830 } else {
2831 proceed.call(this, init);
2832 }
2833 }
2834
2835 // Define the animate method for regular series
2836 wrap(seriesProto, 'animate', polarAnimate);
2837
2838
2839 if (seriesTypes.column) {
2840
2841 colProto = seriesTypes.column.prototype;
2842
2843 colProto.polarArc = function(low, high, start, end) {
2844 var center = this.xAxis.center,
2845 len = this.yAxis.len;
2846
2847 return this.chart.renderer.symbols.arc(
2848 center[0],
2849 center[1],
2850 len - high,
2851 null, {
2852 start: start,
2853 end: end,
2854 innerR: len - pick(low, len)
2855 }
2856 );
2857 };
2858
2859 /**
2860 * Define the animate method for columnseries
2861 */
2862 wrap(colProto, 'animate', polarAnimate);
2863
2864
2865 /**
2866 * Extend the column prototype's translate method
2867 */
2868 wrap(colProto, 'translate', function(proceed) {
2869
2870 var xAxis = this.xAxis,
2871 startAngleRad = xAxis.startAngleRad,
2872 start,
2873 points,
2874 point,
2875 i;
2876
2877 this.preventPostTranslate = true;
2878
2879 // Run uber method
2880 proceed.call(this);
2881
2882 // Postprocess plot coordinates
2883 if (xAxis.isRadial) {
2884 points = this.points;
2885 i = points.length;
2886 while (i--) {
2887 point = points[i];
2888 start = point.barX + startAngleRad;
2889 point.shapeType = 'path';
2890 point.shapeArgs = {
2891 d: this.polarArc(point.yBottom, point.plotY, start, start + point.pointWidth)
2892 };
2893 // Provide correct plotX, plotY for tooltip
2894 this.toXY(point);
2895 point.tooltipPos = [point.plotX, point.plotY];
2896 point.ttBelow = point.plotY > xAxis.center[1];
2897 }
2898 }
2899 });
2900
2901
2902 /**
2903 * Align column data labels outside the columns. #1199.
2904 */
2905 wrap(colProto, 'alignDataLabel', function(proceed, point, dataLabel, options, alignTo, isNew) {
2906
2907 if (this.chart.polar) {
2908 var angle = point.rectPlotX / Math.PI * 180,
2909 align,
2910 verticalAlign;
2911
2912 // Align nicely outside the perimeter of the columns
2913 if (options.align === null) {
2914 if (angle > 20 && angle < 160) {
2915 align = 'left'; // right hemisphere
2916 } else if (angle > 200 && angle < 340) {
2917 align = 'right'; // left hemisphere
2918 } else {
2919 align = 'center'; // top or bottom
2920 }
2921 options.align = align;
2922 }
2923 if (options.verticalAlign === null) {
2924 if (angle < 45 || angle > 315) {
2925 verticalAlign = 'bottom'; // top part
2926 } else if (angle > 135 && angle < 225) {
2927 verticalAlign = 'top'; // bottom part
2928 } else {
2929 verticalAlign = 'middle'; // left or right
2930 }
2931 options.verticalAlign = verticalAlign;
2932 }
2933
2934 seriesProto.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);
2935 } else {
2936 proceed.call(this, point, dataLabel, options, alignTo, isNew);
2937 }
2938
2939 });
2940 }
2941
2942 /**
2943 * Extend getCoordinates to prepare for polar axis values
2944 */
2945 wrap(pointerProto, 'getCoordinates', function(proceed, e) {
2946 var chart = this.chart,
2947 ret = {
2948 xAxis: [],
2949 yAxis: []
2950 };
2951
2952 if (chart.polar) {
2953
2954 each(chart.axes, function(axis) {
2955 var isXAxis = axis.isXAxis,
2956 center = axis.center,
2957 x = e.chartX - center[0] - chart.plotLeft,
2958 y = e.chartY - center[1] - chart.plotTop;
2959
2960 ret[isXAxis ? 'xAxis' : 'yAxis'].push({
2961 axis: axis,
2962 value: axis.translate(
2963 isXAxis ?
2964 Math.PI - Math.atan2(x, y) : // angle
2965 Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), // distance from center
2966 true
2967 )
2968 });
2969 });
2970
2971 } else {
2972 ret = proceed.call(this, e);
2973 }
2974
2975 return ret;
2976 });
2977
2978 }(Highcharts));
2979}));