Subversion-Projekte lars-tiefland.ci

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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