UNPKG

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