懒羊羊
2023-08-30 71e81ed1d12e4d69f53c8ad9e066650ad4186293
提交 | 用户 | 时间
71e81e 1 // ==ClosureCompiler==
2 // @compilation_level SIMPLE_OPTIMIZATIONS
3
4 /**
5  * @license Highcharts JS v3.0.6 (2013-10-04)
6  *
7  * (c) 2009-2013 Torstein Hønsi
8  *
9  * License: www.highcharts.com/license
10  */
11
12 // JSLint options:
13 /*global Highcharts, HighchartsAdapter, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console */
14
15 (function (Highcharts, UNDEFINED) {
16 var arrayMin = Highcharts.arrayMin,
17     arrayMax = Highcharts.arrayMax,
18     each = Highcharts.each,
19     extend = Highcharts.extend,
20     merge = Highcharts.merge,
21     map = Highcharts.map,
22     pick = Highcharts.pick,
23     pInt = Highcharts.pInt,
24     defaultPlotOptions = Highcharts.getOptions().plotOptions,
25     seriesTypes = Highcharts.seriesTypes,
26     extendClass = Highcharts.extendClass,
27     splat = Highcharts.splat,
28     wrap = Highcharts.wrap,
29     Axis = Highcharts.Axis,
30     Tick = Highcharts.Tick,
31     Series = Highcharts.Series,
32     colProto = seriesTypes.column.prototype,
33     math = Math,
34     mathRound = math.round,
35     mathFloor = math.floor,
36     mathMax = math.max,
37     noop = function () {};/**
38  * The Pane object allows options that are common to a set of X and Y axes.
39  * 
40  * In the future, this can be extended to basic Highcharts and Highstock.
41  */
42 function Pane(options, chart, firstAxis) {
43     this.init.call(this, options, chart, firstAxis);
44 }
45
46 // Extend the Pane prototype
47 extend(Pane.prototype, {
48     
49     /**
50      * Initiate the Pane object
51      */
52     init: function (options, chart, firstAxis) {
53         var pane = this,
54             backgroundOption,
55             defaultOptions = pane.defaultOptions;
56         
57         pane.chart = chart;
58         
59         // Set options
60         if (chart.angular) { // gauges
61             defaultOptions.background = {}; // gets extended by this.defaultBackgroundOptions
62         }
63         pane.options = options = merge(defaultOptions, options);
64         
65         backgroundOption = options.background;
66         
67         // To avoid having weighty logic to place, update and remove the backgrounds,
68         // push them to the first axis' plot bands and borrow the existing logic there.
69         if (backgroundOption) {
70             each([].concat(splat(backgroundOption)).reverse(), function (config) {
71                 var backgroundColor = config.backgroundColor; // if defined, replace the old one (specific for gradients)
72                 config = merge(pane.defaultBackgroundOptions, config);
73                 if (backgroundColor) {
74                     config.backgroundColor = backgroundColor;
75                 }
76                 config.color = config.backgroundColor; // due to naming in plotBands
77                 firstAxis.options.plotBands.unshift(config);
78             });
79         }
80     },
81     
82     /**
83      * The default options object
84      */
85     defaultOptions: {
86         // background: {conditional},
87         center: ['50%', '50%'],
88         size: '85%',
89         startAngle: 0
90         //endAngle: startAngle + 360
91     },    
92     
93     /**
94      * The default background options
95      */
96     defaultBackgroundOptions: {
97         shape: 'circle',
98         borderWidth: 1,
99         borderColor: 'silver',
100         backgroundColor: {
101             linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },
102             stops: [
103                 [0, '#FFF'],
104                 [1, '#DDD']
105             ]
106         },
107         from: Number.MIN_VALUE, // corrected to axis min
108         innerRadius: 0,
109         to: Number.MAX_VALUE, // corrected to axis max
110         outerRadius: '105%'
111     }
112     
113 });
114 var axisProto = Axis.prototype,
115     tickProto = Tick.prototype;
116     
117 /**
118  * Augmented methods for the x axis in order to hide it completely, used for the X axis in gauges
119  */
120 var hiddenAxisMixin = {
121     getOffset: noop,
122     redraw: function () {
123         this.isDirty = false; // prevent setting Y axis dirty
124     },
125     render: function () {
126         this.isDirty = false; // prevent setting Y axis dirty
127     },
128     setScale: noop,
129     setCategories: noop,
130     setTitle: noop
131 };
132
133 /**
134  * Augmented methods for the value axis
135  */
136 /*jslint unparam: true*/
137 var radialAxisMixin = {
138     isRadial: true,
139     
140     /**
141      * The default options extend defaultYAxisOptions
142      */
143     defaultRadialGaugeOptions: {
144         labels: {
145             align: 'center',
146             x: 0,
147             y: null // auto
148         },
149         minorGridLineWidth: 0,
150         minorTickInterval: 'auto',
151         minorTickLength: 10,
152         minorTickPosition: 'inside',
153         minorTickWidth: 1,
154         plotBands: [],
155         tickLength: 10,
156         tickPosition: 'inside',
157         tickWidth: 2,
158         title: {
159             rotation: 0
160         },
161         zIndex: 2 // behind dials, points in the series group
162     },
163     
164     // Circular axis around the perimeter of a polar chart
165     defaultRadialXOptions: {
166         gridLineWidth: 1, // spokes
167         labels: {
168             align: null, // auto
169             distance: 15,
170             x: 0,
171             y: null // auto
172         },
173         maxPadding: 0,
174         minPadding: 0,
175         plotBands: [],
176         showLastLabel: false, 
177         tickLength: 0
178     },
179     
180     // Radial axis, like a spoke in a polar chart
181     defaultRadialYOptions: {
182         gridLineInterpolation: 'circle',
183         labels: {
184             align: 'right',
185             x: -3,
186             y: -2
187         },
188         plotBands: [],
189         showLastLabel: false,
190         title: {
191             x: 4,
192             text: null,
193             rotation: 90
194         }
195     },
196     
197     /**
198      * Merge and set options
199      */
200     setOptions: function (userOptions) {
201         
202         this.options = merge(
203             this.defaultOptions,
204             this.defaultRadialOptions,
205             userOptions
206         );
207         
208     },
209     
210     /**
211      * Wrap the getOffset method to return zero offset for title or labels in a radial 
212      * axis
213      */
214     getOffset: function () {
215         // Call the Axis prototype method (the method we're in now is on the instance)
216         axisProto.getOffset.call(this);
217         
218         // Title or label offsets are not counted
219         this.chart.axisOffset[this.side] = 0;
220     },
221
222
223     /**
224      * Get the path for the axis line. This method is also referenced in the getPlotLinePath
225      * method.
226      */
227     getLinePath: function (lineWidth, radius) {
228         var center = this.center;
229         radius = pick(radius, center[2] / 2 - this.offset);
230         
231         return this.chart.renderer.symbols.arc(
232             this.left + center[0],
233             this.top + center[1],
234             radius,
235             radius, 
236             {
237                 start: this.startAngleRad,
238                 end: this.endAngleRad,
239                 open: true,
240                 innerR: 0
241             }
242         );
243     },
244
245     /**
246      * Override setAxisTranslation by setting the translation to the difference
247      * in rotation. This allows the translate method to return angle for 
248      * any given value.
249      */
250     setAxisTranslation: function () {
251         
252         // Call uber method        
253         axisProto.setAxisTranslation.call(this);
254             
255         // Set transA and minPixelPadding
256         if (this.center) { // it's not defined the first time
257             if (this.isCircular) {
258                 
259                 this.transA = (this.endAngleRad - this.startAngleRad) / 
260                     ((this.max - this.min) || 1);
261                     
262                 
263             } else { 
264                 this.transA = (this.center[2] / 2) / ((this.max - this.min) || 1);
265             }
266             
267             if (this.isXAxis) {
268                 this.minPixelPadding = this.transA * this.minPointOffset +
269                     (this.reversed ? (this.endAngleRad - this.startAngleRad) / 4 : 0); // ???
270             }
271         }
272     },
273     
274     /**
275      * In case of auto connect, add one closestPointRange to the max value right before
276      * tickPositions are computed, so that ticks will extend passed the real max.
277      */
278     beforeSetTickPositions: function () {
279         if (this.autoConnect) {
280             this.max += (this.categories && 1) || this.pointRange || this.closestPointRange || 0; // #1197, #2260
281         }
282     },
283     
284     /**
285      * Override the setAxisSize method to use the arc's circumference as length. This
286      * allows tickPixelInterval to apply to pixel lengths along the perimeter
287      */
288     setAxisSize: function () {
289         
290         axisProto.setAxisSize.call(this);
291
292         if (this.isRadial) {
293
294             // Set the center array
295             this.center = this.pane.center = seriesTypes.pie.prototype.getCenter.call(this.pane);
296             
297             this.len = this.width = this.height = this.isCircular ?
298                 this.center[2] * (this.endAngleRad - this.startAngleRad) / 2 :
299                 this.center[2] / 2;
300         }
301     },
302     
303     /**
304      * Returns the x, y coordinate of a point given by a value and a pixel distance
305      * from center
306      */
307     getPosition: function (value, length) {
308         if (!this.isCircular) {
309             length = this.translate(value);
310             value = this.min;    
311         }
312         
313         return this.postTranslate(
314             this.translate(value),
315             pick(length, this.center[2] / 2) - this.offset
316         );        
317     },
318     
319     /**
320      * Translate from intermediate plotX (angle), plotY (axis.len - radius) to final chart coordinates. 
321      */
322     postTranslate: function (angle, radius) {
323         
324         var chart = this.chart,
325             center = this.center;
326             
327         angle = this.startAngleRad + angle;
328         
329         return {
330             x: chart.plotLeft + center[0] + Math.cos(angle) * radius,
331             y: chart.plotTop + center[1] + Math.sin(angle) * radius
332         }; 
333         
334     },
335     
336     /**
337      * Find the path for plot bands along the radial axis
338      */
339     getPlotBandPath: function (from, to, options) {
340         var center = this.center,
341             startAngleRad = this.startAngleRad,
342             fullRadius = center[2] / 2,
343             radii = [
344                 pick(options.outerRadius, '100%'),
345                 options.innerRadius,
346                 pick(options.thickness, 10)
347             ],
348             percentRegex = /%$/,
349             start,
350             end,
351             open,
352             isCircular = this.isCircular, // X axis in a polar chart
353             ret;
354             
355         // Polygonal plot bands
356         if (this.options.gridLineInterpolation === 'polygon') {
357             ret = this.getPlotLinePath(from).concat(this.getPlotLinePath(to, true));
358         
359         // Circular grid bands
360         } else {
361             
362             // Plot bands on Y axis (radial axis) - inner and outer radius depend on to and from
363             if (!isCircular) {
364                 radii[0] = this.translate(from);
365                 radii[1] = this.translate(to);
366             }
367             
368             // Convert percentages to pixel values
369             radii = map(radii, function (radius) {
370                 if (percentRegex.test(radius)) {
371                     radius = (pInt(radius, 10) * fullRadius) / 100;
372                 }
373                 return radius;
374             });
375             
376             // Handle full circle
377             if (options.shape === 'circle' || !isCircular) {
378                 start = -Math.PI / 2;
379                 end = Math.PI * 1.5;
380                 open = true;
381             } else {
382                 start = startAngleRad + this.translate(from);
383                 end = startAngleRad + this.translate(to);
384             }
385         
386         
387             ret = this.chart.renderer.symbols.arc(
388                 this.left + center[0],
389                 this.top + center[1],
390                 radii[0],
391                 radii[0],
392                 {
393                     start: start,
394                     end: end,
395                     innerR: pick(radii[1], radii[0] - radii[2]),
396                     open: open
397                 }
398             );
399         }
400          
401         return ret;
402     },
403     
404     /**
405      * Find the path for plot lines perpendicular to the radial axis.
406      */
407     getPlotLinePath: function (value, reverse) {
408         var axis = this,
409             center = axis.center,
410             chart = axis.chart,
411             end = axis.getPosition(value),
412             xAxis,
413             xy,
414             tickPositions,
415             ret;
416         
417         // Spokes
418         if (axis.isCircular) {
419             ret = ['M', center[0] + chart.plotLeft, center[1] + chart.plotTop, 'L', end.x, end.y];
420         
421         // Concentric circles            
422         } else if (axis.options.gridLineInterpolation === 'circle') {
423             value = axis.translate(value);
424             if (value) { // a value of 0 is in the center
425                 ret = axis.getLinePath(0, value);
426             }
427         // Concentric polygons 
428         } else {
429             xAxis = chart.xAxis[0];
430             ret = [];
431             value = axis.translate(value);
432             tickPositions = xAxis.tickPositions;
433             if (xAxis.autoConnect) {
434                 tickPositions = tickPositions.concat([tickPositions[0]]);
435             }
436             // Reverse the positions for concatenation of polygonal plot bands
437             if (reverse) {
438                 tickPositions = [].concat(tickPositions).reverse();
439             }
440                 
441             each(tickPositions, function (pos, i) {
442                 xy = xAxis.getPosition(pos, value);
443                 ret.push(i ? 'L' : 'M', xy.x, xy.y);
444             });
445             
446         }
447         return ret;
448     },
449     
450     /**
451      * Find the position for the axis title, by default inside the gauge
452      */
453     getTitlePosition: function () {
454         var center = this.center,
455             chart = this.chart,
456             titleOptions = this.options.title;
457         
458         return { 
459             x: chart.plotLeft + center[0] + (titleOptions.x || 0), 
460             y: chart.plotTop + center[1] - ({ high: 0.5, middle: 0.25, low: 0 }[titleOptions.align] * 
461                 center[2]) + (titleOptions.y || 0)  
462         };
463     }
464     
465 };
466 /*jslint unparam: false*/
467
468 /**
469  * Override axisProto.init to mix in special axis instance functions and function overrides
470  */
471 wrap(axisProto, 'init', function (proceed, chart, userOptions) {
472     var axis = this,
473         angular = chart.angular,
474         polar = chart.polar,
475         isX = userOptions.isX,
476         isHidden = angular && isX,
477         isCircular,
478         startAngleRad,
479         endAngleRad,
480         options,
481         chartOptions = chart.options,
482         paneIndex = userOptions.pane || 0,
483         pane,
484         paneOptions;
485         
486     // Before prototype.init
487     if (angular) {
488         extend(this, isHidden ? hiddenAxisMixin : radialAxisMixin);
489         isCircular =  !isX;
490         if (isCircular) {
491             this.defaultRadialOptions = this.defaultRadialGaugeOptions;
492         }
493         
494     } else if (polar) {
495         //extend(this, userOptions.isX ? radialAxisMixin : radialAxisMixin);
496         extend(this, radialAxisMixin);
497         isCircular = isX;
498         this.defaultRadialOptions = isX ? this.defaultRadialXOptions : merge(this.defaultYAxisOptions, this.defaultRadialYOptions);
499         
500     }
501     
502     // Run prototype.init
503     proceed.call(this, chart, userOptions);
504     
505     if (!isHidden && (angular || polar)) {
506         options = this.options;
507         
508         // Create the pane and set the pane options.
509         if (!chart.panes) {
510             chart.panes = [];
511         }
512         this.pane = pane = chart.panes[paneIndex] = chart.panes[paneIndex] || new Pane(
513             splat(chartOptions.pane)[paneIndex],
514             chart,
515             axis
516         );
517         paneOptions = pane.options;
518         
519             
520         // Disable certain features on angular and polar axes
521         chart.inverted = false;
522         chartOptions.chart.zoomType = null;
523         
524         // Start and end angle options are
525         // given in degrees relative to top, while internal computations are
526         // in radians relative to right (like SVG).
527         this.startAngleRad = startAngleRad = (paneOptions.startAngle - 90) * Math.PI / 180;
528         this.endAngleRad = endAngleRad = (pick(paneOptions.endAngle, paneOptions.startAngle + 360)  - 90) * Math.PI / 180;
529         this.offset = options.offset || 0;
530         
531         this.isCircular = isCircular;
532         
533         // Automatically connect grid lines?
534         if (isCircular && userOptions.max === UNDEFINED && endAngleRad - startAngleRad === 2 * Math.PI) {
535             this.autoConnect = true;
536         }
537     }
538     
539 });
540
541 /**
542  * Add special cases within the Tick class' methods for radial axes.
543  */    
544 wrap(tickProto, 'getPosition', function (proceed, horiz, pos, tickmarkOffset, old) {
545     var axis = this.axis;
546     
547     return axis.getPosition ? 
548         axis.getPosition(pos) :
549         proceed.call(this, horiz, pos, tickmarkOffset, old);    
550 });
551
552 /**
553  * Wrap the getLabelPosition function to find the center position of the label
554  * based on the distance option
555  */    
556 wrap(tickProto, 'getLabelPosition', function (proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
557     var axis = this.axis,
558         optionsY = labelOptions.y,
559         ret,
560         align = labelOptions.align,
561         angle = ((axis.translate(this.pos) + axis.startAngleRad + Math.PI / 2) / Math.PI * 180) % 360;
562     
563     if (axis.isRadial) {
564         ret = axis.getPosition(this.pos, (axis.center[2] / 2) + pick(labelOptions.distance, -25));
565         
566         // Automatically rotated
567         if (labelOptions.rotation === 'auto') {
568             label.attr({ 
569                 rotation: angle
570             });
571         
572         // Vertically centered
573         } else if (optionsY === null) {
574             optionsY = pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
575         
576         }
577         
578         // Automatic alignment
579         if (align === null) {
580             if (axis.isCircular) {
581                 if (angle > 20 && angle < 160) {
582                     align = 'left'; // right hemisphere
583                 } else if (angle > 200 && angle < 340) {
584                     align = 'right'; // left hemisphere
585                 } else {
586                     align = 'center'; // top or bottom
587                 }
588             } else {
589                 align = 'center';
590             }
591             label.attr({
592                 align: align
593             });
594         }
595         
596         ret.x += labelOptions.x;
597         ret.y += optionsY;
598         
599     } else {
600         ret = proceed.call(this, x, y, label, horiz, labelOptions, tickmarkOffset, index, step);
601     }
602     return ret;
603 });
604
605 /**
606  * Wrap the getMarkPath function to return the path of the radial marker
607  */
608 wrap(tickProto, 'getMarkPath', function (proceed, x, y, tickLength, tickWidth, horiz, renderer) {
609     var axis = this.axis,
610         endPoint,
611         ret;
612         
613     if (axis.isRadial) {
614         endPoint = axis.getPosition(this.pos, axis.center[2] / 2 + tickLength);
615         ret = [
616             'M',
617             x,
618             y,
619             'L',
620             endPoint.x,
621             endPoint.y
622         ];
623     } else {
624         ret = proceed.call(this, x, y, tickLength, tickWidth, horiz, renderer);
625     }
626     return ret;
627 });/* 
628  * The AreaRangeSeries class
629  * 
630  */
631
632 /**
633  * Extend the default options with map options
634  */
635 defaultPlotOptions.arearange = merge(defaultPlotOptions.area, {
636     lineWidth: 1,
637     marker: null,
638     threshold: null,
639     tooltip: {
640         pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.low}</b> - <b>{point.high}</b><br/>' 
641     },
642     trackByArea: true,
643     dataLabels: {
644         verticalAlign: null,
645         xLow: 0,
646         xHigh: 0,
647         yLow: 0,
648         yHigh: 0    
649     }
650 });
651
652 /**
653  * Add the series type
654  */
655 seriesTypes.arearange = Highcharts.extendClass(seriesTypes.area, {
656     type: 'arearange',
657     pointArrayMap: ['low', 'high'],
658     toYData: function (point) {
659         return [point.low, point.high];
660     },
661     pointValKey: 'low',
662     
663     /**
664      * Extend getSegments to force null points if the higher value is null. #1703.
665      */
666     getSegments: function () {
667         var series = this;
668
669         each(series.points, function (point) {
670             if (!series.options.connectNulls && (point.low === null || point.high === null)) {
671                 point.y = null;
672             } else if (point.low === null && point.high !== null) {
673                 point.y = point.high;
674             }
675         });
676         Series.prototype.getSegments.call(this);
677     },
678     
679     /**
680      * Translate data points from raw values x and y to plotX and plotY
681      */
682     translate: function () {
683         var series = this,
684             yAxis = series.yAxis;
685
686         seriesTypes.area.prototype.translate.apply(series);
687
688         // Set plotLow and plotHigh
689         each(series.points, function (point) {
690
691             var low = point.low,
692                 high = point.high,
693                 plotY = point.plotY;
694
695             if (high === null && low === null) {
696                 point.y = null;
697             } else if (low === null) {
698                 point.plotLow = point.plotY = null;
699                 point.plotHigh = yAxis.translate(high, 0, 1, 0, 1);
700             } else if (high === null) {
701                 point.plotLow = plotY;
702                 point.plotHigh = null;
703             } else {
704                 point.plotLow = plotY;
705                 point.plotHigh = yAxis.translate(high, 0, 1, 0, 1);
706             }
707         });
708     },
709     
710     /**
711      * Extend the line series' getSegmentPath method by applying the segment
712      * path to both lower and higher values of the range
713      */
714     getSegmentPath: function (segment) {
715         
716         var lowSegment,
717             highSegment = [],
718             i = segment.length,
719             baseGetSegmentPath = Series.prototype.getSegmentPath,
720             point,
721             linePath,
722             lowerPath,
723             options = this.options,
724             step = options.step,
725             higherPath;
726             
727         // Remove nulls from low segment
728         lowSegment = HighchartsAdapter.grep(segment, function (point) {
729             return point.plotLow !== null;
730         });
731         
732         // Make a segment with plotX and plotY for the top values
733         while (i--) {
734             point = segment[i];
735             if (point.plotHigh !== null) {
736                 highSegment.push({
737                     plotX: point.plotX,
738                     plotY: point.plotHigh
739                 });
740             }
741         }
742         
743         // Get the paths
744         lowerPath = baseGetSegmentPath.call(this, lowSegment);
745         if (step) {
746             if (step === true) {
747                 step = 'left';
748             }
749             options.step = { left: 'right', center: 'center', right: 'left' }[step]; // swap for reading in getSegmentPath
750         }
751         higherPath = baseGetSegmentPath.call(this, highSegment);
752         options.step = step;
753         
754         // Create a line on both top and bottom of the range
755         linePath = [].concat(lowerPath, higherPath);
756         
757         // For the area path, we need to change the 'move' statement into 'lineTo' or 'curveTo'
758         higherPath[0] = 'L'; // this probably doesn't work for spline            
759         this.areaPath = this.areaPath.concat(lowerPath, higherPath);
760         
761         return linePath;
762     },
763     
764     /**
765      * Extend the basic drawDataLabels method by running it for both lower and higher
766      * values.
767      */
768     drawDataLabels: function () {
769         
770         var data = this.data,
771             length = data.length,
772             i,
773             originalDataLabels = [],
774             seriesProto = Series.prototype,
775             dataLabelOptions = this.options.dataLabels,
776             point,
777             inverted = this.chart.inverted;
778             
779         if (dataLabelOptions.enabled || this._hasPointLabels) {
780             
781             // Step 1: set preliminary values for plotY and dataLabel and draw the upper labels
782             i = length;
783             while (i--) {
784                 point = data[i];
785                 
786                 // Set preliminary values
787                 point.y = point.high;
788                 point.plotY = point.plotHigh;
789                 
790                 // Store original data labels and set preliminary label objects to be picked up 
791                 // in the uber method
792                 originalDataLabels[i] = point.dataLabel;
793                 point.dataLabel = point.dataLabelUpper;
794                 
795                 // Set the default offset
796                 point.below = false;
797                 if (inverted) {
798                     dataLabelOptions.align = 'left';
799                     dataLabelOptions.x = dataLabelOptions.xHigh;                                
800                 } else {
801                     dataLabelOptions.y = dataLabelOptions.yHigh;
802                 }
803             }
804             seriesProto.drawDataLabels.apply(this, arguments); // #1209
805             
806             // Step 2: reorganize and handle data labels for the lower values
807             i = length;
808             while (i--) {
809                 point = data[i];
810                 
811                 // Move the generated labels from step 1, and reassign the original data labels
812                 point.dataLabelUpper = point.dataLabel;
813                 point.dataLabel = originalDataLabels[i];
814                 
815                 // Reset values
816                 point.y = point.low;
817                 point.plotY = point.plotLow;
818                 
819                 // Set the default offset
820                 point.below = true;
821                 if (inverted) {
822                     dataLabelOptions.align = 'right';
823                     dataLabelOptions.x = dataLabelOptions.xLow;
824                 } else {
825                     dataLabelOptions.y = dataLabelOptions.yLow;
826                 }
827             }
828             seriesProto.drawDataLabels.apply(this, arguments);
829         }
830     
831     },
832     
833     alignDataLabel: seriesTypes.column.prototype.alignDataLabel,
834     
835     getSymbol: seriesTypes.column.prototype.getSymbol,
836     
837     drawPoints: noop
838 });/**
839  * The AreaSplineRangeSeries class
840  */
841
842 defaultPlotOptions.areasplinerange = merge(defaultPlotOptions.arearange);
843
844 /**
845  * AreaSplineRangeSeries object
846  */
847 seriesTypes.areasplinerange = extendClass(seriesTypes.arearange, {
848     type: 'areasplinerange',
849     getPointSpline: seriesTypes.spline.prototype.getPointSpline
850 });/**
851  * The ColumnRangeSeries class
852  */
853 defaultPlotOptions.columnrange = merge(defaultPlotOptions.column, defaultPlotOptions.arearange, {
854     lineWidth: 1,
855     pointRange: null
856 });
857
858 /**
859  * ColumnRangeSeries object
860  */
861 seriesTypes.columnrange = extendClass(seriesTypes.arearange, {
862     type: 'columnrange',
863     /**
864      * Translate data points from raw values x and y to plotX and plotY
865      */
866     translate: function () {
867         var series = this,
868             yAxis = series.yAxis,
869             plotHigh;
870
871         colProto.translate.apply(series);
872
873         // Set plotLow and plotHigh
874         each(series.points, function (point) {
875             var shapeArgs = point.shapeArgs,
876                 minPointLength = series.options.minPointLength,
877                 heightDifference,
878                 height,
879                 y;
880
881             point.plotHigh = plotHigh = yAxis.translate(point.high, 0, 1, 0, 1);
882             point.plotLow = point.plotY;
883
884             // adjust shape
885             y = plotHigh;
886             height = point.plotY - plotHigh;
887
888             if (height < minPointLength) {
889                 heightDifference = (minPointLength - height);
890                 height += heightDifference;
891                 y -= heightDifference / 2;
892             }
893             shapeArgs.height = height;
894             shapeArgs.y = y;
895         });
896     },
897     trackerGroups: ['group', 'dataLabels'],
898     drawGraph: noop,
899     pointAttrToOptions: colProto.pointAttrToOptions,
900     drawPoints: colProto.drawPoints,
901     drawTracker: colProto.drawTracker,
902     animate: colProto.animate,
903     getColumnMetrics: colProto.getColumnMetrics
904 });
905 /* 
906  * The GaugeSeries class
907  */
908
909
910
911 /**
912  * Extend the default options
913  */
914 defaultPlotOptions.gauge = merge(defaultPlotOptions.line, {
915     dataLabels: {
916         enabled: true,
917         y: 15,
918         borderWidth: 1,
919         borderColor: 'silver',
920         borderRadius: 3,
921         style: {
922             fontWeight: 'bold'
923         },
924         verticalAlign: 'top',
925         zIndex: 2
926     },
927     dial: {
928         // radius: '80%',
929         // backgroundColor: 'black',
930         // borderColor: 'silver',
931         // borderWidth: 0,
932         // baseWidth: 3,
933         // topWidth: 1,
934         // baseLength: '70%' // of radius
935         // rearLength: '10%'
936     },
937     pivot: {
938         //radius: 5,
939         //borderWidth: 0
940         //borderColor: 'silver',
941         //backgroundColor: 'black'
942     },
943     tooltip: {
944         headerFormat: ''
945     },
946     showInLegend: false
947 });
948
949 /**
950  * Extend the point object
951  */
952 var GaugePoint = Highcharts.extendClass(Highcharts.Point, {
953     /**
954      * Don't do any hover colors or anything
955      */
956     setState: function (state) {
957         this.state = state;
958     }
959 });
960
961
962 /**
963  * Add the series type
964  */
965 var GaugeSeries = {
966     type: 'gauge',
967     pointClass: GaugePoint,
968     
969     // chart.angular will be set to true when a gauge series is present, and this will
970     // be used on the axes
971     angular: true, 
972     drawGraph: noop,
973     fixedBox: true,
974     trackerGroups: ['group', 'dataLabels'],
975     
976     /**
977      * Calculate paths etc
978      */
979     translate: function () {
980         
981         var series = this,
982             yAxis = series.yAxis,
983             options = series.options,
984             center = yAxis.center;
985             
986         series.generatePoints();
987         
988         each(series.points, function (point) {
989             
990             var dialOptions = merge(options.dial, point.dial),
991                 radius = (pInt(pick(dialOptions.radius, 80)) * center[2]) / 200,
992                 baseLength = (pInt(pick(dialOptions.baseLength, 70)) * radius) / 100,
993                 rearLength = (pInt(pick(dialOptions.rearLength, 10)) * radius) / 100,
994                 baseWidth = dialOptions.baseWidth || 3,
995                 topWidth = dialOptions.topWidth || 1,
996                 rotation = yAxis.startAngleRad + yAxis.translate(point.y, null, null, null, true);
997
998             // Handle the wrap option
999             if (options.wrap === false) {
1000                 rotation = Math.max(yAxis.startAngleRad, Math.min(yAxis.endAngleRad, rotation));
1001             }
1002             rotation = rotation * 180 / Math.PI;
1003                 
1004             point.shapeType = 'path';
1005             point.shapeArgs = {
1006                 d: dialOptions.path || [
1007                     'M', 
1008                     -rearLength, -baseWidth / 2, 
1009                     'L', 
1010                     baseLength, -baseWidth / 2,
1011                     radius, -topWidth / 2,
1012                     radius, topWidth / 2,
1013                     baseLength, baseWidth / 2,
1014                     -rearLength, baseWidth / 2,
1015                     'z'
1016                 ],
1017                 translateX: center[0],
1018                 translateY: center[1],
1019                 rotation: rotation
1020             };
1021             
1022             // Positions for data label
1023             point.plotX = center[0];
1024             point.plotY = center[1];
1025         });
1026     },
1027     
1028     /**
1029      * Draw the points where each point is one needle
1030      */
1031     drawPoints: function () {
1032         
1033         var series = this,
1034             center = series.yAxis.center,
1035             pivot = series.pivot,
1036             options = series.options,
1037             pivotOptions = options.pivot,
1038             renderer = series.chart.renderer;
1039         
1040         each(series.points, function (point) {
1041             
1042             var graphic = point.graphic,
1043                 shapeArgs = point.shapeArgs,
1044                 d = shapeArgs.d,
1045                 dialOptions = merge(options.dial, point.dial); // #1233
1046             
1047             if (graphic) {
1048                 graphic.animate(shapeArgs);
1049                 shapeArgs.d = d; // animate alters it
1050             } else {
1051                 point.graphic = renderer[point.shapeType](shapeArgs)
1052                     .attr({
1053                         stroke: dialOptions.borderColor || 'none',
1054                         'stroke-width': dialOptions.borderWidth || 0,
1055                         fill: dialOptions.backgroundColor || 'black',
1056                         rotation: shapeArgs.rotation // required by VML when animation is false
1057                     })
1058                     .add(series.group);
1059             }
1060         });
1061         
1062         // Add or move the pivot
1063         if (pivot) {
1064             pivot.animate({ // #1235
1065                 translateX: center[0],
1066                 translateY: center[1]
1067             });
1068         } else {
1069             series.pivot = renderer.circle(0, 0, pick(pivotOptions.radius, 5))
1070                 .attr({
1071                     'stroke-width': pivotOptions.borderWidth || 0,
1072                     stroke: pivotOptions.borderColor || 'silver',
1073                     fill: pivotOptions.backgroundColor || 'black'
1074                 })
1075                 .translate(center[0], center[1])
1076                 .add(series.group);
1077         }
1078     },
1079     
1080     /**
1081      * Animate the arrow up from startAngle
1082      */
1083     animate: function (init) {
1084         var series = this;
1085
1086         if (!init) {
1087             each(series.points, function (point) {
1088                 var graphic = point.graphic;
1089
1090                 if (graphic) {
1091                     // start value
1092                     graphic.attr({
1093                         rotation: series.yAxis.startAngleRad * 180 / Math.PI
1094                     });
1095
1096                     // animate
1097                     graphic.animate({
1098                         rotation: point.shapeArgs.rotation
1099                     }, series.options.animation);
1100                 }
1101             });
1102
1103             // delete this function to allow it only once
1104             series.animate = null;
1105         }
1106     },
1107     
1108     render: function () {
1109         this.group = this.plotGroup(
1110             'group', 
1111             'series', 
1112             this.visible ? 'visible' : 'hidden', 
1113             this.options.zIndex, 
1114             this.chart.seriesGroup
1115         );
1116         seriesTypes.pie.prototype.render.call(this);
1117         this.group.clip(this.chart.clipRect);
1118     },
1119     
1120     setData: seriesTypes.pie.prototype.setData,
1121     drawTracker: seriesTypes.column.prototype.drawTracker
1122 };
1123 seriesTypes.gauge = Highcharts.extendClass(seriesTypes.line, GaugeSeries);/* ****************************************************************************
1124  * Start Box plot series code                                                  *
1125  *****************************************************************************/
1126
1127 // Set default options
1128 defaultPlotOptions.boxplot = merge(defaultPlotOptions.column, {
1129     fillColor: '#FFFFFF',
1130     lineWidth: 1,
1131     //medianColor: null,
1132     medianWidth: 2,
1133     states: {
1134         hover: {
1135             brightness: -0.3
1136         }
1137     },
1138     //stemColor: null,
1139     //stemDashStyle: 'solid'
1140     //stemWidth: null,
1141     threshold: null,
1142     tooltip: {
1143         pointFormat: '<span style="color:{series.color};font-weight:bold">{series.name}</span><br/>' +
1144             'Maximum: {point.high}<br/>' +
1145             'Upper quartile: {point.q3}<br/>' +
1146             'Median: {point.median}<br/>' +
1147             'Lower quartile: {point.q1}<br/>' +
1148             'Minimum: {point.low}<br/>'
1149             
1150     },
1151     //whiskerColor: null,
1152     whiskerLength: '50%',
1153     whiskerWidth: 2
1154 });
1155
1156 // Create the series object
1157 seriesTypes.boxplot = extendClass(seriesTypes.column, {
1158     type: 'boxplot',
1159     pointArrayMap: ['low', 'q1', 'median', 'q3', 'high'], // array point configs are mapped to this
1160     toYData: function (point) { // return a plain array for speedy calculation
1161         return [point.low, point.q1, point.median, point.q3, point.high];
1162     },
1163     pointValKey: 'high', // defines the top of the tracker
1164     
1165     /**
1166      * One-to-one mapping from options to SVG attributes
1167      */
1168     pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
1169         fill: 'fillColor',
1170         stroke: 'color',
1171         'stroke-width': 'lineWidth'
1172     },
1173     
1174     /**
1175      * Disable data labels for box plot
1176      */
1177     drawDataLabels: noop,
1178
1179     /**
1180      * Translate data points from raw values x and y to plotX and plotY
1181      */
1182     translate: function () {
1183         var series = this,
1184             yAxis = series.yAxis,
1185             pointArrayMap = series.pointArrayMap;
1186
1187         seriesTypes.column.prototype.translate.apply(series);
1188
1189         // do the translation on each point dimension
1190         each(series.points, function (point) {
1191             each(pointArrayMap, function (key) {
1192                 if (point[key] !== null) {
1193                     point[key + 'Plot'] = yAxis.translate(point[key], 0, 1, 0, 1);
1194                 }
1195             });
1196         });
1197     },
1198
1199     /**
1200      * Draw the data points
1201      */
1202     drawPoints: function () {
1203         var series = this,  //state = series.state,
1204             points = series.points,
1205             options = series.options,
1206             chart = series.chart,
1207             renderer = chart.renderer,
1208             pointAttr,
1209             q1Plot,
1210             q3Plot,
1211             highPlot,
1212             lowPlot,
1213             medianPlot,
1214             crispCorr,
1215             crispX,
1216             graphic,
1217             stemPath,
1218             stemAttr,
1219             boxPath,
1220             whiskersPath,
1221             whiskersAttr,
1222             medianPath,
1223             medianAttr,
1224             width,
1225             left,
1226             right,
1227             halfWidth,
1228             shapeArgs,
1229             color,
1230             doQuartiles = series.doQuartiles !== false, // error bar inherits this series type but doesn't do quartiles
1231             whiskerLength = parseInt(series.options.whiskerLength, 10) / 100;
1232
1233
1234         each(points, function (point) {
1235
1236             graphic = point.graphic;
1237             shapeArgs = point.shapeArgs; // the box
1238             stemAttr = {};
1239             whiskersAttr = {};
1240             medianAttr = {};
1241             color = point.color || series.color;
1242             
1243             if (point.plotY !== UNDEFINED) {
1244
1245                 pointAttr = point.pointAttr[point.selected ? 'selected' : ''];
1246
1247                 // crisp vector coordinates
1248                 width = shapeArgs.width;
1249                 left = mathFloor(shapeArgs.x);
1250                 right = left + width;
1251                 halfWidth = mathRound(width / 2);
1252                 //crispX = mathRound(left + halfWidth) + crispCorr;
1253                 q1Plot = mathFloor(doQuartiles ? point.q1Plot : point.lowPlot);// + crispCorr;
1254                 q3Plot = mathFloor(doQuartiles ? point.q3Plot : point.lowPlot);// + crispCorr;
1255                 highPlot = mathFloor(point.highPlot);// + crispCorr;
1256                 lowPlot = mathFloor(point.lowPlot);// + crispCorr;
1257                 
1258                 // Stem attributes
1259                 stemAttr.stroke = point.stemColor || options.stemColor || color;
1260                 stemAttr['stroke-width'] = pick(point.stemWidth, options.stemWidth, options.lineWidth);
1261                 stemAttr.dashstyle = point.stemDashStyle || options.stemDashStyle;
1262                 
1263                 // Whiskers attributes
1264                 whiskersAttr.stroke = point.whiskerColor || options.whiskerColor || color;
1265                 whiskersAttr['stroke-width'] = pick(point.whiskerWidth, options.whiskerWidth, options.lineWidth);
1266                 
1267                 // Median attributes
1268                 medianAttr.stroke = point.medianColor || options.medianColor || color;
1269                 medianAttr['stroke-width'] = pick(point.medianWidth, options.medianWidth, options.lineWidth);
1270                 
1271                 
1272                 // The stem
1273                 crispCorr = (stemAttr['stroke-width'] % 2) / 2;
1274                 crispX = left + halfWidth + crispCorr;                
1275                 stemPath = [
1276                     // stem up
1277                     'M',
1278                     crispX, q3Plot,
1279                     'L',
1280                     crispX, highPlot,
1281                     
1282                     // stem down
1283                     'M',
1284                     crispX, q1Plot,
1285                     'L',
1286                     crispX, lowPlot,
1287                     'z'
1288                 ];
1289                 
1290                 // The box
1291                 if (doQuartiles) {
1292                     crispCorr = (pointAttr['stroke-width'] % 2) / 2;
1293                     crispX = mathFloor(crispX) + crispCorr;
1294                     q1Plot = mathFloor(q1Plot) + crispCorr;
1295                     q3Plot = mathFloor(q3Plot) + crispCorr;
1296                     left += crispCorr;
1297                     right += crispCorr;
1298                     boxPath = [
1299                         'M',
1300                         left, q3Plot,
1301                         'L',
1302                         left, q1Plot,
1303                         'L',
1304                         right, q1Plot,
1305                         'L',
1306                         right, q3Plot,
1307                         'L',
1308                         left, q3Plot,
1309                         'z'
1310                     ];
1311                 }
1312                 
1313                 // The whiskers
1314                 if (whiskerLength) {
1315                     crispCorr = (whiskersAttr['stroke-width'] % 2) / 2;
1316                     highPlot = highPlot + crispCorr;
1317                     lowPlot = lowPlot + crispCorr;
1318                     whiskersPath = [
1319                         // High whisker
1320                         'M',
1321                         crispX - halfWidth * whiskerLength, 
1322                         highPlot,
1323                         'L',
1324                         crispX + halfWidth * whiskerLength, 
1325                         highPlot,
1326                         
1327                         // Low whisker
1328                         'M',
1329                         crispX - halfWidth * whiskerLength, 
1330                         lowPlot,
1331                         'L',
1332                         crispX + halfWidth * whiskerLength, 
1333                         lowPlot
1334                     ];
1335                 }
1336                 
1337                 // The median
1338                 crispCorr = (medianAttr['stroke-width'] % 2) / 2;                
1339                 medianPlot = mathRound(point.medianPlot) + crispCorr;
1340                 medianPath = [
1341                     'M',
1342                     left, 
1343                     medianPlot,
1344                     'L',
1345                     right, 
1346                     medianPlot,
1347                     'z'
1348                 ];
1349                 
1350                 // Create or update the graphics
1351                 if (graphic) { // update
1352                     
1353                     point.stem.animate({ d: stemPath });
1354                     if (whiskerLength) {
1355                         point.whiskers.animate({ d: whiskersPath });
1356                     }
1357                     if (doQuartiles) {
1358                         point.box.animate({ d: boxPath });
1359                     }
1360                     point.medianShape.animate({ d: medianPath });
1361                     
1362                 } else { // create new
1363                     point.graphic = graphic = renderer.g()
1364                         .add(series.group);
1365                     
1366                     point.stem = renderer.path(stemPath)
1367                         .attr(stemAttr)
1368                         .add(graphic);
1369                         
1370                     if (whiskerLength) {
1371                         point.whiskers = renderer.path(whiskersPath) 
1372                             .attr(whiskersAttr)
1373                             .add(graphic);
1374                     }
1375                     if (doQuartiles) {
1376                         point.box = renderer.path(boxPath)
1377                             .attr(pointAttr)
1378                             .add(graphic);
1379                     }    
1380                     point.medianShape = renderer.path(medianPath)
1381                         .attr(medianAttr)
1382                         .add(graphic);
1383                 }
1384             }
1385         });
1386
1387     }
1388
1389
1390 });
1391
1392 /* ****************************************************************************
1393  * End Box plot series code                                                *
1394  *****************************************************************************/
1395 /* ****************************************************************************
1396  * Start error bar series code                                                *
1397  *****************************************************************************/
1398
1399 // 1 - set default options
1400 defaultPlotOptions.errorbar = merge(defaultPlotOptions.boxplot, {
1401     color: '#000000',
1402     grouping: false,
1403     linkedTo: ':previous',
1404     tooltip: {
1405         pointFormat: defaultPlotOptions.arearange.tooltip.pointFormat
1406     },
1407     whiskerWidth: null
1408 });
1409
1410 // 2 - Create the series object
1411 seriesTypes.errorbar = extendClass(seriesTypes.boxplot, {
1412     type: 'errorbar',
1413     pointArrayMap: ['low', 'high'], // array point configs are mapped to this
1414     toYData: function (point) { // return a plain array for speedy calculation
1415         return [point.low, point.high];
1416     },
1417     pointValKey: 'high', // defines the top of the tracker
1418     doQuartiles: false,
1419
1420     /**
1421      * Get the width and X offset, either on top of the linked series column
1422      * or standalone
1423      */
1424     getColumnMetrics: function () {
1425         return (this.linkedParent && this.linkedParent.columnMetrics) || 
1426             seriesTypes.column.prototype.getColumnMetrics.call(this);
1427     }
1428 });
1429
1430 /* ****************************************************************************
1431  * End error bar series code                                                  *
1432  *****************************************************************************/
1433 /* ****************************************************************************
1434  * Start Waterfall series code                                                *
1435  *****************************************************************************/
1436
1437 // 1 - set default options
1438 defaultPlotOptions.waterfall = merge(defaultPlotOptions.column, {
1439     lineWidth: 1,
1440     lineColor: '#333',
1441     dashStyle: 'dot',
1442     borderColor: '#333'
1443 });
1444
1445
1446 // 2 - Create the series object
1447 seriesTypes.waterfall = extendClass(seriesTypes.column, {
1448     type: 'waterfall',
1449
1450     upColorProp: 'fill',
1451
1452     pointArrayMap: ['low', 'y'],
1453
1454     pointValKey: 'y',
1455
1456     /**
1457      * Init waterfall series, force stacking
1458      */
1459     init: function (chart, options) {
1460         // force stacking
1461         options.stacking = true;
1462
1463         seriesTypes.column.prototype.init.call(this, chart, options);
1464     },
1465
1466
1467     /**
1468      * Translate data points from raw values
1469      */
1470     translate: function () {
1471         var series = this,
1472             options = series.options,
1473             axis = series.yAxis,
1474             len,
1475             i,
1476             points,
1477             point,
1478             shapeArgs,
1479             stack,
1480             y,
1481             previousY,
1482             stackPoint,
1483             threshold = options.threshold,
1484             crispCorr = (options.borderWidth % 2) / 2;
1485
1486         // run column series translate
1487         seriesTypes.column.prototype.translate.apply(this);
1488
1489         previousY = threshold;
1490         points = series.points;
1491
1492         for (i = 0, len = points.length; i < len; i++) {
1493             // cache current point object
1494             point = points[i];
1495             shapeArgs = point.shapeArgs;
1496
1497             // get current stack
1498             stack = series.getStack(i);
1499             stackPoint = stack.points[series.index];
1500
1501             // override point value for sums
1502             if (isNaN(point.y)) {
1503                 point.y = series.yData[i];
1504             }
1505
1506             // up points
1507             y = mathMax(previousY, previousY + point.y) + stackPoint[0];
1508             shapeArgs.y = axis.translate(y, 0, 1);
1509
1510
1511             // sum points
1512             if (point.isSum || point.isIntermediateSum) {
1513                 shapeArgs.y = axis.translate(stackPoint[1], 0, 1);
1514                 shapeArgs.height = axis.translate(stackPoint[0], 0, 1) - shapeArgs.y;
1515
1516             // if it's not the sum point, update previous stack end position
1517             } else {
1518                 previousY += stack.total;
1519             }
1520
1521             // negative points
1522             if (shapeArgs.height < 0) {
1523                 shapeArgs.y += shapeArgs.height;
1524                 shapeArgs.height *= -1;
1525             }
1526
1527             point.plotY = shapeArgs.y = mathRound(shapeArgs.y) - crispCorr;
1528             shapeArgs.height = mathRound(shapeArgs.height);
1529             point.yBottom = shapeArgs.y + shapeArgs.height;
1530         }
1531     },
1532
1533     /**
1534      * Call default processData then override yData to reflect waterfall's extremes on yAxis
1535      */
1536     processData: function (force) {
1537         var series = this,
1538             options = series.options,
1539             yData = series.yData,
1540             points = series.points,
1541             point,
1542             dataLength = yData.length,
1543             threshold = options.threshold || 0,
1544             subSum,
1545             sum,
1546             dataMin,
1547             dataMax,
1548             y,
1549             i;
1550
1551         sum = subSum = dataMin = dataMax = threshold;
1552
1553         for (i = 0; i < dataLength; i++) {
1554             y = yData[i];
1555             point = points && points[i] ? points[i] : {};
1556
1557             if (y === "sum" || point.isSum) {
1558                 yData[i] = sum;
1559             } else if (y === "intermediateSum" || point.isIntermediateSum) {
1560                 yData[i] = subSum;
1561                 subSum = threshold;
1562             } else {
1563                 sum += y;
1564                 subSum += y;
1565             }
1566             dataMin = Math.min(sum, dataMin);
1567             dataMax = Math.max(sum, dataMax);
1568         }
1569
1570         Series.prototype.processData.call(this, force);
1571
1572         // Record extremes
1573         series.dataMin = dataMin;
1574         series.dataMax = dataMax;
1575     },
1576
1577     /**
1578      * Return y value or string if point is sum
1579      */
1580     toYData: function (pt) {
1581         if (pt.isSum) {
1582             return "sum";
1583         } else if (pt.isIntermediateSum) {
1584             return "intermediateSum";
1585         }
1586
1587         return pt.y;
1588     },
1589
1590     /**
1591      * Postprocess mapping between options and SVG attributes
1592      */
1593     getAttribs: function () {
1594         seriesTypes.column.prototype.getAttribs.apply(this, arguments);
1595
1596         var series = this,
1597             options = series.options,
1598             stateOptions = options.states,
1599             upColor = options.upColor || series.color,
1600             hoverColor = Highcharts.Color(upColor).brighten(0.1).get(),
1601             seriesDownPointAttr = merge(series.pointAttr),
1602             upColorProp = series.upColorProp;
1603
1604         seriesDownPointAttr[''][upColorProp] = upColor;
1605         seriesDownPointAttr.hover[upColorProp] = stateOptions.hover.upColor || hoverColor;
1606         seriesDownPointAttr.select[upColorProp] = stateOptions.select.upColor || upColor;
1607
1608         each(series.points, function (point) {
1609             if (point.y > 0 && !point.color) {
1610                 point.pointAttr = seriesDownPointAttr;
1611                 point.color = upColor;
1612             }
1613         });
1614     },
1615
1616     /**
1617      * Draw columns' connector lines
1618      */
1619     getGraphPath: function () {
1620
1621         var data = this.data,
1622             length = data.length,
1623             lineWidth = this.options.lineWidth + this.options.borderWidth,
1624             normalizer = mathRound(lineWidth) % 2 / 2,
1625             path = [],
1626             M = 'M',
1627             L = 'L',
1628             prevArgs,
1629             pointArgs,
1630             i,
1631             d;
1632
1633         for (i = 1; i < length; i++) {
1634             pointArgs = data[i].shapeArgs;
1635             prevArgs = data[i - 1].shapeArgs;
1636
1637             d = [
1638                 M,
1639                 prevArgs.x + prevArgs.width, prevArgs.y + normalizer,
1640                 L,
1641                 pointArgs.x, prevArgs.y + normalizer
1642             ];
1643
1644             if (data[i - 1].y < 0) {
1645                 d[2] += prevArgs.height;
1646                 d[5] += prevArgs.height;
1647             }
1648
1649             path = path.concat(d);
1650         }
1651
1652         return path;
1653     },
1654
1655     /**
1656      * Extremes are recorded in processData
1657      */
1658     getExtremes: noop,
1659
1660     /**
1661      * Return stack for given index
1662      */
1663     getStack: function (i) {
1664         var axis = this.yAxis,
1665             stacks = axis.stacks,
1666             key = this.stackKey;
1667
1668         if (this.processedYData[i] < this.options.threshold) {
1669             key = '-' + key;
1670         }
1671
1672         return stacks[key][i];
1673     },
1674
1675     drawGraph: Series.prototype.drawGraph
1676 });
1677
1678 /* ****************************************************************************
1679  * End Waterfall series code                                                  *
1680  *****************************************************************************/
1681 /* ****************************************************************************
1682  * Start Bubble series code                                                      *
1683  *****************************************************************************/
1684
1685 // 1 - set default options
1686 defaultPlotOptions.bubble = merge(defaultPlotOptions.scatter, {
1687     dataLabels: {
1688         inside: true,
1689         style: {
1690             color: 'white',
1691             textShadow: '0px 0px 3px black'
1692         },
1693         verticalAlign: 'middle'
1694     },
1695     // displayNegative: true,
1696     marker: {
1697         // fillOpacity: 0.5,
1698         lineColor: null, // inherit from series.color
1699         lineWidth: 1
1700     },
1701     minSize: 8,
1702     maxSize: '20%',
1703     // negativeColor: null,
1704     tooltip: {
1705         pointFormat: '({point.x}, {point.y}), Size: {point.z}'
1706     },
1707     turboThreshold: 0,
1708     zThreshold: 0
1709 });
1710
1711 // 2 - Create the series object
1712 seriesTypes.bubble = extendClass(seriesTypes.scatter, {
1713     type: 'bubble',
1714     pointArrayMap: ['y', 'z'],
1715     trackerGroups: ['group', 'dataLabelsGroup'],
1716     
1717     /**
1718      * Mapping between SVG attributes and the corresponding options
1719      */
1720     pointAttrToOptions: { 
1721         stroke: 'lineColor',
1722         'stroke-width': 'lineWidth',
1723         fill: 'fillColor'
1724     },
1725     
1726     /**
1727      * Apply the fillOpacity to all fill positions
1728      */
1729     applyOpacity: function (fill) {
1730         var markerOptions = this.options.marker,
1731             fillOpacity = pick(markerOptions.fillOpacity, 0.5);
1732         
1733         // When called from Legend.colorizeItem, the fill isn't predefined
1734         fill = fill || markerOptions.fillColor || this.color; 
1735         
1736         if (fillOpacity !== 1) {
1737             fill = Highcharts.Color(fill).setOpacity(fillOpacity).get('rgba');
1738         }
1739         return fill;
1740     },
1741     
1742     /**
1743      * Extend the convertAttribs method by applying opacity to the fill
1744      */
1745     convertAttribs: function () {
1746         var obj = Series.prototype.convertAttribs.apply(this, arguments);
1747         
1748         obj.fill = this.applyOpacity(obj.fill);
1749         
1750         return obj;
1751     },
1752
1753     /**
1754      * Get the radius for each point based on the minSize, maxSize and each point's Z value. This
1755      * must be done prior to Series.translate because the axis needs to add padding in 
1756      * accordance with the point sizes.
1757      */
1758     getRadii: function (zMin, zMax, minSize, maxSize) {
1759         var len,
1760             i,
1761             pos,
1762             zData = this.zData,
1763             radii = [],
1764             zRange;
1765         
1766         // Set the shape type and arguments to be picked up in drawPoints
1767         for (i = 0, len = zData.length; i < len; i++) {
1768             zRange = zMax - zMin;
1769             pos = zRange > 0 ? // relative size, a number between 0 and 1
1770                 (zData[i] - zMin) / (zMax - zMin) : 
1771                 0.5;
1772             radii.push(math.ceil(minSize + pos * (maxSize - minSize)) / 2);
1773         }
1774         this.radii = radii;
1775     },
1776     
1777     /**
1778      * Perform animation on the bubbles
1779      */
1780     animate: function (init) {
1781         var animation = this.options.animation;
1782         
1783         if (!init) { // run the animation
1784             each(this.points, function (point) {
1785                 var graphic = point.graphic,
1786                     shapeArgs = point.shapeArgs;
1787
1788                 if (graphic && shapeArgs) {
1789                     // start values
1790                     graphic.attr('r', 1);
1791
1792                     // animate
1793                     graphic.animate({
1794                         r: shapeArgs.r
1795                     }, animation);
1796                 }
1797             });
1798
1799             // delete this function to allow it only once
1800             this.animate = null;
1801         }
1802     },
1803     
1804     /**
1805      * Extend the base translate method to handle bubble size
1806      */
1807     translate: function () {
1808         
1809         var i,
1810             data = this.data,
1811             point,
1812             radius,
1813             radii = this.radii;
1814         
1815         // Run the parent method
1816         seriesTypes.scatter.prototype.translate.call(this);
1817         
1818         // Set the shape type and arguments to be picked up in drawPoints
1819         i = data.length;
1820         
1821         while (i--) {
1822             point = data[i];
1823             radius = radii ? radii[i] : 0; // #1737
1824
1825             // Flag for negativeColor to be applied in Series.js
1826             point.negative = point.z < (this.options.zThreshold || 0);
1827             
1828             if (radius >= this.minPxSize / 2) {
1829                 // Shape arguments
1830                 point.shapeType = 'circle';
1831                 point.shapeArgs = {
1832                     x: point.plotX,
1833                     y: point.plotY,
1834                     r: radius
1835                 };
1836                 
1837                 // Alignment box for the data label
1838                 point.dlBox = {
1839                     x: point.plotX - radius,
1840                     y: point.plotY - radius,
1841                     width: 2 * radius,
1842                     height: 2 * radius
1843                 };
1844             } else { // below zThreshold
1845                 point.shapeArgs = point.plotY = point.dlBox = UNDEFINED; // #1691
1846             }
1847         }
1848     },
1849     
1850     /**
1851      * Get the series' symbol in the legend
1852      * 
1853      * @param {Object} legend The legend object
1854      * @param {Object} item The series (this) or point
1855      */
1856     drawLegendSymbol: function (legend, item) {
1857         var radius = pInt(legend.itemStyle.fontSize) / 2;
1858         
1859         item.legendSymbol = this.chart.renderer.circle(
1860             radius,
1861             legend.baseline - radius,
1862             radius
1863         ).attr({
1864             zIndex: 3
1865         }).add(item.legendGroup);
1866         item.legendSymbol.isMarker = true;    
1867         
1868     },
1869     
1870     drawPoints: seriesTypes.column.prototype.drawPoints,
1871     alignDataLabel: seriesTypes.column.prototype.alignDataLabel
1872 });
1873
1874 /**
1875  * Add logic to pad each axis with the amount of pixels
1876  * necessary to avoid the bubbles to overflow.
1877  */
1878 Axis.prototype.beforePadding = function () {
1879     var axis = this,
1880         axisLength = this.len,
1881         chart = this.chart,
1882         pxMin = 0, 
1883         pxMax = axisLength,
1884         isXAxis = this.isXAxis,
1885         dataKey = isXAxis ? 'xData' : 'yData',
1886         min = this.min,
1887         extremes = {},
1888         smallestSize = math.min(chart.plotWidth, chart.plotHeight),
1889         zMin = Number.MAX_VALUE,
1890         zMax = -Number.MAX_VALUE,
1891         range = this.max - min,
1892         transA = axisLength / range,
1893         activeSeries = [];
1894
1895     // Handle padding on the second pass, or on redraw
1896     if (this.tickPositions) {
1897         each(this.series, function (series) {
1898
1899             var seriesOptions = series.options,
1900                 zData;
1901
1902             if (series.type === 'bubble' && series.visible) {
1903
1904                 // Correction for #1673
1905                 axis.allowZoomOutside = true;
1906
1907                 // Cache it
1908                 activeSeries.push(series);
1909
1910                 if (isXAxis) { // because X axis is evaluated first
1911                 
1912                     // For each series, translate the size extremes to pixel values
1913                     each(['minSize', 'maxSize'], function (prop) {
1914                         var length = seriesOptions[prop],
1915                             isPercent = /%$/.test(length);
1916                         
1917                         length = pInt(length);
1918                         extremes[prop] = isPercent ?
1919                             smallestSize * length / 100 :
1920                             length;
1921                         
1922                     });
1923                     series.minPxSize = extremes.minSize;
1924                     
1925                     // Find the min and max Z
1926                     zData = series.zData;
1927                     if (zData.length) { // #1735
1928                         zMin = math.min(
1929                             zMin,
1930                             math.max(
1931                                 arrayMin(zData), 
1932                                 seriesOptions.displayNegative === false ? seriesOptions.zThreshold : -Number.MAX_VALUE
1933                             )
1934                         );
1935                         zMax = math.max(zMax, arrayMax(zData));
1936                     }
1937                 }
1938             }
1939         });
1940
1941         each(activeSeries, function (series) {
1942
1943             var data = series[dataKey],
1944                 i = data.length,
1945                 radius;
1946
1947             if (isXAxis) {
1948                 series.getRadii(zMin, zMax, extremes.minSize, extremes.maxSize);
1949             }
1950             
1951             if (range > 0) {
1952                 while (i--) {
1953                     radius = series.radii[i];
1954                     pxMin = Math.min(((data[i] - min) * transA) - radius, pxMin);
1955                     pxMax = Math.max(((data[i] - min) * transA) + radius, pxMax);
1956                 }
1957             }
1958         });
1959         
1960         if (activeSeries.length && range > 0 && pick(this.options.min, this.userMin) === UNDEFINED && pick(this.options.max, this.userMax) === UNDEFINED) {
1961             pxMax -= axisLength;
1962             transA *= (axisLength + pxMin - pxMax) / axisLength;
1963             this.min += pxMin / transA;
1964             this.max += pxMax / transA;
1965         }
1966     }
1967 };
1968
1969 /* ****************************************************************************
1970  * End Bubble series code                                                     *
1971  *****************************************************************************/
1972 /**
1973  * Extensions for polar charts. Additionally, much of the geometry required for polar charts is
1974  * gathered in RadialAxes.js.
1975  * 
1976  */
1977
1978 var seriesProto = Series.prototype,
1979     pointerProto = Highcharts.Pointer.prototype;
1980
1981
1982
1983 /**
1984  * Translate a point's plotX and plotY from the internal angle and radius measures to 
1985  * true plotX, plotY coordinates
1986  */
1987 seriesProto.toXY = function (point) {
1988     var xy,
1989         chart = this.chart,
1990         plotX = point.plotX,
1991         plotY = point.plotY;
1992     
1993     // Save rectangular plotX, plotY for later computation
1994     point.rectPlotX = plotX;
1995     point.rectPlotY = plotY;
1996     
1997     // Record the angle in degrees for use in tooltip
1998     point.clientX = ((plotX / Math.PI * 180) + this.xAxis.pane.options.startAngle) % 360;
1999     
2000     // Find the polar plotX and plotY
2001     xy = this.xAxis.postTranslate(point.plotX, this.yAxis.len - plotY);
2002     point.plotX = point.polarPlotX = xy.x - chart.plotLeft;
2003     point.plotY = point.polarPlotY = xy.y - chart.plotTop;
2004 };
2005
2006 /** 
2007  * Order the tooltip points to get the mouse capture ranges correct. #1915. 
2008  */
2009 seriesProto.orderTooltipPoints = function (points) {
2010     if (this.chart.polar) {
2011         points.sort(function (a, b) {
2012             return a.clientX - b.clientX;
2013         });
2014
2015         // Wrap mouse tracking around to capture movement on the segment to the left
2016         // of the north point (#1469, #2093).
2017         if (points[0]) {
2018             points[0].wrappedClientX = points[0].clientX + 360;
2019             points.push(points[0]);
2020         }
2021     }
2022 };
2023
2024
2025 /**
2026  * Add some special init logic to areas and areasplines
2027  */
2028 function initArea(proceed, chart, options) {
2029     proceed.call(this, chart, options);
2030     if (this.chart.polar) {
2031         
2032         /**
2033          * Overridden method to close a segment path. While in a cartesian plane the area 
2034          * goes down to the threshold, in the polar chart it goes to the center.
2035          */
2036         this.closeSegment = function (path) {
2037             var center = this.xAxis.center;
2038             path.push(
2039                 'L',
2040                 center[0],
2041                 center[1]
2042             );            
2043         };
2044         
2045         // Instead of complicated logic to draw an area around the inner area in a stack,
2046         // just draw it behind
2047         this.closedStacks = true;
2048     }
2049 }
2050 wrap(seriesTypes.area.prototype, 'init', initArea);
2051 wrap(seriesTypes.areaspline.prototype, 'init', initArea);
2052         
2053
2054 /**
2055  * Overridden method for calculating a spline from one point to the next
2056  */
2057 wrap(seriesTypes.spline.prototype, 'getPointSpline', function (proceed, segment, point, i) {
2058     
2059     var ret,
2060         smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc;
2061         denom = smoothing + 1,
2062         plotX, 
2063         plotY,
2064         lastPoint,
2065         nextPoint,
2066         lastX,
2067         lastY,
2068         nextX,
2069         nextY,
2070         leftContX,
2071         leftContY,
2072         rightContX,
2073         rightContY,
2074         distanceLeftControlPoint,
2075         distanceRightControlPoint,
2076         leftContAngle,
2077         rightContAngle,
2078         jointAngle;
2079         
2080         
2081     if (this.chart.polar) {
2082         
2083         plotX = point.plotX;
2084         plotY = point.plotY;
2085         lastPoint = segment[i - 1];
2086         nextPoint = segment[i + 1];
2087             
2088         // Connect ends
2089         if (this.connectEnds) {
2090             if (!lastPoint) {
2091                 lastPoint = segment[segment.length - 2]; // not the last but the second last, because the segment is already connected
2092             }
2093             if (!nextPoint) {
2094                 nextPoint = segment[1];
2095             }    
2096         }
2097
2098         // find control points
2099         if (lastPoint && nextPoint) {
2100         
2101             lastX = lastPoint.plotX;
2102             lastY = lastPoint.plotY;
2103             nextX = nextPoint.plotX;
2104             nextY = nextPoint.plotY;
2105             leftContX = (smoothing * plotX + lastX) / denom;
2106             leftContY = (smoothing * plotY + lastY) / denom;
2107             rightContX = (smoothing * plotX + nextX) / denom;
2108             rightContY = (smoothing * plotY + nextY) / denom;
2109             distanceLeftControlPoint = Math.sqrt(Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2));
2110             distanceRightControlPoint = Math.sqrt(Math.pow(rightContX - plotX, 2) + Math.pow(rightContY - plotY, 2));
2111             leftContAngle = Math.atan2(leftContY - plotY, leftContX - plotX);
2112             rightContAngle = Math.atan2(rightContY - plotY, rightContX - plotX);
2113             jointAngle = (Math.PI / 2) + ((leftContAngle + rightContAngle) / 2);
2114                 
2115                 
2116             // Ensure the right direction, jointAngle should be in the same quadrant as leftContAngle
2117             if (Math.abs(leftContAngle - jointAngle) > Math.PI / 2) {
2118                 jointAngle -= Math.PI;
2119             }
2120             
2121             // Find the corrected control points for a spline straight through the point
2122             leftContX = plotX + Math.cos(jointAngle) * distanceLeftControlPoint;
2123             leftContY = plotY + Math.sin(jointAngle) * distanceLeftControlPoint;
2124             rightContX = plotX + Math.cos(Math.PI + jointAngle) * distanceRightControlPoint;
2125             rightContY = plotY + Math.sin(Math.PI + jointAngle) * distanceRightControlPoint;
2126             
2127             // Record for drawing in next point
2128             point.rightContX = rightContX;
2129             point.rightContY = rightContY;
2130
2131         }
2132         
2133         
2134         // moveTo or lineTo
2135         if (!i) {
2136             ret = ['M', plotX, plotY];
2137         } else { // curve from last point to this
2138             ret = [
2139                 'C',
2140                 lastPoint.rightContX || lastPoint.plotX,
2141                 lastPoint.rightContY || lastPoint.plotY,
2142                 leftContX || plotX,
2143                 leftContY || plotY,
2144                 plotX,
2145                 plotY
2146             ];
2147             lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
2148         }
2149         
2150         
2151     } else {
2152         ret = proceed.call(this, segment, point, i);
2153     }
2154     return ret;
2155 });
2156
2157 /**
2158  * Extend translate. The plotX and plotY values are computed as if the polar chart were a
2159  * cartesian plane, where plotX denotes the angle in radians and (yAxis.len - plotY) is the pixel distance from
2160  * center. 
2161  */
2162 wrap(seriesProto, 'translate', function (proceed) {
2163         
2164     // Run uber method
2165     proceed.call(this);
2166     
2167     // Postprocess plot coordinates
2168     if (this.chart.polar && !this.preventPostTranslate) {
2169         var points = this.points,
2170             i = points.length;
2171         while (i--) {
2172             // Translate plotX, plotY from angle and radius to true plot coordinates
2173             this.toXY(points[i]);
2174         }
2175     }
2176 });
2177
2178 /** 
2179  * Extend getSegmentPath to allow connecting ends across 0 to provide a closed circle in 
2180  * line-like series.
2181  */
2182 wrap(seriesProto, 'getSegmentPath', function (proceed, segment) {
2183         
2184     var points = this.points;
2185     
2186     // Connect the path
2187     if (this.chart.polar && this.options.connectEnds !== false && 
2188             segment[segment.length - 1] === points[points.length - 1] && points[0].y !== null) {
2189         this.connectEnds = true; // re-used in splines
2190         segment = [].concat(segment, [points[0]]);
2191     }
2192     
2193     // Run uber method
2194     return proceed.call(this, segment);
2195     
2196 });
2197
2198
2199 function polarAnimate(proceed, init) {
2200     var chart = this.chart,
2201         animation = this.options.animation,
2202         group = this.group,
2203         markerGroup = this.markerGroup,
2204         center = this.xAxis.center,
2205         plotLeft = chart.plotLeft,
2206         plotTop = chart.plotTop,
2207         attribs;
2208
2209     // Specific animation for polar charts
2210     if (chart.polar) {
2211         
2212         // Enable animation on polar charts only in SVG. In VML, the scaling is different, plus animation
2213         // would be so slow it would't matter.
2214         if (chart.renderer.isSVG) {
2215
2216             if (animation === true) {
2217                 animation = {};
2218             }
2219     
2220             // Initialize the animation
2221             if (init) {
2222                 
2223                 // Scale down the group and place it in the center
2224                 attribs = {
2225                     translateX: center[0] + plotLeft,
2226                     translateY: center[1] + plotTop,
2227                     scaleX: 0.001, // #1499
2228                     scaleY: 0.001
2229                 };
2230                     
2231                 group.attr(attribs);
2232                 if (markerGroup) {
2233                     markerGroup.attrSetters = group.attrSetters;
2234                     markerGroup.attr(attribs);
2235                 }
2236                 
2237             // Run the animation
2238             } else {
2239                 attribs = {
2240                     translateX: plotLeft,
2241                     translateY: plotTop,
2242                     scaleX: 1,
2243                     scaleY: 1
2244                 };
2245                 group.animate(attribs, animation);
2246                 if (markerGroup) {
2247                     markerGroup.animate(attribs, animation);
2248                 }
2249                 
2250                 // Delete this function to allow it only once
2251                 this.animate = null;
2252             }
2253         }
2254     
2255     // For non-polar charts, revert to the basic animation
2256     } else {
2257         proceed.call(this, init);
2258     } 
2259 }
2260
2261 // Define the animate method for both regular series and column series and their derivatives
2262 wrap(seriesProto, 'animate', polarAnimate);
2263 wrap(colProto, 'animate', polarAnimate);
2264
2265
2266 /**
2267  * Throw in a couple of properties to let setTooltipPoints know we're indexing the points
2268  * in degrees (0-360), not plot pixel width.
2269  */
2270 wrap(seriesProto, 'setTooltipPoints', function (proceed, renew) {
2271         
2272     if (this.chart.polar) {
2273         extend(this.xAxis, {
2274             tooltipLen: 360 // degrees are the resolution unit of the tooltipPoints array
2275         });    
2276     }
2277     
2278     // Run uber method
2279     return proceed.call(this, renew);
2280 });
2281
2282
2283 /**
2284  * Extend the column prototype's translate method
2285  */
2286 wrap(colProto, 'translate', function (proceed) {
2287         
2288     var xAxis = this.xAxis,
2289         len = this.yAxis.len,
2290         center = xAxis.center,
2291         startAngleRad = xAxis.startAngleRad,
2292         renderer = this.chart.renderer,
2293         start,
2294         points,
2295         point,
2296         i;
2297     
2298     this.preventPostTranslate = true;
2299     
2300     // Run uber method
2301     proceed.call(this);
2302     
2303     // Postprocess plot coordinates
2304     if (xAxis.isRadial) {
2305         points = this.points;
2306         i = points.length;
2307         while (i--) {
2308             point = points[i];
2309             start = point.barX + startAngleRad;
2310             point.shapeType = 'path';
2311             point.shapeArgs = {
2312                 d: renderer.symbols.arc(
2313                     center[0],
2314                     center[1],
2315                     len - point.plotY,
2316                     null, 
2317                     {
2318                         start: start,
2319                         end: start + point.pointWidth,
2320                         innerR: len - pick(point.yBottom, len)
2321                     }
2322                 )
2323             };
2324             this.toXY(point); // provide correct plotX, plotY for tooltip
2325         }
2326     }
2327 });
2328
2329
2330 /**
2331  * Align column data labels outside the columns. #1199.
2332  */
2333 wrap(colProto, 'alignDataLabel', function (proceed, point, dataLabel, options, alignTo, isNew) {
2334     
2335     if (this.chart.polar) {
2336         var angle = point.rectPlotX / Math.PI * 180,
2337             align,
2338             verticalAlign;
2339         
2340         // Align nicely outside the perimeter of the columns
2341         if (options.align === null) {
2342             if (angle > 20 && angle < 160) {
2343                 align = 'left'; // right hemisphere
2344             } else if (angle > 200 && angle < 340) {
2345                 align = 'right'; // left hemisphere
2346             } else {
2347                 align = 'center'; // top or bottom
2348             }
2349             options.align = align;
2350         }
2351         if (options.verticalAlign === null) {
2352             if (angle < 45 || angle > 315) {
2353                 verticalAlign = 'bottom'; // top part
2354             } else if (angle > 135 && angle < 225) {
2355                 verticalAlign = 'top'; // bottom part
2356             } else {
2357                 verticalAlign = 'middle'; // left or right
2358             }
2359             options.verticalAlign = verticalAlign;
2360         }
2361         
2362         seriesProto.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);
2363     } else {
2364         proceed.call(this, point, dataLabel, options, alignTo, isNew);
2365     }
2366     
2367 });
2368
2369 /**
2370  * Extend the mouse tracker to return the tooltip position index in terms of
2371  * degrees rather than pixels
2372  */
2373 wrap(pointerProto, 'getIndex', function (proceed, e) {
2374     var ret,
2375         chart = this.chart,
2376         center,
2377         x,
2378         y;
2379     
2380     if (chart.polar) {
2381         center = chart.xAxis[0].center;
2382         x = e.chartX - center[0] - chart.plotLeft;
2383         y = e.chartY - center[1] - chart.plotTop;
2384         
2385         ret = 180 - Math.round(Math.atan2(x, y) / Math.PI * 180);
2386     
2387     } else {
2388     
2389         // Run uber method
2390         ret = proceed.call(this, e);
2391     }
2392     return ret;
2393 });
2394
2395 /**
2396  * Extend getCoordinates to prepare for polar axis values
2397  */
2398 wrap(pointerProto, 'getCoordinates', function (proceed, e) {
2399     var chart = this.chart,
2400         ret = {
2401             xAxis: [],
2402             yAxis: []
2403         };
2404     
2405     if (chart.polar) {    
2406
2407         each(chart.axes, function (axis) {
2408             var isXAxis = axis.isXAxis,
2409                 center = axis.center,
2410                 x = e.chartX - center[0] - chart.plotLeft,
2411                 y = e.chartY - center[1] - chart.plotTop;
2412             
2413             ret[isXAxis ? 'xAxis' : 'yAxis'].push({
2414                 axis: axis,
2415                 value: axis.translate(
2416                     isXAxis ?
2417                         Math.PI - Math.atan2(x, y) : // angle 
2418                         Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), // distance from center
2419                     true
2420                 )
2421             });
2422         });
2423         
2424     } else {
2425         ret = proceed.call(this, e);
2426     }
2427     
2428     return ret;
2429 });
2430 }(Highcharts));