Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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