Passed
Push — master ( 1713a6...bcb549 )
by Peter
02:05
created

FuzzEd/static/lib/highcharts/highcharts-3.0.2.js   F

Complexity

Total Complexity 2610
Complexity/F 4.33

Size

Lines of Code 16306
Function Count 603

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 0
dl 0
loc 16306
rs 2.4
c 0
b 0
f 0
wmc 2610
nc 0
mnd 18
bc 1903
fnc 603
bpm 3.1558
cpm 4.3282
noi 126

How to fix   Complexity   

Complexity

Complex classes like FuzzEd/static/lib/highcharts/highcharts-3.0.2.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
// ==ClosureCompiler==
2
// @compilation_level SIMPLE_OPTIMIZATIONS
3
4
/**
5
 * @license Highcharts JS v3.0.2 (2013-06-05)
6
 *
7
 * (c) 2009-2013 Torstein Hønsi
8
 *
9
 * License: www.highcharts.com/license
10
 */
11
12
// JSLint options:
13
/*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console, each, grep */
14
15
(function () {
16
// encapsulated variables
17
var UNDEFINED,
18
	doc = document,
19
	win = window,
20
	math = Math,
21
	mathRound = math.round,
22
	mathFloor = math.floor,
23
	mathCeil = math.ceil,
24
	mathMax = math.max,
25
	mathMin = math.min,
26
	mathAbs = math.abs,
27
	mathCos = math.cos,
28
	mathSin = math.sin,
29
	mathPI = math.PI,
30
	deg2rad = mathPI * 2 / 360,
31
32
33
	// some variables
34
	userAgent = navigator.userAgent,
35
	isOpera = win.opera,
36
	isIE = /msie/i.test(userAgent) && !isOpera,
37
	docMode8 = doc.documentMode === 8,
38
	isWebKit = /AppleWebKit/.test(userAgent),
39
	isFirefox = /Firefox/.test(userAgent),
40
	isTouchDevice = /(Mobile|Android|Windows Phone)/.test(userAgent),
41
	SVG_NS = 'http://www.w3.org/2000/svg',
42
	hasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
43
	hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38
44
	useCanVG = !hasSVG && !isIE && !!doc.createElement('canvas').getContext,
45
	Renderer,
46
	hasTouch = doc.documentElement.ontouchstart !== UNDEFINED,
47
	symbolSizes = {},
48
	idCounter = 0,
49
	garbageBin,
50
	defaultOptions,
51
	dateFormat, // function
52
	globalAnimation,
53
	pathAnim,
54
	timeUnits,
55
	noop = function () {},
56
	charts = [],
57
	PRODUCT = 'Highcharts',
58
	VERSION = '3.0.2',
59
60
	// some constants for frequently used strings
61
	DIV = 'div',
62
	ABSOLUTE = 'absolute',
63
	RELATIVE = 'relative',
64
	HIDDEN = 'hidden',
65
	PREFIX = 'highcharts-',
66
	VISIBLE = 'visible',
67
	PX = 'px',
68
	NONE = 'none',
69
	M = 'M',
70
	L = 'L',
71
	/*
72
	 * Empirical lowest possible opacities for TRACKER_FILL
73
	 * IE6: 0.002
74
	 * IE7: 0.002
75
	 * IE8: 0.002
76
	 * IE9: 0.00000000001 (unlimited)
77
	 * IE10: 0.0001 (exporting only)
78
	 * FF: 0.00000000001 (unlimited)
79
	 * Chrome: 0.000001
80
	 * Safari: 0.000001
81
	 * Opera: 0.00000000001 (unlimited)
82
	 */
83
	TRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.0001 : 0.002) + ')', // invisible but clickable
84
	//TRACKER_FILL = 'rgba(192,192,192,0.5)',
85
	NORMAL_STATE = '',
86
	HOVER_STATE = 'hover',
87
	SELECT_STATE = 'select',
88
	MILLISECOND = 'millisecond',
89
	SECOND = 'second',
90
	MINUTE = 'minute',
91
	HOUR = 'hour',
92
	DAY = 'day',
93
	WEEK = 'week',
94
	MONTH = 'month',
95
	YEAR = 'year',
96
97
	// constants for attributes
98
	LINEAR_GRADIENT = 'linearGradient',
99
	STOPS = 'stops',
100
	STROKE_WIDTH = 'stroke-width',
101
102
	// time methods, changed based on whether or not UTC is used
103
	makeTime,
104
	getMinutes,
105
	getHours,
106
	getDay,
107
	getDate,
108
	getMonth,
109
	getFullYear,
110
	setMinutes,
111
	setHours,
112
	setDate,
113
	setMonth,
114
	setFullYear,
115
116
117
	// lookup over the types and the associated classes
118
	seriesTypes = {};
119
120
// The Highcharts namespace
121
win.Highcharts = win.Highcharts ? error(16, true) : {};
122
123
/**
124
 * Extend an object with the members of another
125
 * @param {Object} a The object to be extended
126
 * @param {Object} b The object to add to the first one
127
 */
128
function extend(a, b) {
129
	var n;
130
	if (!a) {
131
		a = {};
132
	}
133
	for (n in b) {
134
		a[n] = b[n];
135
	}
136
	return a;
137
}
138
	
139
/**
140
 * Deep merge two or more objects and return a third object.
141
 * Previously this function redirected to jQuery.extend(true), but this had two limitations.
142
 * First, it deep merged arrays, which lead to workarounds in Highcharts. Second,
143
 * it copied properties from extended prototypes. 
144
 */
145
function merge() {
146
	var i,
147
		len = arguments.length,
148
		ret = {},
149
		doCopy = function (copy, original) {
150
			var value, key;
151
152
			for (key in original) {
153
				if (original.hasOwnProperty(key)) {
154
					value = original[key];
155
156
					// An object is replacing a primitive
157
					if (typeof copy !== 'object') {
158
						copy = {};
159
					}
160
						
161
					// Copy the contents of objects, but not arrays or DOM nodes
162
					if (value && typeof value === 'object' && Object.prototype.toString.call(value) !== '[object Array]'
163
							&& typeof value.nodeType !== 'number') {
164
						copy[key] = doCopy(copy[key] || {}, value);
165
				
166
					// Primitives and arrays are copied over directly
167
					} else {
168
						copy[key] = original[key];
169
					}
170
				}
171
			}
172
			return copy;
173
		};
174
175
	// For each argument, extend the return
176
	for (i = 0; i < len; i++) {
177
		ret = doCopy(ret, arguments[i]);
178
	}
179
180
	return ret;
181
}
182
183
/**
184
 * Take an array and turn into a hash with even number arguments as keys and odd numbers as
185
 * values. Allows creating constants for commonly used style properties, attributes etc.
186
 * Avoid it in performance critical situations like looping
187
 */
188
function hash() {
189
	var i = 0,
190
		args = arguments,
191
		length = args.length,
192
		obj = {};
193
	for (; i < length; i++) {
194
		obj[args[i++]] = args[i];
195
	}
196
	return obj;
197
}
198
199
/**
200
 * Shortcut for parseInt
201
 * @param {Object} s
202
 * @param {Number} mag Magnitude
203
 */
204
function pInt(s, mag) {
205
	return parseInt(s, mag || 10);
206
}
207
208
/**
209
 * Check for string
210
 * @param {Object} s
211
 */
212
function isString(s) {
213
	return typeof s === 'string';
214
}
215
216
/**
217
 * Check for object
218
 * @param {Object} obj
219
 */
220
function isObject(obj) {
221
	return typeof obj === 'object';
222
}
223
224
/**
225
 * Check for array
226
 * @param {Object} obj
227
 */
228
function isArray(obj) {
229
	return Object.prototype.toString.call(obj) === '[object Array]';
230
}
231
232
/**
233
 * Check for number
234
 * @param {Object} n
235
 */
236
function isNumber(n) {
237
	return typeof n === 'number';
238
}
239
240
function log2lin(num) {
241
	return math.log(num) / math.LN10;
242
}
243
function lin2log(num) {
244
	return math.pow(10, num);
245
}
246
247
/**
248
 * Remove last occurence of an item from an array
249
 * @param {Array} arr
250
 * @param {Mixed} item
251
 */
252
function erase(arr, item) {
253
	var i = arr.length;
254
	while (i--) {
255
		if (arr[i] === item) {
256
			arr.splice(i, 1);
257
			break;
258
		}
259
	}
260
	//return arr;
261
}
262
263
/**
264
 * Returns true if the object is not null or undefined. Like MooTools' $.defined.
265
 * @param {Object} obj
266
 */
267
function defined(obj) {
268
	return obj !== UNDEFINED && obj !== null;
269
}
270
271
/**
272
 * Set or get an attribute or an object of attributes. Can't use jQuery attr because
273
 * it attempts to set expando properties on the SVG element, which is not allowed.
274
 *
275
 * @param {Object} elem The DOM element to receive the attribute(s)
276
 * @param {String|Object} prop The property or an abject of key-value pairs
277
 * @param {String} value The value if a single property is set
278
 */
279
function attr(elem, prop, value) {
280
	var key,
281
		setAttribute = 'setAttribute',
282
		ret;
283
284
	// if the prop is a string
285
	if (isString(prop)) {
286
		// set the value
287
		if (defined(value)) {
288
289
			elem[setAttribute](prop, value);
290
291
		// get the value
292
		} else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...
293
			ret = elem.getAttribute(prop);
294
		}
295
296
	// else if prop is defined, it is a hash of key/value pairs
297
	} else if (defined(prop) && isObject(prop)) {
298
		for (key in prop) {
299
			elem[setAttribute](key, prop[key]);
300
		}
301
	}
302
	return ret;
303
}
304
/**
305
 * Check if an element is an array, and if not, make it into an array. Like
306
 * MooTools' $.splat.
307
 */
308
function splat(obj) {
309
	return isArray(obj) ? obj : [obj];
310
}
311
312
313
/**
314
 * Return the first value that is defined. Like MooTools' $.pick.
315
 */
316
function pick() {
317
	var args = arguments,
318
		i,
319
		arg,
320
		length = args.length;
321
	for (i = 0; i < length; i++) {
322
		arg = args[i];
323
		if (typeof arg !== 'undefined' && arg !== null) {
324
			return arg;
325
		}
326
	}
327
}
328
329
/**
330
 * Set CSS on a given element
331
 * @param {Object} el
332
 * @param {Object} styles Style object with camel case property names
333
 */
334
function css(el, styles) {
335
	if (isIE) {
336
		if (styles && styles.opacity !== UNDEFINED) {
337
			styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';
338
		}
339
	}
340
	extend(el.style, styles);
341
}
342
343
/**
344
 * Utility function to create element with attributes and styles
345
 * @param {Object} tag
346
 * @param {Object} attribs
347
 * @param {Object} styles
348
 * @param {Object} parent
349
 * @param {Object} nopad
350
 */
351
function createElement(tag, attribs, styles, parent, nopad) {
352
	var el = doc.createElement(tag);
353
	if (attribs) {
354
		extend(el, attribs);
355
	}
356
	if (nopad) {
357
		css(el, {padding: 0, border: NONE, margin: 0});
358
	}
359
	if (styles) {
360
		css(el, styles);
361
	}
362
	if (parent) {
363
		parent.appendChild(el);
364
	}
365
	return el;
366
}
367
368
/**
369
 * Extend a prototyped class by new members
370
 * @param {Object} parent
371
 * @param {Object} members
372
 */
373
function extendClass(parent, members) {
374
	var object = function () {};
375
	object.prototype = new parent();
376
	extend(object.prototype, members);
377
	return object;
378
}
379
380
/**
381
 * Format a number and return a string based on input settings
382
 * @param {Number} number The input number to format
383
 * @param {Number} decimals The amount of decimals
384
 * @param {String} decPoint The decimal point, defaults to the one given in the lang options
385
 * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options
386
 */
387
function numberFormat(number, decimals, decPoint, thousandsSep) {
388
	var lang = defaultOptions.lang,
389
		// http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/
390
		n = number,
391
		c = decimals === -1 ?
392
			((n || 0).toString().split('.')[1] || '').length : // preserve decimals
393
			(isNaN(decimals = mathAbs(decimals)) ? 2 : decimals),
394
		d = decPoint === undefined ? lang.decimalPoint : decPoint,
395
		t = thousandsSep === undefined ? lang.thousandsSep : thousandsSep,
396
		s = n < 0 ? "-" : "",
397
		i = String(pInt(n = mathAbs(+n || 0).toFixed(c))),
398
		j = i.length > 3 ? i.length % 3 : 0;
399
400
	return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) +
401
		(c ? d + mathAbs(n - i).toFixed(c).slice(2) : "");
402
}
403
404
/**
405
 * Pad a string to a given length by adding 0 to the beginning
406
 * @param {Number} number
407
 * @param {Number} length
408
 */
409
function pad(number, length) {
410
	// Create an array of the remaining length +1 and join it with 0's
411
	return new Array((length || 2) + 1 - String(number).length).join(0) + number;
412
}
413
414
/**
415
 * Wrap a method with extended functionality, preserving the original function
416
 * @param {Object} obj The context object that the method belongs to 
417
 * @param {String} method The name of the method to extend
418
 * @param {Function} func A wrapper function callback. This function is called with the same arguments
419
 * as the original function, except that the original function is unshifted and passed as the first 
420
 * argument. 
421
 */
422
function wrap(obj, method, func) {
423
	var proceed = obj[method];
424
	obj[method] = function () {
425
		var args = Array.prototype.slice.call(arguments);
426
		args.unshift(proceed);
427
		return func.apply(this, args);
428
	};
429
}
430
431
/**
432
 * Based on http://www.php.net/manual/en/function.strftime.php
433
 * @param {String} format
434
 * @param {Number} timestamp
435
 * @param {Boolean} capitalize
436
 */
437
dateFormat = function (format, timestamp, capitalize) {
438
	if (!defined(timestamp) || isNaN(timestamp)) {
439
		return 'Invalid date';
440
	}
441
	format = pick(format, '%Y-%m-%d %H:%M:%S');
442
443
	var date = new Date(timestamp),
444
		key, // used in for constuct below
445
		// get the basic time values
446
		hours = date[getHours](),
447
		day = date[getDay](),
448
		dayOfMonth = date[getDate](),
449
		month = date[getMonth](),
450
		fullYear = date[getFullYear](),
451
		lang = defaultOptions.lang,
452
		langWeekdays = lang.weekdays,
453
454
		// List all format keys. Custom formats can be added from the outside. 
455
		replacements = extend({
456
457
			// Day
458
			'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'
459
			'A': langWeekdays[day], // Long weekday, like 'Monday'
460
			'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31
461
			'e': dayOfMonth, // Day of the month, 1 through 31
462
463
			// Week (none implemented)
464
			//'W': weekNumber(),
465
466
			// Month
467
			'b': lang.shortMonths[month], // Short month, like 'Jan'
468
			'B': lang.months[month], // Long month, like 'January'
469
			'm': pad(month + 1), // Two digit month number, 01 through 12
470
471
			// Year
472
			'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009
473
			'Y': fullYear, // Four digits year, like 2009
474
475
			// Time
476
			'H': pad(hours), // Two digits hours in 24h format, 00 through 23
477
			'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11
478
			'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12
479
			'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59
480
			'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM
481
			'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM
482
			'S': pad(date.getSeconds()), // Two digits seconds, 00 through  59
483
			'L': pad(mathRound(timestamp % 1000), 3) // Milliseconds (naming from Ruby)
484
		}, Highcharts.dateFormats);
485
486
487
	// do the replaces
488
	for (key in replacements) {
489
		while (format.indexOf('%' + key) !== -1) { // regex would do it in one line, but this is faster
490
			format = format.replace('%' + key, typeof replacements[key] === 'function' ? replacements[key](timestamp) : replacements[key]);
491
		}
492
	}
493
494
	// Optionally capitalize the string and return
495
	return capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;
496
};
497
498
/** 
499
 * Format a single variable. Similar to sprintf, without the % prefix.
500
 */
501
function formatSingle(format, val) {
502
	var floatRegex = /f$/,
503
		decRegex = /\.([0-9])/,
504
		lang = defaultOptions.lang,
505
		decimals;
506
507
	if (floatRegex.test(format)) { // float
508
		decimals = format.match(decRegex);
509
		decimals = decimals ? decimals[1] : -1;
510
		val = numberFormat(
511
			val,
512
			decimals,
513
			lang.decimalPoint,
514
			format.indexOf(',') > -1 ? lang.thousandsSep : ''
515
		);
516
	} else {
517
		val = dateFormat(format, val);
518
	}
519
	return val;
520
}
521
522
/**
523
 * Format a string according to a subset of the rules of Python's String.format method.
524
 */
525
function format(str, ctx) {
526
	var splitter = '{',
527
		isInside = false,
528
		segment,
529
		valueAndFormat,
530
		path,
531
		i,
532
		len,
533
		ret = [],
534
		val,
535
		index;
536
	
537
	while ((index = str.indexOf(splitter)) !== -1) {
538
		
539
		segment = str.slice(0, index);
540
		if (isInside) { // we're on the closing bracket looking back
541
			
542
			valueAndFormat = segment.split(':');
543
			path = valueAndFormat.shift().split('.'); // get first and leave format
544
			len = path.length;
545
			val = ctx;
546
547
			// Assign deeper paths
548
			for (i = 0; i < len; i++) {
549
				val = val[path[i]];
550
			}
551
552
			// Format the replacement
553
			if (valueAndFormat.length) {
554
				val = formatSingle(valueAndFormat.join(':'), val);
555
			}
556
557
			// Push the result and advance the cursor
558
			ret.push(val);
559
			
560
		} else {
561
			ret.push(segment);
562
			
563
		}
564
		str = str.slice(index + 1); // the rest
565
		isInside = !isInside; // toggle
566
		splitter = isInside ? '}' : '{'; // now look for next matching bracket
567
	}
568
	ret.push(str);
569
	return ret.join('');
570
}
571
572
/**
573
 * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5
574
 * @param {Number} interval
575
 * @param {Array} multiples
576
 * @param {Number} magnitude
577
 * @param {Object} options
578
 */
579
function normalizeTickInterval(interval, multiples, magnitude, options) {
580
	var normalized, i;
581
582
	// round to a tenfold of 1, 2, 2.5 or 5
583
	magnitude = pick(magnitude, 1);
584
	normalized = interval / magnitude;
585
586
	// multiples for a linear scale
587
	if (!multiples) {
588
		multiples = [1, 2, 2.5, 5, 10];
589
590
		// the allowDecimals option
591
		if (options && options.allowDecimals === false) {
592
			if (magnitude === 1) {
593
				multiples = [1, 2, 5, 10];
594
			} else if (magnitude <= 0.1) {
595
				multiples = [1 / magnitude];
596
			}
597
		}
598
	}
599
600
	// normalize the interval to the nearest multiple
601
	for (i = 0; i < multiples.length; i++) {
602
		interval = multiples[i];
603
		if (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) {
604
			break;
605
		}
606
	}
607
608
	// multiply back to the correct magnitude
609
	interval *= magnitude;
610
611
	return interval;
612
}
613
614
/**
615
 * Get a normalized tick interval for dates. Returns a configuration object with
616
 * unit range (interval), count and name. Used to prepare data for getTimeTicks. 
617
 * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs
618
 * of segments in stock charts, the normalizing logic was extracted in order to 
619
 * prevent it for running over again for each segment having the same interval. 
620
 * #662, #697.
621
 */
622
function normalizeTimeTickInterval(tickInterval, unitsOption) {
623
	var units = unitsOption || [[
624
				MILLISECOND, // unit name
625
				[1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
626
			], [
627
				SECOND,
628
				[1, 2, 5, 10, 15, 30]
629
			], [
630
				MINUTE,
631
				[1, 2, 5, 10, 15, 30]
632
			], [
633
				HOUR,
634
				[1, 2, 3, 4, 6, 8, 12]
635
			], [
636
				DAY,
637
				[1, 2]
638
			], [
639
				WEEK,
640
				[1, 2]
641
			], [
642
				MONTH,
643
				[1, 2, 3, 4, 6]
644
			], [
645
				YEAR,
646
				null
647
			]],
648
		unit = units[units.length - 1], // default unit is years
649
		interval = timeUnits[unit[0]],
650
		multiples = unit[1],
651
		count,
652
		i;
653
		
654
	// loop through the units to find the one that best fits the tickInterval
655
	for (i = 0; i < units.length; i++) {
656
		unit = units[i];
657
		interval = timeUnits[unit[0]];
658
		multiples = unit[1];
659
660
661
		if (units[i + 1]) {
662
			// lessThan is in the middle between the highest multiple and the next unit.
663
			var lessThan = (interval * multiples[multiples.length - 1] +
664
						timeUnits[units[i + 1][0]]) / 2;
665
666
			// break and keep the current unit
667
			if (tickInterval <= lessThan) {
668
				break;
669
			}
670
		}
671
	}
672
673
	// prevent 2.5 years intervals, though 25, 250 etc. are allowed
674
	if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {
675
		multiples = [1, 2, 5];
676
	}
677
	
678
	// prevent 2.5 years intervals, though 25, 250 etc. are allowed
679
	if (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {
680
		multiples = [1, 2, 5];
681
	}
682
683
	// get the count
684
	count = normalizeTickInterval(tickInterval / interval, multiples);
685
	
686
	return {
687
		unitRange: interval,
688
		count: count,
689
		unitName: unit[0]
690
	};
691
}
692
693
/**
694
 * Set the tick positions to a time unit that makes sense, for example
695
 * on the first of each month or on every Monday. Return an array
696
 * with the time positions. Used in datetime axes as well as for grouping
697
 * data on a datetime axis.
698
 *
699
 * @param {Object} normalizedInterval The interval in axis values (ms) and the count
700
 * @param {Number} min The minimum in axis values
701
 * @param {Number} max The maximum in axis values
702
 * @param {Number} startOfWeek
703
 */
704
function getTimeTicks(normalizedInterval, min, max, startOfWeek) {
705
	var tickPositions = [],
706
		i,
707
		higherRanks = {},
708
		useUTC = defaultOptions.global.useUTC,
709
		minYear, // used in months and years as a basis for Date.UTC()
710
		minDate = new Date(min),
711
		interval = normalizedInterval.unitRange,
712
		count = normalizedInterval.count;
713
714
	if (defined(min)) { // #1300
715
		if (interval >= timeUnits[SECOND]) { // second
716
			minDate.setMilliseconds(0);
717
			minDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 :
718
				count * mathFloor(minDate.getSeconds() / count));
719
		}
720
	
721
		if (interval >= timeUnits[MINUTE]) { // minute
722
			minDate[setMinutes](interval >= timeUnits[HOUR] ? 0 :
723
				count * mathFloor(minDate[getMinutes]() / count));
724
		}
725
	
726
		if (interval >= timeUnits[HOUR]) { // hour
727
			minDate[setHours](interval >= timeUnits[DAY] ? 0 :
728
				count * mathFloor(minDate[getHours]() / count));
729
		}
730
	
731
		if (interval >= timeUnits[DAY]) { // day
732
			minDate[setDate](interval >= timeUnits[MONTH] ? 1 :
733
				count * mathFloor(minDate[getDate]() / count));
734
		}
735
	
736
		if (interval >= timeUnits[MONTH]) { // month
737
			minDate[setMonth](interval >= timeUnits[YEAR] ? 0 :
738
				count * mathFloor(minDate[getMonth]() / count));
739
			minYear = minDate[getFullYear]();
740
		}
741
	
742
		if (interval >= timeUnits[YEAR]) { // year
743
			minYear -= minYear % count;
744
			minDate[setFullYear](minYear);
745
		}
746
	
747
		// week is a special case that runs outside the hierarchy
748
		if (interval === timeUnits[WEEK]) {
749
			// get start of current week, independent of count
750
			minDate[setDate](minDate[getDate]() - minDate[getDay]() +
751
				pick(startOfWeek, 1));
752
		}
753
	
754
	
755
		// get tick positions
756
		i = 1;
757
		minYear = minDate[getFullYear]();
758
		var time = minDate.getTime(),
759
			minMonth = minDate[getMonth](),
760
			minDateDate = minDate[getDate](),
761
			timezoneOffset = useUTC ? 
762
				0 : 
763
				(24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950
764
	
765
		// iterate and add tick positions at appropriate values
766
		while (time < max) {
767
			tickPositions.push(time);
768
	
769
			// if the interval is years, use Date.UTC to increase years
770
			if (interval === timeUnits[YEAR]) {
771
				time = makeTime(minYear + i * count, 0);
772
	
773
			// if the interval is months, use Date.UTC to increase months
774
			} else if (interval === timeUnits[MONTH]) {
775
				time = makeTime(minYear, minMonth + i * count);
776
	
777
			// if we're using global time, the interval is not fixed as it jumps
778
			// one hour at the DST crossover
779
			} else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) {
780
				time = makeTime(minYear, minMonth, minDateDate +
781
					i * count * (interval === timeUnits[DAY] ? 1 : 7));
782
	
783
			// else, the interval is fixed and we use simple addition
784
			} else {
785
				time += interval * count;
786
			}
787
	
788
			i++;
789
		}
790
	
791
		// push the last time
792
		tickPositions.push(time);
793
794
795
		// mark new days if the time is dividible by day (#1649, #1760)
796
		each(grep(tickPositions, function (time) {
797
			return interval <= timeUnits[HOUR] && time % timeUnits[DAY] === timezoneOffset;
798
		}), function (time) {
799
			higherRanks[time] = DAY;
800
		});
801
	}
802
803
804
	// record information on the chosen unit - for dynamic label formatter
805
	tickPositions.info = extend(normalizedInterval, {
806
		higherRanks: higherRanks,
807
		totalRange: interval * count
808
	});
809
810
	return tickPositions;
811
}
812
813
/**
814
 * Helper class that contains variuos counters that are local to the chart.
815
 */
816
function ChartCounters() {
817
	this.color = 0;
818
	this.symbol = 0;
819
}
820
821
ChartCounters.prototype =  {
822
	/**
823
	 * Wraps the color counter if it reaches the specified length.
824
	 */
825
	wrapColor: function (length) {
826
		if (this.color >= length) {
827
			this.color = 0;
828
		}
829
	},
830
831
	/**
832
	 * Wraps the symbol counter if it reaches the specified length.
833
	 */
834
	wrapSymbol: function (length) {
835
		if (this.symbol >= length) {
836
			this.symbol = 0;
837
		}
838
	}
839
};
840
841
842
/**
843
 * Utility method that sorts an object array and keeping the order of equal items.
844
 * ECMA script standard does not specify the behaviour when items are equal.
845
 */
846
function stableSort(arr, sortFunction) {
847
	var length = arr.length,
848
		sortValue,
849
		i;
850
851
	// Add index to each item
852
	for (i = 0; i < length; i++) {
853
		arr[i].ss_i = i; // stable sort index
854
	}
855
856
	arr.sort(function (a, b) {
857
		sortValue = sortFunction(a, b);
858
		return sortValue === 0 ? a.ss_i - b.ss_i : sortValue;
859
	});
860
861
	// Remove index from items
862
	for (i = 0; i < length; i++) {
863
		delete arr[i].ss_i; // stable sort index
864
	}
865
}
866
867
/**
868
 * Non-recursive method to find the lowest member of an array. Math.min raises a maximum
869
 * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
870
 * method is slightly slower, but safe.
871
 */
872
function arrayMin(data) {
873
	var i = data.length,
874
		min = data[0];
875
876
	while (i--) {
877
		if (data[i] < min) {
878
			min = data[i];
879
		}
880
	}
881
	return min;
882
}
883
884
/**
885
 * Non-recursive method to find the lowest member of an array. Math.min raises a maximum
886
 * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This
887
 * method is slightly slower, but safe.
888
 */
889
function arrayMax(data) {
890
	var i = data.length,
891
		max = data[0];
892
893
	while (i--) {
894
		if (data[i] > max) {
895
			max = data[i];
896
		}
897
	}
898
	return max;
899
}
900
901
/**
902
 * Utility method that destroys any SVGElement or VMLElement that are properties on the given object.
903
 * It loops all properties and invokes destroy if there is a destroy method. The property is
904
 * then delete'ed.
905
 * @param {Object} The object to destroy properties on
906
 * @param {Object} Exception, do not destroy this property, only delete it.
907
 */
908
function destroyObjectProperties(obj, except) {
909
	var n;
910
	for (n in obj) {
911
		// If the object is non-null and destroy is defined
912
		if (obj[n] && obj[n] !== except && obj[n].destroy) {
913
			// Invoke the destroy
914
			obj[n].destroy();
915
		}
916
917
		// Delete the property from the object.
918
		delete obj[n];
919
	}
920
}
921
922
923
/**
924
 * Discard an element by moving it to the bin and delete
925
 * @param {Object} The HTML node to discard
926
 */
927
function discardElement(element) {
928
	// create a garbage bin element, not part of the DOM
929
	if (!garbageBin) {
930
		garbageBin = createElement(DIV);
931
	}
932
933
	// move the node and empty bin
934
	if (element) {
935
		garbageBin.appendChild(element);
936
	}
937
	garbageBin.innerHTML = '';
938
}
939
940
/**
941
 * Provide error messages for debugging, with links to online explanation 
942
 */
943
function error(code, stop) {
944
	var msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code;
945
	if (stop) {
946
		throw msg;
947
	} else if (win.console) {
948
		console.log(msg);
949
	}
950
}
951
952
/**
953
 * Fix JS round off float errors
954
 * @param {Number} num
955
 */
956
function correctFloat(num) {
957
	return parseFloat(
958
		num.toPrecision(14)
959
	);
960
}
961
962
/**
963
 * Set the global animation to either a given value, or fall back to the
964
 * given chart's animation option
965
 * @param {Object} animation
966
 * @param {Object} chart
967
 */
968
function setAnimation(animation, chart) {
969
	globalAnimation = pick(animation, chart.animation);
970
}
971
972
/**
973
 * The time unit lookup
974
 */
975
/*jslint white: true*/
976
timeUnits = hash(
977
	MILLISECOND, 1,
978
	SECOND, 1000,
979
	MINUTE, 60000,
980
	HOUR, 3600000,
981
	DAY, 24 * 3600000,
982
	WEEK, 7 * 24 * 3600000,
983
	MONTH, 31 * 24 * 3600000,
984
	YEAR, 31556952000
985
);
986
/*jslint white: false*/
987
/**
988
 * Path interpolation algorithm used across adapters
989
 */
990
pathAnim = {
991
	/**
992
	 * Prepare start and end values so that the path can be animated one to one
993
	 */
994
	init: function (elem, fromD, toD) {
995
		fromD = fromD || '';
996
		var shift = elem.shift,
997
			bezier = fromD.indexOf('C') > -1,
998
			numParams = bezier ? 7 : 3,
999
			endLength,
1000
			slice,
1001
			i,
1002
			start = fromD.split(' '),
1003
			end = [].concat(toD), // copy
1004
			startBaseLine,
1005
			endBaseLine,
1006
			sixify = function (arr) { // in splines make move points have six parameters like bezier curves
1007
				i = arr.length;
1008
				while (i--) {
1009
					if (arr[i] === M) {
1010
						arr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]);
1011
					}
1012
				}
1013
			};
1014
1015
		if (bezier) {
1016
			sixify(start);
1017
			sixify(end);
1018
		}
1019
1020
		// pull out the base lines before padding
1021
		if (elem.isArea) {
1022
			startBaseLine = start.splice(start.length - 6, 6);
1023
			endBaseLine = end.splice(end.length - 6, 6);
1024
		}
1025
1026
		// if shifting points, prepend a dummy point to the end path
1027
		if (shift <= end.length / numParams) {
1028
			while (shift--) {
1029
				end = [].concat(end).splice(0, numParams).concat(end);
1030
			}
1031
		}
1032
		elem.shift = 0; // reset for following animations
1033
1034
		// copy and append last point until the length matches the end length
1035
		if (start.length) {
1036
			endLength = end.length;
1037
			while (start.length < endLength) {
1038
1039
				//bezier && sixify(start);
1040
				slice = [].concat(start).splice(start.length - numParams, numParams);
1041
				if (bezier) { // disable first control point
1042
					slice[numParams - 6] = slice[numParams - 2];
1043
					slice[numParams - 5] = slice[numParams - 1];
1044
				}
1045
				start = start.concat(slice);
1046
			}
1047
		}
1048
1049
		if (startBaseLine) { // append the base lines for areas
1050
			start = start.concat(startBaseLine);
1051
			end = end.concat(endBaseLine);
1052
		}
1053
		return [start, end];
1054
	},
1055
1056
	/**
1057
	 * Interpolate each value of the path and return the array
1058
	 */
1059
	step: function (start, end, pos, complete) {
1060
		var ret = [],
1061
			i = start.length,
1062
			startVal;
1063
1064
		if (pos === 1) { // land on the final path without adjustment points appended in the ends
1065
			ret = complete;
1066
1067
		} else if (i === end.length && pos < 1) {
1068
			while (i--) {
1069
				startVal = parseFloat(start[i]);
1070
				ret[i] =
1071
					isNaN(startVal) ? // a letter instruction like M or L
1072
						start[i] :
1073
						pos * (parseFloat(end[i] - startVal)) + startVal;
1074
1075
			}
1076
		} else { // if animation is finished or length not matching, land on right value
1077
			ret = end;
1078
		}
1079
		return ret;
1080
	}
1081
};
1082
1083
(function ($) {
1084
	/**
1085
	 * The default HighchartsAdapter for jQuery
1086
	 */
1087
	win.HighchartsAdapter = win.HighchartsAdapter || ($ && {
1088
		
1089
		/**
1090
		 * Initialize the adapter by applying some extensions to jQuery
1091
		 */
1092
		init: function (pathAnim) {
1093
			
1094
			// extend the animate function to allow SVG animations
1095
			var Fx = $.fx,
1096
				Step = Fx.step,
1097
				dSetter,
1098
				Tween = $.Tween,
1099
				propHooks = Tween && Tween.propHooks,
1100
				opacityHook = $.cssHooks.opacity;
1101
			
1102
			/*jslint unparam: true*//* allow unused param x in this function */
1103
			$.extend($.easing, {
1104
				easeOutQuad: function (x, t, b, c, d) {
1105
					return -c * (t /= d) * (t - 2) + b;
1106
				}
1107
			});
1108
			/*jslint unparam: false*/
1109
		
1110
			// extend some methods to check for elem.attr, which means it is a Highcharts SVG object
1111
			$.each(['cur', '_default', 'width', 'height', 'opacity'], function (i, fn) {
1112
				var obj = Step,
1113
					base,
1114
					elem;
1115
					
1116
				// Handle different parent objects
1117
				if (fn === 'cur') {
1118
					obj = Fx.prototype; // 'cur', the getter, relates to Fx.prototype
1119
				
1120
				} else if (fn === '_default' && Tween) { // jQuery 1.8 model
1121
					obj = propHooks[fn];
1122
					fn = 'set';
1123
				}
1124
		
1125
				// Overwrite the method
1126
				base = obj[fn];
1127
				if (base) { // step.width and step.height don't exist in jQuery < 1.7
1128
		
1129
					// create the extended function replacement
1130
					obj[fn] = function (fx) {
1131
		
1132
						// Fx.prototype.cur does not use fx argument
1133
						fx = i ? fx : this;
1134
		
1135
						// shortcut
1136
						elem = fx.elem;
1137
		
1138
						// Fx.prototype.cur returns the current value. The other ones are setters
1139
						// and returning a value has no effect.
1140
						return elem.attr ? // is SVG element wrapper
1141
							elem.attr(fx.prop, fn === 'cur' ? UNDEFINED : fx.now) : // apply the SVG wrapper's method
1142
							base.apply(this, arguments); // use jQuery's built-in method
1143
					};
1144
				}
1145
			});
1146
1147
			// Extend the opacity getter, needed for fading opacity with IE9 and jQuery 1.10+
1148
			wrap(opacityHook, 'get', function (proceed, elem, computed) {
1149
				return elem.attr ? (elem.opacity || 0) : proceed.call(this, elem, computed);
1150
			});
1151
			
1152
			
1153
			// Define the setter function for d (path definitions)
1154
			dSetter = function (fx) {
1155
				var elem = fx.elem,
1156
					ends;
1157
		
1158
				// Normally start and end should be set in state == 0, but sometimes,
1159
				// for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped
1160
				// in these cases
1161
				if (!fx.started) {
1162
					ends = pathAnim.init(elem, elem.d, elem.toD);
1163
					fx.start = ends[0];
1164
					fx.end = ends[1];
1165
					fx.started = true;
1166
				}
1167
		
1168
		
1169
				// interpolate each value of the path
1170
				elem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));
1171
			};
1172
			
1173
			// jQuery 1.8 style
1174
			if (Tween) {
1175
				propHooks.d = {
1176
					set: dSetter
1177
				};
1178
			// pre 1.8
1179
			} else {
1180
				// animate paths
1181
				Step.d = dSetter;
1182
			}
1183
			
1184
			/**
1185
			 * Utility for iterating over an array. Parameters are reversed compared to jQuery.
1186
			 * @param {Array} arr
1187
			 * @param {Function} fn
1188
			 */
1189
			this.each = Array.prototype.forEach ?
1190
				function (arr, fn) { // modern browsers
1191
					return Array.prototype.forEach.call(arr, fn);
1192
					
1193
				} : 
1194
				function (arr, fn) { // legacy
1195
					var i = 0, 
1196
						len = arr.length;
1197
					for (; i < len; i++) {
1198
						if (fn.call(arr[i], arr[i], i, arr) === false) {
1199
							return i;
1200
						}
1201
					}
1202
				};
1203
			
1204
			/**
1205
			 * Register Highcharts as a plugin in the respective framework
1206
			 */
1207
			$.fn.highcharts = function () {
1208
				var constr = 'Chart', // default constructor
1209
					args = arguments,
1210
					options,
1211
					ret,
1212
					chart;
1213
1214
				if (isString(args[0])) {
1215
					constr = args[0];
1216
					args = Array.prototype.slice.call(args, 1); 
1217
				}
1218
				options = args[0];
1219
1220
				// Create the chart
1221
				if (options !== UNDEFINED) {
1222
					/*jslint unused:false*/
1223
					options.chart = options.chart || {};
1224
					options.chart.renderTo = this[0];
1225
					chart = new Highcharts[constr](options, args[1]);
1226
					ret = this;
1227
					/*jslint unused:true*/
1228
				}
1229
1230
				// When called without parameters or with the return argument, get a predefined chart
1231
				if (options === UNDEFINED) {
1232
					ret = charts[attr(this[0], 'data-highcharts-chart')];
1233
				}	
1234
1235
				return ret;
1236
			};
1237
1238
		},
1239
1240
		
1241
		/**
1242
		 * Downloads a script and executes a callback when done.
1243
		 * @param {String} scriptLocation
1244
		 * @param {Function} callback
1245
		 */
1246
		getScript: $.getScript,
1247
		
1248
		/**
1249
		 * Return the index of an item in an array, or -1 if not found
1250
		 */
1251
		inArray: $.inArray,
1252
		
1253
		/**
1254
		 * A direct link to jQuery methods. MooTools and Prototype adapters must be implemented for each case of method.
1255
		 * @param {Object} elem The HTML element
1256
		 * @param {String} method Which method to run on the wrapped element
1257
		 */
1258
		adapterRun: function (elem, method) {
1259
			return $(elem)[method]();
1260
		},
1261
	
1262
		/**
1263
		 * Filter an array
1264
		 */
1265
		grep: $.grep,
1266
	
1267
		/**
1268
		 * Map an array
1269
		 * @param {Array} arr
1270
		 * @param {Function} fn
1271
		 */
1272
		map: function (arr, fn) {
1273
			//return jQuery.map(arr, fn);
1274
			var results = [],
1275
				i = 0,
1276
				len = arr.length;
1277
			for (; i < len; i++) {
1278
				results[i] = fn.call(arr[i], arr[i], i, arr);
1279
			}
1280
			return results;
1281
	
1282
		},
1283
	
1284
		/**
1285
		 * Get the position of an element relative to the top left of the page
1286
		 */
1287
		offset: function (el) {
1288
			return $(el).offset();
1289
		},
1290
	
1291
		/**
1292
		 * Add an event listener
1293
		 * @param {Object} el A HTML element or custom object
1294
		 * @param {String} event The event type
1295
		 * @param {Function} fn The event handler
1296
		 */
1297
		addEvent: function (el, event, fn) {
1298
			$(el).bind(event, fn);
1299
		},
1300
	
1301
		/**
1302
		 * Remove event added with addEvent
1303
		 * @param {Object} el The object
1304
		 * @param {String} eventType The event type. Leave blank to remove all events.
1305
		 * @param {Function} handler The function to remove
1306
		 */
1307
		removeEvent: function (el, eventType, handler) {
1308
			// workaround for jQuery issue with unbinding custom events:
1309
			// http://forum.jQuery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jQuery-1-4-2
1310
			var func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';
1311
			if (doc[func] && el && !el[func]) {
1312
				el[func] = function () {};
1313
			}
1314
	
1315
			$(el).unbind(eventType, handler);
1316
		},
1317
	
1318
		/**
1319
		 * Fire an event on a custom object
1320
		 * @param {Object} el
1321
		 * @param {String} type
1322
		 * @param {Object} eventArguments
1323
		 * @param {Function} defaultFunction
1324
		 */
1325
		fireEvent: function (el, type, eventArguments, defaultFunction) {
1326
			var event = $.Event(type),
1327
				detachedType = 'detached' + type,
1328
				defaultPrevented;
1329
	
1330
			// Remove warnings in Chrome when accessing layerX and layerY. Although Highcharts
1331
			// never uses these properties, Chrome includes them in the default click event and
1332
			// raises the warning when they are copied over in the extend statement below.
1333
			//
1334
			// To avoid problems in IE (see #1010) where we cannot delete the properties and avoid
1335
			// testing if they are there (warning in chrome) the only option is to test if running IE.
1336
			if (!isIE && eventArguments) {
1337
				delete eventArguments.layerX;
1338
				delete eventArguments.layerY;
1339
			}
1340
	
1341
			extend(event, eventArguments);
1342
	
1343
			// Prevent jQuery from triggering the object method that is named the
1344
			// same as the event. For example, if the event is 'select', jQuery
1345
			// attempts calling el.select and it goes into a loop.
1346
			if (el[type]) {
1347
				el[detachedType] = el[type];
1348
				el[type] = null;
1349
			}
1350
	
1351
			// Wrap preventDefault and stopPropagation in try/catch blocks in
1352
			// order to prevent JS errors when cancelling events on non-DOM
1353
			// objects. #615.
1354
			/*jslint unparam: true*/
1355
			$.each(['preventDefault', 'stopPropagation'], function (i, fn) {
1356
				var base = event[fn];
1357
				event[fn] = function () {
1358
					try {
1359
						base.call(event);
1360
					} catch (e) {
1361
						if (fn === 'preventDefault') {
1362
							defaultPrevented = true;
1363
						}
1364
					}
1365
				};
1366
			});
1367
			/*jslint unparam: false*/
1368
	
1369
			// trigger it
1370
			$(el).trigger(event);
1371
	
1372
			// attach the method
1373
			if (el[detachedType]) {
1374
				el[type] = el[detachedType];
1375
				el[detachedType] = null;
1376
			}
1377
	
1378
			if (defaultFunction && !event.isDefaultPrevented() && !defaultPrevented) {
1379
				defaultFunction(event);
1380
			}
1381
		},
1382
		
1383
		/**
1384
		 * Extension method needed for MooTools
1385
		 */
1386
		washMouseEvent: function (e) {
1387
			var ret = e.originalEvent || e;
1388
			
1389
			// computed by jQuery, needed by IE8
1390
			if (ret.pageX === UNDEFINED) { // #1236
1391
				ret.pageX = e.pageX;
1392
				ret.pageY = e.pageY;
1393
			}
1394
			
1395
			return ret;
1396
		},
1397
	
1398
		/**
1399
		 * Animate a HTML element or SVG element wrapper
1400
		 * @param {Object} el
1401
		 * @param {Object} params
1402
		 * @param {Object} options jQuery-like animation options: duration, easing, callback
1403
		 */
1404
		animate: function (el, params, options) {
1405
			var $el = $(el);
1406
			if (!el.style) {
1407
				el.style = {}; // #1881
1408
			}
1409
			if (params.d) {
1410
				el.toD = params.d; // keep the array form for paths, used in $.fx.step.d
1411
				params.d = 1; // because in jQuery, animating to an array has a different meaning
1412
			}
1413
	
1414
			$el.stop();
1415
			$el.animate(params, options);
1416
	
1417
		},
1418
		/**
1419
		 * Stop running animation
1420
		 */
1421
		stop: function (el) {
1422
			$(el).stop();
1423
		}
1424
	});
1425
}(win.jQuery));
1426
1427
1428
// check for a custom HighchartsAdapter defined prior to this file
1429
var globalAdapter = win.HighchartsAdapter,
1430
	adapter = globalAdapter || {};
1431
	
1432
// Initialize the adapter
1433
if (globalAdapter) {
1434
	globalAdapter.init.call(globalAdapter, pathAnim);
1435
}
1436
1437
1438
// Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object
1439
// and all the utility functions will be null. In that case they are populated by the
1440
// default adapters below.
1441
var adapterRun = adapter.adapterRun,
1442
	getScript = adapter.getScript,
1443
	inArray = adapter.inArray,
1444
	each = adapter.each,
1445
	grep = adapter.grep,
1446
	offset = adapter.offset,
1447
	map = adapter.map,
1448
	addEvent = adapter.addEvent,
1449
	removeEvent = adapter.removeEvent,
1450
	fireEvent = adapter.fireEvent,
1451
	washMouseEvent = adapter.washMouseEvent,
1452
	animate = adapter.animate,
1453
	stop = adapter.stop;
1454
1455
1456
1457
/* ****************************************************************************
1458
 * Handle the options                                                         *
1459
 *****************************************************************************/
1460
var
1461
1462
defaultLabelOptions = {
1463
	enabled: true,
1464
	// rotation: 0,
1465
	align: 'center',
1466
	x: 0,
1467
	y: 15,
1468
	/*formatter: function () {
1469
		return this.value;
1470
	},*/
1471
	style: {
1472
		color: '#666',
1473
		cursor: 'default',
1474
		fontSize: '11px',
1475
		lineHeight: '14px'
1476
	}
1477
};
1478
1479
defaultOptions = {
1480
	colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970',
1481
		'#f28f43', '#77a1e5', '#c42525', '#a6c96a'],
1482
	symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
1483
	lang: {
1484
		loading: 'Loading...',
1485
		months: ['January', 'February', 'March', 'April', 'May', 'June', 'July',
1486
				'August', 'September', 'October', 'November', 'December'],
1487
		shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
1488
		weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
1489
		decimalPoint: '.',
1490
		numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], // SI prefixes used in axis labels
1491
		resetZoom: 'Reset zoom',
1492
		resetZoomTitle: 'Reset zoom level 1:1',
1493
		thousandsSep: ','
1494
	},
1495
	global: {
1496
		useUTC: true,
1497
		canvasToolsURL: 'http://code.highcharts.com/3.0.2/modules/canvas-tools.js',
1498
		VMLRadialGradientURL: 'http://code.highcharts.com/3.0.2/gfx/vml-radial-gradient.png'
1499
	},
1500
	chart: {
1501
		//animation: true,
1502
		//alignTicks: false,
1503
		//reflow: true,
1504
		//className: null,
1505
		//events: { load, selection },
1506
		//margin: [null],
1507
		//marginTop: null,
1508
		//marginRight: null,
1509
		//marginBottom: null,
1510
		//marginLeft: null,
1511
		borderColor: '#4572A7',
1512
		//borderWidth: 0,
1513
		borderRadius: 5,
1514
		defaultSeriesType: 'line',
1515
		ignoreHiddenSeries: true,
1516
		//inverted: false,
1517
		//shadow: false,
1518
		spacingTop: 10,
1519
		spacingRight: 10,
1520
		spacingBottom: 15,
1521
		spacingLeft: 10,
1522
		style: {
1523
			fontFamily: '"Lucida Grande", "Lucida Sans Unicode", Verdana, Arial, Helvetica, sans-serif', // default font
1524
			fontSize: '12px'
1525
		},
1526
		backgroundColor: '#FFFFFF',
1527
		//plotBackgroundColor: null,
1528
		plotBorderColor: '#C0C0C0',
1529
		//plotBorderWidth: 0,
1530
		//plotShadow: false,
1531
		//zoomType: ''
1532
		resetZoomButton: {
1533
			theme: {
1534
				zIndex: 20
1535
			},
1536
			position: {
1537
				align: 'right',
1538
				x: -10,
1539
				//verticalAlign: 'top',
1540
				y: 10
1541
			}
1542
			// relativeTo: 'plot'
1543
		}
1544
	},
1545
	title: {
1546
		text: 'Chart title',
1547
		align: 'center',
1548
		// floating: false,
1549
		// margin: 15,
1550
		// x: 0,
1551
		// verticalAlign: 'top',
1552
		y: 15,
1553
		style: {
1554
			color: '#274b6d',//#3E576F',
1555
			fontSize: '16px'
1556
		}
1557
1558
	},
1559
	subtitle: {
1560
		text: '',
1561
		align: 'center',
1562
		// floating: false
1563
		// x: 0,
1564
		// verticalAlign: 'top',
1565
		y: 30,
1566
		style: {
1567
			color: '#4d759e'
1568
		}
1569
	},
1570
1571
	plotOptions: {
1572
		line: { // base series options
1573
			allowPointSelect: false,
1574
			showCheckbox: false,
1575
			animation: {
1576
				duration: 1000
1577
			},
1578
			//connectNulls: false,
1579
			//cursor: 'default',
1580
			//clip: true,
1581
			//dashStyle: null,
1582
			//enableMouseTracking: true,
1583
			events: {},
1584
			//legendIndex: 0,
1585
			lineWidth: 2,
1586
			//shadow: false,
1587
			// stacking: null,
1588
			marker: {
1589
				enabled: true,
1590
				//symbol: null,
1591
				lineWidth: 0,
1592
				radius: 4,
1593
				lineColor: '#FFFFFF',
1594
				//fillColor: null,
1595
				states: { // states for a single point
1596
					hover: {
1597
						enabled: true
1598
						//radius: base + 2
1599
					},
1600
					select: {
1601
						fillColor: '#FFFFFF',
1602
						lineColor: '#000000',
1603
						lineWidth: 2
1604
					}
1605
				}
1606
			},
1607
			point: {
1608
				events: {}
1609
			},
1610
			dataLabels: merge(defaultLabelOptions, {
1611
				enabled: false,
1612
				formatter: function () {
1613
					return numberFormat(this.y, -1);
1614
				},
1615
				verticalAlign: 'bottom', // above singular point
1616
				y: 0
1617
				// backgroundColor: undefined,
1618
				// borderColor: undefined,
1619
				// borderRadius: undefined,
1620
				// borderWidth: undefined,
1621
				// padding: 3,
1622
				// shadow: false
1623
			}),
1624
			cropThreshold: 300, // draw points outside the plot area when the number of points is less than this
1625
			pointRange: 0,
1626
			//pointStart: 0,
1627
			//pointInterval: 1,
1628
			showInLegend: true,
1629
			states: { // states for the entire series
1630
				hover: {
1631
					//enabled: false,
1632
					//lineWidth: base + 1,
1633
					marker: {
1634
						// lineWidth: base + 1,
1635
						// radius: base + 1
1636
					}
1637
				},
1638
				select: {
1639
					marker: {}
1640
				}
1641
			},
1642
			stickyTracking: true
1643
			//tooltip: {
1644
				//pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b>'
1645
				//valueDecimals: null,
1646
				//xDateFormat: '%A, %b %e, %Y',
1647
				//valuePrefix: '',
1648
				//ySuffix: ''				
1649
			//}
1650
			// turboThreshold: 1000
1651
			// zIndex: null
1652
		}
1653
	},
1654
	labels: {
1655
		//items: [],
1656
		style: {
1657
			//font: defaultFont,
1658
			position: ABSOLUTE,
1659
			color: '#3E576F'
1660
		}
1661
	},
1662
	legend: {
1663
		enabled: true,
1664
		align: 'center',
1665
		//floating: false,
1666
		layout: 'horizontal',
1667
		labelFormatter: function () {
1668
			return this.name;
1669
		},
1670
		borderWidth: 1,
1671
		borderColor: '#909090',
1672
		borderRadius: 5,
1673
		navigation: {
1674
			// animation: true,
1675
			activeColor: '#274b6d',
1676
			// arrowSize: 12
1677
			inactiveColor: '#CCC'
1678
			// style: {} // text styles
1679
		},
1680
		// margin: 10,
1681
		// reversed: false,
1682
		shadow: false,
1683
		// backgroundColor: null,
1684
		/*style: {
1685
			padding: '5px'
1686
		},*/
1687
		itemStyle: {
1688
			cursor: 'pointer',
1689
			color: '#274b6d',
1690
			fontSize: '12px'
1691
		},
1692
		itemHoverStyle: {
1693
			//cursor: 'pointer', removed as of #601
1694
			color: '#000'
1695
		},
1696
		itemHiddenStyle: {
1697
			color: '#CCC'
1698
		},
1699
		itemCheckboxStyle: {
1700
			position: ABSOLUTE,
1701
			width: '13px', // for IE precision
1702
			height: '13px'
1703
		},
1704
		// itemWidth: undefined,
1705
		symbolWidth: 16,
1706
		symbolPadding: 5,
1707
		verticalAlign: 'bottom',
1708
		// width: undefined,
1709
		x: 0,
1710
		y: 0,
1711
		title: {
1712
			//text: null,
1713
			style: {
1714
				fontWeight: 'bold'
1715
			}
1716
		}			
1717
	},
1718
1719
	loading: {
1720
		// hideDuration: 100,
1721
		labelStyle: {
1722
			fontWeight: 'bold',
1723
			position: RELATIVE,
1724
			top: '1em'
1725
		},
1726
		// showDuration: 0,
1727
		style: {
1728
			position: ABSOLUTE,
1729
			backgroundColor: 'white',
1730
			opacity: 0.5,
1731
			textAlign: 'center'
1732
		}
1733
	},
1734
1735
	tooltip: {
1736
		enabled: true,
1737
		animation: hasSVG,
1738
		//crosshairs: null,
1739
		backgroundColor: 'rgba(255, 255, 255, .85)',
1740
		borderWidth: 1,
1741
		borderRadius: 3,
1742
		dateTimeLabelFormats: { 
1743
			millisecond: '%A, %b %e, %H:%M:%S.%L',
1744
			second: '%A, %b %e, %H:%M:%S',
1745
			minute: '%A, %b %e, %H:%M',
1746
			hour: '%A, %b %e, %H:%M',
1747
			day: '%A, %b %e, %Y',
1748
			week: 'Week from %A, %b %e, %Y',
1749
			month: '%B %Y',
1750
			year: '%Y'
1751
		},
1752
		//formatter: defaultFormatter,
1753
		headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>',
1754
		pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b><br/>',
1755
		shadow: true,
1756
		//shared: false,
1757
		snap: isTouchDevice ? 25 : 10,
1758
		style: {
1759
			color: '#333333',
1760
			cursor: 'default',
1761
			fontSize: '12px',
1762
			padding: '8px',
1763
			whiteSpace: 'nowrap'
1764
		}
1765
		//xDateFormat: '%A, %b %e, %Y',
1766
		//valueDecimals: null,
1767
		//valuePrefix: '',
1768
		//valueSuffix: ''
1769
	},
1770
1771
	credits: {
1772
		enabled: true,
1773
		text: 'Highcharts.com',
1774
		href: 'http://www.highcharts.com',
1775
		position: {
1776
			align: 'right',
1777
			x: -10,
1778
			verticalAlign: 'bottom',
1779
			y: -5
1780
		},
1781
		style: {
1782
			cursor: 'pointer',
1783
			color: '#909090',
1784
			fontSize: '9px'
1785
		}
1786
	}
1787
};
1788
1789
1790
1791
1792
// Series defaults
1793
var defaultPlotOptions = defaultOptions.plotOptions,
1794
	defaultSeriesOptions = defaultPlotOptions.line;
1795
1796
// set the default time methods
1797
setTimeMethods();
1798
1799
1800
1801
/**
1802
 * Set the time methods globally based on the useUTC option. Time method can be either
1803
 * local time or UTC (default).
1804
 */
1805
function setTimeMethods() {
1806
	var useUTC = defaultOptions.global.useUTC,
1807
		GET = useUTC ? 'getUTC' : 'get',
1808
		SET = useUTC ? 'setUTC' : 'set';
1809
1810
	makeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) {
1811
		return new Date(
1812
			year,
1813
			month,
1814
			pick(date, 1),
1815
			pick(hours, 0),
1816
			pick(minutes, 0),
1817
			pick(seconds, 0)
1818
		).getTime();
1819
	};
1820
	getMinutes =  GET + 'Minutes';
1821
	getHours =    GET + 'Hours';
1822
	getDay =      GET + 'Day';
1823
	getDate =     GET + 'Date';
1824
	getMonth =    GET + 'Month';
1825
	getFullYear = GET + 'FullYear';
1826
	setMinutes =  SET + 'Minutes';
1827
	setHours =    SET + 'Hours';
1828
	setDate =     SET + 'Date';
1829
	setMonth =    SET + 'Month';
1830
	setFullYear = SET + 'FullYear';
1831
1832
}
1833
1834
/**
1835
 * Merge the default options with custom options and return the new options structure
1836
 * @param {Object} options The new custom options
1837
 */
1838
function setOptions(options) {
1839
	
1840
	// Pull out axis options and apply them to the respective default axis options 
1841
	/*defaultXAxisOptions = merge(defaultXAxisOptions, options.xAxis);
1842
	defaultYAxisOptions = merge(defaultYAxisOptions, options.yAxis);
1843
	options.xAxis = options.yAxis = UNDEFINED;*/
1844
	
1845
	// Merge in the default options
1846
	defaultOptions = merge(defaultOptions, options);
1847
	
1848
	// Apply UTC
1849
	setTimeMethods();
1850
1851
	return defaultOptions;
1852
}
1853
1854
/**
1855
 * Get the updated default options. Merely exposing defaultOptions for outside modules
1856
 * isn't enough because the setOptions method creates a new object.
1857
 */
1858
function getOptions() {
1859
	return defaultOptions;
1860
}
1861
1862
1863
/**
1864
 * Handle color operations. The object methods are chainable.
1865
 * @param {String} input The input color in either rbga or hex format
1866
 */
1867
var Color = function (input) {
1868
	// declare variables
1869
	var rgba = [], result, stops;
1870
1871
	/**
1872
	 * Parse the input color to rgba array
1873
	 * @param {String} input
1874
	 */
1875
	function init(input) {
1876
1877
		// Gradients
1878
		if (input && input.stops) {
1879
			stops = map(input.stops, function (stop) {
1880
				return Color(stop[1]);
1881
			});
1882
1883
		// Solid colors
1884
		} else {
1885
			// rgba
1886
			result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/.exec(input);
1887
			if (result) {
1888
				rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
1889
			} else { 
1890
				// hex
1891
				result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input);
1892
				if (result) {
1893
					rgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1];
1894
				} else {
1895
					// rgb
1896
					result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(input);
1897
					if (result) {
1898
						rgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1];
1899
					}
1900
				}
1901
			}
1902
		}		
1903
1904
	}
1905
	/**
1906
	 * Return the color a specified format
1907
	 * @param {String} format
1908
	 */
1909
	function get(format) {
1910
		var ret;
1911
1912
		if (stops) {
1913
			ret = merge(input);
1914
			ret.stops = [].concat(ret.stops);
1915
			each(stops, function (stop, i) {
1916
				ret.stops[i] = [ret.stops[i][0], stop.get(format)];
1917
			});
1918
1919
		// it's NaN if gradient colors on a column chart
1920
		} else if (rgba && !isNaN(rgba[0])) {
1921
			if (format === 'rgb') {
1922
				ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
1923
			} else if (format === 'a') {
1924
				ret = rgba[3];
1925
			} else {
1926
				ret = 'rgba(' + rgba.join(',') + ')';
1927
			}
1928
		} else {
1929
			ret = input;
1930
		}
1931
		return ret;
1932
	}
1933
1934
	/**
1935
	 * Brighten the color
1936
	 * @param {Number} alpha
1937
	 */
1938
	function brighten(alpha) {
1939
		if (stops) {
1940
			each(stops, function (stop) {
1941
				stop.brighten(alpha);
1942
			});
1943
		
1944
		} else if (isNumber(alpha) && alpha !== 0) {
1945
			var i;
1946
			for (i = 0; i < 3; i++) {
1947
				rgba[i] += pInt(alpha * 255);
1948
1949
				if (rgba[i] < 0) {
1950
					rgba[i] = 0;
1951
				}
1952
				if (rgba[i] > 255) {
1953
					rgba[i] = 255;
1954
				}
1955
			}
1956
		}
1957
		return this;
1958
	}
1959
	/**
1960
	 * Set the color's opacity to a given alpha value
1961
	 * @param {Number} alpha
1962
	 */
1963
	function setOpacity(alpha) {
1964
		rgba[3] = alpha;
1965
		return this;
1966
	}
1967
1968
	// initialize: parse the input
1969
	init(input);
1970
1971
	// public methods
1972
	return {
1973
		get: get,
1974
		brighten: brighten,
1975
		rgba: rgba,
1976
		setOpacity: setOpacity
1977
	};
1978
};
1979
1980
1981
/**
1982
 * A wrapper object for SVG elements
1983
 */
1984
function SVGElement() {}
1985
1986
SVGElement.prototype = {
1987
	/**
1988
	 * Initialize the SVG renderer
1989
	 * @param {Object} renderer
1990
	 * @param {String} nodeName
1991
	 */
1992
	init: function (renderer, nodeName) {
1993
		var wrapper = this;
1994
		wrapper.element = nodeName === 'span' ?
1995
			createElement(nodeName) :
1996
			doc.createElementNS(SVG_NS, nodeName);
1997
		wrapper.renderer = renderer;
1998
		/**
1999
		 * A collection of attribute setters. These methods, if defined, are called right before a certain
2000
		 * attribute is set on an element wrapper. Returning false prevents the default attribute
2001
		 * setter to run. Returning a value causes the default setter to set that value. Used in
2002
		 * Renderer.label.
2003
		 */
2004
		wrapper.attrSetters = {};
2005
	},
2006
	/**
2007
	 * Default base for animation
2008
	 */
2009
	opacity: 1,
2010
	/**
2011
	 * Animate a given attribute
2012
	 * @param {Object} params
2013
	 * @param {Number} options The same options as in jQuery animation
2014
	 * @param {Function} complete Function to perform at the end of animation
2015
	 */
2016
	animate: function (params, options, complete) {
2017
		var animOptions = pick(options, globalAnimation, true);
2018
		stop(this); // stop regardless of animation actually running, or reverting to .attr (#607)
2019
		if (animOptions) {
2020
			animOptions = merge(animOptions);
2021
			if (complete) { // allows using a callback with the global animation without overwriting it
2022
				animOptions.complete = complete;
2023
			}
2024
			animate(this, params, animOptions);
2025
		} else {
2026
			this.attr(params);
2027
			if (complete) {
2028
				complete();
2029
			}
2030
		}
2031
	},
2032
	/**
2033
	 * Set or get a given attribute
2034
	 * @param {Object|String} hash
2035
	 * @param {Mixed|Undefined} val
2036
	 */
2037
	attr: function (hash, val) {
2038
		var wrapper = this,
2039
			key,
2040
			value,
2041
			result,
2042
			i,
2043
			child,
2044
			element = wrapper.element,
2045
			nodeName = element.nodeName.toLowerCase(), // Android2 requires lower for "text"
2046
			renderer = wrapper.renderer,
2047
			skipAttr,
2048
			titleNode,
2049
			attrSetters = wrapper.attrSetters,
2050
			shadows = wrapper.shadows,
2051
			hasSetSymbolSize,
2052
			doTransform,
2053
			ret = wrapper;
2054
2055
		// single key-value pair
2056
		if (isString(hash) && defined(val)) {
2057
			key = hash;
2058
			hash = {};
2059
			hash[key] = val;
2060
		}
2061
2062
		// used as a getter: first argument is a string, second is undefined
2063
		if (isString(hash)) {
2064
			key = hash;
2065
			if (nodeName === 'circle') {
2066
				key = { x: 'cx', y: 'cy' }[key] || key;
2067
			} else if (key === 'strokeWidth') {
2068
				key = 'stroke-width';
2069
			}
2070
			ret = attr(element, key) || wrapper[key] || 0;
2071
			if (key !== 'd' && key !== 'visibility') { // 'd' is string in animation step
2072
				ret = parseFloat(ret);
2073
			}
2074
2075
		// setter
2076
		} else {
2077
2078
			for (key in hash) {
2079
				skipAttr = false; // reset
2080
				value = hash[key];
2081
2082
				// check for a specific attribute setter
2083
				result = attrSetters[key] && attrSetters[key].call(wrapper, value, key);
2084
2085
				if (result !== false) {
2086
					if (result !== UNDEFINED) {
2087
						value = result; // the attribute setter has returned a new value to set
2088
					}
2089
2090
				
2091
					// paths
2092
					if (key === 'd') {
2093
						if (value && value.join) { // join path
2094
							value = value.join(' ');
2095
						}
2096
						if (/(NaN| {2}|^$)/.test(value)) {
2097
							value = 'M 0 0';
2098
						}
2099
						//wrapper.d = value; // shortcut for animations
2100
2101
					// update child tspans x values
2102
					} else if (key === 'x' && nodeName === 'text') {
2103
						for (i = 0; i < element.childNodes.length; i++) {
2104
							child = element.childNodes[i];
2105
							// if the x values are equal, the tspan represents a linebreak
2106
							if (attr(child, 'x') === attr(element, 'x')) {
2107
								//child.setAttribute('x', value);
2108
								attr(child, 'x', value);
2109
							}
2110
						}
2111
2112
					} else if (wrapper.rotation && (key === 'x' || key === 'y')) {
2113
						doTransform = true;
2114
2115
					// apply gradients
2116
					} else if (key === 'fill') {
2117
						value = renderer.color(value, element, key);
2118
2119
					// circle x and y
2120
					} else if (nodeName === 'circle' && (key === 'x' || key === 'y')) {
2121
						key = { x: 'cx', y: 'cy' }[key] || key;
2122
2123
					// rectangle border radius
2124
					} else if (nodeName === 'rect' && key === 'r') {
2125
						attr(element, {
2126
							rx: value,
2127
							ry: value
2128
						});
2129
						skipAttr = true;
2130
2131
					// translation and text rotation
2132
					} else if (key === 'translateX' || key === 'translateY' || key === 'rotation' || 
2133
							key === 'verticalAlign' || key === 'scaleX' || key === 'scaleY') {
2134
						doTransform = true;
2135
						skipAttr = true;
2136
2137
					// apply opacity as subnode (required by legacy WebKit and Batik)
2138
					} else if (key === 'stroke') {
2139
						value = renderer.color(value, element, key);
2140
2141
					// emulate VML's dashstyle implementation
2142
					} else if (key === 'dashstyle') {
2143
						key = 'stroke-dasharray';
2144
						value = value && value.toLowerCase();
2145
						if (value === 'solid') {
2146
							value = NONE;
2147
						} else if (value) {
2148
							value = value
2149
								.replace('shortdashdotdot', '3,1,1,1,1,1,')
2150
								.replace('shortdashdot', '3,1,1,1')
2151
								.replace('shortdot', '1,1,')
2152
								.replace('shortdash', '3,1,')
2153
								.replace('longdash', '8,3,')
2154
								.replace(/dot/g, '1,3,')
2155
								.replace('dash', '4,3,')
2156
								.replace(/,$/, '')
2157
								.split(','); // ending comma
2158
2159
							i = value.length;
2160
							while (i--) {
2161
								value[i] = pInt(value[i]) * hash['stroke-width'];
2162
							}
2163
							value = value.join(',');
2164
						}
2165
2166
					// IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2
2167
					// is unable to cast them. Test again with final IE9.
2168
					} else if (key === 'width') {
2169
						value = pInt(value);
2170
2171
					// Text alignment
2172
					} else if (key === 'align') {
2173
						key = 'text-anchor';
2174
						value = { left: 'start', center: 'middle', right: 'end' }[value];
2175
2176
					// Title requires a subnode, #431
2177
					} else if (key === 'title') {
2178
						titleNode = element.getElementsByTagName('title')[0];
2179
						if (!titleNode) {
2180
							titleNode = doc.createElementNS(SVG_NS, 'title');
2181
							element.appendChild(titleNode);
2182
						}
2183
						titleNode.textContent = value;
2184
					}
2185
2186
					// jQuery animate changes case
2187
					if (key === 'strokeWidth') {
2188
						key = 'stroke-width';
2189
					}
2190
2191
					// In Chrome/Win < 6 as well as Batik, the stroke attribute can't be set when the stroke-
2192
					// width is 0. #1369
2193
					if (key === 'stroke-width' || key === 'stroke') {
2194
						wrapper[key] = value;
2195
						// Only apply the stroke attribute if the stroke width is defined and larger than 0
2196
						if (wrapper.stroke && wrapper['stroke-width']) {
2197
							attr(element, 'stroke', wrapper.stroke);
2198
							attr(element, 'stroke-width', wrapper['stroke-width']);
2199
							wrapper.hasStroke = true;
2200
						} else if (key === 'stroke-width' && value === 0 && wrapper.hasStroke) {
2201
							element.removeAttribute('stroke');
2202
							wrapper.hasStroke = false;
2203
						}
2204
						skipAttr = true;
2205
					}
2206
2207
					// symbols
2208
					if (wrapper.symbolName && /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(key)) {
2209
2210
2211
						if (!hasSetSymbolSize) {
2212
							wrapper.symbolAttr(hash);
2213
							hasSetSymbolSize = true;
2214
						}
2215
						skipAttr = true;
2216
					}
2217
2218
					// let the shadow follow the main element
2219
					if (shadows && /^(width|height|visibility|x|y|d|transform)$/.test(key)) {
2220
						i = shadows.length;
2221
						while (i--) {
2222
							attr(
2223
								shadows[i], 
2224
								key, 
2225
								key === 'height' ? 
2226
									mathMax(value - (shadows[i].cutHeight || 0), 0) :
2227
									value
2228
							);
2229
						}
2230
					}
2231
2232
					// validate heights
2233
					if ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) {
2234
						value = 0;
2235
					}
2236
2237
					// Record for animation and quick access without polling the DOM
2238
					wrapper[key] = value;
2239
					
2240
					
2241
					if (key === 'text') {
2242
						// Delete bBox memo when the text changes
2243
						if (value !== wrapper.textStr) {
2244
							delete wrapper.bBox;
2245
						}
2246
						wrapper.textStr = value;
2247
						if (wrapper.added) {
2248
							renderer.buildText(wrapper);
2249
						}
2250
					} else if (!skipAttr) {
2251
						attr(element, key, value);
2252
					}
2253
2254
				}
2255
2256
			}
2257
2258
			// Update transform. Do this outside the loop to prevent redundant updating for batch setting
2259
			// of attributes.
2260
			if (doTransform) {
2261
				wrapper.updateTransform();
2262
			}
2263
2264
		}
2265
		
2266
		return ret;
2267
	},
2268
2269
	 
2270
	/**
2271
	 * Add a class name to an element
2272
	 */
2273
	addClass: function (className) {
2274
		attr(this.element, 'class', attr(this.element, 'class') + ' ' + className);
2275
		return this;
2276
	},
2277
	/* hasClass and removeClass are not (yet) needed
2278
	hasClass: function (className) {
2279
		return attr(this.element, 'class').indexOf(className) !== -1;
2280
	},
2281
	removeClass: function (className) {
2282
		attr(this.element, 'class', attr(this.element, 'class').replace(className, ''));
2283
		return this;
2284
	},
2285
	*/
2286
2287
	/**
2288
	 * If one of the symbol size affecting parameters are changed,
2289
	 * check all the others only once for each call to an element's
2290
	 * .attr() method
2291
	 * @param {Object} hash
2292
	 */
2293
	symbolAttr: function (hash) {
2294
		var wrapper = this;
2295
2296
		each(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) {
2297
			wrapper[key] = pick(hash[key], wrapper[key]);
2298
		});
2299
2300
		wrapper.attr({
2301
			d: wrapper.renderer.symbols[wrapper.symbolName](
2302
				wrapper.x, 
2303
				wrapper.y, 
2304
				wrapper.width, 
2305
				wrapper.height, 
2306
				wrapper
2307
			)
2308
		});
2309
	},
2310
2311
	/**
2312
	 * Apply a clipping path to this object
2313
	 * @param {String} id
2314
	 */
2315
	clip: function (clipRect) {
2316
		return this.attr('clip-path', clipRect ? 'url(' + this.renderer.url + '#' + clipRect.id + ')' : NONE);
2317
	},
2318
2319
	/**
2320
	 * Calculate the coordinates needed for drawing a rectangle crisply and return the
2321
	 * calculated attributes
2322
	 * @param {Number} strokeWidth
2323
	 * @param {Number} x
2324
	 * @param {Number} y
2325
	 * @param {Number} width
2326
	 * @param {Number} height
2327
	 */
2328
	crisp: function (strokeWidth, x, y, width, height) {
2329
2330
		var wrapper = this,
2331
			key,
2332
			attribs = {},
2333
			values = {},
2334
			normalizer;
2335
2336
		strokeWidth = strokeWidth || wrapper.strokeWidth || (wrapper.attr && wrapper.attr('stroke-width')) || 0;
2337
		normalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors
2338
2339
		// normalize for crisp edges
2340
		values.x = mathFloor(x || wrapper.x || 0) + normalizer;
2341
		values.y = mathFloor(y || wrapper.y || 0) + normalizer;
2342
		values.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer);
2343
		values.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer);
2344
		values.strokeWidth = strokeWidth;
2345
2346
		for (key in values) {
2347
			if (wrapper[key] !== values[key]) { // only set attribute if changed
2348
				wrapper[key] = attribs[key] = values[key];
2349
			}
2350
		}
2351
2352
		return attribs;
2353
	},
2354
2355
	/**
2356
	 * Set styles for the element
2357
	 * @param {Object} styles
2358
	 */
2359
	css: function (styles) {
2360
		/*jslint unparam: true*//* allow unused param a in the regexp function below */
2361
		var elemWrapper = this,
2362
			elem = elemWrapper.element,
2363
			textWidth = styles && styles.width && elem.nodeName.toLowerCase() === 'text',
2364
			n,
2365
			serializedCss = '',
2366
			hyphenate = function (a, b) { return '-' + b.toLowerCase(); };
2367
		/*jslint unparam: false*/
2368
2369
		// convert legacy
2370
		if (styles && styles.color) {
2371
			styles.fill = styles.color;
2372
		}
2373
2374
		// Merge the new styles with the old ones
2375
		styles = extend(
2376
			elemWrapper.styles,
2377
			styles
2378
		);
2379
2380
		// store object
2381
		elemWrapper.styles = styles;
2382
		
2383
		
2384
		// Don't handle line wrap on canvas
2385
		if (useCanVG && textWidth) {
2386
			delete styles.width;
2387
		}
2388
			
2389
		// serialize and set style attribute
2390
		if (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute
2391
			if (textWidth) {
2392
				delete styles.width;
2393
			}
2394
			css(elemWrapper.element, styles);
2395
		} else {
2396
			for (n in styles) {
2397
				serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';';
2398
			}
2399
			attr(elem, 'style', serializedCss); // #1881
2400
		}
2401
2402
2403
		// re-build text
2404
		if (textWidth && elemWrapper.added) {
2405
			elemWrapper.renderer.buildText(elemWrapper);
2406
		}
2407
2408
		return elemWrapper;
2409
	},
2410
2411
	/**
2412
	 * Add an event listener
2413
	 * @param {String} eventType
2414
	 * @param {Function} handler
2415
	 */
2416
	on: function (eventType, handler) {
2417
		// touch
2418
		if (hasTouch && eventType === 'click') {
2419
			this.element.ontouchstart = function (e) {
2420
				e.preventDefault();
2421
				handler();
2422
			};
2423
		}
2424
		// simplest possible event model for internal use
2425
		this.element['on' + eventType] = handler;
2426
		return this;
2427
	},
2428
	
2429
	/**
2430
	 * Set the coordinates needed to draw a consistent radial gradient across
2431
	 * pie slices regardless of positioning inside the chart. The format is
2432
	 * [centerX, centerY, diameter] in pixels.
2433
	 */
2434
	setRadialReference: function (coordinates) {
2435
		this.element.radialReference = coordinates;
2436
		return this;
2437
	},
2438
2439
	/**
2440
	 * Move an object and its children by x and y values
2441
	 * @param {Number} x
2442
	 * @param {Number} y
2443
	 */
2444
	translate: function (x, y) {
2445
		return this.attr({
2446
			translateX: x,
2447
			translateY: y
2448
		});
2449
	},
2450
2451
	/**
2452
	 * Invert a group, rotate and flip
2453
	 */
2454
	invert: function () {
2455
		var wrapper = this;
2456
		wrapper.inverted = true;
2457
		wrapper.updateTransform();
2458
		return wrapper;
2459
	},
2460
2461
	/**
2462
	 * Apply CSS to HTML elements. This is used in text within SVG rendering and
2463
	 * by the VML renderer
2464
	 */
2465
	htmlCss: function (styles) {
2466
		var wrapper = this,
2467
			element = wrapper.element,
2468
			textWidth = styles && element.tagName === 'SPAN' && styles.width;
2469
2470
		if (textWidth) {
2471
			delete styles.width;
2472
			wrapper.textWidth = textWidth;
2473
			wrapper.updateTransform();
2474
		}
2475
2476
		wrapper.styles = extend(wrapper.styles, styles);
2477
		css(wrapper.element, styles);
2478
2479
		return wrapper;
2480
	},
2481
2482
2483
2484
	/**
2485
	 * VML and useHTML method for calculating the bounding box based on offsets
2486
	 * @param {Boolean} refresh Whether to force a fresh value from the DOM or to
2487
	 * use the cached value
2488
	 *
2489
	 * @return {Object} A hash containing values for x, y, width and height
2490
	 */
2491
2492
	htmlGetBBox: function () {
2493
		var wrapper = this,
2494
			element = wrapper.element,
2495
			bBox = wrapper.bBox;
2496
2497
		// faking getBBox in exported SVG in legacy IE
2498
		if (!bBox) {
2499
			// faking getBBox in exported SVG in legacy IE (is this a duplicate of the fix for #1079?)
2500
			if (element.nodeName === 'text') {
2501
				element.style.position = ABSOLUTE;
2502
			}
2503
2504
			bBox = wrapper.bBox = {
2505
				x: element.offsetLeft,
2506
				y: element.offsetTop,
2507
				width: element.offsetWidth,
2508
				height: element.offsetHeight
2509
			};
2510
		}
2511
2512
		return bBox;
2513
	},
2514
2515
	/**
2516
	 * VML override private method to update elements based on internal
2517
	 * properties based on SVG transform
2518
	 */
2519
	htmlUpdateTransform: function () {
2520
		// aligning non added elements is expensive
2521
		if (!this.added) {
2522
			this.alignOnAdd = true;
2523
			return;
2524
		}
2525
2526
		var wrapper = this,
2527
			renderer = wrapper.renderer,
2528
			elem = wrapper.element,
2529
			translateX = wrapper.translateX || 0,
2530
			translateY = wrapper.translateY || 0,
2531
			x = wrapper.x || 0,
2532
			y = wrapper.y || 0,
2533
			align = wrapper.textAlign || 'left',
2534
			alignCorrection = { left: 0, center: 0.5, right: 1 }[align],
2535
			nonLeft = align && align !== 'left',
2536
			shadows = wrapper.shadows;
2537
2538
		// apply translate
2539
		css(elem, {
2540
			marginLeft: translateX,
2541
			marginTop: translateY
2542
		});
2543
		if (shadows) { // used in labels/tooltip
2544
			each(shadows, function (shadow) {
2545
				css(shadow, {
2546
					marginLeft: translateX + 1,
2547
					marginTop: translateY + 1
2548
				});
2549
			});
2550
		}
2551
2552
		// apply inversion
2553
		if (wrapper.inverted) { // wrapper is a group
2554
			each(elem.childNodes, function (child) {
2555
				renderer.invertChild(child, elem);
2556
			});
2557
		}
2558
2559
		if (elem.tagName === 'SPAN') {
2560
2561
			var width, height,
2562
				rotation = wrapper.rotation,
2563
				baseline,
2564
				radians = 0,
2565
				costheta = 1,
2566
				sintheta = 0,
2567
				quad,
2568
				textWidth = pInt(wrapper.textWidth),
2569
				xCorr = wrapper.xCorr || 0,
2570
				yCorr = wrapper.yCorr || 0,
2571
				currentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(','),
2572
				rotationStyle = {},
2573
				cssTransformKey;
2574
2575
			if (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed
2576
2577
				if (defined(rotation)) {
2578
					
2579
					if (renderer.isSVG) { // #916
2580
						cssTransformKey = isIE ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : isOpera ? '-o-transform' : '';
2581
						rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)';
2582
						
2583
					} else {
2584
						radians = rotation * deg2rad; // deg to rad
2585
						costheta = mathCos(radians);
2586
						sintheta = mathSin(radians);
2587
	
2588
						// Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented
2589
						// but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+
2590
						// has support for CSS3 transform. The getBBox method also needs to be updated
2591
						// to compensate for the rotation, like it currently does for SVG.
2592
						// Test case: http://highcharts.com/tests/?file=text-rotation
2593
						rotationStyle.filter = rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,
2594
								', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,
2595
								', sizingMethod=\'auto expand\')'].join('') : NONE;
2596
					}
2597
					css(elem, rotationStyle);
2598
				}
2599
2600
				width = pick(wrapper.elemWidth, elem.offsetWidth);
2601
				height = pick(wrapper.elemHeight, elem.offsetHeight);
2602
2603
				// update textWidth
2604
				if (width > textWidth && /[ \-]/.test(elem.textContent || elem.innerText)) { // #983, #1254
2605
					css(elem, {
2606
						width: textWidth + PX,
2607
						display: 'block',
2608
						whiteSpace: 'normal'
2609
					});
2610
					width = textWidth;
2611
				}
2612
2613
				// correct x and y
2614
				baseline = renderer.fontMetrics(elem.style.fontSize).b;
2615
				xCorr = costheta < 0 && -width;
2616
				yCorr = sintheta < 0 && -height;
2617
2618
				// correct for baseline and corners spilling out after rotation
2619
				quad = costheta * sintheta < 0;
2620
				xCorr += sintheta * baseline * (quad ? 1 - alignCorrection : alignCorrection);
2621
				yCorr -= costheta * baseline * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1);
2622
2623
				// correct for the length/height of the text
2624
				if (nonLeft) {
2625
					xCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);
2626
					if (rotation) {
2627
						yCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1);
2628
					}
2629
					css(elem, {
2630
						textAlign: align
2631
					});
2632
				}
2633
2634
				// record correction
2635
				wrapper.xCorr = xCorr;
2636
				wrapper.yCorr = yCorr;
2637
			}
2638
2639
			// apply position with correction
2640
			css(elem, {
2641
				left: (x + xCorr) + PX,
2642
				top: (y + yCorr) + PX
2643
			});
2644
			
2645
			// force reflow in webkit to apply the left and top on useHTML element (#1249)
2646
			if (isWebKit) {
2647
				height = elem.offsetHeight; // assigned to height for JSLint purpose
2648
			}
2649
2650
			// record current text transform
2651
			wrapper.cTT = currentTextTransform;
2652
		}
2653
	},
2654
2655
	/**
2656
	 * Private method to update the transform attribute based on internal
2657
	 * properties
2658
	 */
2659
	updateTransform: function () {
2660
		var wrapper = this,
2661
			translateX = wrapper.translateX || 0,
2662
			translateY = wrapper.translateY || 0,
2663
			scaleX = wrapper.scaleX,
2664
			scaleY = wrapper.scaleY,
2665
			inverted = wrapper.inverted,
2666
			rotation = wrapper.rotation,
2667
			transform;
2668
2669
		// flipping affects translate as adjustment for flipping around the group's axis
2670
		if (inverted) {
2671
			translateX += wrapper.attr('width');
2672
			translateY += wrapper.attr('height');
2673
		}
2674
2675
		// Apply translate. Nearly all transformed elements have translation, so instead
2676
		// of checking for translate = 0, do it always (#1767, #1846).
2677
		transform = ['translate(' + translateX + ',' + translateY + ')'];
2678
2679
		// apply rotation
2680
		if (inverted) {
2681
			transform.push('rotate(90) scale(-1,1)');
2682
		} else if (rotation) { // text rotation
2683
			transform.push('rotate(' + rotation + ' ' + (wrapper.x || 0) + ' ' + (wrapper.y || 0) + ')');
2684
		}
2685
2686
		// apply scale
2687
		if (defined(scaleX) || defined(scaleY)) {
2688
			transform.push('scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')');
2689
		}
2690
2691
		if (transform.length) {
2692
			attr(wrapper.element, 'transform', transform.join(' '));
2693
		}
2694
	},
2695
	/**
2696
	 * Bring the element to the front
2697
	 */
2698
	toFront: function () {
2699
		var element = this.element;
2700
		element.parentNode.appendChild(element);
2701
		return this;
2702
	},
2703
2704
2705
	/**
2706
	 * Break down alignment options like align, verticalAlign, x and y
2707
	 * to x and y relative to the chart.
2708
	 *
2709
	 * @param {Object} alignOptions
2710
	 * @param {Boolean} alignByTranslate
2711
	 * @param {String[Object} box The box to align to, needs a width and height. When the
2712
	 *        box is a string, it refers to an object in the Renderer. For example, when 
2713
	 *        box is 'spacingBox', it refers to Renderer.spacingBox which holds width, height
2714
	 *        x and y properties.
2715
	 *
2716
	 */
2717
	align: function (alignOptions, alignByTranslate, box) {
2718
		var align,
2719
			vAlign,
2720
			x,
2721
			y,
2722
			attribs = {},
2723
			alignTo,
2724
			renderer = this.renderer,
2725
			alignedObjects = renderer.alignedObjects;
2726
2727
		// First call on instanciate
2728
		if (alignOptions) {
2729
			this.alignOptions = alignOptions;
2730
			this.alignByTranslate = alignByTranslate;
2731
			if (!box || isString(box)) { // boxes other than renderer handle this internally
2732
				this.alignTo = alignTo = box || 'renderer';
2733
				erase(alignedObjects, this); // prevent duplicates, like legendGroup after resize
2734
				alignedObjects.push(this);
2735
				box = null; // reassign it below
2736
			}
2737
		
2738
		// When called on resize, no arguments are supplied
2739
		} else {
2740
			alignOptions = this.alignOptions;
2741
			alignByTranslate = this.alignByTranslate;
2742
			alignTo = this.alignTo;
2743
		}
2744
2745
		box = pick(box, renderer[alignTo], renderer);
2746
2747
		// Assign variables
2748
		align = alignOptions.align;
2749
		vAlign = alignOptions.verticalAlign;
2750
		x = (box.x || 0) + (alignOptions.x || 0); // default: left align
2751
		y = (box.y || 0) + (alignOptions.y || 0); // default: top align
2752
2753
		// Align
2754
		if (align === 'right' || align === 'center') {
2755
			x += (box.width - (alignOptions.width || 0)) /
2756
					{ right: 1, center: 2 }[align];
2757
		}
2758
		attribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x);
2759
2760
2761
		// Vertical align
2762
		if (vAlign === 'bottom' || vAlign === 'middle') {
2763
			y += (box.height - (alignOptions.height || 0)) /
2764
					({ bottom: 1, middle: 2 }[vAlign] || 1);
2765
2766
		}
2767
		attribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);
2768
2769
		// Animate only if already placed
2770
		this[this.placed ? 'animate' : 'attr'](attribs);
2771
		this.placed = true;
2772
		this.alignAttr = attribs;
2773
2774
		return this;
2775
	},
2776
2777
	/**
2778
	 * Get the bounding box (width, height, x and y) for the element
2779
	 */
2780
	getBBox: function () {
2781
		var wrapper = this,
2782
			bBox = wrapper.bBox,
2783
			renderer = wrapper.renderer,
2784
			width,
2785
			height,
2786
			rotation = wrapper.rotation,
2787
			element = wrapper.element,
2788
			styles = wrapper.styles,
2789
			rad = rotation * deg2rad;
2790
			
2791
		if (!bBox) {
2792
			// SVG elements
2793
			if (element.namespaceURI === SVG_NS || renderer.forExport) {
2794
				try { // Fails in Firefox if the container has display: none.
2795
					
2796
					bBox = element.getBBox ?
2797
						// SVG: use extend because IE9 is not allowed to change width and height in case
2798
						// of rotation (below)
2799
						extend({}, element.getBBox()) :
2800
						// Canvas renderer and legacy IE in export mode
2801
						{
2802
							width: element.offsetWidth,
2803
							height: element.offsetHeight
2804
						};
2805
				} catch (e) {}
2806
				
2807
				// If the bBox is not set, the try-catch block above failed. The other condition
2808
				// is for Opera that returns a width of -Infinity on hidden elements.
2809
				if (!bBox || bBox.width < 0) {
2810
					bBox = { width: 0, height: 0 };
2811
				}
2812
				
2813
	
2814
			// VML Renderer or useHTML within SVG
2815
			} else {
2816
				
2817
				bBox = wrapper.htmlGetBBox();
2818
				
2819
			}
2820
			
2821
			// True SVG elements as well as HTML elements in modern browsers using the .useHTML option
2822
			// need to compensated for rotation
2823
			if (renderer.isSVG) {
2824
				width = bBox.width;
2825
				height = bBox.height;
2826
				
2827
				// Workaround for wrong bounding box in IE9 and IE10 (#1101, #1505, #1669)
2828
				if (isIE && styles && styles.fontSize === '11px' && height.toPrecision(3) === '22.7') {
2829
					bBox.height = height = 14;
2830
				}
2831
			
2832
				// Adjust for rotated text
2833
				if (rotation) {
2834
					bBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));
2835
					bBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));
2836
				}
2837
			}
2838
			
2839
			wrapper.bBox = bBox;
2840
		}
2841
		return bBox;
2842
	},
2843
2844
	/**
2845
	 * Show the element
2846
	 */
2847
	show: function () {
2848
		return this.attr({ visibility: VISIBLE });
2849
	},
2850
2851
	/**
2852
	 * Hide the element
2853
	 */
2854
	hide: function () {
2855
		return this.attr({ visibility: HIDDEN });
2856
	},
2857
	
2858
	fadeOut: function (duration) {
2859
		var elemWrapper = this;
2860
		elemWrapper.animate({
2861
			opacity: 0
2862
		}, {
2863
			duration: duration || 150,
2864
			complete: function () {
2865
				elemWrapper.hide();
2866
			}
2867
		});
2868
	},
2869
	
2870
	/**
2871
	 * Add the element
2872
	 * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined
2873
	 *    to append the element to the renderer.box.
2874
	 */
2875
	add: function (parent) {
2876
2877
		var renderer = this.renderer,
2878
			parentWrapper = parent || renderer,
2879
			parentNode = parentWrapper.element || renderer.box,
2880
			childNodes = parentNode.childNodes,
2881
			element = this.element,
2882
			zIndex = attr(element, 'zIndex'),
2883
			otherElement,
2884
			otherZIndex,
2885
			i,
2886
			inserted;
2887
			
2888
		if (parent) {
2889
			this.parentGroup = parent;
2890
		}
2891
2892
		// mark as inverted
2893
		this.parentInverted = parent && parent.inverted;
2894
2895
		// build formatted text
2896
		if (this.textStr !== undefined) {
2897
			renderer.buildText(this);
2898
		}
2899
2900
		// mark the container as having z indexed children
2901
		if (zIndex) {
2902
			parentWrapper.handleZ = true;
2903
			zIndex = pInt(zIndex);
2904
		}
2905
2906
		// insert according to this and other elements' zIndex
2907
		if (parentWrapper.handleZ) { // this element or any of its siblings has a z index
2908
			for (i = 0; i < childNodes.length; i++) {
2909
				otherElement = childNodes[i];
2910
				otherZIndex = attr(otherElement, 'zIndex');
2911
				if (otherElement !== element && (
2912
						// insert before the first element with a higher zIndex
2913
						pInt(otherZIndex) > zIndex ||
2914
						// if no zIndex given, insert before the first element with a zIndex
2915
						(!defined(zIndex) && defined(otherZIndex))
2916
2917
						)) {
2918
					parentNode.insertBefore(element, otherElement);
2919
					inserted = true;
2920
					break;
2921
				}
2922
			}
2923
		}
2924
2925
		// default: append at the end
2926
		if (!inserted) {
2927
			parentNode.appendChild(element);
2928
		}
2929
2930
		// mark as added
2931
		this.added = true;
2932
2933
		// fire an event for internal hooks
2934
		fireEvent(this, 'add');
2935
2936
		return this;
2937
	},
2938
2939
	/**
2940
	 * Removes a child either by removeChild or move to garbageBin.
2941
	 * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
2942
	 */
2943
	safeRemoveChild: function (element) {
2944
		var parentNode = element.parentNode;
2945
		if (parentNode) {
2946
			parentNode.removeChild(element);
2947
		}
2948
	},
2949
2950
	/**
2951
	 * Destroy the element and element wrapper
2952
	 */
2953
	destroy: function () {
2954
		var wrapper = this,
2955
			element = wrapper.element || {},
2956
			shadows = wrapper.shadows,
2957
			key,
2958
			i;
2959
2960
		// remove events
2961
		element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = element.point = null;
2962
		stop(wrapper); // stop running animations
2963
2964
		if (wrapper.clipPath) {
2965
			wrapper.clipPath = wrapper.clipPath.destroy();
2966
		}
2967
2968
		// Destroy stops in case this is a gradient object
2969
		if (wrapper.stops) {
2970
			for (i = 0; i < wrapper.stops.length; i++) {
2971
				wrapper.stops[i] = wrapper.stops[i].destroy();
2972
			}
2973
			wrapper.stops = null;
2974
		}
2975
2976
		// remove element
2977
		wrapper.safeRemoveChild(element);
2978
2979
		// destroy shadows
2980
		if (shadows) {
2981
			each(shadows, function (shadow) {
2982
				wrapper.safeRemoveChild(shadow);
2983
			});
2984
		}
2985
2986
		// remove from alignObjects
2987
		if (wrapper.alignTo) {
2988
			erase(wrapper.renderer.alignedObjects, wrapper);
2989
		}
2990
2991
		for (key in wrapper) {
2992
			delete wrapper[key];
2993
		}
2994
2995
		return null;
2996
	},
2997
2998
	/**
2999
	 * Add a shadow to the element. Must be done after the element is added to the DOM
3000
	 * @param {Boolean|Object} shadowOptions
3001
	 */
3002
	shadow: function (shadowOptions, group, cutOff) {
3003
		var shadows = [],
3004
			i,
3005
			shadow,
3006
			element = this.element,
3007
			strokeWidth,
3008
			shadowWidth,
3009
			shadowElementOpacity,
3010
3011
			// compensate for inverted plot area
3012
			transform;
3013
3014
3015
		if (shadowOptions) {
3016
			shadowWidth = pick(shadowOptions.width, 3);
3017
			shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;
3018
			transform = this.parentInverted ? 
3019
				'(-1,-1)' : 
3020
				'(' + pick(shadowOptions.offsetX, 1) + ', ' + pick(shadowOptions.offsetY, 1) + ')';
3021
			for (i = 1; i <= shadowWidth; i++) {
3022
				shadow = element.cloneNode(0);
3023
				strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
3024
				attr(shadow, {
3025
					'isShadow': 'true',
3026
					'stroke': shadowOptions.color || 'black',
3027
					'stroke-opacity': shadowElementOpacity * i,
3028
					'stroke-width': strokeWidth,
3029
					'transform': 'translate' + transform,
3030
					'fill': NONE
3031
				});
3032
				if (cutOff) {
3033
					attr(shadow, 'height', mathMax(attr(shadow, 'height') - strokeWidth, 0));
3034
					shadow.cutHeight = strokeWidth;
3035
				}
3036
3037
				if (group) {
3038
					group.element.appendChild(shadow);
3039
				} else {
3040
					element.parentNode.insertBefore(shadow, element);
3041
				}
3042
3043
				shadows.push(shadow);
3044
			}
3045
3046
			this.shadows = shadows;
3047
		}
3048
		return this;
3049
3050
	}
3051
};
3052
3053
3054
/**
3055
 * The default SVG renderer
3056
 */
3057
var SVGRenderer = function () {
3058
	this.init.apply(this, arguments);
3059
};
3060
SVGRenderer.prototype = {
3061
	Element: SVGElement,
3062
3063
	/**
3064
	 * Initialize the SVGRenderer
3065
	 * @param {Object} container
3066
	 * @param {Number} width
3067
	 * @param {Number} height
3068
	 * @param {Boolean} forExport
3069
	 */
3070
	init: function (container, width, height, forExport) {
3071
		var renderer = this,
3072
			loc = location,
3073
			boxWrapper,
3074
			desc;
3075
3076
		boxWrapper = renderer.createElement('svg')
3077
			.attr({
3078
				xmlns: SVG_NS,
3079
				version: '1.1'
3080
			});
3081
		container.appendChild(boxWrapper.element);
3082
3083
		// object properties
3084
		renderer.isSVG = true;
3085
		renderer.box = boxWrapper.element;
3086
		renderer.boxWrapper = boxWrapper;
3087
		renderer.alignedObjects = [];
3088
		
3089
		// Page url used for internal references. #24, #672, #1070
3090
		renderer.url = (isFirefox || isWebKit) && doc.getElementsByTagName('base').length ? 
3091
			loc.href
3092
				.replace(/#.*?$/, '') // remove the hash
3093
				.replace(/([\('\)])/g, '\\$1') // escape parantheses and quotes
3094
				.replace(/ /g, '%20') : // replace spaces (needed for Safari only)
3095
			''; 
3096
			
3097
		// Add description
3098
		desc = this.createElement('desc').add();
3099
		desc.element.appendChild(doc.createTextNode('Created with ' + PRODUCT + ' ' + VERSION));
3100
3101
		
3102
		renderer.defs = this.createElement('defs').add();
3103
		renderer.forExport = forExport;
3104
		renderer.gradients = {}; // Object where gradient SvgElements are stored
3105
3106
		renderer.setSize(width, height, false);
3107
3108
3109
3110
		// Issue 110 workaround:
3111
		// In Firefox, if a div is positioned by percentage, its pixel position may land
3112
		// between pixels. The container itself doesn't display this, but an SVG element
3113
		// inside this container will be drawn at subpixel precision. In order to draw
3114
		// sharp lines, this must be compensated for. This doesn't seem to work inside
3115
		// iframes though (like in jsFiddle).
3116
		var subPixelFix, rect;
3117
		if (isFirefox && container.getBoundingClientRect) {
3118
			renderer.subPixelFix = subPixelFix = function () {
3119
				css(container, { left: 0, top: 0 });
3120
				rect = container.getBoundingClientRect();
3121
				css(container, {
3122
					left: (mathCeil(rect.left) - rect.left) + PX,
3123
					top: (mathCeil(rect.top) - rect.top) + PX
3124
				});
3125
			};
3126
3127
			// run the fix now
3128
			subPixelFix();
3129
3130
			// run it on resize
3131
			addEvent(win, 'resize', subPixelFix);
3132
		}
3133
	},
3134
3135
	/**
3136
	 * Detect whether the renderer is hidden. This happens when one of the parent elements
3137
	 * has display: none. #608.
3138
	 */
3139
	isHidden: function () {
3140
		return !this.boxWrapper.getBBox().width;			
3141
	},
3142
3143
	/**
3144
	 * Destroys the renderer and its allocated members.
3145
	 */
3146
	destroy: function () {
3147
		var renderer = this,
3148
			rendererDefs = renderer.defs;
3149
		renderer.box = null;
3150
		renderer.boxWrapper = renderer.boxWrapper.destroy();
3151
3152
		// Call destroy on all gradient elements
3153
		destroyObjectProperties(renderer.gradients || {});
3154
		renderer.gradients = null;
3155
3156
		// Defs are null in VMLRenderer
3157
		// Otherwise, destroy them here.
3158
		if (rendererDefs) {
3159
			renderer.defs = rendererDefs.destroy();
3160
		}
3161
3162
		// Remove sub pixel fix handler
3163
		// We need to check that there is a handler, otherwise all functions that are registered for event 'resize' are removed
3164
		// See issue #982
3165
		if (renderer.subPixelFix) {
3166
			removeEvent(win, 'resize', renderer.subPixelFix);
3167
		}
3168
3169
		renderer.alignedObjects = null;
3170
3171
		return null;
3172
	},
3173
3174
	/**
3175
	 * Create a wrapper for an SVG element
3176
	 * @param {Object} nodeName
3177
	 */
3178
	createElement: function (nodeName) {
3179
		var wrapper = new this.Element();
3180
		wrapper.init(this, nodeName);
3181
		return wrapper;
3182
	},
3183
3184
	/**
3185
	 * Dummy function for use in canvas renderer
3186
	 */
3187
	draw: function () {},
3188
3189
	/**
3190
	 * Parse a simple HTML string into SVG tspans
3191
	 *
3192
	 * @param {Object} textNode The parent text SVG node
3193
	 */
3194
	buildText: function (wrapper) {
3195
		var textNode = wrapper.element,
3196
			renderer = this,
3197
			forExport = renderer.forExport,
3198
			lines = pick(wrapper.textStr, '').toString()
3199
				.replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
3200
				.replace(/<(i|em)>/g, '<span style="font-style:italic">')
3201
				.replace(/<a/g, '<span')
3202
				.replace(/<\/(b|strong|i|em|a)>/g, '</span>')
3203
				.split(/<br.*?>/g),
3204
			childNodes = textNode.childNodes,
3205
			styleRegex = /style="([^"]+)"/,
3206
			hrefRegex = /href="([^"]+)"/,
3207
			parentX = attr(textNode, 'x'),
3208
			textStyles = wrapper.styles,
3209
			width = textStyles && textStyles.width && pInt(textStyles.width),
3210
			textLineHeight = textStyles && textStyles.lineHeight,
3211
			i = childNodes.length;
3212
3213
		/// remove old text
3214
		while (i--) {
3215
			textNode.removeChild(childNodes[i]);
3216
		}
3217
3218
		if (width && !wrapper.added) {
3219
			this.box.appendChild(textNode); // attach it to the DOM to read offset width
3220
		}
3221
3222
		// remove empty line at end
3223
		if (lines[lines.length - 1] === '') {
3224
			lines.pop();
3225
		}
3226
3227
		// build the lines
3228
		each(lines, function (line, lineNo) {
3229
			var spans, spanNo = 0;
3230
3231
			line = line.replace(/<span/g, '|||<span').replace(/<\/span>/g, '</span>|||');
3232
			spans = line.split('|||');
3233
3234
			each(spans, function (span) {
3235
				if (span !== '' || spans.length === 1) {
3236
					var attributes = {},
3237
						tspan = doc.createElementNS(SVG_NS, 'tspan'),
3238
						spanStyle; // #390
3239
					if (styleRegex.test(span)) {
3240
						spanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2');
3241
						attr(tspan, 'style', spanStyle);
3242
					}
3243
					if (hrefRegex.test(span) && !forExport) { // Not for export - #1529
3244
						attr(tspan, 'onclick', 'location.href=\"' + span.match(hrefRegex)[1] + '\"');
3245
						css(tspan, { cursor: 'pointer' });
3246
					}
3247
3248
					span = (span.replace(/<(.|\n)*?>/g, '') || ' ')
3249
						.replace(/&lt;/g, '<')
3250
						.replace(/&gt;/g, '>');
3251
3252
					// add the text node
3253
					tspan.appendChild(doc.createTextNode(span));
3254
3255
					if (!spanNo) { // first span in a line, align it to the left
3256
						attributes.x = parentX;
3257
					} else {
3258
						attributes.dx = 0; // #16
3259
					}
3260
3261
					// add attributes
3262
					attr(tspan, attributes);
3263
3264
					// first span on subsequent line, add the line height
3265
					if (!spanNo && lineNo) {
3266
3267
						// allow getting the right offset height in exporting in IE
3268
						if (!hasSVG && forExport) {
3269
							css(tspan, { display: 'block' });
3270
						}
3271
3272
						// Set the line height based on the font size of either 
3273
						// the text element or the tspan element
3274
						attr(
3275
							tspan, 
3276
							'dy',
3277
							textLineHeight || renderer.fontMetrics(
3278
								/px$/.test(tspan.style.fontSize) ?
3279
									tspan.style.fontSize : 
3280
									textStyles.fontSize
3281
							).h,
3282
							// Safari 6.0.2 - too optimized for its own good (#1539)
3283
							// TODO: revisit this with future versions of Safari
3284
							isWebKit && tspan.offsetHeight
3285
						);
3286
					}
3287
3288
					// Append it
3289
					textNode.appendChild(tspan);
3290
3291
					spanNo++;
3292
3293
					// check width and apply soft breaks
3294
					if (width) {
3295
						var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273
3296
							tooLong,
3297
							actualWidth,
3298
							rest = [];
3299
3300
						while (words.length || rest.length) {
3301
							delete wrapper.bBox; // delete cache
3302
							actualWidth = wrapper.getBBox().width;
3303
							tooLong = actualWidth > width;
3304
							if (!tooLong || words.length === 1) { // new line needed
3305
								words = rest;
3306
								rest = [];
3307
								if (words.length) {
3308
									tspan = doc.createElementNS(SVG_NS, 'tspan');
3309
									attr(tspan, {
3310
										dy: textLineHeight || 16,
3311
										x: parentX
3312
									});
3313
									if (spanStyle) { // #390
3314
										attr(tspan, 'style', spanStyle);
3315
									}
3316
									textNode.appendChild(tspan);
3317
3318
									if (actualWidth > width) { // a single word is pressing it out
3319
										width = actualWidth;
3320
									}
3321
								}
3322
							} else { // append to existing line tspan
3323
								tspan.removeChild(tspan.firstChild);
3324
								rest.unshift(words.pop());
3325
							}
3326
							if (words.length) {
3327
								tspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));
3328
							}
3329
						}
3330
					}
3331
				}
3332
			});
3333
		});
3334
	},
3335
3336
	/**
3337
	 * Create a button with preset states
3338
	 * @param {String} text
3339
	 * @param {Number} x
3340
	 * @param {Number} y
3341
	 * @param {Function} callback
3342
	 * @param {Object} normalState
3343
	 * @param {Object} hoverState
3344
	 * @param {Object} pressedState
3345
	 */
3346
	button: function (text, x, y, callback, normalState, hoverState, pressedState) {
3347
		var label = this.label(text, x, y, null, null, null, null, null, 'button'),
3348
			curState = 0,
3349
			stateOptions,
3350
			stateStyle,
3351
			normalStyle,
3352
			hoverStyle,
3353
			pressedStyle,
3354
			STYLE = 'style',
3355
			verticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 };
3356
3357
		// Normal state - prepare the attributes
3358
		normalState = merge({
3359
			'stroke-width': 1,
3360
			stroke: '#CCCCCC',
3361
			fill: {
3362
				linearGradient: verticalGradient,
3363
				stops: [
3364
					[0, '#FEFEFE'],
3365
					[1, '#F6F6F6']
3366
				]
3367
			},
3368
			r: 2,
3369
			padding: 5,
3370
			style: {
3371
				color: 'black'
3372
			}
3373
		}, normalState);
3374
		normalStyle = normalState[STYLE];
3375
		delete normalState[STYLE];
3376
3377
		// Hover state
3378
		hoverState = merge(normalState, {
3379
			stroke: '#68A',
3380
			fill: {
3381
				linearGradient: verticalGradient,
3382
				stops: [
3383
					[0, '#FFF'],
3384
					[1, '#ACF']
3385
				]
3386
			}
3387
		}, hoverState);
3388
		hoverStyle = hoverState[STYLE];
3389
		delete hoverState[STYLE];
3390
3391
		// Pressed state
3392
		pressedState = merge(normalState, {
3393
			stroke: '#68A',
3394
			fill: {
3395
				linearGradient: verticalGradient,
3396
				stops: [
3397
					[0, '#9BD'],
3398
					[1, '#CDF']
3399
				]
3400
			}
3401
		}, pressedState);
3402
		pressedStyle = pressedState[STYLE];
3403
		delete pressedState[STYLE];
3404
3405
		// add the events
3406
		addEvent(label.element, 'mouseenter', function () {
3407
			label.attr(hoverState)
3408
				.css(hoverStyle);
3409
		});
3410
		addEvent(label.element, 'mouseleave', function () {
3411
			stateOptions = [normalState, hoverState, pressedState][curState];
3412
			stateStyle = [normalStyle, hoverStyle, pressedStyle][curState];
3413
			label.attr(stateOptions)
3414
				.css(stateStyle);
3415
		});
3416
3417
		label.setState = function (state) {
3418
			curState = state;
3419
			if (!state) {
3420
				label.attr(normalState)
3421
					.css(normalStyle);
3422
			} else if (state === 2) {
3423
				label.attr(pressedState)
3424
					.css(pressedStyle);
3425
			}
3426
		};
3427
3428
		return label
3429
			.on('click', function () {
3430
				callback.call(label);
3431
			})
3432
			.attr(normalState)
3433
			.css(extend({ cursor: 'default' }, normalStyle));
3434
	},
3435
3436
	/**
3437
	 * Make a straight line crisper by not spilling out to neighbour pixels
3438
	 * @param {Array} points
3439
	 * @param {Number} width
3440
	 */
3441
	crispLine: function (points, width) {
3442
		// points format: [M, 0, 0, L, 100, 0]
3443
		// normalize to a crisp line
3444
		if (points[1] === points[4]) {
3445
			// Substract due to #1129. Now bottom and left axis gridlines behave the same.
3446
			points[1] = points[4] = mathRound(points[1]) - (width % 2 / 2); 
3447
		}
3448
		if (points[2] === points[5]) {
3449
			points[2] = points[5] = mathRound(points[2]) + (width % 2 / 2);
3450
		}
3451
		return points;
3452
	},
3453
3454
3455
	/**
3456
	 * Draw a path
3457
	 * @param {Array} path An SVG path in array form
3458
	 */
3459
	path: function (path) {
3460
		var attr = {
3461
			fill: NONE
3462
		};
3463
		if (isArray(path)) {
3464
			attr.d = path;
3465
		} else if (isObject(path)) { // attributes
3466
			extend(attr, path);
3467
		}
3468
		return this.createElement('path').attr(attr);
3469
	},
3470
3471
	/**
3472
	 * Draw and return an SVG circle
3473
	 * @param {Number} x The x position
3474
	 * @param {Number} y The y position
3475
	 * @param {Number} r The radius
3476
	 */
3477
	circle: function (x, y, r) {
3478
		var attr = isObject(x) ?
3479
			x :
3480
			{
3481
				x: x,
3482
				y: y,
3483
				r: r
3484
			};
3485
3486
		return this.createElement('circle').attr(attr);
3487
	},
3488
3489
	/**
3490
	 * Draw and return an arc
3491
	 * @param {Number} x X position
3492
	 * @param {Number} y Y position
3493
	 * @param {Number} r Radius
3494
	 * @param {Number} innerR Inner radius like used in donut charts
3495
	 * @param {Number} start Starting angle
3496
	 * @param {Number} end Ending angle
3497
	 */
3498
	arc: function (x, y, r, innerR, start, end) {
3499
		// arcs are defined as symbols for the ability to set
3500
		// attributes in attr and animate
3501
3502
		if (isObject(x)) {
3503
			y = x.y;
3504
			r = x.r;
3505
			innerR = x.innerR;
3506
			start = x.start;
3507
			end = x.end;
3508
			x = x.x;
3509
		}
3510
		return this.symbol('arc', x || 0, y || 0, r || 0, r || 0, {
3511
			innerR: innerR || 0,
3512
			start: start || 0,
3513
			end: end || 0
3514
		});
3515
	},
3516
	
3517
	/**
3518
	 * Draw and return a rectangle
3519
	 * @param {Number} x Left position
3520
	 * @param {Number} y Top position
3521
	 * @param {Number} width
3522
	 * @param {Number} height
3523
	 * @param {Number} r Border corner radius
3524
	 * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing
3525
	 */
3526
	rect: function (x, y, width, height, r, strokeWidth) {
3527
		
3528
		r = isObject(x) ? x.r : r;
3529
		
3530
		var wrapper = this.createElement('rect').attr({
3531
				rx: r,
3532
				ry: r,
3533
				fill: NONE
3534
			});
3535
		return wrapper.attr(
3536
				isObject(x) ? 
3537
					x : 
3538
					// do not crispify when an object is passed in (as in column charts)
3539
					wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0))
3540
			);
3541
	},
3542
3543
	/**
3544
	 * Resize the box and re-align all aligned elements
3545
	 * @param {Object} width
3546
	 * @param {Object} height
3547
	 * @param {Boolean} animate
3548
	 *
3549
	 */
3550
	setSize: function (width, height, animate) {
3551
		var renderer = this,
3552
			alignedObjects = renderer.alignedObjects,
3553
			i = alignedObjects.length;
3554
3555
		renderer.width = width;
3556
		renderer.height = height;
3557
3558
		renderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({
3559
			width: width,
3560
			height: height
3561
		});
3562
3563
		while (i--) {
3564
			alignedObjects[i].align();
3565
		}
3566
	},
3567
3568
	/**
3569
	 * Create a group
3570
	 * @param {String} name The group will be given a class name of 'highcharts-{name}'.
3571
	 *     This can be used for styling and scripting.
3572
	 */
3573
	g: function (name) {
3574
		var elem = this.createElement('g');
3575
		return defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem;
3576
	},
3577
3578
	/**
3579
	 * Display an image
3580
	 * @param {String} src
3581
	 * @param {Number} x
3582
	 * @param {Number} y
3583
	 * @param {Number} width
3584
	 * @param {Number} height
3585
	 */
3586
	image: function (src, x, y, width, height) {
3587
		var attribs = {
3588
				preserveAspectRatio: NONE
3589
			},
3590
			elemWrapper;
3591
3592
		// optional properties
3593
		if (arguments.length > 1) {
3594
			extend(attribs, {
3595
				x: x,
3596
				y: y,
3597
				width: width,
3598
				height: height
3599
			});
3600
		}
3601
3602
		elemWrapper = this.createElement('image').attr(attribs);
3603
3604
		// set the href in the xlink namespace
3605
		if (elemWrapper.element.setAttributeNS) {
3606
			elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
3607
				'href', src);
3608
		} else {
3609
			// could be exporting in IE
3610
			// using href throws "not supported" in ie7 and under, requries regex shim to fix later
3611
			elemWrapper.element.setAttribute('hc-svg-href', src);
3612
	}
3613
3614
		return elemWrapper;
3615
	},
3616
3617
	/**
3618
	 * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object.
3619
	 *
3620
	 * @param {Object} symbol
3621
	 * @param {Object} x
3622
	 * @param {Object} y
3623
	 * @param {Object} radius
3624
	 * @param {Object} options
3625
	 */
3626
	symbol: function (symbol, x, y, width, height, options) {
3627
3628
		var obj,
3629
3630
			// get the symbol definition function
3631
			symbolFn = this.symbols[symbol],
3632
3633
			// check if there's a path defined for this symbol
3634
			path = symbolFn && symbolFn(
3635
				mathRound(x),
3636
				mathRound(y),
3637
				width,
3638
				height,
3639
				options
3640
			),
3641
3642
			imageElement,
3643
			imageRegex = /^url\((.*?)\)$/,
3644
			imageSrc,
3645
			imageSize,
3646
			centerImage;
3647
3648
		if (path) {
3649
3650
			obj = this.path(path);
3651
			// expando properties for use in animate and attr
3652
			extend(obj, {
3653
				symbolName: symbol,
3654
				x: x,
3655
				y: y,
3656
				width: width,
3657
				height: height
3658
			});
3659
			if (options) {
3660
				extend(obj, options);
3661
			}
3662
3663
3664
		// image symbols
3665
		} else if (imageRegex.test(symbol)) {
3666
3667
			// On image load, set the size and position
3668
			centerImage = function (img, size) {
3669
				if (img.element) { // it may be destroyed in the meantime (#1390)
3670
					img.attr({
3671
						width: size[0],
3672
						height: size[1]
3673
					});
3674
3675
					if (!img.alignByTranslate) { // #185
3676
						img.translate(
3677
							mathRound((width - size[0]) / 2), // #1378
3678
							mathRound((height - size[1]) / 2)
3679
						);
3680
					}
3681
				}
3682
			};
3683
3684
			imageSrc = symbol.match(imageRegex)[1];
3685
			imageSize = symbolSizes[imageSrc];
3686
3687
			// Ireate the image synchronously, add attribs async
3688
			obj = this.image(imageSrc)
3689
				.attr({
3690
					x: x,
3691
					y: y
3692
				});
3693
			obj.isImg = true;
3694
3695
			if (imageSize) {
3696
				centerImage(obj, imageSize);
3697
			} else {
3698
				// Initialize image to be 0 size so export will still function if there's no cached sizes.
3699
				// 
3700
				obj.attr({ width: 0, height: 0 });
3701
3702
				// Create a dummy JavaScript image to get the width and height. Due to a bug in IE < 8,
3703
				// the created element must be assigned to a variable in order to load (#292).
3704
				imageElement = createElement('img', {
3705
					onload: function () {
3706
						centerImage(obj, symbolSizes[imageSrc] = [this.width, this.height]);
3707
					},
3708
					src: imageSrc
3709
				});
3710
			}
3711
		}
3712
3713
		return obj;
3714
	},
3715
3716
	/**
3717
	 * An extendable collection of functions for defining symbol paths.
3718
	 */
3719
	symbols: {
3720
		'circle': function (x, y, w, h) {
3721
			var cpw = 0.166 * w;
3722
			return [
3723
				M, x + w / 2, y,
3724
				'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h,
3725
				'C', x - cpw, y + h, x - cpw, y, x + w / 2, y,
3726
				'Z'
3727
			];
3728
		},
3729
3730
		'square': function (x, y, w, h) {
3731
			return [
3732
				M, x, y,
3733
				L, x + w, y,
3734
				x + w, y + h,
3735
				x, y + h,
3736
				'Z'
3737
			];
3738
		},
3739
3740
		'triangle': function (x, y, w, h) {
3741
			return [
3742
				M, x + w / 2, y,
3743
				L, x + w, y + h,
3744
				x, y + h,
3745
				'Z'
3746
			];
3747
		},
3748
3749
		'triangle-down': function (x, y, w, h) {
3750
			return [
3751
				M, x, y,
3752
				L, x + w, y,
3753
				x + w / 2, y + h,
3754
				'Z'
3755
			];
3756
		},
3757
		'diamond': function (x, y, w, h) {
3758
			return [
3759
				M, x + w / 2, y,
3760
				L, x + w, y + h / 2,
3761
				x + w / 2, y + h,
3762
				x, y + h / 2,
3763
				'Z'
3764
			];
3765
		},
3766
		'arc': function (x, y, w, h, options) {
3767
			var start = options.start,
3768
				radius = options.r || w || h,
3769
				end = options.end - 0.001, // to prevent cos and sin of start and end from becoming equal on 360 arcs (related: #1561)
3770
				innerRadius = options.innerR,
3771
				open = options.open,
3772
				cosStart = mathCos(start),
3773
				sinStart = mathSin(start),
3774
				cosEnd = mathCos(end),
3775
				sinEnd = mathSin(end),
3776
				longArc = options.end - start < mathPI ? 0 : 1;
3777
3778
			return [
3779
				M,
3780
				x + radius * cosStart,
3781
				y + radius * sinStart,
3782
				'A', // arcTo
3783
				radius, // x radius
3784
				radius, // y radius
3785
				0, // slanting
3786
				longArc, // long or short arc
3787
				1, // clockwise
3788
				x + radius * cosEnd,
3789
				y + radius * sinEnd,
3790
				open ? M : L,
3791
				x + innerRadius * cosEnd,
3792
				y + innerRadius * sinEnd,
3793
				'A', // arcTo
3794
				innerRadius, // x radius
3795
				innerRadius, // y radius
3796
				0, // slanting
3797
				longArc, // long or short arc
3798
				0, // clockwise
3799
				x + innerRadius * cosStart,
3800
				y + innerRadius * sinStart,
3801
3802
				open ? '' : 'Z' // close
3803
			];
3804
		}
3805
	},
3806
3807
	/**
3808
	 * Define a clipping rectangle
3809
	 * @param {String} id
3810
	 * @param {Number} x
3811
	 * @param {Number} y
3812
	 * @param {Number} width
3813
	 * @param {Number} height
3814
	 */
3815
	clipRect: function (x, y, width, height) {
3816
		var wrapper,
3817
			id = PREFIX + idCounter++,
3818
3819
			clipPath = this.createElement('clipPath').attr({
3820
				id: id
3821
			}).add(this.defs);
3822
3823
		wrapper = this.rect(x, y, width, height, 0).add(clipPath);
3824
		wrapper.id = id;
3825
		wrapper.clipPath = clipPath;
3826
3827
		return wrapper;
3828
	},
3829
3830
3831
	/**
3832
	 * Take a color and return it if it's a string, make it a gradient if it's a
3833
	 * gradient configuration object. Prior to Highstock, an array was used to define
3834
	 * a linear gradient with pixel positions relative to the SVG. In newer versions
3835
	 * we change the coordinates to apply relative to the shape, using coordinates
3836
	 * 0-1 within the shape. To preserve backwards compatibility, linearGradient
3837
	 * in this definition is an object of x1, y1, x2 and y2.
3838
	 *
3839
	 * @param {Object} color The color or config object
3840
	 */
3841
	color: function (color, elem, prop) {
3842
		var renderer = this,
3843
			colorObject,
3844
			regexRgba = /^rgba/,
3845
			gradName, 
3846
			gradAttr,
3847
			gradients,
3848
			gradientObject,
3849
			stops,
3850
			stopColor,
3851
			stopOpacity,
3852
			radialReference,
3853
			n,
3854
			id,
3855
			key = [];
3856
		
3857
		// Apply linear or radial gradients
3858
		if (color && color.linearGradient) {
3859
			gradName = 'linearGradient';
3860
		} else if (color && color.radialGradient) {
3861
			gradName = 'radialGradient';
3862
		}
3863
		
3864
		if (gradName) {
3865
			gradAttr = color[gradName];
3866
			gradients = renderer.gradients;
3867
			stops = color.stops;
3868
			radialReference = elem.radialReference;
3869
			
3870
			// Keep < 2.2 kompatibility
3871
			if (isArray(gradAttr)) {
3872
				color[gradName] = gradAttr = {
3873
					x1: gradAttr[0],
3874
					y1: gradAttr[1],
3875
					x2: gradAttr[2],
3876
					y2: gradAttr[3],
3877
					gradientUnits: 'userSpaceOnUse'
3878
				};				
3879
			}
3880
			
3881
			// Correct the radial gradient for the radial reference system
3882
			if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) {
3883
				gradAttr = merge(gradAttr, {
3884
					cx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2],
3885
					cy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2],
3886
					r: gradAttr.r * radialReference[2],
3887
					gradientUnits: 'userSpaceOnUse'
3888
				});
3889
			}
3890
			
3891
			// Build the unique key to detect whether we need to create a new element (#1282)
3892
			for (n in gradAttr) {
3893
				if (n !== 'id') {
3894
					key.push(n, gradAttr[n]);
3895
				}
3896
			}
3897
			for (n in stops) {
3898
				key.push(stops[n]);
3899
			}
3900
			key = key.join(',');
3901
			
3902
			// Check if a gradient object with the same config object is created within this renderer
3903
			if (gradients[key]) {
3904
				id = gradients[key].id;
3905
				
3906
			} else {
3907
3908
				// Set the id and create the element
3909
				gradAttr.id = id = PREFIX + idCounter++;
3910
				gradients[key] = gradientObject = renderer.createElement(gradName)
3911
					.attr(gradAttr)
3912
					.add(renderer.defs);
3913
				
3914
				
3915
				// The gradient needs to keep a list of stops to be able to destroy them
3916
				gradientObject.stops = [];
3917
				each(stops, function (stop) {
3918
					var stopObject;
3919
					if (regexRgba.test(stop[1])) {
3920
						colorObject = Color(stop[1]);
3921
						stopColor = colorObject.get('rgb');
3922
						stopOpacity = colorObject.get('a');
3923
					} else {
3924
						stopColor = stop[1];
3925
						stopOpacity = 1;
3926
					}
3927
					stopObject = renderer.createElement('stop').attr({
3928
						offset: stop[0],
3929
						'stop-color': stopColor,
3930
						'stop-opacity': stopOpacity
3931
					}).add(gradientObject);
3932
3933
					// Add the stop element to the gradient
3934
					gradientObject.stops.push(stopObject);
3935
				});
3936
			}
3937
3938
			// Return the reference to the gradient object
3939
			return 'url(' + renderer.url + '#' + id + ')';
3940
			
3941
		// Webkit and Batik can't show rgba.
3942
		} else if (regexRgba.test(color)) {
3943
			colorObject = Color(color);
3944
			attr(elem, prop + '-opacity', colorObject.get('a'));
3945
3946
			return colorObject.get('rgb');
3947
3948
3949
		} else {
3950
			// Remove the opacity attribute added above. Does not throw if the attribute is not there.
3951
			elem.removeAttribute(prop + '-opacity');
3952
3953
			return color;
3954
		}
3955
3956
	},
3957
3958
3959
	/**
3960
	 * Add text to the SVG object
3961
	 * @param {String} str
3962
	 * @param {Number} x Left position
3963
	 * @param {Number} y Top position
3964
	 * @param {Boolean} useHTML Use HTML to render the text
3965
	 */
3966
	text: function (str, x, y, useHTML) {
3967
3968
		// declare variables
3969
		var renderer = this,
3970
			defaultChartStyle = defaultOptions.chart.style,
3971
			fakeSVG = useCanVG || (!hasSVG && renderer.forExport),
3972
			wrapper;
3973
3974
		if (useHTML && !renderer.forExport) {
3975
			return renderer.html(str, x, y);
3976
		}
3977
3978
		x = mathRound(pick(x, 0));
3979
		y = mathRound(pick(y, 0));
3980
3981
		wrapper = renderer.createElement('text')
3982
			.attr({
3983
				x: x,
3984
				y: y,
3985
				text: str
3986
			})
3987
			.css({
3988
				fontFamily: defaultChartStyle.fontFamily,
3989
				fontSize: defaultChartStyle.fontSize
3990
			});
3991
		
3992
		// Prevent wrapping from creating false offsetWidths in export in legacy IE (#1079, #1063)	
3993
		if (fakeSVG) {
3994
			wrapper.css({
3995
				position: ABSOLUTE
3996
			});
3997
		}
3998
3999
		wrapper.x = x;
4000
		wrapper.y = y;
4001
		return wrapper;
4002
	},
4003
4004
4005
	/**
4006
	 * Create HTML text node. This is used by the VML renderer as well as the SVG
4007
	 * renderer through the useHTML option.
4008
	 *
4009
	 * @param {String} str
4010
	 * @param {Number} x
4011
	 * @param {Number} y
4012
	 */
4013
	html: function (str, x, y) {
4014
		var defaultChartStyle = defaultOptions.chart.style,
4015
			wrapper = this.createElement('span'),
4016
			attrSetters = wrapper.attrSetters,
4017
			element = wrapper.element,
4018
			renderer = wrapper.renderer;
4019
4020
		// Text setter
4021
		attrSetters.text = function (value) {
4022
			if (value !== element.innerHTML) {
4023
				delete this.bBox;
4024
			}
4025
			element.innerHTML = value;
4026
			return false;
4027
		};
4028
4029
		// Various setters which rely on update transform
4030
		attrSetters.x = attrSetters.y = attrSetters.align = function (value, key) {
4031
			if (key === 'align') {
4032
				key = 'textAlign'; // Do not overwrite the SVGElement.align method. Same as VML.
4033
			}
4034
			wrapper[key] = value;
4035
			wrapper.htmlUpdateTransform();
4036
			return false;
4037
		};
4038
4039
		// Set the default attributes
4040
		wrapper.attr({
4041
				text: str,
4042
				x: mathRound(x),
4043
				y: mathRound(y)
4044
			})
4045
			.css({
4046
				position: ABSOLUTE,
4047
				whiteSpace: 'nowrap',
4048
				fontFamily: defaultChartStyle.fontFamily,
4049
				fontSize: defaultChartStyle.fontSize
4050
			});
4051
4052
		// Use the HTML specific .css method
4053
		wrapper.css = wrapper.htmlCss;
4054
4055
		// This is specific for HTML within SVG
4056
		if (renderer.isSVG) {
4057
			wrapper.add = function (svgGroupWrapper) {
4058
4059
				var htmlGroup,
4060
					container = renderer.box.parentNode,
4061
					parentGroup,
4062
					parents = [];
4063
4064
				// Create a mock group to hold the HTML elements
4065
				if (svgGroupWrapper) {
4066
					htmlGroup = svgGroupWrapper.div;
4067
					if (!htmlGroup) {
4068
						
4069
						// Read the parent chain into an array and read from top down
4070
						parentGroup = svgGroupWrapper;
4071
						while (parentGroup) {
4072
						
4073
							parents.push(parentGroup);
4074
						
4075
							// Move up to the next parent group
4076
							parentGroup = parentGroup.parentGroup;
4077
						}
4078
						
4079
						// Ensure dynamically updating position when any parent is translated
4080
						each(parents.reverse(), function (parentGroup) {
4081
							var htmlGroupStyle;
4082
								
4083
							// Create a HTML div and append it to the parent div to emulate 
4084
							// the SVG group structure
4085
							htmlGroup = parentGroup.div = parentGroup.div || createElement(DIV, {
4086
								className: attr(parentGroup.element, 'class')
4087
							}, {
4088
								position: ABSOLUTE,
4089
								left: (parentGroup.translateX || 0) + PX,
4090
								top: (parentGroup.translateY || 0) + PX
4091
							}, htmlGroup || container); // the top group is appended to container
4092
							
4093
							// Shortcut
4094
							htmlGroupStyle = htmlGroup.style;
4095
							
4096
							// Set listeners to update the HTML div's position whenever the SVG group
4097
							// position is changed
4098
							extend(parentGroup.attrSetters, {
4099
								translateX: function (value) {
4100
									htmlGroupStyle.left = value + PX;
4101
								},
4102
								translateY: function (value) {
4103
									htmlGroupStyle.top = value + PX;
4104
								},
4105
								visibility: function (value, key) {
4106
									htmlGroupStyle[key] = value;
4107
								}
4108
							});
4109
						});
4110
4111
					}
4112
				} else {
4113
					htmlGroup = container;
4114
				}
4115
4116
				htmlGroup.appendChild(element);
4117
4118
				// Shared with VML:
4119
				wrapper.added = true;
4120
				if (wrapper.alignOnAdd) {
4121
					wrapper.htmlUpdateTransform();
4122
				}
4123
4124
				return wrapper;
4125
			};
4126
		}
4127
		return wrapper;
4128
	},
4129
4130
	/**
4131
	 * Utility to return the baseline offset and total line height from the font size
4132
	 */
4133
	fontMetrics: function (fontSize) {
4134
		fontSize = pInt(fontSize || 11);
4135
		
4136
		// Empirical values found by comparing font size and bounding box height.
4137
		// Applies to the default font family. http://jsfiddle.net/highcharts/7xvn7/
4138
		var lineHeight = fontSize < 24 ? fontSize + 4 : mathRound(fontSize * 1.2),
4139
			baseline = mathRound(lineHeight * 0.8);
4140
		
4141
		return {
4142
			h: lineHeight, 
4143
			b: baseline
4144
		};
4145
	},
4146
4147
	/**
4148
	 * Add a label, a text item that can hold a colored or gradient background
4149
	 * as well as a border and shadow.
4150
	 * @param {string} str
4151
	 * @param {Number} x
4152
	 * @param {Number} y
4153
	 * @param {String} shape
4154
	 * @param {Number} anchorX In case the shape has a pointer, like a flag, this is the
4155
	 *    coordinates it should be pinned to
4156
	 * @param {Number} anchorY
4157
	 * @param {Boolean} baseline Whether to position the label relative to the text baseline,
4158
	 *    like renderer.text, or to the upper border of the rectangle. 
4159
	 * @param {String} className Class name for the group 
4160
	 */
4161
	label: function (str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) {
4162
4163
		var renderer = this,
4164
			wrapper = renderer.g(className),
4165
			text = renderer.text('', 0, 0, useHTML)
4166
				.attr({
4167
					zIndex: 1
4168
				}),
4169
				//.add(wrapper),
4170
			box,
4171
			bBox,
4172
			alignFactor = 0,
4173
			padding = 3,
4174
			paddingLeft = 0,
4175
			width,
4176
			height,
4177
			wrapperX,
4178
			wrapperY,
4179
			crispAdjust = 0,
4180
			deferredAttr = {},
4181
			baselineOffset,
4182
			attrSetters = wrapper.attrSetters,
4183
			needsBox;
4184
4185
		/**
4186
		 * This function runs after the label is added to the DOM (when the bounding box is
4187
		 * available), and after the text of the label is updated to detect the new bounding
4188
		 * box and reflect it in the border box.
4189
		 */
4190
		function updateBoxSize() {
4191
			var boxX,
4192
				boxY,
4193
				style = text.element.style;
4194
				
4195
			bBox = (width === undefined || height === undefined || wrapper.styles.textAlign) &&
4196
				text.getBBox();
4197
			wrapper.width = (width || bBox.width || 0) + 2 * padding + paddingLeft;
4198
			wrapper.height = (height || bBox.height || 0) + 2 * padding;
4199
			
4200
			// update the label-scoped y offset
4201
			baselineOffset = padding + renderer.fontMetrics(style && style.fontSize).b;
4202
				
4203
			if (needsBox) {
4204
				
4205
				// create the border box if it is not already present
4206
				if (!box) {
4207
					boxX = mathRound(-alignFactor * padding);
4208
					boxY = baseline ? -baselineOffset : 0;
4209
				
4210
					wrapper.box = box = shape ?
4211
						renderer.symbol(shape, boxX, boxY, wrapper.width, wrapper.height) :
4212
						renderer.rect(boxX, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]);
4213
					box.add(wrapper);
4214
				}
4215
	
4216
				// apply the box attributes
4217
				if (!box.isImg) { // #1630
4218
					box.attr(merge({
4219
						width: wrapper.width,
4220
						height: wrapper.height
4221
					}, deferredAttr));
4222
				}
4223
				deferredAttr = null;
4224
			}
4225
		}
4226
4227
		/**
4228
		 * This function runs after setting text or padding, but only if padding is changed
4229
		 */
4230
		function updateTextPadding() {
4231
			var styles = wrapper.styles,
4232
				textAlign = styles && styles.textAlign,
4233
				x = paddingLeft + padding * (1 - alignFactor),
4234
				y;
4235
			
4236
			// determin y based on the baseline
4237
			y = baseline ? 0 : baselineOffset;
4238
4239
			// compensate for alignment
4240
			if (defined(width) && (textAlign === 'center' || textAlign === 'right')) {
4241
				x += { center: 0.5, right: 1 }[textAlign] * (width - bBox.width);
4242
			}
4243
4244
			// update if anything changed
4245
			if (x !== text.x || y !== text.y) {
4246
				text.attr({
4247
					x: x,
4248
					y: y
4249
				});
4250
			}
4251
4252
			// record current values
4253
			text.x = x;
4254
			text.y = y;
4255
		}
4256
4257
		/**
4258
		 * Set a box attribute, or defer it if the box is not yet created
4259
		 * @param {Object} key
4260
		 * @param {Object} value
4261
		 */
4262
		function boxAttr(key, value) {
4263
			if (box) {
4264
				box.attr(key, value);
4265
			} else {
4266
				deferredAttr[key] = value;
4267
			}
4268
		}
4269
4270
		function getSizeAfterAdd() {
4271
			text.add(wrapper);
4272
			wrapper.attr({
4273
				text: str, // alignment is available now
4274
				x: x,
4275
				y: y
4276
			});
4277
			
4278
			if (box && defined(anchorX)) {
4279
				wrapper.attr({
4280
					anchorX: anchorX,
4281
					anchorY: anchorY
4282
				});
4283
			}
4284
		}
4285
4286
		/**
4287
		 * After the text element is added, get the desired size of the border box
4288
		 * and add it before the text in the DOM.
4289
		 */
4290
		addEvent(wrapper, 'add', getSizeAfterAdd);
4291
4292
		/*
4293
		 * Add specific attribute setters.
4294
		 */
4295
4296
		// only change local variables
4297
		attrSetters.width = function (value) {
4298
			width = value;
4299
			return false;
4300
		};
4301
		attrSetters.height = function (value) {
4302
			height = value;
4303
			return false;
4304
		};
4305
		attrSetters.padding =  function (value) {
4306
			if (defined(value) && value !== padding) {
4307
				padding = value;
4308
				updateTextPadding();
4309
			}
4310
			return false;
4311
		};
4312
		attrSetters.paddingLeft =  function (value) {
4313
			if (defined(value) && value !== paddingLeft) {
4314
				paddingLeft = value;
4315
				updateTextPadding();
4316
			}
4317
			return false;
4318
		};
4319
		
4320
4321
		// change local variable and set attribue as well
4322
		attrSetters.align = function (value) {
4323
			alignFactor = { left: 0, center: 0.5, right: 1 }[value];
4324
			return false; // prevent setting text-anchor on the group
4325
		};
4326
		
4327
		// apply these to the box and the text alike
4328
		attrSetters.text = function (value, key) {
4329
			text.attr(key, value);
4330
			updateBoxSize();
4331
			updateTextPadding();
4332
			return false;
4333
		};
4334
4335
		// apply these to the box but not to the text
4336
		attrSetters[STROKE_WIDTH] = function (value, key) {
4337
			needsBox = true;
4338
			crispAdjust = value % 2 / 2;
4339
			boxAttr(key, value);
4340
			return false;
4341
		};
4342
		attrSetters.stroke = attrSetters.fill = attrSetters.r = function (value, key) {
4343
			if (key === 'fill') {
4344
				needsBox = true;
4345
			}
4346
			boxAttr(key, value);
4347
			return false;
4348
		};
4349
		attrSetters.anchorX = function (value, key) {
4350
			anchorX = value;
4351
			boxAttr(key, value + crispAdjust - wrapperX);
4352
			return false;
4353
		};
4354
		attrSetters.anchorY = function (value, key) {
4355
			anchorY = value;
4356
			boxAttr(key, value - wrapperY);
4357
			return false;
4358
		};
4359
		
4360
		// rename attributes
4361
		attrSetters.x = function (value) {
4362
			wrapper.x = value; // for animation getter
4363
			value -= alignFactor * ((width || bBox.width) + padding);
4364
			wrapperX = mathRound(value); 
4365
			
4366
			wrapper.attr('translateX', wrapperX);
4367
			return false;
4368
		};
4369
		attrSetters.y = function (value) {
4370
			wrapperY = wrapper.y = mathRound(value);
4371
			wrapper.attr('translateY', wrapperY);
4372
			return false;
4373
		};
4374
4375
		// Redirect certain methods to either the box or the text
4376
		var baseCss = wrapper.css;
4377
		return extend(wrapper, {
4378
			/**
4379
			 * Pick up some properties and apply them to the text instead of the wrapper
4380
			 */
4381
			css: function (styles) {
4382
				if (styles) {
4383
					var textStyles = {};
4384
					styles = merge(styles); // create a copy to avoid altering the original object (#537)
4385
					each(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width', 'textDecoration'], function (prop) {
4386
						if (styles[prop] !== UNDEFINED) {
4387
							textStyles[prop] = styles[prop];
4388
							delete styles[prop];
4389
						}
4390
					});
4391
					text.css(textStyles);
4392
				}
4393
				return baseCss.call(wrapper, styles);
4394
			},
4395
			/**
4396
			 * Return the bounding box of the box, not the group
4397
			 */
4398
			getBBox: function () {
4399
				return {
4400
					width: bBox.width + 2 * padding,
4401
					height: bBox.height + 2 * padding,
4402
					x: bBox.x - padding,
4403
					y: bBox.y - padding
4404
				};
4405
			},
4406
			/**
4407
			 * Apply the shadow to the box
4408
			 */
4409
			shadow: function (b) {
4410
				if (box) {
4411
					box.shadow(b);
4412
				}
4413
				return wrapper;
4414
			},
4415
			/**
4416
			 * Destroy and release memory.
4417
			 */
4418
			destroy: function () {
4419
				removeEvent(wrapper, 'add', getSizeAfterAdd);
4420
4421
				// Added by button implementation
4422
				removeEvent(wrapper.element, 'mouseenter');
4423
				removeEvent(wrapper.element, 'mouseleave');
4424
4425
				if (text) {
4426
					text = text.destroy();
4427
				}
4428
				if (box) {
4429
					box = box.destroy();
4430
				}
4431
				// Call base implementation to destroy the rest
4432
				SVGElement.prototype.destroy.call(wrapper);
4433
				
4434
				// Release local pointers (#1298)
4435
				wrapper = renderer = updateBoxSize = updateTextPadding = boxAttr = getSizeAfterAdd = null;
4436
			}
4437
		});
4438
	}
4439
}; // end SVGRenderer
4440
4441
4442
// general renderer
4443
Renderer = SVGRenderer;
4444
4445
4446
/* ****************************************************************************
4447
 *                                                                            *
4448
 * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE                              *
4449
 *                                                                            *
4450
 * For applications and websites that don't need IE support, like platform    *
4451
 * targeted mobile apps and web apps, this code can be removed.               *
4452
 *                                                                            *
4453
 *****************************************************************************/
4454
4455
/**
4456
 * @constructor
4457
 */
4458
var VMLRenderer, VMLElement;
4459
if (!hasSVG && !useCanVG) {
4460
4461
/**
4462
 * The VML element wrapper.
4463
 */
4464
Highcharts.VMLElement = VMLElement = {
4465
4466
	/**
4467
	 * Initialize a new VML element wrapper. It builds the markup as a string
4468
	 * to minimize DOM traffic.
4469
	 * @param {Object} renderer
4470
	 * @param {Object} nodeName
4471
	 */
4472
	init: function (renderer, nodeName) {
4473
		var wrapper = this,
4474
			markup =  ['<', nodeName, ' filled="f" stroked="f"'],
4475
			style = ['position: ', ABSOLUTE, ';'],
4476
			isDiv = nodeName === DIV;
4477
4478
		// divs and shapes need size
4479
		if (nodeName === 'shape' || isDiv) {
4480
			style.push('left:0;top:0;width:1px;height:1px;');
4481
		}
4482
		style.push('visibility: ', isDiv ? HIDDEN : VISIBLE);
4483
		
4484
		markup.push(' style="', style.join(''), '"/>');
4485
4486
		// create element with default attributes and style
4487
		if (nodeName) {
4488
			markup = isDiv || nodeName === 'span' || nodeName === 'img' ?
4489
				markup.join('')
4490
				: renderer.prepVML(markup);
4491
			wrapper.element = createElement(markup);
4492
		}
4493
4494
		wrapper.renderer = renderer;
4495
		wrapper.attrSetters = {};
4496
	},
4497
4498
	/**
4499
	 * Add the node to the given parent
4500
	 * @param {Object} parent
4501
	 */
4502
	add: function (parent) {
4503
		var wrapper = this,
4504
			renderer = wrapper.renderer,
4505
			element = wrapper.element,
4506
			box = renderer.box,
4507
			inverted = parent && parent.inverted,
4508
4509
			// get the parent node
4510
			parentNode = parent ?
4511
				parent.element || parent :
4512
				box;
4513
4514
4515
		// if the parent group is inverted, apply inversion on all children
4516
		if (inverted) { // only on groups
4517
			renderer.invertChild(element, parentNode);
4518
		}
4519
4520
		// append it
4521
		parentNode.appendChild(element);
4522
4523
		// align text after adding to be able to read offset
4524
		wrapper.added = true;
4525
		if (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) {
4526
			wrapper.updateTransform();
4527
		}
4528
		
4529
		// fire an event for internal hooks
4530
		fireEvent(wrapper, 'add');
4531
4532
		return wrapper;
4533
	},
4534
4535
	/**
4536
	 * VML always uses htmlUpdateTransform
4537
	 */
4538
	updateTransform: SVGElement.prototype.htmlUpdateTransform,
4539
4540
	/**
4541
	 * Get or set attributes
4542
	 */
4543
	attr: function (hash, val) {
4544
		var wrapper = this,
4545
			key,
4546
			value,
4547
			i,
4548
			result,
4549
			element = wrapper.element || {},
4550
			elemStyle = element.style,
4551
			nodeName = element.nodeName,
4552
			renderer = wrapper.renderer,
4553
			symbolName = wrapper.symbolName,
4554
			hasSetSymbolSize,
4555
			shadows = wrapper.shadows,
4556
			skipAttr,
4557
			attrSetters = wrapper.attrSetters,
4558
			ret = wrapper;
4559
4560
		// single key-value pair
4561
		if (isString(hash) && defined(val)) {
4562
			key = hash;
4563
			hash = {};
4564
			hash[key] = val;
4565
		}
4566
4567
		// used as a getter, val is undefined
4568
		if (isString(hash)) {
4569
			key = hash;
4570
			if (key === 'strokeWidth' || key === 'stroke-width') {
4571
				ret = wrapper.strokeweight;
4572
			} else {
4573
				ret = wrapper[key];
4574
			}
4575
4576
		// setter
4577
		} else {
4578
			for (key in hash) {
4579
				value = hash[key];
4580
				skipAttr = false;
4581
4582
				// check for a specific attribute setter
4583
				result = attrSetters[key] && attrSetters[key].call(wrapper, value, key);
4584
4585
				if (result !== false && value !== null) { // #620
4586
4587
					if (result !== UNDEFINED) {
4588
						value = result; // the attribute setter has returned a new value to set
4589
					}
4590
4591
4592
					// prepare paths
4593
					// symbols
4594
					if (symbolName && /^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(key)) {
4595
						// if one of the symbol size affecting parameters are changed,
4596
						// check all the others only once for each call to an element's
4597
						// .attr() method
4598
						if (!hasSetSymbolSize) {
4599
							wrapper.symbolAttr(hash);
4600
4601
							hasSetSymbolSize = true;
4602
						}
4603
						skipAttr = true;
4604
4605
					} else if (key === 'd') {
4606
						value = value || [];
4607
						wrapper.d = value.join(' '); // used in getter for animation
4608
4609
						// convert paths
4610
						i = value.length;
4611
						var convertedPath = [],
4612
							clockwise;
4613
						while (i--) {
4614
4615
							// Multiply by 10 to allow subpixel precision.
4616
							// Substracting half a pixel seems to make the coordinates
4617
							// align with SVG, but this hasn't been tested thoroughly
4618
							if (isNumber(value[i])) {
4619
								convertedPath[i] = mathRound(value[i] * 10) - 5;
4620
							} else if (value[i] === 'Z') { // close the path
4621
								convertedPath[i] = 'x';
4622
							} else {
4623
								convertedPath[i] = value[i];
4624
4625
								// When the start X and end X coordinates of an arc are too close,
4626
								// they are rounded to the same value above. In this case, substract 1 from the end X
4627
								// position. #760, #1371. 
4628
								if (value.isArc && (value[i] === 'wa' || value[i] === 'at')) {
4629
									clockwise = value[i] === 'wa' ? 1 : -1; // #1642
4630
									if (convertedPath[i + 5] === convertedPath[i + 7]) {
4631
										convertedPath[i + 7] -= clockwise;
4632
									}
4633
									// Start and end Y (#1410)
4634
									if (convertedPath[i + 6] === convertedPath[i + 8]) {
4635
										convertedPath[i + 8] -= clockwise;
4636
									}
4637
								}
4638
							}
4639
4640
						}
4641
						value = convertedPath.join(' ') || 'x';
4642
						element.path = value;
4643
4644
						// update shadows
4645
						if (shadows) {
4646
							i = shadows.length;
4647
							while (i--) {
4648
								shadows[i].path = shadows[i].cutOff ? this.cutOffPath(value, shadows[i].cutOff) : value;
4649
							}
4650
						}
4651
						skipAttr = true;
4652
4653
					// handle visibility
4654
					} else if (key === 'visibility') {
4655
4656
						// let the shadow follow the main element
4657
						if (shadows) {
4658
							i = shadows.length;
4659
							while (i--) {
4660
								shadows[i].style[key] = value;
4661
							}
4662
						}
4663
						
4664
						// Instead of toggling the visibility CSS property, move the div out of the viewport. 
4665
						// This works around #61 and #586							
4666
						if (nodeName === 'DIV') {
4667
							value = value === HIDDEN ? '-999em' : 0;
4668
							
4669
							// In order to redraw, IE7 needs the div to be visible when tucked away
4670
							// outside the viewport. So the visibility is actually opposite of 
4671
							// the expected value. This applies to the tooltip only. 
4672
							if (!docMode8) {
4673
								elemStyle[key] = value ? VISIBLE : HIDDEN;
4674
							}
4675
							key = 'top';
4676
						}
4677
						elemStyle[key] = value;	
4678
						skipAttr = true;
4679
4680
					// directly mapped to css
4681
					} else if (key === 'zIndex') {
4682
4683
						if (value) {
4684
							elemStyle[key] = value;
4685
						}
4686
						skipAttr = true;
4687
4688
					// x, y, width, height
4689
					} else if (inArray(key, ['x', 'y', 'width', 'height']) !== -1) {
4690
						
4691
						wrapper[key] = value; // used in getter
4692
						
4693
						if (key === 'x' || key === 'y') {
4694
							key = { x: 'left', y: 'top' }[key];
4695
						} else {
4696
							value = mathMax(0, value); // don't set width or height below zero (#311)
4697
						}
4698
						
4699
						// clipping rectangle special
4700
						if (wrapper.updateClipping) {
4701
							wrapper[key] = value; // the key is now 'left' or 'top' for 'x' and 'y'
4702
							wrapper.updateClipping();
4703
						} else {
4704
							// normal
4705
							elemStyle[key] = value;
4706
						}
4707
4708
						skipAttr = true;						
4709
4710
					// class name
4711
					} else if (key === 'class' && nodeName === 'DIV') {
4712
						// IE8 Standards mode has problems retrieving the className
4713
						element.className = value;						
4714
4715
					// stroke
4716
					} else if (key === 'stroke') {
4717
4718
						value = renderer.color(value, element, key);
4719
4720
						key = 'strokecolor';
4721
4722
					// stroke width
4723
					} else if (key === 'stroke-width' || key === 'strokeWidth') {
4724
						element.stroked = value ? true : false;
4725
						key = 'strokeweight';
4726
						wrapper[key] = value; // used in getter, issue #113
4727
						if (isNumber(value)) {
4728
							value += PX;
4729
						}
4730
4731
					// dashStyle
4732
					} else if (key === 'dashstyle') {
4733
						var strokeElem = element.getElementsByTagName('stroke')[0] ||
4734
							createElement(renderer.prepVML(['<stroke/>']), null, null, element);
4735
						strokeElem[key] = value || 'solid';
4736
						wrapper.dashstyle = value; /* because changing stroke-width will change the dash length
4737
							and cause an epileptic effect */
4738
						skipAttr = true;
4739
4740
					// fill
4741
					} else if (key === 'fill') {
4742
4743
						if (nodeName === 'SPAN') { // text color
4744
							elemStyle.color = value;
4745
						} else if (nodeName !== 'IMG') { // #1336
4746
							element.filled = value !== NONE ? true : false;
4747
4748
							value = renderer.color(value, element, key, wrapper);
4749
4750
							key = 'fillcolor';
4751
						}
4752
4753
					// opacity: don't bother - animation is too slow and filters introduce artifacts
4754
					} else if (key === 'opacity') {
4755
						/*css(element, {
4756
							opacity: value
4757
						});*/
4758
						skipAttr = true;
4759
						
4760
					// rotation on VML elements
4761
					} else if (nodeName === 'shape' && key === 'rotation') {
4762
						wrapper[key] = value;
4763
						// Correction for the 1x1 size of the shape container. Used in gauge needles.
4764
						element.style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX;
4765
						element.style.top = mathRound(mathCos(value * deg2rad)) + PX;
4766
4767
					// translation for animation
4768
					} else if (key === 'translateX' || key === 'translateY' || key === 'rotation') {
4769
						wrapper[key] = value;
4770
						wrapper.updateTransform();
4771
4772
						skipAttr = true;
4773
4774
					// text for rotated and non-rotated elements
4775
					} else if (key === 'text') {
4776
						this.bBox = null;
4777
						element.innerHTML = value;
4778
						skipAttr = true;
4779
					} 
4780
4781
4782
					if (!skipAttr) {
4783
						if (docMode8) { // IE8 setAttribute bug
4784
							element[key] = value;
4785
						} else {
4786
							attr(element, key, value);
4787
						}
4788
					}
4789
4790
				}
4791
			}
4792
		}
4793
		return ret;
4794
	},
4795
4796
	/**
4797
	 * Set the element's clipping to a predefined rectangle
4798
	 *
4799
	 * @param {String} id The id of the clip rectangle
4800
	 */
4801
	clip: function (clipRect) {
4802
		var wrapper = this,
4803
			clipMembers,
4804
			cssRet;
4805
4806
		if (clipRect) {
4807
			clipMembers = clipRect.members;
4808
			erase(clipMembers, wrapper); // Ensure unique list of elements (#1258)
4809
			clipMembers.push(wrapper);
4810
			wrapper.destroyClip = function () {
4811
				erase(clipMembers, wrapper);
4812
			};
4813
			cssRet = clipRect.getCSS(wrapper);
4814
			
4815
		} else {
4816
			if (wrapper.destroyClip) {
4817
				wrapper.destroyClip();
4818
			}
4819
			cssRet = { clip: docMode8 ? 'inherit' : 'rect(auto)' }; // #1214
4820
		}
4821
		
4822
		return wrapper.css(cssRet);
4823
			
4824
	},
4825
4826
	/**
4827
	 * Set styles for the element
4828
	 * @param {Object} styles
4829
	 */
4830
	css: SVGElement.prototype.htmlCss,
4831
4832
	/**
4833
	 * Removes a child either by removeChild or move to garbageBin.
4834
	 * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.
4835
	 */
4836
	safeRemoveChild: function (element) {
4837
		// discardElement will detach the node from its parent before attaching it
4838
		// to the garbage bin. Therefore it is important that the node is attached and have parent.
4839
		if (element.parentNode) {
4840
			discardElement(element);
4841
		}
4842
	},
4843
4844
	/**
4845
	 * Extend element.destroy by removing it from the clip members array
4846
	 */
4847
	destroy: function () {
4848
		if (this.destroyClip) {
4849
			this.destroyClip();
4850
		}
4851
4852
		return SVGElement.prototype.destroy.apply(this);
4853
	},
4854
4855
	/**
4856
	 * Add an event listener. VML override for normalizing event parameters.
4857
	 * @param {String} eventType
4858
	 * @param {Function} handler
4859
	 */
4860
	on: function (eventType, handler) {
4861
		// simplest possible event model for internal use
4862
		this.element['on' + eventType] = function () {
4863
			var evt = win.event;
4864
			evt.target = evt.srcElement;
4865
			handler(evt);
4866
		};
4867
		return this;
4868
	},
4869
	
4870
	/**
4871
	 * In stacked columns, cut off the shadows so that they don't overlap
4872
	 */
4873
	cutOffPath: function (path, length) {
4874
		
4875
		var len;
4876
		
4877
		path = path.split(/[ ,]/);
4878
		len = path.length;
4879
		
4880
		if (len === 9 || len === 11) {
4881
			path[len - 4] = path[len - 2] = pInt(path[len - 2]) - 10 * length;
4882
		}
4883
		return path.join(' ');		
4884
	},
4885
4886
	/**
4887
	 * Apply a drop shadow by copying elements and giving them different strokes
4888
	 * @param {Boolean|Object} shadowOptions
4889
	 */
4890
	shadow: function (shadowOptions, group, cutOff) {
4891
		var shadows = [],
4892
			i,
4893
			element = this.element,
4894
			renderer = this.renderer,
4895
			shadow,
4896
			elemStyle = element.style,
4897
			markup,
4898
			path = element.path,
4899
			strokeWidth,
4900
			modifiedPath,
4901
			shadowWidth,
4902
			shadowElementOpacity;
4903
4904
		// some times empty paths are not strings
4905
		if (path && typeof path.value !== 'string') {
4906
			path = 'x';
4907
		}
4908
		modifiedPath = path;
4909
4910
		if (shadowOptions) {
4911
			shadowWidth = pick(shadowOptions.width, 3);
4912
			shadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;
4913
			for (i = 1; i <= 3; i++) {
4914
				
4915
				strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
4916
				
4917
				// Cut off shadows for stacked column items
4918
				if (cutOff) {
4919
					modifiedPath = this.cutOffPath(path.value, strokeWidth + 0.5);
4920
				}
4921
				
4922
				markup = ['<shape isShadow="true" strokeweight="', strokeWidth,
4923
					'" filled="false" path="', modifiedPath,
4924
					'" coordsize="10 10" style="', element.style.cssText, '" />'];
4925
				
4926
				shadow = createElement(renderer.prepVML(markup),
4927
					null, {
4928
						left: pInt(elemStyle.left) + pick(shadowOptions.offsetX, 1),
4929
						top: pInt(elemStyle.top) + pick(shadowOptions.offsetY, 1)
4930
					}
4931
				);
4932
				if (cutOff) {
4933
					shadow.cutOff = strokeWidth + 1;
4934
				}
4935
				
4936
				// apply the opacity
4937
				markup = ['<stroke color="', shadowOptions.color || 'black', '" opacity="', shadowElementOpacity * i, '"/>'];
4938
				createElement(renderer.prepVML(markup), null, null, shadow);
4939
4940
4941
				// insert it
4942
				if (group) {
4943
					group.element.appendChild(shadow);
4944
				} else {
4945
					element.parentNode.insertBefore(shadow, element);
4946
				}
4947
4948
				// record it
4949
				shadows.push(shadow);
4950
4951
			}
4952
4953
			this.shadows = shadows;
4954
		}
4955
		return this;
4956
4957
	}
4958
};
4959
VMLElement = extendClass(SVGElement, VMLElement);
4960
4961
/**
4962
 * The VML renderer
4963
 */
4964
var VMLRendererExtension = { // inherit SVGRenderer
4965
4966
	Element: VMLElement,
4967
	isIE8: userAgent.indexOf('MSIE 8.0') > -1,
4968
4969
4970
	/**
4971
	 * Initialize the VMLRenderer
4972
	 * @param {Object} container
4973
	 * @param {Number} width
4974
	 * @param {Number} height
4975
	 */
4976
	init: function (container, width, height) {
4977
		var renderer = this,
4978
			boxWrapper,
4979
			box;
4980
4981
		renderer.alignedObjects = [];
4982
4983
		boxWrapper = renderer.createElement(DIV);
4984
		box = boxWrapper.element;
4985
		box.style.position = RELATIVE; // for freeform drawing using renderer directly
4986
		container.appendChild(boxWrapper.element);
4987
4988
4989
		// generate the containing box
4990
		renderer.isVML = true;
4991
		renderer.box = box;
4992
		renderer.boxWrapper = boxWrapper;
4993
4994
4995
		renderer.setSize(width, height, false);
4996
4997
		// The only way to make IE6 and IE7 print is to use a global namespace. However,
4998
		// with IE8 the only way to make the dynamic shapes visible in screen and print mode
4999
		// seems to be to add the xmlns attribute and the behaviour style inline.
5000
		if (!doc.namespaces.hcv) {
5001
5002
			doc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');
5003
5004
			// setup default css
5005
			doc.createStyleSheet().cssText =
5006
				'hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke' +
5007
				'{ behavior:url(#default#VML); display: inline-block; } ';
5008
5009
		}
5010
	},
5011
	
5012
	
5013
	/**
5014
	 * Detect whether the renderer is hidden. This happens when one of the parent elements
5015
	 * has display: none
5016
	 */
5017
	isHidden: function () {
5018
		return !this.box.offsetWidth;			
5019
	},
5020
5021
	/**
5022
	 * Define a clipping rectangle. In VML it is accomplished by storing the values
5023
	 * for setting the CSS style to all associated members.
5024
	 *
5025
	 * @param {Number} x
5026
	 * @param {Number} y
5027
	 * @param {Number} width
5028
	 * @param {Number} height
5029
	 */
5030
	clipRect: function (x, y, width, height) {
5031
5032
		// create a dummy element
5033
		var clipRect = this.createElement(),
5034
			isObj = isObject(x);
5035
		
5036
		// mimic a rectangle with its style object for automatic updating in attr
5037
		return extend(clipRect, {
5038
			members: [],
5039
			left: isObj ? x.x : x,
5040
			top: isObj ? x.y : y,
5041
			width: isObj ? x.width : width,
5042
			height: isObj ? x.height : height,
5043
			getCSS: function (wrapper) {
5044
				var element = wrapper.element,
5045
					nodeName = element.nodeName,
5046
					isShape = nodeName === 'shape',
5047
					inverted = wrapper.inverted,
5048
					rect = this,
5049
					top = rect.top - (isShape ? element.offsetTop : 0),
5050
					left = rect.left,
5051
					right = left + rect.width,
5052
					bottom = top + rect.height,
5053
					ret = {
5054
						clip: 'rect(' +
5055
							mathRound(inverted ? left : top) + 'px,' +
5056
							mathRound(inverted ? bottom : right) + 'px,' +
5057
							mathRound(inverted ? right : bottom) + 'px,' +
5058
							mathRound(inverted ? top : left) + 'px)'
5059
					};
5060
5061
				// issue 74 workaround
5062
				if (!inverted && docMode8 && nodeName === 'DIV') {
5063
					extend(ret, {
5064
						width: right + PX,
5065
						height: bottom + PX
5066
					});
5067
				}
5068
				return ret;
5069
			},
5070
5071
			// used in attr and animation to update the clipping of all members
5072
			updateClipping: function () {
5073
				each(clipRect.members, function (member) {
5074
					member.css(clipRect.getCSS(member));
5075
				});
5076
			}
5077
		});
5078
5079
	},
5080
5081
5082
	/**
5083
	 * Take a color and return it if it's a string, make it a gradient if it's a
5084
	 * gradient configuration object, and apply opacity.
5085
	 *
5086
	 * @param {Object} color The color or config object
5087
	 */
5088
	color: function (color, elem, prop, wrapper) {
5089
		var renderer = this,
5090
			colorObject,
5091
			regexRgba = /^rgba/,
5092
			markup,
5093
			fillType,
5094
			ret = NONE;
5095
5096
		// Check for linear or radial gradient
5097
		if (color && color.linearGradient) {
5098
			fillType = 'gradient';
5099
		} else if (color && color.radialGradient) {
5100
			fillType = 'pattern';
5101
		}
5102
		
5103
		
5104
		if (fillType) {
5105
5106
			var stopColor,
5107
				stopOpacity,
5108
				gradient = color.linearGradient || color.radialGradient,
5109
				x1,
5110
				y1, 
5111
				x2,
5112
				y2,
5113
				opacity1,
5114
				opacity2,
5115
				color1,
5116
				color2,
5117
				fillAttr = '',
5118
				stops = color.stops,
5119
				firstStop,
5120
				lastStop,
5121
				colors = [],
5122
				addFillNode = function () {
5123
					// Add the fill subnode. When colors attribute is used, the meanings of opacity and o:opacity2
5124
					// are reversed.
5125
					markup = ['<fill colors="' + colors.join(',') + '" opacity="', opacity2, '" o:opacity2="', opacity1,
5126
						'" type="', fillType, '" ', fillAttr, 'focus="100%" method="any" />'];
5127
					createElement(renderer.prepVML(markup), null, null, elem);
5128
				};
5129
			
5130
			// Extend from 0 to 1
5131
			firstStop = stops[0];
5132
			lastStop = stops[stops.length - 1];
5133
			if (firstStop[0] > 0) {
5134
				stops.unshift([
5135
					0,
5136
					firstStop[1]
5137
				]);
5138
			}
5139
			if (lastStop[0] < 1) {
5140
				stops.push([
5141
					1,
5142
					lastStop[1]
5143
				]);
5144
			}
5145
5146
			// Compute the stops
5147
			each(stops, function (stop, i) {
5148
				if (regexRgba.test(stop[1])) {
5149
					colorObject = Color(stop[1]);
5150
					stopColor = colorObject.get('rgb');
5151
					stopOpacity = colorObject.get('a');
5152
				} else {
5153
					stopColor = stop[1];
5154
					stopOpacity = 1;
5155
				}
5156
				
5157
				// Build the color attribute
5158
				colors.push((stop[0] * 100) + '% ' + stopColor); 
5159
5160
				// Only start and end opacities are allowed, so we use the first and the last
5161
				if (!i) {
5162
					opacity1 = stopOpacity;
5163
					color2 = stopColor;
5164
				} else {
5165
					opacity2 = stopOpacity;
5166
					color1 = stopColor;
5167
				}
5168
			});
5169
			
5170
			// Apply the gradient to fills only.
5171
			if (prop === 'fill') {
5172
				
5173
				// Handle linear gradient angle
5174
				if (fillType === 'gradient') {
5175
					x1 = gradient.x1 || gradient[0] || 0;
5176
					y1 = gradient.y1 || gradient[1] || 0;
5177
					x2 = gradient.x2 || gradient[2] || 0;
5178
					y2 = gradient.y2 || gradient[3] || 0;
5179
					fillAttr = 'angle="' + (90  - math.atan(
5180
						(y2 - y1) / // y vector
5181
						(x2 - x1) // x vector
5182
						) * 180 / mathPI) + '"';
5183
						
5184
					addFillNode();
5185
					
5186
				// Radial (circular) gradient
5187
				} else { 
5188
					
5189
					var r = gradient.r,
5190
						sizex = r * 2,
5191
						sizey = r * 2,
5192
						cx = gradient.cx,
5193
						cy = gradient.cy,
5194
						radialReference = elem.radialReference,
5195
						bBox,
5196
						applyRadialGradient = function () {
5197
							if (radialReference) {
5198
								bBox = wrapper.getBBox();
5199
								cx += (radialReference[0] - bBox.x) / bBox.width - 0.5;
5200
								cy += (radialReference[1] - bBox.y) / bBox.height - 0.5;
5201
								sizex *= radialReference[2] / bBox.width;
5202
								sizey *= radialReference[2] / bBox.height;							
5203
							}
5204
							fillAttr = 'src="' + defaultOptions.global.VMLRadialGradientURL + '" ' +
5205
								'size="' + sizex + ',' + sizey + '" ' +
5206
								'origin="0.5,0.5" ' +
5207
								'position="' + cx + ',' + cy + '" ' +
5208
								'color2="' + color2 + '" ';
5209
							
5210
							addFillNode();
5211
						};
5212
					
5213
					// Apply radial gradient
5214
					if (wrapper.added) {
5215
						applyRadialGradient();
5216
					} else {
5217
						// We need to know the bounding box to get the size and position right
5218
						addEvent(wrapper, 'add', applyRadialGradient);
5219
					}
5220
					
5221
					// The fill element's color attribute is broken in IE8 standards mode, so we
5222
					// need to set the parent shape's fillcolor attribute instead.
5223
					ret = color1;
5224
				}
5225
			
5226
			// Gradients are not supported for VML stroke, return the first color. #722.
5227
			} else {
5228
				ret = stopColor;
5229
			}
5230
5231
		// if the color is an rgba color, split it and add a fill node
5232
		// to hold the opacity component
5233
		} else if (regexRgba.test(color) && elem.tagName !== 'IMG') {
5234
5235
			colorObject = Color(color);
5236
5237
			markup = ['<', prop, ' opacity="', colorObject.get('a'), '"/>'];
5238
			createElement(this.prepVML(markup), null, null, elem);
5239
5240
			ret = colorObject.get('rgb');
5241
5242
5243
		} else {
5244
			var propNodes = elem.getElementsByTagName(prop); // 'stroke' or 'fill' node
5245
			if (propNodes.length) {
5246
				propNodes[0].opacity = 1;
5247
				propNodes[0].type = 'solid';
5248
			}
5249
			ret = color;
5250
		}
5251
5252
		return ret;
5253
	},
5254
5255
	/**
5256
	 * Take a VML string and prepare it for either IE8 or IE6/IE7.
5257
	 * @param {Array} markup A string array of the VML markup to prepare
5258
	 */
5259
	prepVML: function (markup) {
5260
		var vmlStyle = 'display:inline-block;behavior:url(#default#VML);',
5261
			isIE8 = this.isIE8;
5262
5263
		markup = markup.join('');
5264
5265
		if (isIE8) { // add xmlns and style inline
5266
			markup = markup.replace('/>', ' xmlns="urn:schemas-microsoft-com:vml" />');
5267
			if (markup.indexOf('style="') === -1) {
5268
				markup = markup.replace('/>', ' style="' + vmlStyle + '" />');
5269
			} else {
5270
				markup = markup.replace('style="', 'style="' + vmlStyle);
5271
			}
5272
5273
		} else { // add namespace
5274
			markup = markup.replace('<', '<hcv:');
5275
		}
5276
5277
		return markup;
5278
	},
5279
5280
	/**
5281
	 * Create rotated and aligned text
5282
	 * @param {String} str
5283
	 * @param {Number} x
5284
	 * @param {Number} y
5285
	 */
5286
	text: SVGRenderer.prototype.html,
5287
5288
	/**
5289
	 * Create and return a path element
5290
	 * @param {Array} path
5291
	 */
5292
	path: function (path) {
5293
		var attr = {
5294
			// subpixel precision down to 0.1 (width and height = 1px)
5295
			coordsize: '10 10'
5296
		};
5297
		if (isArray(path)) {
5298
			attr.d = path;
5299
		} else if (isObject(path)) { // attributes
5300
			extend(attr, path);
5301
		}
5302
		// create the shape
5303
		return this.createElement('shape').attr(attr);
5304
	},
5305
5306
	/**
5307
	 * Create and return a circle element. In VML circles are implemented as
5308
	 * shapes, which is faster than v:oval
5309
	 * @param {Number} x
5310
	 * @param {Number} y
5311
	 * @param {Number} r
5312
	 */
5313
	circle: function (x, y, r) {
5314
		var circle = this.symbol('circle');
5315
		if (isObject(x)) {
5316
			r = x.r;
5317
			y = x.y;
5318
			x = x.x;
5319
		}
5320
		circle.isCircle = true; // Causes x and y to mean center (#1682)
5321
		return circle.attr({ x: x, y: y, width: 2 * r, height: 2 * r });
5322
	},
5323
5324
	/**
5325
	 * Create a group using an outer div and an inner v:group to allow rotating
5326
	 * and flipping. A simple v:group would have problems with positioning
5327
	 * child HTML elements and CSS clip.
5328
	 *
5329
	 * @param {String} name The name of the group
5330
	 */
5331
	g: function (name) {
5332
		var wrapper,
5333
			attribs;
5334
5335
		// set the class name
5336
		if (name) {
5337
			attribs = { 'className': PREFIX + name, 'class': PREFIX + name };
5338
		}
5339
5340
		// the div to hold HTML and clipping
5341
		wrapper = this.createElement(DIV).attr(attribs);
5342
5343
		return wrapper;
5344
	},
5345
5346
	/**
5347
	 * VML override to create a regular HTML image
5348
	 * @param {String} src
5349
	 * @param {Number} x
5350
	 * @param {Number} y
5351
	 * @param {Number} width
5352
	 * @param {Number} height
5353
	 */
5354
	image: function (src, x, y, width, height) {
5355
		var obj = this.createElement('img')
5356
			.attr({ src: src });
5357
5358
		if (arguments.length > 1) {
5359
			obj.attr({
5360
				x: x,
5361
				y: y,
5362
				width: width,
5363
				height: height
5364
			});
5365
		}
5366
		return obj;
5367
	},
5368
5369
	/**
5370
	 * VML uses a shape for rect to overcome bugs and rotation problems
5371
	 */
5372
	rect: function (x, y, width, height, r, strokeWidth) {
5373
5374
		if (isObject(x)) {
5375
			y = x.y;
5376
			width = x.width;
5377
			height = x.height;
5378
			strokeWidth = x.strokeWidth;
5379
			x = x.x;
5380
		}
5381
		var wrapper = this.symbol('rect');
5382
		wrapper.r = r;
5383
5384
		return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));
5385
	},
5386
5387
	/**
5388
	 * In the VML renderer, each child of an inverted div (group) is inverted
5389
	 * @param {Object} element
5390
	 * @param {Object} parentNode
5391
	 */
5392
	invertChild: function (element, parentNode) {
5393
		var parentStyle = parentNode.style;
5394
		css(element, {
5395
			flip: 'x',
5396
			left: pInt(parentStyle.width) - 1,
5397
			top: pInt(parentStyle.height) - 1,
5398
			rotation: -90
5399
		});
5400
	},
5401
5402
	/**
5403
	 * Symbol definitions that override the parent SVG renderer's symbols
5404
	 *
5405
	 */
5406
	symbols: {
5407
		// VML specific arc function
5408
		arc: function (x, y, w, h, options) {
5409
			var start = options.start,
5410
				end = options.end,
5411
				radius = options.r || w || h,
5412
				innerRadius = options.innerR,
5413
				cosStart = mathCos(start),
5414
				sinStart = mathSin(start),
5415
				cosEnd = mathCos(end),
5416
				sinEnd = mathSin(end),
5417
				ret;
5418
5419
			if (end - start === 0) { // no angle, don't show it.
5420
				return ['x'];
5421
			}
5422
5423
			ret = [
5424
				'wa', // clockwise arc to
5425
				x - radius, // left
5426
				y - radius, // top
5427
				x + radius, // right
5428
				y + radius, // bottom
5429
				x + radius * cosStart, // start x
5430
				y + radius * sinStart, // start y
5431
				x + radius * cosEnd, // end x
5432
				y + radius * sinEnd  // end y
5433
			];
5434
5435
			if (options.open && !innerRadius) {
5436
				ret.push(
5437
					'e',
5438
					M, 
5439
					x,// - innerRadius, 
5440
					y// - innerRadius
5441
				);
5442
			}
5443
5444
			ret.push(
5445
				'at', // anti clockwise arc to
5446
				x - innerRadius, // left
5447
				y - innerRadius, // top
5448
				x + innerRadius, // right
5449
				y + innerRadius, // bottom
5450
				x + innerRadius * cosEnd, // start x
5451
				y + innerRadius * sinEnd, // start y
5452
				x + innerRadius * cosStart, // end x
5453
				y + innerRadius * sinStart, // end y
5454
				'x', // finish path
5455
				'e' // close
5456
			);
5457
			
5458
			ret.isArc = true;
5459
			return ret;
5460
5461
		},
5462
		// Add circle symbol path. This performs significantly faster than v:oval.
5463
		circle: function (x, y, w, h, wrapper) {
5464
			// Center correction, #1682
5465
			if (wrapper && wrapper.isCircle) {
5466
				x -= w / 2;
5467
				y -= h / 2;
5468
			}
5469
5470
			// Return the path
5471
			return [
5472
				'wa', // clockwisearcto
5473
				x, // left
5474
				y, // top
5475
				x + w, // right
5476
				y + h, // bottom
5477
				x + w, // start x
5478
				y + h / 2,     // start y
5479
				x + w, // end x
5480
				y + h / 2,     // end y
5481
				//'x', // finish path
5482
				'e' // close
5483
			];
5484
		},
5485
		/**
5486
		 * Add rectangle symbol path which eases rotation and omits arcsize problems
5487
		 * compared to the built-in VML roundrect shape
5488
		 *
5489
		 * @param {Number} left Left position
5490
		 * @param {Number} top Top position
5491
		 * @param {Number} r Border radius
5492
		 * @param {Object} options Width and height
5493
		 */
5494
5495
		rect: function (left, top, width, height, options) {
5496
			
5497
			var right = left + width,
5498
				bottom = top + height,
5499
				ret,
5500
				r;
5501
5502
			// No radius, return the more lightweight square
5503
			if (!defined(options) || !options.r) {
5504
				ret = SVGRenderer.prototype.symbols.square.apply(0, arguments);
5505
				
5506
			// Has radius add arcs for the corners
5507
			} else {
5508
			
5509
				r = mathMin(options.r, width, height);
5510
				ret = [
5511
					M,
5512
					left + r, top,
5513
	
5514
					L,
5515
					right - r, top,
5516
					'wa',
5517
					right - 2 * r, top,
5518
					right, top + 2 * r,
5519
					right - r, top,
5520
					right, top + r,
5521
	
5522
					L,
5523
					right, bottom - r,
5524
					'wa',
5525
					right - 2 * r, bottom - 2 * r,
5526
					right, bottom,
5527
					right, bottom - r,
5528
					right - r, bottom,
5529
	
5530
					L,
5531
					left + r, bottom,
5532
					'wa',
5533
					left, bottom - 2 * r,
5534
					left + 2 * r, bottom,
5535
					left + r, bottom,
5536
					left, bottom - r,
5537
	
5538
					L,
5539
					left, top + r,
5540
					'wa',
5541
					left, top,
5542
					left + 2 * r, top + 2 * r,
5543
					left, top + r,
5544
					left + r, top,
5545
	
5546
	
5547
					'x',
5548
					'e'
5549
				];
5550
			}
5551
			return ret;
5552
		}
5553
	}
5554
};
5555
Highcharts.VMLRenderer = VMLRenderer = function () {
5556
	this.init.apply(this, arguments);
5557
};
5558
VMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension);
5559
5560
	// general renderer
5561
	Renderer = VMLRenderer;
5562
}
5563
5564
/* ****************************************************************************
5565
 *                                                                            *
5566
 * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE                                *
5567
 *                                                                            *
5568
 *****************************************************************************/
5569
/* ****************************************************************************
5570
 *                                                                            *
5571
 * START OF ANDROID < 3 SPECIFIC CODE. THIS CAN BE REMOVED IF YOU'RE NOT      *
5572
 * TARGETING THAT SYSTEM.                                                     *
5573
 *                                                                            *
5574
 *****************************************************************************/
5575
var CanVGRenderer,
5576
	CanVGController;
5577
5578
if (useCanVG) {
5579
	/**
5580
	 * The CanVGRenderer is empty from start to keep the source footprint small.
5581
	 * When requested, the CanVGController downloads the rest of the source packaged
5582
	 * together with the canvg library.
5583
	 */
5584
	Highcharts.CanVGRenderer = CanVGRenderer = function () {
5585
		// Override the global SVG namespace to fake SVG/HTML that accepts CSS
5586
		SVG_NS = 'http://www.w3.org/1999/xhtml';
5587
	};
5588
5589
	/**
5590
	 * Start with an empty symbols object. This is needed when exporting is used (exporting.src.js will add a few symbols), but 
5591
	 * the implementation from SvgRenderer will not be merged in until first render.
5592
	 */
5593
	CanVGRenderer.prototype.symbols = {};
5594
5595
	/**
5596
	 * Handles on demand download of canvg rendering support.
5597
	 */
5598
	CanVGController = (function () {
5599
		// List of renderering calls
5600
		var deferredRenderCalls = [];
5601
5602
		/**
5603
		 * When downloaded, we are ready to draw deferred charts.
5604
		 */
5605
		function drawDeferred() {
5606
			var callLength = deferredRenderCalls.length,
5607
				callIndex;
5608
5609
			// Draw all pending render calls
5610
			for (callIndex = 0; callIndex < callLength; callIndex++) {
5611
				deferredRenderCalls[callIndex]();
5612
			}
5613
			// Clear the list
5614
			deferredRenderCalls = [];
5615
		}
5616
5617
		return {
5618
			push: function (func, scriptLocation) {
5619
				// Only get the script once
5620
				if (deferredRenderCalls.length === 0) {
5621
					getScript(scriptLocation, drawDeferred);
5622
				}
5623
				// Register render call
5624
				deferredRenderCalls.push(func);
5625
			}
5626
		};
5627
	}());
5628
5629
	Renderer = CanVGRenderer;
5630
} // end CanVGRenderer
5631
5632
/* ****************************************************************************
5633
 *                                                                            *
5634
 * END OF ANDROID < 3 SPECIFIC CODE                                           *
5635
 *                                                                            *
5636
 *****************************************************************************/
5637
5638
/**
5639
 * The Tick class
5640
 */
5641
function Tick(axis, pos, type, noLabel) {
5642
	this.axis = axis;
5643
	this.pos = pos;
5644
	this.type = type || '';
5645
	this.isNew = true;
5646
5647
	if (!type && !noLabel) {
5648
		this.addLabel();
5649
	}
5650
}
5651
5652
Tick.prototype = {
5653
	/**
5654
	 * Write the tick label
5655
	 */
5656
	addLabel: function () {
5657
		var tick = this,
5658
			axis = tick.axis,
5659
			options = axis.options,
5660
			chart = axis.chart,
5661
			horiz = axis.horiz,
5662
			categories = axis.categories,
5663
			names = axis.series[0] && axis.series[0].names,
5664
			pos = tick.pos,
5665
			labelOptions = options.labels,
5666
			str,
5667
			tickPositions = axis.tickPositions,
5668
			width = (horiz && categories &&
5669
				!labelOptions.step && !labelOptions.staggerLines &&
5670
				!labelOptions.rotation &&
5671
				chart.plotWidth / tickPositions.length) ||
5672
				(!horiz && (chart.optionsMarginLeft || chart.plotWidth / 2)), // #1580
5673
			isFirst = pos === tickPositions[0],
5674
			isLast = pos === tickPositions[tickPositions.length - 1],
5675
			css,
5676
			attr,
5677
			value = categories ?
5678
				pick(categories[pos], names && names[pos], pos) : 
5679
				pos,
5680
			label = tick.label,
5681
			tickPositionInfo = tickPositions.info,
5682
			dateTimeLabelFormat;
5683
5684
		// Set the datetime label format. If a higher rank is set for this position, use that. If not,
5685
		// use the general format.
5686
		if (axis.isDatetimeAxis && tickPositionInfo) {
5687
			dateTimeLabelFormat = options.dateTimeLabelFormats[tickPositionInfo.higherRanks[pos] || tickPositionInfo.unitName];
5688
		}
5689
5690
		// set properties for access in render method
5691
		tick.isFirst = isFirst;
5692
		tick.isLast = isLast;
5693
5694
		// get the string
5695
		str = axis.labelFormatter.call({
5696
			axis: axis,
5697
			chart: chart,
5698
			isFirst: isFirst,
5699
			isLast: isLast,
5700
			dateTimeLabelFormat: dateTimeLabelFormat,
5701
			value: axis.isLog ? correctFloat(lin2log(value)) : value
5702
		});
5703
5704
		// prepare CSS
5705
		css = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX };
5706
		css = extend(css, labelOptions.style);
5707
5708
		// first call
5709
		if (!defined(label)) {
5710
			attr = {
5711
				align: labelOptions.align
5712
			};
5713
			if (isNumber(labelOptions.rotation)) {
5714
				attr.rotation = labelOptions.rotation;
5715
			}			
5716
			tick.label =
5717
				defined(str) && labelOptions.enabled ?
5718
					chart.renderer.text(
5719
							str,
5720
							0,
5721
							0,
5722
							labelOptions.useHTML
5723
						)
5724
						.attr(attr)
5725
						// without position absolute, IE export sometimes is wrong
5726
						.css(css)
5727
						.add(axis.labelGroup) :
5728
					null;
5729
5730
		// update
5731
		} else if (label) {
5732
			label.attr({
5733
					text: str
5734
				})
5735
				.css(css);
5736
		}
5737
	},
5738
5739
	/**
5740
	 * Get the offset height or width of the label
5741
	 */
5742
	getLabelSize: function () {
5743
		var label = this.label,
5744
			axis = this.axis;
5745
		return label ?
5746
			((this.labelBBox = label.getBBox()))[axis.horiz ? 'height' : 'width'] :
5747
			0;
5748
	},
5749
5750
	/**
5751
	 * Find how far the labels extend to the right and left of the tick's x position. Used for anti-collision
5752
	 * detection with overflow logic.
5753
	 */
5754
	getLabelSides: function () {
5755
		var bBox = this.labelBBox, // assume getLabelSize has run at this point
5756
			axis = this.axis,
5757
			options = axis.options,
5758
			labelOptions = options.labels,
5759
			width = bBox.width,
5760
			leftSide = width * { left: 0, center: 0.5, right: 1 }[labelOptions.align] - labelOptions.x;
5761
5762
		return [-leftSide, width - leftSide];
5763
	},
5764
5765
	/**
5766
	 * Handle the label overflow by adjusting the labels to the left and right edge, or
5767
	 * hide them if they collide into the neighbour label.
5768
	 */
5769
	handleOverflow: function (index, xy) {
5770
		var show = true,
5771
			axis = this.axis,
5772
			chart = axis.chart,
5773
			isFirst = this.isFirst,
5774
			isLast = this.isLast,
5775
			x = xy.x,
5776
			reversed = axis.reversed,
5777
			tickPositions = axis.tickPositions;
5778
5779
		if (isFirst || isLast) {
5780
5781
			var sides = this.getLabelSides(),
5782
				leftSide = sides[0],
5783
				rightSide = sides[1],
5784
				plotLeft = chart.plotLeft,
5785
				plotRight = plotLeft + axis.len,
5786
				neighbour = axis.ticks[tickPositions[index + (isFirst ? 1 : -1)]],
5787
				neighbourEdge = neighbour && neighbour.label.xy && neighbour.label.xy.x + neighbour.getLabelSides()[isFirst ? 0 : 1];
5788
5789
			if ((isFirst && !reversed) || (isLast && reversed)) {
5790
				// Is the label spilling out to the left of the plot area?
5791
				if (x + leftSide < plotLeft) {
5792
5793
					// Align it to plot left
5794
					x = plotLeft - leftSide;
5795
5796
					// Hide it if it now overlaps the neighbour label
5797
					if (neighbour && x + rightSide > neighbourEdge) {
5798
						show = false;
5799
					}
5800
				}
5801
5802
			} else {
5803
				// Is the label spilling out to the right of the plot area?
5804
				if (x + rightSide > plotRight) {
5805
5806
					// Align it to plot right
5807
					x = plotRight - rightSide;
5808
5809
					// Hide it if it now overlaps the neighbour label
5810
					if (neighbour && x + leftSide < neighbourEdge) {
5811
						show = false;
5812
					}
5813
5814
				}
5815
			}
5816
5817
			// Set the modified x position of the label
5818
			xy.x = x;
5819
		}
5820
		return show;
5821
	},
5822
5823
	/**
5824
	 * Get the x and y position for ticks and labels
5825
	 */
5826
	getPosition: function (horiz, pos, tickmarkOffset, old) {
5827
		var axis = this.axis,
5828
			chart = axis.chart,
5829
			cHeight = (old && chart.oldChartHeight) || chart.chartHeight;
5830
		
5831
		return {
5832
			x: horiz ?
5833
				axis.translate(pos + tickmarkOffset, null, null, old) + axis.transB :
5834
				axis.left + axis.offset + (axis.opposite ? ((old && chart.oldChartWidth) || chart.chartWidth) - axis.right - axis.left : 0),
5835
5836
			y: horiz ?
5837
				cHeight - axis.bottom + axis.offset - (axis.opposite ? axis.height : 0) :
5838
				cHeight - axis.translate(pos + tickmarkOffset, null, null, old) - axis.transB
5839
		};
5840
		
5841
	},
5842
	
5843
	/**
5844
	 * Get the x, y position of the tick label
5845
	 */
5846
	getLabelPosition: function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
5847
		var axis = this.axis,
5848
			transA = axis.transA,
5849
			reversed = axis.reversed,
5850
			staggerLines = axis.staggerLines;
5851
			
5852
		x = x + labelOptions.x - (tickmarkOffset && horiz ?
5853
			tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
5854
		y = y + labelOptions.y - (tickmarkOffset && !horiz ?
5855
			tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
5856
		
5857
		// Vertically centered
5858
		if (!defined(labelOptions.y)) {
5859
			y += pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;
5860
		}
5861
		
5862
		// Correct for staggered labels
5863
		if (staggerLines) {
5864
			y += (index / (step || 1) % staggerLines) * 16;
5865
		}
5866
		
5867
		return {
5868
			x: x,
5869
			y: y
5870
		};
5871
	},
5872
	
5873
	/**
5874
	 * Extendible method to return the path of the marker
5875
	 */
5876
	getMarkPath: function (x, y, tickLength, tickWidth, horiz, renderer) {
5877
		return renderer.crispLine([
5878
				M,
5879
				x,
5880
				y,
5881
				L,
5882
				x + (horiz ? 0 : -tickLength),
5883
				y + (horiz ? tickLength : 0)
5884
			], tickWidth);
5885
	},
5886
5887
	/**
5888
	 * Put everything in place
5889
	 *
5890
	 * @param index {Number}
5891
	 * @param old {Boolean} Use old coordinates to prepare an animation into new position
5892
	 */
5893
	render: function (index, old, opacity) {
5894
		var tick = this,
5895
			axis = tick.axis,
5896
			options = axis.options,
5897
			chart = axis.chart,
5898
			renderer = chart.renderer,
5899
			horiz = axis.horiz,
5900
			type = tick.type,
5901
			label = tick.label,
5902
			pos = tick.pos,
5903
			labelOptions = options.labels,
5904
			gridLine = tick.gridLine,
5905
			gridPrefix = type ? type + 'Grid' : 'grid',
5906
			tickPrefix = type ? type + 'Tick' : 'tick',
5907
			gridLineWidth = options[gridPrefix + 'LineWidth'],
5908
			gridLineColor = options[gridPrefix + 'LineColor'],
5909
			dashStyle = options[gridPrefix + 'LineDashStyle'],
5910
			tickLength = options[tickPrefix + 'Length'],
5911
			tickWidth = options[tickPrefix + 'Width'] || 0,
5912
			tickColor = options[tickPrefix + 'Color'],
5913
			tickPosition = options[tickPrefix + 'Position'],
5914
			gridLinePath,
5915
			mark = tick.mark,
5916
			markPath,
5917
			step = labelOptions.step,
5918
			attribs,
5919
			show = true,
5920
			tickmarkOffset = axis.tickmarkOffset,
5921
			xy = tick.getPosition(horiz, pos, tickmarkOffset, old),
5922
			x = xy.x,
5923
			y = xy.y,
5924
			reverseCrisp = ((horiz && x === axis.pos) || (!horiz && y === axis.pos + axis.len)) ? -1 : 1, // #1480
5925
			staggerLines = axis.staggerLines;
5926
5927
		this.isActive = true;
5928
		
5929
		// create the grid line
5930
		if (gridLineWidth) {
5931
			gridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLineWidth * reverseCrisp, old, true);
5932
5933
			if (gridLine === UNDEFINED) {
5934
				attribs = {
5935
					stroke: gridLineColor,
5936
					'stroke-width': gridLineWidth
5937
				};
5938
				if (dashStyle) {
5939
					attribs.dashstyle = dashStyle;
5940
				}
5941
				if (!type) {
5942
					attribs.zIndex = 1;
5943
				}
5944
				if (old) {
5945
					attribs.opacity = 0;
5946
				}
5947
				tick.gridLine = gridLine =
5948
					gridLineWidth ?
5949
						renderer.path(gridLinePath)
5950
							.attr(attribs).add(axis.gridGroup) :
5951
						null;
5952
			}
5953
5954
			// If the parameter 'old' is set, the current call will be followed
5955
			// by another call, therefore do not do any animations this time
5956
			if (!old && gridLine && gridLinePath) {
5957
				gridLine[tick.isNew ? 'attr' : 'animate']({
5958
					d: gridLinePath,
5959
					opacity: opacity
5960
				});
5961
			}
5962
		}
5963
5964
		// create the tick mark
5965
		if (tickWidth && tickLength) {
5966
5967
			// negate the length
5968
			if (tickPosition === 'inside') {
5969
				tickLength = -tickLength;
5970
			}
5971
			if (axis.opposite) {
5972
				tickLength = -tickLength;
5973
			}
5974
5975
			markPath = tick.getMarkPath(x, y, tickLength, tickWidth * reverseCrisp, horiz, renderer);
5976
5977
			if (mark) { // updating
5978
				mark.animate({
5979
					d: markPath,
5980
					opacity: opacity
5981
				});
5982
			} else { // first time
5983
				tick.mark = renderer.path(
5984
					markPath
5985
				).attr({
5986
					stroke: tickColor,
5987
					'stroke-width': tickWidth,
5988
					opacity: opacity
5989
				}).add(axis.axisGroup);
5990
			}
5991
		}
5992
5993
		// the label is created on init - now move it into place
5994
		if (label && !isNaN(x)) {
5995
			label.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step);
5996
5997
			// apply show first and show last
5998
			if ((tick.isFirst && !pick(options.showFirstLabel, 1)) ||
5999
					(tick.isLast && !pick(options.showLastLabel, 1))) {
6000
				show = false;
6001
6002
			// Handle label overflow and show or hide accordingly
6003
			} else if (!staggerLines && horiz && labelOptions.overflow === 'justify' && !tick.handleOverflow(index, xy)) {
6004
				show = false;
6005
			}
6006
6007
			// apply step
6008
			if (step && index % step) {
6009
				// show those indices dividable by step
6010
				show = false;
6011
			}
6012
6013
			// Set the new position, and show or hide
6014
			if (show && !isNaN(xy.y)) {
6015
				xy.opacity = opacity;
6016
				label[tick.isNew ? 'attr' : 'animate'](xy);
6017
				tick.isNew = false;
6018
			} else {
6019
				label.attr('y', -9999); // #1338
6020
			}
6021
		}
6022
	},
6023
6024
	/**
6025
	 * Destructor for the tick prototype
6026
	 */
6027
	destroy: function () {
6028
		destroyObjectProperties(this, this.axis);
6029
	}
6030
};
6031
6032
/**
6033
 * The object wrapper for plot lines and plot bands
6034
 * @param {Object} options
6035
 */
6036
function PlotLineOrBand(axis, options) {
6037
	this.axis = axis;
6038
6039
	if (options) {
6040
		this.options = options;
6041
		this.id = options.id;
6042
	}
6043
}
6044
6045
PlotLineOrBand.prototype = {
6046
	
6047
	/**
6048
	 * Render the plot line or plot band. If it is already existing,
6049
	 * move it.
6050
	 */
6051
	render: function () {
6052
		var plotLine = this,
6053
			axis = plotLine.axis,
6054
			horiz = axis.horiz,
6055
			halfPointRange = (axis.pointRange || 0) / 2,
6056
			options = plotLine.options,
6057
			optionsLabel = options.label,
6058
			label = plotLine.label,
6059
			width = options.width,
6060
			to = options.to,
6061
			from = options.from,
6062
			isBand = defined(from) && defined(to),
6063
			value = options.value,
6064
			dashStyle = options.dashStyle,
6065
			svgElem = plotLine.svgElem,
6066
			path = [],
6067
			addEvent,
6068
			eventType,
6069
			xs,
6070
			ys,
6071
			x,
6072
			y,
6073
			color = options.color,
6074
			zIndex = options.zIndex,
6075
			events = options.events,
6076
			attribs,
6077
			renderer = axis.chart.renderer;
6078
6079
		// logarithmic conversion
6080
		if (axis.isLog) {
6081
			from = log2lin(from);
6082
			to = log2lin(to);
6083
			value = log2lin(value);
6084
		}
6085
6086
		// plot line
6087
		if (width) {
6088
			path = axis.getPlotLinePath(value, width);
6089
			attribs = {
6090
				stroke: color,
6091
				'stroke-width': width
6092
			};
6093
			if (dashStyle) {
6094
				attribs.dashstyle = dashStyle;
6095
			}
6096
		} else if (isBand) { // plot band
6097
			
6098
			// keep within plot area
6099
			from = mathMax(from, axis.min - halfPointRange);
6100
			to = mathMin(to, axis.max + halfPointRange);
6101
			
6102
			path = axis.getPlotBandPath(from, to, options);
6103
			attribs = {
6104
				fill: color
6105
			};
6106
			if (options.borderWidth) {
6107
				attribs.stroke = options.borderColor;
6108
				attribs['stroke-width'] = options.borderWidth;
6109
			}
6110
		} else {
6111
			return;
6112
		}
6113
		// zIndex
6114
		if (defined(zIndex)) {
6115
			attribs.zIndex = zIndex;
6116
		}
6117
6118
		// common for lines and bands
6119
		if (svgElem) {
6120
			if (path) {
6121
				svgElem.animate({
6122
					d: path
6123
				}, null, svgElem.onGetPath);
6124
			} else {
6125
				svgElem.hide();
6126
				svgElem.onGetPath = function () {
6127
					svgElem.show();
6128
				};
6129
			}
6130
		} else if (path && path.length) {
6131
			plotLine.svgElem = svgElem = renderer.path(path)
6132
				.attr(attribs).add();
6133
6134
			// events
6135
			if (events) {
6136
				addEvent = function (eventType) {
6137
					svgElem.on(eventType, function (e) {
6138
						events[eventType].apply(plotLine, [e]);
6139
					});
6140
				};
6141
				for (eventType in events) {
6142
					addEvent(eventType);
6143
				}
6144
			}
6145
		}
6146
6147
		// the plot band/line label
6148
		if (optionsLabel && defined(optionsLabel.text) && path && path.length && axis.width > 0 && axis.height > 0) {
6149
			// apply defaults
6150
			optionsLabel = merge({
6151
				align: horiz && isBand && 'center',
6152
				x: horiz ? !isBand && 4 : 10,
6153
				verticalAlign : !horiz && isBand && 'middle',
6154
				y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4,
6155
				rotation: horiz && !isBand && 90
6156
			}, optionsLabel);
6157
6158
			// add the SVG element
6159
			if (!label) {
6160
				plotLine.label = label = renderer.text(
6161
						optionsLabel.text,
6162
						0,
6163
						0
6164
					)
6165
					.attr({
6166
						align: optionsLabel.textAlign || optionsLabel.align,
6167
						rotation: optionsLabel.rotation,
6168
						zIndex: zIndex
6169
					})
6170
					.css(optionsLabel.style)
6171
					.add();
6172
			}
6173
6174
			// get the bounding box and align the label
6175
			xs = [path[1], path[4], pick(path[6], path[1])];
6176
			ys = [path[2], path[5], pick(path[7], path[2])];
6177
			x = arrayMin(xs);
6178
			y = arrayMin(ys);
6179
6180
			label.align(optionsLabel, false, {
6181
				x: x,
6182
				y: y,
6183
				width: arrayMax(xs) - x,
6184
				height: arrayMax(ys) - y
6185
			});
6186
			label.show();
6187
6188
		} else if (label) { // move out of sight
6189
			label.hide();
6190
		}
6191
6192
		// chainable
6193
		return plotLine;
6194
	},
6195
6196
	/**
6197
	 * Remove the plot line or band
6198
	 */
6199
	destroy: function () {
6200
		var plotLine = this,
6201
			axis = plotLine.axis;
6202
6203
		// remove it from the lookup
6204
		erase(axis.plotLinesAndBands, plotLine);
6205
6206
		destroyObjectProperties(plotLine, this.axis);
6207
	}
6208
};
6209
/**
6210
 * The class for stack items
6211
 */
6212
function StackItem(axis, options, isNegative, x, stackOption, stacking) {
6213
	
6214
	var inverted = axis.chart.inverted;
6215
6216
	this.axis = axis;
6217
6218
	// Tells if the stack is negative
6219
	this.isNegative = isNegative;
6220
6221
	// Save the options to be able to style the label
6222
	this.options = options;
6223
6224
	// Save the x value to be able to position the label later
6225
	this.x = x;
6226
6227
	// Save the stack option on the series configuration object, and whether to treat it as percent
6228
	this.stack = stackOption;
6229
	this.percent = stacking === 'percent';
6230
6231
	// The align options and text align varies on whether the stack is negative and
6232
	// if the chart is inverted or not.
6233
	// First test the user supplied value, then use the dynamic.
6234
	this.alignOptions = {
6235
		align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'),
6236
		verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
6237
		y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),
6238
		x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)
6239
	};
6240
6241
	this.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center');
6242
}
6243
6244
StackItem.prototype = {
6245
	destroy: function () {
6246
		destroyObjectProperties(this, this.axis);
6247
	},
6248
6249
	/**
6250
	 * Sets the total of this stack. Should be called when a serie is hidden or shown
6251
	 * since that will affect the total of other stacks.
6252
	 */
6253
	setTotal: function (total) {
6254
		this.total = total;
6255
		this.cum = total;
6256
	},
6257
6258
	/**
6259
	 * Renders the stack total label and adds it to the stack label group.
6260
	 */
6261
	render: function (group) {
6262
		var options = this.options,
6263
			formatOption = options.format, // docs: added stackLabel.format option
6264
			str = formatOption ?
6265
				format(formatOption, this) : 
6266
				options.formatter.call(this);  // format the text in the label
6267
6268
		// Change the text to reflect the new total and set visibility to hidden in case the serie is hidden
6269
		if (this.label) {
6270
			this.label.attr({text: str, visibility: HIDDEN});
6271
		// Create new label
6272
		} else {
6273
			this.label =
6274
				this.axis.chart.renderer.text(str, 0, 0, options.useHTML)		// dummy positions, actual position updated with setOffset method in columnseries
6275
					.css(options.style)				// apply style
6276
					.attr({
6277
						align: this.textAlign,				// fix the text-anchor
6278
						rotation: options.rotation,	// rotation
6279
						visibility: HIDDEN					// hidden until setOffset is called
6280
					})				
6281
					.add(group);							// add to the labels-group
6282
		}
6283
	},
6284
6285
	/**
6286
	 * Sets the offset that the stack has from the x value and repositions the label.
6287
	 */
6288
	setOffset: function (xOffset, xWidth) {
6289
		var stackItem = this,
6290
			axis = stackItem.axis,
6291
			chart = axis.chart,
6292
			inverted = chart.inverted,
6293
			neg = this.isNegative,							// special treatment is needed for negative stacks
6294
			y = axis.translate(this.percent ? 100 : this.total, 0, 0, 0, 1), // stack value translated mapped to chart coordinates
6295
			yZero = axis.translate(0),						// stack origin
6296
			h = mathAbs(y - yZero),							// stack height
6297
			x = chart.xAxis[0].translate(this.x) + xOffset,	// stack x position
6298
			plotHeight = chart.plotHeight,
6299
			stackBox = {	// this is the box for the complete stack
6300
				x: inverted ? (neg ? y : y - h) : x,
6301
				y: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y),
6302
				width: inverted ? h : xWidth,
6303
				height: inverted ? xWidth : h
6304
			},
6305
			label = this.label,
6306
			alignAttr;
6307
		
6308
		if (label) {
6309
			label.align(this.alignOptions, null, stackBox);	// align the label to the box
6310
				
6311
			// Set visibility (#678)
6312
			alignAttr = label.alignAttr;
6313
			label.attr({ 
6314
				visibility: this.options.crop === false || chart.isInsidePlot(alignAttr.x, alignAttr.y) ? 
6315
					(hasSVG ? 'inherit' : VISIBLE) : 
6316
					HIDDEN
6317
			});
6318
		}
6319
	}
6320
};
6321
/**
6322
 * Create a new axis object
6323
 * @param {Object} chart
6324
 * @param {Object} options
6325
 */
6326
function Axis() {
6327
	this.init.apply(this, arguments);
6328
}
6329
6330
Axis.prototype = {
6331
	
6332
	/**
6333
	 * Default options for the X axis - the Y axis has extended defaults 
6334
	 */
6335
	defaultOptions: {
6336
		// allowDecimals: null,
6337
		// alternateGridColor: null,
6338
		// categories: [],
6339
		dateTimeLabelFormats: {
6340
			millisecond: '%H:%M:%S.%L',
6341
			second: '%H:%M:%S',
6342
			minute: '%H:%M',
6343
			hour: '%H:%M',
6344
			day: '%e. %b',
6345
			week: '%e. %b',
6346
			month: '%b \'%y',
6347
			year: '%Y'
6348
		},
6349
		endOnTick: false,
6350
		gridLineColor: '#C0C0C0',
6351
		// gridLineDashStyle: 'solid',
6352
		// gridLineWidth: 0,
6353
		// reversed: false,
6354
	
6355
		labels: defaultLabelOptions,
6356
			// { step: null },
6357
		lineColor: '#C0D0E0',
6358
		lineWidth: 1,
6359
		//linkedTo: null,
6360
		//max: undefined,
6361
		//min: undefined,
6362
		minPadding: 0.01,
6363
		maxPadding: 0.01,
6364
		//minRange: null,
6365
		minorGridLineColor: '#E0E0E0',
6366
		// minorGridLineDashStyle: null,
6367
		minorGridLineWidth: 1,
6368
		minorTickColor: '#A0A0A0',
6369
		//minorTickInterval: null,
6370
		minorTickLength: 2,
6371
		minorTickPosition: 'outside', // inside or outside
6372
		//minorTickWidth: 0,
6373
		//opposite: false,
6374
		//offset: 0,
6375
		//plotBands: [{
6376
		//	events: {},
6377
		//	zIndex: 1,
6378
		//	labels: { align, x, verticalAlign, y, style, rotation, textAlign }
6379
		//}],
6380
		//plotLines: [{
6381
		//	events: {}
6382
		//  dashStyle: {}
6383
		//	zIndex:
6384
		//	labels: { align, x, verticalAlign, y, style, rotation, textAlign }
6385
		//}],
6386
		//reversed: false,
6387
		// showFirstLabel: true,
6388
		// showLastLabel: true,
6389
		startOfWeek: 1,
6390
		startOnTick: false,
6391
		tickColor: '#C0D0E0',
6392
		//tickInterval: null,
6393
		tickLength: 5,
6394
		tickmarkPlacement: 'between', // on or between
6395
		tickPixelInterval: 100,
6396
		tickPosition: 'outside',
6397
		tickWidth: 1,
6398
		title: {
6399
			//text: null,
6400
			align: 'middle', // low, middle or high
6401
			//margin: 0 for horizontal, 10 for vertical axes,
6402
			//rotation: 0,
6403
			//side: 'outside',
6404
			style: {
6405
				color: '#4d759e',
6406
				//font: defaultFont.replace('normal', 'bold')
6407
				fontWeight: 'bold'
6408
			}
6409
			//x: 0,
6410
			//y: 0
6411
		},
6412
		type: 'linear' // linear, logarithmic or datetime
6413
	},
6414
	
6415
	/**
6416
	 * This options set extends the defaultOptions for Y axes
6417
	 */
6418
	defaultYAxisOptions: {
6419
		endOnTick: true,
6420
		gridLineWidth: 1,
6421
		tickPixelInterval: 72,
6422
		showLastLabel: true,
6423
		labels: {
6424
			align: 'right',
6425
			x: -8,
6426
			y: 3
6427
		},
6428
		lineWidth: 0,
6429
		maxPadding: 0.05,
6430
		minPadding: 0.05,
6431
		startOnTick: true,
6432
		tickWidth: 0,
6433
		title: {
6434
			rotation: 270,
6435
			text: 'Values'
6436
		},
6437
		stackLabels: {
6438
			enabled: false,
6439
			//align: dynamic,
6440
			//y: dynamic,
6441
			//x: dynamic,
6442
			//verticalAlign: dynamic,
6443
			//textAlign: dynamic,
6444
			//rotation: 0,
6445
			formatter: function () {
6446
				return numberFormat(this.total, -1);
6447
			},
6448
			style: defaultLabelOptions.style
6449
		}
6450
	},
6451
	
6452
	/**
6453
	 * These options extend the defaultOptions for left axes
6454
	 */
6455
	defaultLeftAxisOptions: {
6456
		labels: {
6457
			align: 'right',
6458
			x: -8,
6459
			y: null
6460
		},
6461
		title: {
6462
			rotation: 270
6463
		}
6464
	},
6465
	
6466
	/**
6467
	 * These options extend the defaultOptions for right axes
6468
	 */
6469
	defaultRightAxisOptions: {
6470
		labels: {
6471
			align: 'left',
6472
			x: 8,
6473
			y: null
6474
		},
6475
		title: {
6476
			rotation: 90
6477
		}
6478
	},
6479
	
6480
	/**
6481
	 * These options extend the defaultOptions for bottom axes
6482
	 */
6483
	defaultBottomAxisOptions: {
6484
		labels: {
6485
			align: 'center',
6486
			x: 0,
6487
			y: 14
6488
			// overflow: undefined,
6489
			// staggerLines: null
6490
		},
6491
		title: {
6492
			rotation: 0
6493
		}
6494
	},
6495
	/**
6496
	 * These options extend the defaultOptions for left axes
6497
	 */
6498
	defaultTopAxisOptions: {
6499
		labels: {
6500
			align: 'center',
6501
			x: 0,
6502
			y: -5
6503
			// overflow: undefined
6504
			// staggerLines: null
6505
		},
6506
		title: {
6507
			rotation: 0
6508
		}
6509
	},
6510
	
6511
	/**
6512
	 * Initialize the axis
6513
	 */
6514
	init: function (chart, userOptions) {
6515
			
6516
		
6517
		var isXAxis = userOptions.isX,
6518
			axis = this;
6519
	
6520
		// Flag, is the axis horizontal
6521
		axis.horiz = chart.inverted ? !isXAxis : isXAxis;
6522
		
6523
		// Flag, isXAxis
6524
		axis.isXAxis = isXAxis;
6525
		axis.xOrY = isXAxis ? 'x' : 'y';
6526
	
6527
	
6528
		axis.opposite = userOptions.opposite; // needed in setOptions
6529
		axis.side = axis.horiz ?
6530
				(axis.opposite ? 0 : 2) : // top : bottom
6531
				(axis.opposite ? 1 : 3);  // right : left
6532
	
6533
		axis.setOptions(userOptions);
6534
		
6535
	
6536
		var options = this.options,
6537
			type = options.type,
6538
			isDatetimeAxis = type === 'datetime';
6539
	
6540
		axis.labelFormatter = options.labels.formatter || axis.defaultLabelFormatter; // can be overwritten by dynamic format
6541
	
6542
	
6543
		// Flag, stagger lines or not
6544
		axis.staggerLines = axis.horiz && options.labels.staggerLines;
6545
		axis.userOptions = userOptions;
6546
	
6547
		//axis.axisTitleMargin = UNDEFINED,// = options.title.margin,
6548
		axis.minPixelPadding = 0;
6549
		//axis.ignoreMinPadding = UNDEFINED; // can be set to true by a column or bar series
6550
		//axis.ignoreMaxPadding = UNDEFINED;
6551
	
6552
		axis.chart = chart;
6553
		axis.reversed = options.reversed;
6554
		axis.zoomEnabled = options.zoomEnabled !== false;
6555
	
6556
		// Initial categories
6557
		axis.categories = options.categories || type === 'category';
6558
	
6559
		// Elements
6560
		//axis.axisGroup = UNDEFINED;
6561
		//axis.gridGroup = UNDEFINED;
6562
		//axis.axisTitle = UNDEFINED;
6563
		//axis.axisLine = UNDEFINED;
6564
	
6565
		// Shorthand types
6566
		axis.isLog = type === 'logarithmic';
6567
		axis.isDatetimeAxis = isDatetimeAxis;
6568
	
6569
		// Flag, if axis is linked to another axis
6570
		axis.isLinked = defined(options.linkedTo);
6571
		// Linked axis.
6572
		//axis.linkedParent = UNDEFINED;
6573
	
6574
	
6575
		// Flag if percentage mode
6576
		//axis.usePercentage = UNDEFINED;
6577
	
6578
		
6579
		// Tick positions
6580
		//axis.tickPositions = UNDEFINED; // array containing predefined positions
6581
		// Tick intervals
6582
		//axis.tickInterval = UNDEFINED;
6583
		//axis.minorTickInterval = UNDEFINED;
6584
		
6585
		axis.tickmarkOffset = (axis.categories && options.tickmarkPlacement === 'between') ? 0.5 : 0;
6586
	
6587
		// Major ticks
6588
		axis.ticks = {};
6589
		// Minor ticks
6590
		axis.minorTicks = {};
6591
		//axis.tickAmount = UNDEFINED;
6592
	
6593
		// List of plotLines/Bands
6594
		axis.plotLinesAndBands = [];
6595
	
6596
		// Alternate bands
6597
		axis.alternateBands = {};
6598
	
6599
		// Axis metrics
6600
		//axis.left = UNDEFINED;
6601
		//axis.top = UNDEFINED;
6602
		//axis.width = UNDEFINED;
6603
		//axis.height = UNDEFINED;
6604
		//axis.bottom = UNDEFINED;
6605
		//axis.right = UNDEFINED;
6606
		//axis.transA = UNDEFINED;
6607
		//axis.transB = UNDEFINED;
6608
		//axis.oldTransA = UNDEFINED;
6609
		axis.len = 0;
6610
		//axis.oldMin = UNDEFINED;
6611
		//axis.oldMax = UNDEFINED;
6612
		//axis.oldUserMin = UNDEFINED;
6613
		//axis.oldUserMax = UNDEFINED;
6614
		//axis.oldAxisLength = UNDEFINED;
6615
		axis.minRange = axis.userMinRange = options.minRange || options.maxZoom;
6616
		axis.range = options.range;
6617
		axis.offset = options.offset || 0;
6618
	
6619
	
6620
		// Dictionary for stacks
6621
		axis.stacks = {};
6622
		axis._stacksTouched = 0;
6623
	
6624
		// Min and max in the data
6625
		//axis.dataMin = UNDEFINED,
6626
		//axis.dataMax = UNDEFINED,
6627
	
6628
		// The axis range
6629
		axis.max = null;
6630
		axis.min = null;
6631
	
6632
		// User set min and max
6633
		//axis.userMin = UNDEFINED,
6634
		//axis.userMax = UNDEFINED,
6635
6636
		// Run Axis
6637
		
6638
		var eventType,
6639
			events = axis.options.events;
6640
6641
		// Register
6642
		if (inArray(axis, chart.axes) === -1) { // don't add it again on Axis.update()
6643
			chart.axes.push(axis);
6644
			chart[isXAxis ? 'xAxis' : 'yAxis'].push(axis);
6645
		}
6646
6647
		axis.series = axis.series || []; // populated by Series
6648
6649
		// inverted charts have reversed xAxes as default
6650
		if (chart.inverted && isXAxis && axis.reversed === UNDEFINED) {
6651
			axis.reversed = true;
6652
		}
6653
6654
		axis.removePlotBand = axis.removePlotBandOrLine;
6655
		axis.removePlotLine = axis.removePlotBandOrLine;
6656
6657
6658
		// register event listeners
6659
		for (eventType in events) {
6660
			addEvent(axis, eventType, events[eventType]);
6661
		}
6662
6663
		// extend logarithmic axis
6664
		if (axis.isLog) {
6665
			axis.val2lin = log2lin;
6666
			axis.lin2val = lin2log;
6667
		}
6668
	},
6669
	
6670
	/**
6671
	 * Merge and set options
6672
	 */
6673
	setOptions: function (userOptions) {
6674
		this.options = merge(
6675
			this.defaultOptions,
6676
			this.isXAxis ? {} : this.defaultYAxisOptions,
6677
			[this.defaultTopAxisOptions, this.defaultRightAxisOptions,
6678
				this.defaultBottomAxisOptions, this.defaultLeftAxisOptions][this.side],
6679
			merge(
6680
				defaultOptions[this.isXAxis ? 'xAxis' : 'yAxis'], // if set in setOptions (#1053)
6681
				userOptions
6682
			)
6683
		);
6684
	},
6685
6686
	/**
6687
	 * Update the axis with a new options structure
6688
	 */
6689
	update: function (newOptions, redraw) {
6690
		var chart = this.chart;
6691
6692
		newOptions = chart.options[this.xOrY + 'Axis'][this.options.index] = merge(this.userOptions, newOptions);
6693
6694
		this.destroy();
6695
		this._addedPlotLB = false; // #1611
6696
6697
		this.init(chart, newOptions);
6698
6699
		chart.isDirtyBox = true;
6700
		if (pick(redraw, true)) {
6701
			chart.redraw();
6702
		}
6703
	},	
6704
	
6705
	/**
6706
     * Remove the axis from the chart
6707
     */
6708
	remove: function (redraw) {
6709
		var chart = this.chart,
6710
			key = this.xOrY + 'Axis'; // xAxis or yAxis
6711
6712
		// Remove associated series
6713
		each(this.series, function (series) {
6714
			series.remove(false);
6715
		});
6716
6717
		// Remove the axis
6718
		erase(chart.axes, this);
6719
		erase(chart[key], this);
6720
		chart.options[key].splice(this.options.index, 1);
6721
		each(chart[key], function (axis, i) { // Re-index, #1706
6722
			axis.options.index = i;
6723
		});
6724
		this.destroy();
6725
		chart.isDirtyBox = true;
6726
6727
		if (pick(redraw, true)) {
6728
			chart.redraw();
6729
		}
6730
	},
6731
	
6732
	/** 
6733
	 * The default label formatter. The context is a special config object for the label.
6734
	 */
6735
	defaultLabelFormatter: function () {
6736
		var axis = this.axis,
6737
			value = this.value,
6738
			categories = axis.categories, 
6739
			dateTimeLabelFormat = this.dateTimeLabelFormat,
6740
			numericSymbols = defaultOptions.lang.numericSymbols,
6741
			i = numericSymbols && numericSymbols.length,
6742
			multi,
6743
			ret,
6744
			formatOption = axis.options.labels.format,
6745
			
6746
			// make sure the same symbol is added for all labels on a linear axis
6747
			numericSymbolDetector = axis.isLog ? value : axis.tickInterval;
6748
6749
		if (formatOption) {
6750
			ret = format(formatOption, this);
6751
		
6752
		} else if (categories) {
6753
			ret = value;
6754
		
6755
		} else if (dateTimeLabelFormat) { // datetime axis
6756
			ret = dateFormat(dateTimeLabelFormat, value);
6757
		
6758
		} else if (i && numericSymbolDetector >= 1000) {
6759
			// Decide whether we should add a numeric symbol like k (thousands) or M (millions).
6760
			// If we are to enable this in tooltip or other places as well, we can move this
6761
			// logic to the numberFormatter and enable it by a parameter.
6762
			while (i-- && ret === UNDEFINED) {
6763
				multi = Math.pow(1000, i + 1);
6764
				if (numericSymbolDetector >= multi && numericSymbols[i] !== null) {
6765
					ret = numberFormat(value / multi, -1) + numericSymbols[i];
6766
				}
6767
			}
6768
		}
6769
		
6770
		if (ret === UNDEFINED) {
6771
			if (value >= 1000) { // add thousands separators
6772
				ret = numberFormat(value, 0);
6773
6774
			} else { // small numbers
6775
				ret = numberFormat(value, -1);
6776
			}
6777
		}
6778
		
6779
		return ret;
6780
	},
6781
	
6782
	/**
6783
	 * Get the minimum and maximum for the series of each axis
6784
	 */
6785
	getSeriesExtremes: function () {
6786
		var axis = this,
6787
			chart = axis.chart,
6788
			stacks = axis.stacks,
6789
			posStack = [],
6790
			negStack = [],
6791
			stacksTouched = axis._stacksTouched = axis._stacksTouched + 1,
6792
			type,
6793
			i;
6794
		
6795
		axis.hasVisibleSeries = false;
6796
6797
		// reset dataMin and dataMax in case we're redrawing
6798
		axis.dataMin = axis.dataMax = null;
6799
6800
		// loop through this axis' series
6801
		each(axis.series, function (series) {
6802
6803
			if (series.visible || !chart.options.chart.ignoreHiddenSeries) {
6804
6805
				var seriesOptions = series.options,
6806
					stacking,
6807
					posPointStack,
6808
					negPointStack,
6809
					stackKey,
6810
					stackOption,
6811
					negKey,
6812
					xData,
6813
					yData,
6814
					x,
6815
					y,
6816
					threshold = seriesOptions.threshold,
6817
					yDataLength,
6818
					activeYData = [],
6819
					seriesDataMin,
6820
					seriesDataMax,
6821
					activeCounter = 0;
6822
					
6823
				axis.hasVisibleSeries = true;	
6824
					
6825
				// Validate threshold in logarithmic axes
6826
				if (axis.isLog && threshold <= 0) {
6827
					threshold = seriesOptions.threshold = null;
6828
				}
6829
6830
				// Get dataMin and dataMax for X axes
6831
				if (axis.isXAxis) {
6832
					xData = series.xData;
6833
					if (xData.length) {
6834
						axis.dataMin = mathMin(pick(axis.dataMin, xData[0]), arrayMin(xData));
6835
						axis.dataMax = mathMax(pick(axis.dataMax, xData[0]), arrayMax(xData));
6836
					}
6837
6838
				// Get dataMin and dataMax for Y axes, as well as handle stacking and processed data
6839
				} else {
6840
					var isNegative,
6841
						pointStack,
6842
						key,
6843
						cropped = series.cropped,
6844
						xExtremes = series.xAxis.getExtremes(),
6845
						//findPointRange,
6846
						//pointRange,
6847
						j,
6848
						hasModifyValue = !!series.modifyValue;
6849
6850
					// Handle stacking
6851
					stacking = seriesOptions.stacking;
6852
					axis.usePercentage = stacking === 'percent';
6853
6854
					// create a stack for this particular series type
6855
					if (stacking) {
6856
						stackOption = seriesOptions.stack;
6857
						stackKey = series.type + pick(stackOption, '');
6858
						negKey = '-' + stackKey;
6859
						series.stackKey = stackKey; // used in translate
6860
6861
						posPointStack = posStack[stackKey] || []; // contains the total values for each x
6862
						posStack[stackKey] = posPointStack;
6863
6864
						negPointStack = negStack[negKey] || [];
6865
						negStack[negKey] = negPointStack;
6866
					}
6867
					if (axis.usePercentage) {
6868
						axis.dataMin = 0;
6869
						axis.dataMax = 99;
6870
					}
6871
6872
					// processData can alter series.pointRange, so this goes after
6873
					//findPointRange = series.pointRange === null;
6874
6875
					xData = series.processedXData;
6876
					yData = series.processedYData;
6877
					yDataLength = yData.length;
6878
6879
					// loop over the non-null y values and read them into a local array
6880
					for (i = 0; i < yDataLength; i++) {
6881
						x = xData[i];
6882
						y = yData[i];
6883
						
6884
						// Read stacked values into a stack based on the x value,
6885
						// the sign of y and the stack key. Stacking is also handled for null values (#739)
6886
						if (stacking) {
6887
							isNegative = y < threshold;
6888
							pointStack = isNegative ? negPointStack : posPointStack;
6889
							key = isNegative ? negKey : stackKey;
6890
6891
							// Set the stack value and y for extremes
6892
							if (defined(pointStack[x])) { // we're adding to the stack
6893
								pointStack[x] = correctFloat(pointStack[x] + y);
6894
								y = [y, pointStack[x]]; // consider both the actual value and the stack (#1376)
6895
6896
							} else { // it's the first point in the stack
6897
								pointStack[x] = y;
6898
							}
6899
6900
							// add the series
6901
							if (!stacks[key]) {
6902
								stacks[key] = {};
6903
							}
6904
6905
							// If the StackItem is there, just update the values,
6906
							// if not, create one first
6907
							if (!stacks[key][x]) {
6908
								stacks[key][x] = new StackItem(axis, axis.options.stackLabels, isNegative, x, stackOption, stacking);
6909
							}
6910
							stacks[key][x].setTotal(pointStack[x]);
6911
							stacks[key][x].touched = stacksTouched;
6912
						}
6913
						
6914
						// Handle non null values
6915
						if (y !== null && y !== UNDEFINED && (!axis.isLog || (y.length || y > 0))) {							
6916
6917
							// general hook, used for Highstock compare values feature
6918
							if (hasModifyValue) {
6919
								y = series.modifyValue(y);
6920
							}
6921
6922
							// For points within the visible range, including the first point outside the
6923
							// visible range, consider y extremes
6924
							if (series.getExtremesFromAll || cropped || ((xData[i + 1] || x) >= xExtremes.min && 
6925
								(xData[i - 1] || x) <= xExtremes.max)) {
6926
6927
								j = y.length;
6928
								if (j) { // array, like ohlc or range data
6929
									while (j--) {
6930
										if (y[j] !== null) {
6931
											activeYData[activeCounter++] = y[j];
6932
										}
6933
									}
6934
								} else {
6935
									activeYData[activeCounter++] = y;
6936
								}
6937
							}
6938
						}
6939
					}
6940
6941
					// Get the dataMin and dataMax so far. If percentage is used, the min and max are
6942
					// always 0 and 100. If the length of activeYData is 0, continue with null values.
6943
					if (!axis.usePercentage && activeYData.length) {
6944
						series.dataMin = seriesDataMin = arrayMin(activeYData);
6945
						series.dataMax = seriesDataMax = arrayMax(activeYData);
6946
						axis.dataMin = mathMin(pick(axis.dataMin, seriesDataMin), seriesDataMin);
6947
						axis.dataMax = mathMax(pick(axis.dataMax, seriesDataMax), seriesDataMax);
6948
					}
6949
6950
					// Adjust to threshold
6951
					if (defined(threshold)) {
6952
						if (axis.dataMin >= threshold) {
6953
							axis.dataMin = threshold;
6954
							axis.ignoreMinPadding = true;
6955
						} else if (axis.dataMax < threshold) {
6956
							axis.dataMax = threshold;
6957
							axis.ignoreMaxPadding = true;
6958
						}
6959
					}
6960
				}
6961
			}
6962
		});
6963
6964
		// Destroy unused stacks (#1044)
6965
		for (type in stacks) {
6966
			for (i in stacks[type]) {
6967
				if (stacks[type][i].touched < stacksTouched) {
6968
					stacks[type][i].destroy();
6969
					delete stacks[type][i];
6970
				}
6971
			}
6972
		}
6973
		
6974
	},
6975
6976
	/**
6977
	 * Translate from axis value to pixel position on the chart, or back
6978
	 *
6979
	 */
6980
	translate: function (val, backwards, cvsCoord, old, handleLog, pointPlacementBetween) {
6981
		var axis = this,
6982
			axisLength = axis.len,
6983
			sign = 1,
6984
			cvsOffset = 0,
6985
			localA = old ? axis.oldTransA : axis.transA,
6986
			localMin = old ? axis.oldMin : axis.min,
6987
			returnValue,
6988
			minPixelPadding = axis.minPixelPadding,
6989
			postTranslate = (axis.options.ordinal || (axis.isLog && handleLog)) && axis.lin2val;
6990
6991
		if (!localA) {
6992
			localA = axis.transA;
6993
		}
6994
6995
		// In vertical axes, the canvas coordinates start from 0 at the top like in 
6996
		// SVG. 
6997
		if (cvsCoord) {
6998
			sign *= -1; // canvas coordinates inverts the value
6999
			cvsOffset = axisLength;
7000
		}
7001
7002
		// Handle reversed axis
7003
		if (axis.reversed) { 
7004
			sign *= -1;
7005
			cvsOffset -= sign * axisLength;
7006
		}
7007
7008
		// From pixels to value
7009
		if (backwards) { // reverse translation
7010
			
7011
			val = val * sign + cvsOffset;
7012
			val -= minPixelPadding;
7013
			returnValue = val / localA + localMin; // from chart pixel to value
7014
			if (postTranslate) { // log and ordinal axes
7015
				returnValue = axis.lin2val(returnValue);
7016
			}
7017
7018
		// From value to pixels
7019
		} else {
7020
			if (postTranslate) { // log and ordinal axes
7021
				val = axis.val2lin(val);
7022
			}
7023
7024
			returnValue = sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding) +
7025
				(pointPlacementBetween ? localA * axis.pointRange / 2 : 0);
7026
		}
7027
7028
		return returnValue;
7029
	},
7030
7031
	/**
7032
	 * Utility method to translate an axis value to pixel position. 
7033
	 * @param {Number} value A value in terms of axis units
7034
	 * @param {Boolean} paneCoordinates Whether to return the pixel coordinate relative to the chart
7035
	 *        or just the axis/pane itself.
7036
	 */
7037
	toPixels: function (value, paneCoordinates) {
7038
		return this.translate(value, false, !this.horiz, null, true) + (paneCoordinates ? 0 : this.pos);
7039
	},
7040
7041
	/*
7042
	 * Utility method to translate a pixel position in to an axis value
7043
	 * @param {Number} pixel The pixel value coordinate
7044
	 * @param {Boolean} paneCoordiantes Whether the input pixel is relative to the chart or just the
7045
	 *        axis/pane itself.
7046
	 */
7047
	toValue: function (pixel, paneCoordinates) {
7048
		return this.translate(pixel - (paneCoordinates ? 0 : this.pos), true, !this.horiz, null, true);
7049
	},
7050
7051
	/**
7052
	 * Create the path for a plot line that goes from the given value on
7053
	 * this axis, across the plot to the opposite side
7054
	 * @param {Number} value
7055
	 * @param {Number} lineWidth Used for calculation crisp line
7056
	 * @param {Number] old Use old coordinates (for resizing and rescaling)
7057
	 */
7058
	getPlotLinePath: function (value, lineWidth, old, force) {
7059
		var axis = this,
7060
			chart = axis.chart,
7061
			axisLeft = axis.left,
7062
			axisTop = axis.top,
7063
			x1,
7064
			y1,
7065
			x2,
7066
			y2,
7067
			translatedValue = axis.translate(value, null, null, old),
7068
			cHeight = (old && chart.oldChartHeight) || chart.chartHeight,
7069
			cWidth = (old && chart.oldChartWidth) || chart.chartWidth,
7070
			skip,
7071
			transB = axis.transB;
7072
7073
		x1 = x2 = mathRound(translatedValue + transB);
7074
		y1 = y2 = mathRound(cHeight - translatedValue - transB);
7075
7076
		if (isNaN(translatedValue)) { // no min or max
7077
			skip = true;
7078
7079
		} else if (axis.horiz) {
7080
			y1 = axisTop;
7081
			y2 = cHeight - axis.bottom;
7082
			if (x1 < axisLeft || x1 > axisLeft + axis.width) {
7083
				skip = true;
7084
			}
7085
		} else {
7086
			x1 = axisLeft;
7087
			x2 = cWidth - axis.right;
7088
7089
			if (y1 < axisTop || y1 > axisTop + axis.height) {
7090
				skip = true;
7091
			}
7092
		}
7093
		return skip && !force ?
7094
			null :
7095
			chart.renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0);
7096
	},
7097
	
7098
	/**
7099
	 * Create the path for a plot band
7100
	 */
7101
	getPlotBandPath: function (from, to) {
7102
7103
		var toPath = this.getPlotLinePath(to),
7104
			path = this.getPlotLinePath(from);
7105
			
7106
		if (path && toPath) {
7107
			path.push(
7108
				toPath[4],
7109
				toPath[5],
7110
				toPath[1],
7111
				toPath[2]
7112
			);
7113
		} else { // outside the axis area
7114
			path = null;
7115
		}
7116
		
7117
		return path;
7118
	},
7119
	
7120
	/**
7121
	 * Set the tick positions of a linear axis to round values like whole tens or every five.
7122
	 */
7123
	getLinearTickPositions: function (tickInterval, min, max) {
7124
		var pos,
7125
			lastPos,
7126
			roundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval),
7127
			roundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval),
7128
			tickPositions = [];
7129
7130
		// Populate the intermediate values
7131
		pos = roundedMin;
7132
		while (pos <= roundedMax) {
7133
7134
			// Place the tick on the rounded value
7135
			tickPositions.push(pos);
7136
7137
			// Always add the raw tickInterval, not the corrected one.
7138
			pos = correctFloat(pos + tickInterval);
7139
7140
			// If the interval is not big enough in the current min - max range to actually increase
7141
			// the loop variable, we need to break out to prevent endless loop. Issue #619
7142
			if (pos === lastPos) {
7143
				break;
7144
			}
7145
7146
			// Record the last value
7147
			lastPos = pos;
7148
		}
7149
		return tickPositions;
7150
	},
7151
	
7152
	/**
7153
	 * Set the tick positions of a logarithmic axis
7154
	 */
7155
	getLogTickPositions: function (interval, min, max, minor) {
7156
		var axis = this,
7157
			options = axis.options,
7158
			axisLength = axis.len,
7159
			// Since we use this method for both major and minor ticks,
7160
			// use a local variable and return the result
7161
			positions = []; 
7162
		
7163
		// Reset
7164
		if (!minor) {
7165
			axis._minorAutoInterval = null;
7166
		}
7167
		
7168
		// First case: All ticks fall on whole logarithms: 1, 10, 100 etc.
7169
		if (interval >= 0.5) {
7170
			interval = mathRound(interval);
7171
			positions = axis.getLinearTickPositions(interval, min, max);
7172
			
7173
		// Second case: We need intermediary ticks. For example 
7174
		// 1, 2, 4, 6, 8, 10, 20, 40 etc. 
7175
		} else if (interval >= 0.08) {
7176
			var roundedMin = mathFloor(min),
7177
				intermediate,
7178
				i,
7179
				j,
7180
				len,
7181
				pos,
7182
				lastPos,
7183
				break2;
7184
				
7185
			if (interval > 0.3) {
7186
				intermediate = [1, 2, 4];
7187
			} else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc
7188
				intermediate = [1, 2, 4, 6, 8];
7189
			} else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
7190
				intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
7191
			}
7192
			
7193
			for (i = roundedMin; i < max + 1 && !break2; i++) {
7194
				len = intermediate.length;
7195
				for (j = 0; j < len && !break2; j++) {
7196
					pos = log2lin(lin2log(i) * intermediate[j]);
7197
					
7198
					if (pos > min && (!minor || lastPos <= max)) { // #1670
7199
						positions.push(lastPos);
7200
					}
7201
					
7202
					if (lastPos > max) {
7203
						break2 = true;
7204
					}
7205
					lastPos = pos;
7206
				}
7207
			}
7208
			
7209
		// Third case: We are so deep in between whole logarithmic values that
7210
		// we might as well handle the tick positions like a linear axis. For
7211
		// example 1.01, 1.02, 1.03, 1.04.
7212
		} else {
7213
			var realMin = lin2log(min),
7214
				realMax = lin2log(max),
7215
				tickIntervalOption = options[minor ? 'minorTickInterval' : 'tickInterval'],
7216
				filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption,
7217
				tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1),
7218
				totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength;
7219
			
7220
			interval = pick(
7221
				filteredTickIntervalOption,
7222
				axis._minorAutoInterval,
7223
				(realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)
7224
			);
7225
			
7226
			interval = normalizeTickInterval(
7227
				interval, 
7228
				null, 
7229
				math.pow(10, mathFloor(math.log(interval) / math.LN10))
7230
			);
7231
			
7232
			positions = map(axis.getLinearTickPositions(
7233
				interval, 
7234
				realMin,
7235
				realMax	
7236
			), log2lin);
7237
			
7238
			if (!minor) {
7239
				axis._minorAutoInterval = interval / 5;
7240
			}
7241
		}
7242
		
7243
		// Set the axis-level tickInterval variable 
7244
		if (!minor) {
7245
			axis.tickInterval = interval;
7246
		}
7247
		return positions;
7248
	},
7249
7250
	/**
7251
	 * Return the minor tick positions. For logarithmic axes, reuse the same logic
7252
	 * as for major ticks.
7253
	 */
7254
	getMinorTickPositions: function () {
7255
		var axis = this,
7256
			options = axis.options,
7257
			tickPositions = axis.tickPositions,
7258
			minorTickInterval = axis.minorTickInterval,
7259
			minorTickPositions = [],
7260
			pos,
7261
			i,
7262
			len;
7263
		
7264
		if (axis.isLog) {
7265
			len = tickPositions.length;
7266
			for (i = 1; i < len; i++) {
7267
				minorTickPositions = minorTickPositions.concat(
7268
					axis.getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true)
7269
				);	
7270
			}
7271
		} else if (axis.isDatetimeAxis && options.minorTickInterval === 'auto') { // #1314
7272
			minorTickPositions = minorTickPositions.concat(
7273
				getTimeTicks(
7274
					normalizeTimeTickInterval(minorTickInterval),
7275
					axis.min,
7276
					axis.max,
7277
					options.startOfWeek
7278
				)
7279
			);
7280
			if (minorTickPositions[0] < axis.min) {
7281
				minorTickPositions.shift();
7282
			}
7283
		} else {			
7284
			for (pos = axis.min + (tickPositions[0] - axis.min) % minorTickInterval; pos <= axis.max; pos += minorTickInterval) {
7285
				minorTickPositions.push(pos);
7286
			}
7287
		}
7288
		return minorTickPositions;
7289
	},
7290
7291
	/**
7292
	 * Adjust the min and max for the minimum range. Keep in mind that the series data is 
7293
	 * not yet processed, so we don't have information on data cropping and grouping, or 
7294
	 * updated axis.pointRange or series.pointRange. The data can't be processed until
7295
	 * we have finally established min and max.
7296
	 */
7297
	adjustForMinRange: function () {
7298
		var axis = this,
7299
			options = axis.options,
7300
			min = axis.min,
7301
			max = axis.max,
7302
			zoomOffset,
7303
			spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange,
7304
			closestDataRange,
7305
			i,
7306
			distance,
7307
			xData,
7308
			loopLength,
7309
			minArgs,
7310
			maxArgs;
7311
7312
		// Set the automatic minimum range based on the closest point distance
7313
		if (axis.isXAxis && axis.minRange === UNDEFINED && !axis.isLog) {
7314
7315
			if (defined(options.min) || defined(options.max)) {
7316
				axis.minRange = null; // don't do this again
7317
7318
			} else {
7319
7320
				// Find the closest distance between raw data points, as opposed to
7321
				// closestPointRange that applies to processed points (cropped and grouped)
7322
				each(axis.series, function (series) {
7323
					xData = series.xData;
7324
					loopLength = series.xIncrement ? 1 : xData.length - 1;
7325
					for (i = loopLength; i > 0; i--) {
7326
						distance = xData[i] - xData[i - 1];
7327
						if (closestDataRange === UNDEFINED || distance < closestDataRange) {
7328
							closestDataRange = distance;
7329
						}
7330
					}
7331
				});
7332
				axis.minRange = mathMin(closestDataRange * 5, axis.dataMax - axis.dataMin);
7333
			}
7334
		}
7335
7336
		// if minRange is exceeded, adjust
7337
		if (max - min < axis.minRange) {
7338
			var minRange = axis.minRange;
7339
			zoomOffset = (minRange - max + min) / 2;
7340
7341
			// if min and max options have been set, don't go beyond it
7342
			minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)];
7343
			if (spaceAvailable) { // if space is available, stay within the data range
7344
				minArgs[2] = axis.dataMin;
7345
			}
7346
			min = arrayMax(minArgs);
7347
7348
			maxArgs = [min + minRange, pick(options.max, min + minRange)];
7349
			if (spaceAvailable) { // if space is availabe, stay within the data range
7350
				maxArgs[2] = axis.dataMax;
7351
			}
7352
7353
			max = arrayMin(maxArgs);
7354
7355
			// now if the max is adjusted, adjust the min back
7356
			if (max - min < minRange) {
7357
				minArgs[0] = max - minRange;
7358
				minArgs[1] = pick(options.min, max - minRange);
7359
				min = arrayMax(minArgs);
7360
			}
7361
		}
7362
		
7363
		// Record modified extremes
7364
		axis.min = min;
7365
		axis.max = max;
7366
	},
7367
7368
	/**
7369
	 * Update translation information
7370
	 */
7371
	setAxisTranslation: function (saveOld) {
7372
		var axis = this,
7373
			range = axis.max - axis.min,
7374
			pointRange = 0,
7375
			closestPointRange,
7376
			minPointOffset = 0,
7377
			pointRangePadding = 0,
7378
			linkedParent = axis.linkedParent,
7379
			ordinalCorrection,
7380
			transA = axis.transA;
7381
7382
		// adjust translation for padding
7383
		if (axis.isXAxis) {
7384
			if (linkedParent) {
7385
				minPointOffset = linkedParent.minPointOffset;
7386
				pointRangePadding = linkedParent.pointRangePadding;
7387
				
7388
			} else {
7389
				each(axis.series, function (series) {
7390
					var seriesPointRange = series.pointRange,
7391
						pointPlacement = series.options.pointPlacement,
7392
						seriesClosestPointRange = series.closestPointRange;
7393
						
7394
					if (seriesPointRange > range) { // #1446
7395
						seriesPointRange = 0;
7396
					}
7397
					pointRange = mathMax(pointRange, seriesPointRange);
7398
					
7399
					// minPointOffset is the value padding to the left of the axis in order to make
7400
					// room for points with a pointRange, typically columns. When the pointPlacement option
7401
					// is 'between' or 'on', this padding does not apply.
7402
					minPointOffset = mathMax(
7403
						minPointOffset, 
7404
						pointPlacement ? 0 : seriesPointRange / 2
7405
					);
7406
					
7407
					// Determine the total padding needed to the length of the axis to make room for the 
7408
					// pointRange. If the series' pointPlacement is 'on', no padding is added.
7409
					pointRangePadding = mathMax(
7410
						pointRangePadding,
7411
						pointPlacement === 'on' ? 0 : seriesPointRange
7412
					);
7413
7414
					// Set the closestPointRange
7415
					if (!series.noSharedTooltip && defined(seriesClosestPointRange)) {
7416
						closestPointRange = defined(closestPointRange) ?
7417
							mathMin(closestPointRange, seriesClosestPointRange) :
7418
							seriesClosestPointRange;
7419
					}
7420
				});
7421
			}
7422
			
7423
			// Record minPointOffset and pointRangePadding
7424
			ordinalCorrection = axis.ordinalSlope && closestPointRange ? axis.ordinalSlope / closestPointRange : 1; // #988, #1853
7425
			axis.minPointOffset = minPointOffset = minPointOffset * ordinalCorrection;
7426
			axis.pointRangePadding = pointRangePadding = pointRangePadding * ordinalCorrection;
7427
7428
			// pointRange means the width reserved for each point, like in a column chart
7429
			axis.pointRange = mathMin(pointRange, range);
7430
7431
			// closestPointRange means the closest distance between points. In columns
7432
			// it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange
7433
			// is some other value
7434
			axis.closestPointRange = closestPointRange;
7435
		}
7436
7437
		// Secondary values
7438
		if (saveOld) {
7439
			axis.oldTransA = transA;
7440
		}
7441
		axis.translationSlope = axis.transA = transA = axis.len / ((range + pointRangePadding) || 1);
7442
		axis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend
7443
		axis.minPixelPadding = transA * minPointOffset;
7444
	},
7445
7446
	/**
7447
	 * Set the tick positions to round values and optionally extend the extremes
7448
	 * to the nearest tick
7449
	 */
7450
	setTickPositions: function (secondPass) {
7451
		var axis = this,
7452
			chart = axis.chart,
7453
			options = axis.options,
7454
			isLog = axis.isLog,
7455
			isDatetimeAxis = axis.isDatetimeAxis,
7456
			isXAxis = axis.isXAxis,
7457
			isLinked = axis.isLinked,
7458
			tickPositioner = axis.options.tickPositioner,
7459
			magnitude,
7460
			maxPadding = options.maxPadding,
7461
			minPadding = options.minPadding,
7462
			length,
7463
			linkedParentExtremes,
7464
			tickIntervalOption = options.tickInterval,
7465
			minTickIntervalOption = options.minTickInterval,
7466
			tickPixelIntervalOption = options.tickPixelInterval,
7467
			tickPositions,
7468
			categories = axis.categories;
7469
7470
		// linked axis gets the extremes from the parent axis
7471
		if (isLinked) {
7472
			axis.linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo];
7473
			linkedParentExtremes = axis.linkedParent.getExtremes();
7474
			axis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin);
7475
			axis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);
7476
			if (options.type !== axis.linkedParent.options.type) {
7477
				error(11, 1); // Can't link axes of different type
7478
			}
7479
		} else { // initial min and max from the extreme data values
7480
			axis.min = pick(axis.userMin, options.min, axis.dataMin);
7481
			axis.max = pick(axis.userMax, options.max, axis.dataMax);
7482
		}
7483
7484
		if (isLog) {
7485
			if (!secondPass && mathMin(axis.min, pick(axis.dataMin, axis.min)) <= 0) { // #978
7486
				error(10, 1); // Can't plot negative values on log axis
7487
			}
7488
			axis.min = correctFloat(log2lin(axis.min)); // correctFloat cures #934
7489
			axis.max = correctFloat(log2lin(axis.max));
7490
		}
7491
7492
		// handle zoomed range
7493
		if (axis.range) {
7494
			axis.userMin = axis.min = mathMax(axis.min, axis.max - axis.range); // #618
7495
			axis.userMax = axis.max;
7496
			if (secondPass) {
7497
				axis.range = null;  // don't use it when running setExtremes
7498
			}
7499
		}
7500
		
7501
		// Hook for adjusting this.min and this.max. Used by bubble series.
7502
		if (axis.beforePadding) {
7503
			axis.beforePadding();
7504
		}
7505
7506
		// adjust min and max for the minimum range
7507
		axis.adjustForMinRange();
7508
		
7509
		// Pad the values to get clear of the chart's edges. To avoid tickInterval taking the padding
7510
		// into account, we do this after computing tick interval (#1337).
7511
		if (!categories && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) {
7512
			length = axis.max - axis.min;
7513
			if (length) {
7514
				if (!defined(options.min) && !defined(axis.userMin) && minPadding && (axis.dataMin < 0 || !axis.ignoreMinPadding)) {
7515
					axis.min -= length * minPadding;
7516
				}
7517
				if (!defined(options.max) && !defined(axis.userMax)  && maxPadding && (axis.dataMax > 0 || !axis.ignoreMaxPadding)) {
7518
					axis.max += length * maxPadding;
7519
				}
7520
			}
7521
		}
7522
7523
		// get tickInterval
7524
		if (axis.min === axis.max || axis.min === undefined || axis.max === undefined) {
7525
			axis.tickInterval = 1;
7526
		} else if (isLinked && !tickIntervalOption &&
7527
				tickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) {
7528
			axis.tickInterval = axis.linkedParent.tickInterval;
7529
		} else {
7530
			axis.tickInterval = pick(
7531
				tickIntervalOption,
7532
				categories ? // for categoried axis, 1 is default, for linear axis use tickPix
7533
					1 :
7534
					(axis.max - axis.min) * tickPixelIntervalOption / (axis.len || 1)
7535
			);
7536
		}
7537
7538
		// Now we're finished detecting min and max, crop and group series data. This
7539
		// is in turn needed in order to find tick positions in ordinal axes. 
7540
		if (isXAxis && !secondPass) {
7541
			each(axis.series, function (series) {
7542
				series.processData(axis.min !== axis.oldMin || axis.max !== axis.oldMax);
7543
			});
7544
		}
7545
7546
		// set the translation factor used in translate function
7547
		axis.setAxisTranslation(true);
7548
7549
		// hook for ordinal axes and radial axes
7550
		if (axis.beforeSetTickPositions) {
7551
			axis.beforeSetTickPositions();
7552
		}
7553
		
7554
		// hook for extensions, used in Highstock ordinal axes
7555
		if (axis.postProcessTickInterval) {
7556
			axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval);
7557
		}
7558
		
7559
		// Before normalizing the tick interval, handle minimum tick interval. This applies only if tickInterval is not defined.
7560
		if (!tickIntervalOption && axis.tickInterval < minTickIntervalOption) {
7561
			axis.tickInterval = minTickIntervalOption;
7562
		}
7563
7564
		// for linear axes, get magnitude and normalize the interval
7565
		if (!isDatetimeAxis && !isLog) { // linear
7566
			magnitude = math.pow(10, mathFloor(math.log(axis.tickInterval) / math.LN10));
7567
			if (!tickIntervalOption) {
7568
				axis.tickInterval = normalizeTickInterval(axis.tickInterval, null, magnitude, options);
7569
			}
7570
		}
7571
7572
		// get minorTickInterval
7573
		axis.minorTickInterval = options.minorTickInterval === 'auto' && axis.tickInterval ?
7574
				axis.tickInterval / 5 : options.minorTickInterval;
7575
7576
		// find the tick positions
7577
		axis.tickPositions = tickPositions = options.tickPositions ?
7578
			[].concat(options.tickPositions) : // Work on a copy (#1565)
7579
			(tickPositioner && tickPositioner.apply(axis, [axis.min, axis.max]));
7580
		if (!tickPositions) {
7581
			if (isDatetimeAxis) {
7582
				tickPositions = (axis.getNonLinearTimeTicks || getTimeTicks)(
7583
					normalizeTimeTickInterval(axis.tickInterval, options.units),
7584
					axis.min,
7585
					axis.max,
7586
					options.startOfWeek,
7587
					axis.ordinalPositions,
7588
					axis.closestPointRange,
7589
					true
7590
				);
7591
			} else if (isLog) {
7592
				tickPositions = axis.getLogTickPositions(axis.tickInterval, axis.min, axis.max);
7593
			} else {
7594
				tickPositions = axis.getLinearTickPositions(axis.tickInterval, axis.min, axis.max);
7595
			}
7596
			axis.tickPositions = tickPositions;
7597
		}
7598
7599
		if (!isLinked) {
7600
7601
			// reset min/max or remove extremes based on start/end on tick
7602
			var roundedMin = tickPositions[0],
7603
				roundedMax = tickPositions[tickPositions.length - 1],
7604
				minPointOffset = axis.minPointOffset || 0,
7605
				singlePad;
7606
7607
			if (options.startOnTick) {
7608
				axis.min = roundedMin;
7609
			} else if (axis.min - minPointOffset > roundedMin) {
7610
				tickPositions.shift();
7611
			}
7612
7613
			if (options.endOnTick) {
7614
				axis.max = roundedMax;
7615
			} else if (axis.max + minPointOffset < roundedMax) {
7616
				tickPositions.pop();
7617
			}
7618
			
7619
			// When there is only one point, or all points have the same value on this axis, then min
7620
			// and max are equal and tickPositions.length is 1. In this case, add some padding
7621
			// in order to center the point, but leave it with one tick. #1337.
7622
			if (tickPositions.length === 1) {
7623
				singlePad = 0.001; // The lowest possible number to avoid extra padding on columns
7624
				axis.min -= singlePad;
7625
				axis.max += singlePad;
7626
			}
7627
		}
7628
	},
7629
	
7630
	/**
7631
	 * Set the max ticks of either the x and y axis collection
7632
	 */
7633
	setMaxTicks: function () {
7634
		
7635
		var chart = this.chart,
7636
			maxTicks = chart.maxTicks || {},
7637
			tickPositions = this.tickPositions,
7638
			key = this._maxTicksKey = [this.xOrY, this.pos, this.len].join('-');
7639
		
7640
		if (!this.isLinked && !this.isDatetimeAxis && tickPositions && tickPositions.length > (maxTicks[key] || 0) && this.options.alignTicks !== false) {
7641
			maxTicks[key] = tickPositions.length;
7642
		}
7643
		chart.maxTicks = maxTicks;
7644
	},
7645
7646
	/**
7647
	 * When using multiple axes, adjust the number of ticks to match the highest
7648
	 * number of ticks in that group
7649
	 */
7650
	adjustTickAmount: function () {
7651
		var axis = this,
7652
			chart = axis.chart,
7653
			key = axis._maxTicksKey,
7654
			tickPositions = axis.tickPositions,
7655
			maxTicks = chart.maxTicks;
7656
7657
		if (maxTicks && maxTicks[key] && !axis.isDatetimeAxis && !axis.categories && !axis.isLinked && axis.options.alignTicks !== false) { // only apply to linear scale
7658
			var oldTickAmount = axis.tickAmount,
7659
				calculatedTickAmount = tickPositions.length,
7660
				tickAmount;
7661
7662
			// set the axis-level tickAmount to use below
7663
			axis.tickAmount = tickAmount = maxTicks[key];
7664
7665
			if (calculatedTickAmount < tickAmount) {
7666
				while (tickPositions.length < tickAmount) {
7667
					tickPositions.push(correctFloat(
7668
						tickPositions[tickPositions.length - 1] + axis.tickInterval
7669
					));
7670
				}
7671
				axis.transA *= (calculatedTickAmount - 1) / (tickAmount - 1);
7672
				axis.max = tickPositions[tickPositions.length - 1];
7673
7674
			}
7675
			if (defined(oldTickAmount) && tickAmount !== oldTickAmount) {
7676
				axis.isDirty = true;
7677
			}
7678
		}
7679
	},
7680
7681
	/**
7682
	 * Set the scale based on data min and max, user set min and max or options
7683
	 *
7684
	 */
7685
	setScale: function () {
7686
		var axis = this,
7687
			stacks = axis.stacks,
7688
			type,
7689
			i,
7690
			isDirtyData,
7691
			isDirtyAxisLength;
7692
7693
		axis.oldMin = axis.min;
7694
		axis.oldMax = axis.max;
7695
		axis.oldAxisLength = axis.len;
7696
7697
		// set the new axisLength
7698
		axis.setAxisSize();
7699
		//axisLength = horiz ? axisWidth : axisHeight;
7700
		isDirtyAxisLength = axis.len !== axis.oldAxisLength;
7701
7702
		// is there new data?
7703
		each(axis.series, function (series) {
7704
			if (series.isDirtyData || series.isDirty ||
7705
					series.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well
7706
				isDirtyData = true;
7707
			}
7708
		});
7709
		
7710
		// do we really need to go through all this?
7711
		if (isDirtyAxisLength || isDirtyData || axis.isLinked || axis.forceRedraw ||
7712
			axis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax) {
7713
7714
			axis.forceRedraw = false;
7715
7716
			// get data extremes if needed
7717
			axis.getSeriesExtremes();
7718
7719
			// get fixed positions based on tickInterval
7720
			axis.setTickPositions();
7721
7722
			// record old values to decide whether a rescale is necessary later on (#540)
7723
			axis.oldUserMin = axis.userMin;
7724
			axis.oldUserMax = axis.userMax;
7725
7726
			// Mark as dirty if it is not already set to dirty and extremes have changed. #595.
7727
			if (!axis.isDirty) {
7728
				axis.isDirty = isDirtyAxisLength || axis.min !== axis.oldMin || axis.max !== axis.oldMax;
7729
			}
7730
		}
7731
		
7732
		
7733
		// reset stacks
7734
		if (!axis.isXAxis) {
7735
			for (type in stacks) {
7736
				for (i in stacks[type]) {
7737
					stacks[type][i].cum = stacks[type][i].total;
7738
				}
7739
			}
7740
		}
7741
		
7742
		// Set the maximum tick amount
7743
		axis.setMaxTicks();
7744
	},
7745
7746
	/**
7747
	 * Set the extremes and optionally redraw
7748
	 * @param {Number} newMin
7749
	 * @param {Number} newMax
7750
	 * @param {Boolean} redraw
7751
	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
7752
	 *    configuration
7753
	 * @param {Object} eventArguments 
7754
	 *
7755
	 */
7756
	setExtremes: function (newMin, newMax, redraw, animation, eventArguments) {
7757
		var axis = this,
7758
			chart = axis.chart;
7759
7760
		redraw = pick(redraw, true); // defaults to true
7761
7762
		// Extend the arguments with min and max
7763
		eventArguments = extend(eventArguments, {
7764
			min: newMin,
7765
			max: newMax
7766
		});
7767
7768
		// Fire the event
7769
		fireEvent(axis, 'setExtremes', eventArguments, function () { // the default event handler
7770
7771
			axis.userMin = newMin;
7772
			axis.userMax = newMax;
7773
7774
			// Mark for running afterSetExtremes
7775
			axis.isDirtyExtremes = true;
7776
7777
			// redraw
7778
			if (redraw) {
7779
				chart.redraw(animation);
7780
			}
7781
		});
7782
	},
7783
	
7784
	/**
7785
	 * Overridable method for zooming chart. Pulled out in a separate method to allow overriding
7786
	 * in stock charts.
7787
	 */
7788
	zoom: function (newMin, newMax) {
7789
7790
		// Prevent pinch zooming out of range
7791
		if (!this.allowZoomOutside) {
7792
			if (newMin <= this.dataMin) {
7793
				newMin = UNDEFINED;
7794
			}
7795
			if (newMax >= this.dataMax) {
7796
				newMax = UNDEFINED;
7797
			}
7798
		}
7799
7800
		// In full view, displaying the reset zoom button is not required
7801
		this.displayBtn = newMin !== UNDEFINED || newMax !== UNDEFINED;
7802
		
7803
		// Do it
7804
		this.setExtremes(
7805
			newMin,
7806
			newMax,
7807
			false, 
7808
			UNDEFINED, 
7809
			{ trigger: 'zoom' }
7810
		);
7811
		return true;
7812
	},
7813
	
7814
	/**
7815
	 * Update the axis metrics
7816
	 */
7817
	setAxisSize: function () {
7818
		var chart = this.chart,
7819
			options = this.options,
7820
			offsetLeft = options.offsetLeft || 0,
7821
			offsetRight = options.offsetRight || 0,
7822
			horiz = this.horiz,
7823
			width,
7824
			height,
7825
			top,
7826
			left;
7827
7828
		// Expose basic values to use in Series object and navigator
7829
		this.left = left = pick(options.left, chart.plotLeft + offsetLeft);
7830
		this.top = top = pick(options.top, chart.plotTop);
7831
		this.width = width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight);
7832
		this.height = height = pick(options.height, chart.plotHeight);
7833
		this.bottom = chart.chartHeight - height - top;
7834
		this.right = chart.chartWidth - width - left;
7835
7836
		// Direction agnostic properties
7837
		this.len = mathMax(horiz ? width : height, 0); // mathMax fixes #905
7838
		this.pos = horiz ? left : top; // distance from SVG origin
7839
	},
7840
7841
	/**
7842
	 * Get the actual axis extremes
7843
	 */
7844
	getExtremes: function () {
7845
		var axis = this,
7846
			isLog = axis.isLog;
7847
7848
		return {
7849
			min: isLog ? correctFloat(lin2log(axis.min)) : axis.min,
7850
			max: isLog ? correctFloat(lin2log(axis.max)) : axis.max,
7851
			dataMin: axis.dataMin,
7852
			dataMax: axis.dataMax,
7853
			userMin: axis.userMin,
7854
			userMax: axis.userMax
7855
		};
7856
	},
7857
7858
	/**
7859
	 * Get the zero plane either based on zero or on the min or max value.
7860
	 * Used in bar and area plots
7861
	 */
7862
	getThreshold: function (threshold) {
7863
		var axis = this,
7864
			isLog = axis.isLog;
7865
7866
		var realMin = isLog ? lin2log(axis.min) : axis.min,
7867
			realMax = isLog ? lin2log(axis.max) : axis.max;
7868
		
7869
		if (realMin > threshold || threshold === null) {
7870
			threshold = realMin;
7871
		} else if (realMax < threshold) {
7872
			threshold = realMax;
7873
		}
7874
7875
		return axis.translate(threshold, 0, 1, 0, 1);
7876
	},
7877
7878
	addPlotBand: function (options) {
7879
		this.addPlotBandOrLine(options, 'plotBands');
7880
	},
7881
	
7882
	addPlotLine: function (options) {
7883
		this.addPlotBandOrLine(options, 'plotLines');
7884
	},
7885
7886
	/**
7887
	 * Add a plot band or plot line after render time
7888
	 *
7889
	 * @param options {Object} The plotBand or plotLine configuration object
7890
	 */
7891
	addPlotBandOrLine: function (options, coll) {
7892
		var obj = new PlotLineOrBand(this, options).render(),
7893
			userOptions = this.userOptions;
7894
7895
		// Add it to the user options for exporting and Axis.update
7896
		if (coll) {
7897
			userOptions[coll] = userOptions[coll] || [];
7898
			userOptions[coll].push(options); 
7899
		}
7900
		
7901
		this.plotLinesAndBands.push(obj); 
7902
		
7903
		return obj;
7904
	},
7905
7906
	/**
7907
	 * Render the tick labels to a preliminary position to get their sizes
7908
	 */
7909
	getOffset: function () {
7910
		var axis = this,
7911
			chart = axis.chart,
7912
			renderer = chart.renderer,
7913
			options = axis.options,
7914
			tickPositions = axis.tickPositions,
7915
			ticks = axis.ticks,
7916
			horiz = axis.horiz,
7917
			side = axis.side,
7918
			invertedSide = chart.inverted ? [1, 0, 3, 2][side] : side,
7919
			hasData,
7920
			showAxis,
7921
			titleOffset = 0,
7922
			titleOffsetOption,
7923
			titleMargin = 0,
7924
			axisTitleOptions = options.title,
7925
			labelOptions = options.labels,
7926
			labelOffset = 0, // reset
7927
			axisOffset = chart.axisOffset,
7928
			clipOffset = chart.clipOffset,
7929
			directionFactor = [-1, 1, 1, -1][side],
7930
			n;
7931
			
7932
		// For reuse in Axis.render
7933
		axis.hasData = hasData = (axis.hasVisibleSeries || (defined(axis.min) && defined(axis.max) && !!tickPositions));
7934
		axis.showAxis = showAxis = hasData || pick(options.showEmpty, true);
7935
		
7936
		// Create the axisGroup and gridGroup elements on first iteration
7937
		if (!axis.axisGroup) {
7938
			axis.gridGroup = renderer.g('grid')
7939
				.attr({ zIndex: options.gridZIndex || 1 })
7940
				.add();
7941
			axis.axisGroup = renderer.g('axis')
7942
				.attr({ zIndex: options.zIndex || 2 })
7943
				.add();
7944
			axis.labelGroup = renderer.g('axis-labels')
7945
				.attr({ zIndex: labelOptions.zIndex || 7 })
7946
				.add();
7947
		}
7948
7949
		if (hasData || axis.isLinked) {
7950
			each(tickPositions, function (pos) {
7951
				if (!ticks[pos]) {
7952
					ticks[pos] = new Tick(axis, pos);
7953
				} else {
7954
					ticks[pos].addLabel(); // update labels depending on tick interval
7955
				}
7956
7957
			});
7958
7959
			each(tickPositions, function (pos) {
7960
				// left side must be align: right and right side must have align: left for labels
7961
				if (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === labelOptions.align) {
7962
7963
					// get the highest offset
7964
					labelOffset = mathMax(
7965
						ticks[pos].getLabelSize(),
7966
						labelOffset
7967
					);
7968
				}
7969
7970
			});
7971
7972
			if (axis.staggerLines) {
7973
				labelOffset += (axis.staggerLines - 1) * 16;
7974
			}
7975
7976
		} else { // doesn't have data
7977
			for (n in ticks) {
7978
				ticks[n].destroy();
7979
				delete ticks[n];
7980
			}
7981
		}
7982
7983
		if (axisTitleOptions && axisTitleOptions.text && axisTitleOptions.enabled !== false) { 
7984
			if (!axis.axisTitle) {
7985
				axis.axisTitle = renderer.text(
7986
					axisTitleOptions.text,
7987
					0,
7988
					0,
7989
					axisTitleOptions.useHTML
7990
				)
7991
				.attr({
7992
					zIndex: 7,
7993
					rotation: axisTitleOptions.rotation || 0,
7994
					align:
7995
						axisTitleOptions.textAlign ||
7996
						{ low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align]
7997
				})
7998
				.css(axisTitleOptions.style)
7999
				.add(axis.axisGroup);
8000
				axis.axisTitle.isNew = true;
8001
			}
8002
8003
			if (showAxis) {
8004
				titleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];
8005
				titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);
8006
				titleOffsetOption = axisTitleOptions.offset;
8007
			}
8008
8009
			// hide or show the title depending on whether showEmpty is set
8010
			axis.axisTitle[showAxis ? 'show' : 'hide']();
8011
		}
8012
		
8013
		// handle automatic or user set offset
8014
		axis.offset = directionFactor * pick(options.offset, axisOffset[side]);
8015
		
8016
		axis.axisTitleMargin =
8017
			pick(titleOffsetOption,
8018
				labelOffset + titleMargin +
8019
				(side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x'])
8020
			);
8021
8022
		axisOffset[side] = mathMax(
8023
			axisOffset[side],
8024
			axis.axisTitleMargin + titleOffset + directionFactor * axis.offset
8025
		);
8026
		clipOffset[invertedSide] = mathMax(clipOffset[invertedSide], options.lineWidth);
8027
8028
	},
8029
	
8030
	/**
8031
	 * Get the path for the axis line
8032
	 */
8033
	getLinePath: function (lineWidth) {
8034
		var chart = this.chart,
8035
			opposite = this.opposite,
8036
			offset = this.offset,
8037
			horiz = this.horiz,
8038
			lineLeft = this.left + (opposite ? this.width : 0) + offset,
8039
			lineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset;
8040
			
8041
		this.lineTop = lineTop; // used by flag series
8042
		if (!opposite) {
8043
			lineWidth *= -1; // crispify the other way - #1480
8044
		}
8045
8046
		return chart.renderer.crispLine([
8047
				M,
8048
				horiz ?
8049
					this.left :
8050
					lineLeft,
8051
				horiz ?
8052
					lineTop :
8053
					this.top,
8054
				L,
8055
				horiz ?
8056
					chart.chartWidth - this.right :
8057
					lineLeft,
8058
				horiz ?
8059
					lineTop :
8060
					chart.chartHeight - this.bottom
8061
			], lineWidth);
8062
	},
8063
	
8064
	/**
8065
	 * Position the title
8066
	 */
8067
	getTitlePosition: function () {
8068
		// compute anchor points for each of the title align options
8069
		var horiz = this.horiz,
8070
			axisLeft = this.left,
8071
			axisTop = this.top,
8072
			axisLength = this.len,
8073
			axisTitleOptions = this.options.title,			
8074
			margin = horiz ? axisLeft : axisTop,
8075
			opposite = this.opposite,
8076
			offset = this.offset,
8077
			fontSize = pInt(axisTitleOptions.style.fontSize || 12),
8078
			
8079
			// the position in the length direction of the axis
8080
			alongAxis = {
8081
				low: margin + (horiz ? 0 : axisLength),
8082
				middle: margin + axisLength / 2,
8083
				high: margin + (horiz ? axisLength : 0)
8084
			}[axisTitleOptions.align],
8085
	
8086
			// the position in the perpendicular direction of the axis
8087
			offAxis = (horiz ? axisTop + this.height : axisLeft) +
8088
				(horiz ? 1 : -1) * // horizontal axis reverses the margin
8089
				(opposite ? -1 : 1) * // so does opposite axes
8090
				this.axisTitleMargin +
8091
				(this.side === 2 ? fontSize : 0);
8092
8093
		return {
8094
			x: horiz ?
8095
				alongAxis :
8096
				offAxis + (opposite ? this.width : 0) + offset +
8097
					(axisTitleOptions.x || 0), // x
8098
			y: horiz ?
8099
				offAxis - (opposite ? this.height : 0) + offset :
8100
				alongAxis + (axisTitleOptions.y || 0) // y
8101
		};
8102
	},
8103
	
8104
	/**
8105
	 * Render the axis
8106
	 */
8107
	render: function () {
8108
		var axis = this,
8109
			chart = axis.chart,
8110
			renderer = chart.renderer,
8111
			options = axis.options,
8112
			isLog = axis.isLog,
8113
			isLinked = axis.isLinked,
8114
			tickPositions = axis.tickPositions,
8115
			axisTitle = axis.axisTitle,
8116
			stacks = axis.stacks,
8117
			ticks = axis.ticks,
8118
			minorTicks = axis.minorTicks,
8119
			alternateBands = axis.alternateBands,
8120
			stackLabelOptions = options.stackLabels,
8121
			alternateGridColor = options.alternateGridColor,
8122
			tickmarkOffset = axis.tickmarkOffset,
8123
			lineWidth = options.lineWidth,
8124
			linePath,
8125
			hasRendered = chart.hasRendered,
8126
			slideInTicks = hasRendered && defined(axis.oldMin) && !isNaN(axis.oldMin),
8127
			hasData = axis.hasData,
8128
			showAxis = axis.showAxis,
8129
			from,
8130
			to;
8131
8132
		// Mark all elements inActive before we go over and mark the active ones
8133
		each([ticks, minorTicks, alternateBands], function (coll) {
8134
			var pos;
8135
			for (pos in coll) {
8136
				coll[pos].isActive = false;
8137
			}
8138
		});
8139
8140
		// If the series has data draw the ticks. Else only the line and title
8141
		if (hasData || isLinked) {
8142
8143
			// minor ticks
8144
			if (axis.minorTickInterval && !axis.categories) {
8145
				each(axis.getMinorTickPositions(), function (pos) {
8146
					if (!minorTicks[pos]) {
8147
						minorTicks[pos] = new Tick(axis, pos, 'minor');
8148
					}
8149
8150
					// render new ticks in old position
8151
					if (slideInTicks && minorTicks[pos].isNew) {
8152
						minorTicks[pos].render(null, true);
8153
					}
8154
8155
					minorTicks[pos].render(null, false, 1);
8156
				});
8157
			}
8158
8159
			// Major ticks. Pull out the first item and render it last so that
8160
			// we can get the position of the neighbour label. #808.
8161
			if (tickPositions.length) { // #1300
8162
				each(tickPositions.slice(1).concat([tickPositions[0]]), function (pos, i) {
8163
	
8164
					// Reorganize the indices
8165
					i = (i === tickPositions.length - 1) ? 0 : i + 1;
8166
	
8167
					// linked axes need an extra check to find out if
8168
					if (!isLinked || (pos >= axis.min && pos <= axis.max)) {
8169
	
8170
						if (!ticks[pos]) {
8171
							ticks[pos] = new Tick(axis, pos);
8172
						}
8173
	
8174
						// render new ticks in old position
8175
						if (slideInTicks && ticks[pos].isNew) {
8176
							ticks[pos].render(i, true);
8177
						}
8178
	
8179
						ticks[pos].render(i, false, 1);
8180
					}
8181
	
8182
				});
8183
				// In a categorized axis, the tick marks are displayed between labels. So
8184
				// we need to add a tick mark and grid line at the left edge of the X axis.
8185
				if (tickmarkOffset && axis.min === 0) {
8186
					if (!ticks[-1]) {
8187
						ticks[-1] = new Tick(axis, -1, null, true);
8188
					}
8189
					ticks[-1].render(-1);
8190
				}
8191
				
8192
			}
8193
8194
			// alternate grid color
8195
			if (alternateGridColor) {
8196
				each(tickPositions, function (pos, i) {
8197
					if (i % 2 === 0 && pos < axis.max) {
8198
						if (!alternateBands[pos]) {
8199
							alternateBands[pos] = new PlotLineOrBand(axis);
8200
						}
8201
						from = pos + tickmarkOffset; // #949
8202
						to = tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] + tickmarkOffset : axis.max;
8203
						alternateBands[pos].options = {
8204
							from: isLog ? lin2log(from) : from,
8205
							to: isLog ? lin2log(to) : to,
8206
							color: alternateGridColor
8207
						};
8208
						alternateBands[pos].render();
8209
						alternateBands[pos].isActive = true;
8210
					}
8211
				});
8212
			}
8213
8214
			// custom plot lines and bands
8215
			if (!axis._addedPlotLB) { // only first time
8216
				each((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) {
8217
					axis.addPlotBandOrLine(plotLineOptions);
8218
				});
8219
				axis._addedPlotLB = true;
8220
			}
8221
8222
		} // end if hasData
8223
8224
		// Remove inactive ticks
8225
		each([ticks, minorTicks, alternateBands], function (coll) {
8226
			var pos, 
8227
				i,
8228
				forDestruction = [],
8229
				delay = globalAnimation ? globalAnimation.duration || 500 : 0,
8230
				destroyInactiveItems = function () {
8231
					i = forDestruction.length;
8232
					while (i--) {
8233
						// When resizing rapidly, the same items may be destroyed in different timeouts,
8234
						// or the may be reactivated
8235
						if (coll[forDestruction[i]] && !coll[forDestruction[i]].isActive) {
8236
							coll[forDestruction[i]].destroy();
8237
							delete coll[forDestruction[i]];
8238
						}
8239
					}
8240
					
8241
				};
8242
8243
			for (pos in coll) {
8244
8245
				if (!coll[pos].isActive) {
8246
					// Render to zero opacity
8247
					coll[pos].render(pos, false, 0);
8248
					coll[pos].isActive = false;
8249
					forDestruction.push(pos);
8250
				}
8251
			}
8252
8253
			// When the objects are finished fading out, destroy them
8254
			if (coll === alternateBands || !chart.hasRendered || !delay) {
8255
				destroyInactiveItems();
8256
			} else if (delay) {
8257
				setTimeout(destroyInactiveItems, delay);
8258
			}
8259
		});
8260
8261
		// Static items. As the axis group is cleared on subsequent calls
8262
		// to render, these items are added outside the group.
8263
		// axis line
8264
		if (lineWidth) {
8265
			linePath = axis.getLinePath(lineWidth);
8266
			if (!axis.axisLine) {
8267
				axis.axisLine = renderer.path(linePath)
8268
					.attr({
8269
						stroke: options.lineColor,
8270
						'stroke-width': lineWidth,
8271
						zIndex: 7
8272
					})
8273
					.add(axis.axisGroup);
8274
			} else {
8275
				axis.axisLine.animate({ d: linePath });
8276
			}
8277
8278
			// show or hide the line depending on options.showEmpty
8279
			axis.axisLine[showAxis ? 'show' : 'hide']();
8280
		}
8281
8282
		if (axisTitle && showAxis) {
8283
			
8284
			axisTitle[axisTitle.isNew ? 'attr' : 'animate'](
8285
				axis.getTitlePosition()
8286
			);
8287
			axisTitle.isNew = false;
8288
		}
8289
8290
		// Stacked totals:
8291
		if (stackLabelOptions && stackLabelOptions.enabled) {
8292
			var stackKey, oneStack, stackCategory,
8293
				stackTotalGroup = axis.stackTotalGroup;
8294
8295
			// Create a separate group for the stack total labels
8296
			if (!stackTotalGroup) {
8297
				axis.stackTotalGroup = stackTotalGroup =
8298
					renderer.g('stack-labels')
8299
						.attr({
8300
							visibility: VISIBLE,
8301
							zIndex: 6
8302
						})
8303
						.add();
8304
			}
8305
8306
			// plotLeft/Top will change when y axis gets wider so we need to translate the
8307
			// stackTotalGroup at every render call. See bug #506 and #516
8308
			stackTotalGroup.translate(chart.plotLeft, chart.plotTop);
8309
8310
			// Render each stack total
8311
			for (stackKey in stacks) {
8312
				oneStack = stacks[stackKey];
8313
				for (stackCategory in oneStack) {
8314
					oneStack[stackCategory].render(stackTotalGroup);
8315
				}
8316
			}
8317
		}
8318
		// End stacked totals
8319
8320
		axis.isDirty = false;
8321
	},
8322
8323
	/**
8324
	 * Remove a plot band or plot line from the chart by id
8325
	 * @param {Object} id
8326
	 */
8327
	removePlotBandOrLine: function (id) {
8328
		var plotLinesAndBands = this.plotLinesAndBands,
8329
			i = plotLinesAndBands.length;
8330
		while (i--) {
8331
			if (plotLinesAndBands[i].id === id) {
8332
				plotLinesAndBands[i].destroy();
8333
			}
8334
		}
8335
	},
8336
8337
	/**
8338
	 * Update the axis title by options
8339
	 */
8340
	setTitle: function (newTitleOptions, redraw) {
8341
		this.update({ title: newTitleOptions }, redraw);
8342
	},
8343
8344
	/**
8345
	 * Redraw the axis to reflect changes in the data or axis extremes
8346
	 */
8347
	redraw: function () {
8348
		var axis = this,
8349
			chart = axis.chart,
8350
			pointer = chart.pointer;
8351
8352
		// hide tooltip and hover states
8353
		if (pointer.reset) {
8354
			pointer.reset(true);
8355
		}
8356
8357
		// render the axis
8358
		axis.render();
8359
8360
		// move plot lines and bands
8361
		each(axis.plotLinesAndBands, function (plotLine) {
8362
			plotLine.render();
8363
		});
8364
8365
		// mark associated series as dirty and ready for redraw
8366
		each(axis.series, function (series) {
8367
			series.isDirty = true;
8368
		});
8369
8370
	},
8371
8372
	/**
8373
	 * Set new axis categories and optionally redraw
8374
	 * @param {Array} categories
8375
	 * @param {Boolean} redraw
8376
	 */
8377
	setCategories: function (categories, redraw) {
8378
		this.update({ categories: categories }, redraw);
8379
	},
8380
8381
	/**
8382
	 * Destroys an Axis instance.
8383
	 */
8384
	destroy: function () {
8385
		var axis = this,
8386
			stacks = axis.stacks,
8387
			stackKey;
8388
8389
		// Remove the events
8390
		removeEvent(axis);
8391
8392
		// Destroy each stack total
8393
		for (stackKey in stacks) {
8394
			destroyObjectProperties(stacks[stackKey]);
8395
8396
			stacks[stackKey] = null;
8397
		}
8398
8399
		// Destroy collections
8400
		each([axis.ticks, axis.minorTicks, axis.alternateBands, axis.plotLinesAndBands], function (coll) {
8401
			destroyObjectProperties(coll);
8402
		});
8403
8404
		// Destroy local variables
8405
		each(['stackTotalGroup', 'axisLine', 'axisGroup', 'gridGroup', 'labelGroup', 'axisTitle'], function (prop) {
8406
			if (axis[prop]) {
8407
				axis[prop] = axis[prop].destroy();
8408
			}
8409
		});
8410
	}
8411
8412
	
8413
}; // end Axis
8414
8415
/**
8416
 * The tooltip object
8417
 * @param {Object} chart The chart instance
8418
 * @param {Object} options Tooltip options
8419
 */
8420
function Tooltip() {
8421
	this.init.apply(this, arguments);
8422
}
8423
8424
Tooltip.prototype = {
8425
8426
	init: function (chart, options) {
8427
8428
		var borderWidth = options.borderWidth,
8429
			style = options.style,
8430
			padding = pInt(style.padding);
8431
8432
		// Save the chart and options
8433
		this.chart = chart;
8434
		this.options = options;
8435
8436
		// Keep track of the current series
8437
		//this.currentSeries = UNDEFINED;
8438
8439
		// List of crosshairs
8440
		this.crosshairs = [];
8441
8442
		// Current values of x and y when animating
8443
		this.now = { x: 0, y: 0 };
8444
8445
		// The tooltip is initially hidden
8446
		this.isHidden = true;
8447
8448
8449
		// create the label
8450
		this.label = chart.renderer.label('', 0, 0, options.shape, null, null, options.useHTML, null, 'tooltip')
8451
			.attr({
8452
				padding: padding,
8453
				fill: options.backgroundColor,
8454
				'stroke-width': borderWidth,
8455
				r: options.borderRadius,
8456
				zIndex: 8
8457
			})
8458
			.css(style)
8459
			.css({ padding: 0 }) // Remove it from VML, the padding is applied as an attribute instead (#1117)
8460
			.hide()
8461
			.add();
8462
8463
		// When using canVG the shadow shows up as a gray circle
8464
		// even if the tooltip is hidden.
8465
		if (!useCanVG) {
8466
			this.label.shadow(options.shadow);
8467
		}
8468
8469
		// Public property for getting the shared state.
8470
		this.shared = options.shared;
8471
	},
8472
8473
	/**
8474
	 * Destroy the tooltip and its elements.
8475
	 */
8476
	destroy: function () {
8477
		each(this.crosshairs, function (crosshair) {
8478
			if (crosshair) {
8479
				crosshair.destroy();
8480
			}
8481
		});
8482
8483
		// Destroy and clear local variables
8484
		if (this.label) {
8485
			this.label = this.label.destroy();
8486
		}
8487
		clearTimeout(this.hideTimer);
8488
		clearTimeout(this.tooltipTimeout);
8489
	},
8490
8491
	/**
8492
	 * Provide a soft movement for the tooltip
8493
	 *
8494
	 * @param {Number} x
8495
	 * @param {Number} y
8496
	 * @private
8497
	 */
8498
	move: function (x, y, anchorX, anchorY) {
8499
		var tooltip = this,
8500
			now = tooltip.now,
8501
			animate = tooltip.options.animation !== false && !tooltip.isHidden;
8502
8503
		// get intermediate values for animation
8504
		extend(now, {
8505
			x: animate ? (2 * now.x + x) / 3 : x,
8506
			y: animate ? (now.y + y) / 2 : y,
8507
			anchorX: animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,
8508
			anchorY: animate ? (now.anchorY + anchorY) / 2 : anchorY
8509
		});
8510
8511
		// move to the intermediate value
8512
		tooltip.label.attr(now);
8513
8514
		
8515
		// run on next tick of the mouse tracker
8516
		if (animate && (mathAbs(x - now.x) > 1 || mathAbs(y - now.y) > 1)) {
8517
		
8518
			// never allow two timeouts
8519
			clearTimeout(this.tooltipTimeout);
8520
			
8521
			// set the fixed interval ticking for the smooth tooltip
8522
			this.tooltipTimeout = setTimeout(function () {
8523
				// The interval function may still be running during destroy, so check that the chart is really there before calling.
8524
				if (tooltip) {
8525
					tooltip.move(x, y, anchorX, anchorY);
8526
				}
8527
			}, 32);
8528
			
8529
		}
8530
	},
8531
8532
	/**
8533
	 * Hide the tooltip
8534
	 */
8535
	hide: function () {
8536
		var tooltip = this,
8537
			hoverPoints;
8538
		
8539
		clearTimeout(this.hideTimer); // disallow duplicate timers (#1728, #1766)
8540
		if (!this.isHidden) {
8541
			hoverPoints = this.chart.hoverPoints;
8542
8543
			this.hideTimer = setTimeout(function () {
8544
				tooltip.label.fadeOut();
8545
				tooltip.isHidden = true;
8546
			}, pick(this.options.hideDelay, 500));
8547
8548
			// hide previous hoverPoints and set new
8549
			if (hoverPoints) {
8550
				each(hoverPoints, function (point) {
8551
					point.setState();
8552
				});
8553
			}
8554
8555
			this.chart.hoverPoints = null;
8556
		}
8557
	},
8558
8559
	/**
8560
	 * Hide the crosshairs
8561
	 */
8562
	hideCrosshairs: function () {
8563
		each(this.crosshairs, function (crosshair) {
8564
			if (crosshair) {
8565
				crosshair.hide();
8566
			}
8567
		});
8568
	},
8569
	
8570
	/** 
8571
	 * Extendable method to get the anchor position of the tooltip
8572
	 * from a point or set of points
8573
	 */
8574
	getAnchor: function (points, mouseEvent) {
8575
		var ret,
8576
			chart = this.chart,
8577
			inverted = chart.inverted,
8578
			plotTop = chart.plotTop,
8579
			plotX = 0,
8580
			plotY = 0,
8581
			yAxis;
8582
		
8583
		points = splat(points);
8584
		
8585
		// Pie uses a special tooltipPos
8586
		ret = points[0].tooltipPos;
8587
		
8588
		// When tooltip follows mouse, relate the position to the mouse
8589
		if (this.followPointer && mouseEvent) {
8590
			if (mouseEvent.chartX === UNDEFINED) {
8591
				mouseEvent = chart.pointer.normalize(mouseEvent);
8592
			}
8593
			ret = [
8594
				mouseEvent.chartX - chart.plotLeft,
8595
				mouseEvent.chartY - plotTop
8596
			];
8597
		}
8598
		// When shared, use the average position
8599
		if (!ret) {
8600
			each(points, function (point) {
8601
				yAxis = point.series.yAxis;
8602
				plotX += point.plotX;
8603
				plotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) +
8604
					(!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151
8605
			});
8606
			
8607
			plotX /= points.length;
8608
			plotY /= points.length;
8609
			
8610
			ret = [
8611
				inverted ? chart.plotWidth - plotY : plotX,
8612
				this.shared && !inverted && points.length > 1 && mouseEvent ? 
8613
					mouseEvent.chartY - plotTop : // place shared tooltip next to the mouse (#424)
8614
					inverted ? chart.plotHeight - plotX : plotY
8615
			];
8616
		}
8617
8618
		return map(ret, mathRound);
8619
	},
8620
	
8621
	/**
8622
	 * Place the tooltip in a chart without spilling over
8623
	 * and not covering the point it self.
8624
	 */
8625
	getPosition: function (boxWidth, boxHeight, point) {
8626
		
8627
		// Set up the variables
8628
		var chart = this.chart,
8629
			plotLeft = chart.plotLeft,
8630
			plotTop = chart.plotTop,
8631
			plotWidth = chart.plotWidth,
8632
			plotHeight = chart.plotHeight,
8633
			distance = pick(this.options.distance, 12),
8634
			pointX = point.plotX,
8635
			pointY = point.plotY,
8636
			x = pointX + plotLeft + (chart.inverted ? distance : -boxWidth - distance),
8637
			y = pointY - boxHeight + plotTop + 15, // 15 means the point is 15 pixels up from the bottom of the tooltip
8638
			alignedRight;
8639
	
8640
		// It is too far to the left, adjust it
8641
		if (x < 7) {
8642
			x = plotLeft + mathMax(pointX, 0) + distance;
8643
		}
8644
	
8645
		// Test to see if the tooltip is too far to the right,
8646
		// if it is, move it back to be inside and then up to not cover the point.
8647
		if ((x + boxWidth) > (plotLeft + plotWidth)) {
8648
			x -= (x + boxWidth) - (plotLeft + plotWidth);
8649
			y = pointY - boxHeight + plotTop - distance;
8650
			alignedRight = true;
8651
		}
8652
	
8653
		// If it is now above the plot area, align it to the top of the plot area
8654
		if (y < plotTop + 5) {
8655
			y = plotTop + 5;
8656
	
8657
			// If the tooltip is still covering the point, move it below instead
8658
			if (alignedRight && pointY >= y && pointY <= (y + boxHeight)) {
8659
				y = pointY + plotTop + distance; // below
8660
			}
8661
		} 
8662
	
8663
		// Now if the tooltip is below the chart, move it up. It's better to cover the
8664
		// point than to disappear outside the chart. #834.
8665
		if (y + boxHeight > plotTop + plotHeight) {
8666
			y = mathMax(plotTop, plotTop + plotHeight - boxHeight - distance); // below
8667
		}
8668
	
8669
		return {x: x, y: y};
8670
	},
8671
8672
	/**
8673
	 * In case no user defined formatter is given, this will be used. Note that the context
8674
	 * here is an object holding point, series, x, y etc.
8675
	 */
8676
	defaultFormatter: function (tooltip) {
8677
		var items = this.points || splat(this),
8678
			series = items[0].series,
8679
			s;
8680
8681
		// build the header
8682
		s = [series.tooltipHeaderFormatter(items[0])];
8683
8684
		// build the values
8685
		each(items, function (item) {
8686
			series = item.series;
8687
			s.push((series.tooltipFormatter && series.tooltipFormatter(item)) ||
8688
				item.point.tooltipFormatter(series.tooltipOptions.pointFormat));
8689
		});
8690
8691
		// footer
8692
		s.push(tooltip.options.footerFormat || '');
8693
8694
		return s.join('');
8695
	},
8696
8697
	/**
8698
	 * Refresh the tooltip's text and position.
8699
	 * @param {Object} point
8700
	 */
8701
	refresh: function (point, mouseEvent) {
8702
		var tooltip = this,
8703
			chart = tooltip.chart,
8704
			label = tooltip.label,
8705
			options = tooltip.options,
8706
			x,
8707
			y,
8708
			show,
8709
			anchor,
8710
			textConfig = {},
8711
			text,
8712
			pointConfig = [],
8713
			formatter = options.formatter || tooltip.defaultFormatter,
8714
			hoverPoints = chart.hoverPoints,
8715
			borderColor,
8716
			crosshairsOptions = options.crosshairs,
8717
			shared = tooltip.shared,
8718
			currentSeries;
8719
			
8720
		clearTimeout(this.hideTimer);
8721
		
8722
		// get the reference point coordinates (pie charts use tooltipPos)
8723
		tooltip.followPointer = splat(point)[0].series.tooltipOptions.followPointer;
8724
		anchor = tooltip.getAnchor(point, mouseEvent);
8725
		x = anchor[0];
8726
		y = anchor[1];
8727
8728
		// shared tooltip, array is sent over
8729
		if (shared && !(point.series && point.series.noSharedTooltip)) {
8730
			
8731
			// hide previous hoverPoints and set new
8732
			
8733
			chart.hoverPoints = point;
8734
			if (hoverPoints) {
8735
				each(hoverPoints, function (point) {
8736
					point.setState();
8737
				});
8738
			}
8739
8740
			each(point, function (item) {
8741
				item.setState(HOVER_STATE);
8742
8743
				pointConfig.push(item.getLabelConfig());
8744
			});
8745
8746
			textConfig = {
8747
				x: point[0].category,
8748
				y: point[0].y
8749
			};
8750
			textConfig.points = pointConfig;
8751
			point = point[0];
8752
8753
		// single point tooltip
8754
		} else {
8755
			textConfig = point.getLabelConfig();
8756
		}
8757
		text = formatter.call(textConfig, tooltip);
8758
8759
		// register the current series
8760
		currentSeries = point.series;
8761
8762
8763
		// For line type series, hide tooltip if the point falls outside the plot
8764
		show = shared || !currentSeries.isCartesian || currentSeries.tooltipOutsidePlot || chart.isInsidePlot(x, y);
8765
8766
		// update the inner HTML
8767
		if (text === false || !show) {
8768
			this.hide();
8769
		} else {
8770
8771
			// show it
8772
			if (tooltip.isHidden) {
8773
				stop(label);
8774
				label.attr('opacity', 1).show();
8775
			}
8776
8777
			// update text
8778
			label.attr({
8779
				text: text
8780
			});
8781
8782
			// set the stroke color of the box
8783
			borderColor = options.borderColor || point.color || currentSeries.color || '#606060';
8784
			label.attr({
8785
				stroke: borderColor
8786
			});
8787
			
8788
			tooltip.updatePosition({ plotX: x, plotY: y });
8789
		
8790
			this.isHidden = false;
8791
		}
8792
8793
		// crosshairs
8794
		if (crosshairsOptions) {
8795
			crosshairsOptions = splat(crosshairsOptions); // [x, y]
8796
8797
			var path,
8798
				i = crosshairsOptions.length,
8799
				attribs,
8800
				axis,
8801
				val;
8802
8803
			while (i--) {
8804
				axis = point.series[i ? 'yAxis' : 'xAxis'];
8805
				if (crosshairsOptions[i] && axis) {
8806
					val = i ? pick(point.stackY, point.y) : point.x; // #814
8807
					if (axis.isLog) { // #1671
8808
						val = log2lin(val);
8809
					}
8810
8811
					path = axis.getPlotLinePath(
8812
						val,
8813
						1
8814
					);
8815
8816
					if (tooltip.crosshairs[i]) {
8817
						tooltip.crosshairs[i].attr({ d: path, visibility: VISIBLE });
8818
					} else {
8819
						attribs = {
8820
							'stroke-width': crosshairsOptions[i].width || 1,
8821
							stroke: crosshairsOptions[i].color || '#C0C0C0',
8822
							zIndex: crosshairsOptions[i].zIndex || 2
8823
						};
8824
						if (crosshairsOptions[i].dashStyle) {
8825
							attribs.dashstyle = crosshairsOptions[i].dashStyle;
8826
						}
8827
						tooltip.crosshairs[i] = chart.renderer.path(path)
8828
							.attr(attribs)
8829
							.add();
8830
					}
8831
				}
8832
			}
8833
		}
8834
		fireEvent(chart, 'tooltipRefresh', {
8835
				text: text,
8836
				x: x + chart.plotLeft,
8837
				y: y + chart.plotTop,
8838
				borderColor: borderColor
8839
			});
8840
	},
8841
	
8842
	/**
8843
	 * Find the new position and perform the move
8844
	 */
8845
	updatePosition: function (point) {
8846
		var chart = this.chart,
8847
			label = this.label, 
8848
			pos = (this.options.positioner || this.getPosition).call(
8849
				this,
8850
				label.width,
8851
				label.height,
8852
				point
8853
			);
8854
8855
		// do the move
8856
		this.move(
8857
			mathRound(pos.x), 
8858
			mathRound(pos.y), 
8859
			point.plotX + chart.plotLeft, 
8860
			point.plotY + chart.plotTop
8861
		);
8862
	}
8863
};
8864
/**
8865
 * The mouse tracker object. All methods starting with "on" are primary DOM event handlers. 
8866
 * Subsequent methods should be named differently from what they are doing.
8867
 * @param {Object} chart The Chart instance
8868
 * @param {Object} options The root options object
8869
 */
8870
function Pointer(chart, options) {
8871
	this.init(chart, options);
8872
}
8873
8874
Pointer.prototype = {
8875
	/**
8876
	 * Initialize Pointer
8877
	 */
8878
	init: function (chart, options) {
8879
		
8880
		var zoomType = useCanVG ? '' : options.chart.zoomType,
8881
			inverted = chart.inverted,
8882
			zoomX,
8883
			zoomY;
8884
8885
		// Store references
8886
		this.options = options;
8887
		this.chart = chart;
8888
		
8889
		// Zoom status
8890
		this.zoomX = zoomX = /x/.test(zoomType);
8891
		this.zoomY = zoomY = /y/.test(zoomType);
8892
		this.zoomHor = (zoomX && !inverted) || (zoomY && inverted);
8893
		this.zoomVert = (zoomY && !inverted) || (zoomX && inverted);
8894
8895
		this.pinchDown = [];
8896
		this.lastValidTouch = {};
8897
8898
		if (options.tooltip.enabled) {
8899
			chart.tooltip = new Tooltip(chart, options.tooltip);
8900
		}
8901
8902
		this.setDOMEvents();
8903
	}, 
8904
8905
	/**
8906
	 * Add crossbrowser support for chartX and chartY
8907
	 * @param {Object} e The event object in standard browsers
8908
	 */
8909
	normalize: function (e) {
8910
		var chartPosition,
8911
			chartX,
8912
			chartY,
8913
			ePos;
8914
8915
		// common IE normalizing
8916
		e = e || win.event;
8917
		if (!e.target) {
8918
			e.target = e.srcElement;
8919
		}
8920
8921
		// Framework specific normalizing (#1165)
8922
		e = washMouseEvent(e);
8923
		
8924
		// iOS
8925
		ePos = e.touches ? e.touches.item(0) : e;
8926
8927
		// get mouse position
8928
		this.chartPosition = chartPosition = offset(this.chart.container);
8929
8930
		// chartX and chartY
8931
		if (ePos.pageX === UNDEFINED) { // IE < 9. #886.
8932
			chartX = e.x;
8933
			chartY = e.y;
8934
		} else {
8935
			chartX = ePos.pageX - chartPosition.left;
8936
			chartY = ePos.pageY - chartPosition.top;
8937
		}
8938
8939
		return extend(e, {
8940
			chartX: mathRound(chartX),
8941
			chartY: mathRound(chartY)
8942
		});
8943
	},
8944
8945
	/**
8946
	 * Get the click position in terms of axis values.
8947
	 *
8948
	 * @param {Object} e A pointer event
8949
	 */
8950
	getCoordinates: function (e) {
8951
		var coordinates = {
8952
				xAxis: [],
8953
				yAxis: []
8954
			};
8955
8956
		each(this.chart.axes, function (axis) {
8957
			coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({
8958
				axis: axis,
8959
				value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY'])
8960
			});
8961
		});
8962
		return coordinates;
8963
	},
8964
	
8965
	/**
8966
	 * Return the index in the tooltipPoints array, corresponding to pixel position in 
8967
	 * the plot area.
8968
	 */
8969
	getIndex: function (e) {
8970
		var chart = this.chart;
8971
		return chart.inverted ? 
8972
			chart.plotHeight + chart.plotTop - e.chartY : 
8973
			e.chartX - chart.plotLeft;
8974
	},
8975
8976
	/**
8977
	 * With line type charts with a single tracker, get the point closest to the mouse.
8978
	 * Run Point.onMouseOver and display tooltip for the point or points.
8979
	 */
8980
	runPointActions: function (e) {
8981
		var pointer = this,
8982
			chart = pointer.chart,
8983
			series = chart.series,
8984
			tooltip = chart.tooltip,
8985
			point,
8986
			points,
8987
			hoverPoint = chart.hoverPoint,
8988
			hoverSeries = chart.hoverSeries,
8989
			i,
8990
			j,
8991
			distance = chart.chartWidth,
8992
			index = pointer.getIndex(e),
8993
			anchor;
8994
8995
		// shared tooltip
8996
		if (tooltip && pointer.options.tooltip.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) {
8997
			points = [];
8998
8999
			// loop over all series and find the ones with points closest to the mouse
9000
			i = series.length;
9001
			for (j = 0; j < i; j++) {
9002
				if (series[j].visible &&
9003
						series[j].options.enableMouseTracking !== false &&
9004
						!series[j].noSharedTooltip && series[j].tooltipPoints.length) {
9005
					point = series[j].tooltipPoints[index];
9006
					if (point.series) { // not a dummy point, #1544
9007
						point._dist = mathAbs(index - point.clientX);
9008
						distance = mathMin(distance, point._dist);
9009
						points.push(point);
9010
					}
9011
				}
9012
			}
9013
			// remove furthest points
9014
			i = points.length;
9015
			while (i--) {
9016
				if (points[i]._dist > distance) {
9017
					points.splice(i, 1);
9018
				}
9019
			}
9020
			// refresh the tooltip if necessary
9021
			if (points.length && (points[0].clientX !== pointer.hoverX)) {
9022
				tooltip.refresh(points, e);
9023
				pointer.hoverX = points[0].clientX;
9024
			}
9025
		}
9026
9027
		// separate tooltip and general mouse events
9028
		if (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker
9029
9030
			// get the point
9031
			point = hoverSeries.tooltipPoints[index];
9032
9033
			// a new point is hovered, refresh the tooltip
9034
			if (point && point !== hoverPoint) {
9035
9036
				// trigger the events
9037
				point.onMouseOver(e);
9038
9039
			}
9040
			
9041
		} else if (tooltip && tooltip.followPointer && !tooltip.isHidden) {
9042
			anchor = tooltip.getAnchor([{}], e);
9043
			tooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] });
9044
		}
9045
	},
9046
9047
9048
9049
	/**
9050
	 * Reset the tracking by hiding the tooltip, the hover series state and the hover point
9051
	 * 
9052
	 * @param allowMove {Boolean} Instead of destroying the tooltip altogether, allow moving it if possible
9053
	 */
9054
	reset: function (allowMove) {
9055
		var pointer = this,
9056
			chart = pointer.chart,
9057
			hoverSeries = chart.hoverSeries,
9058
			hoverPoint = chart.hoverPoint,
9059
			tooltip = chart.tooltip,
9060
			tooltipPoints = tooltip && tooltip.shared ? chart.hoverPoints : hoverPoint;
9061
			
9062
		// Narrow in allowMove
9063
		allowMove = allowMove && tooltip && tooltipPoints;
9064
			
9065
		// Check if the points have moved outside the plot area, #1003
9066
		if (allowMove && splat(tooltipPoints)[0].plotX === UNDEFINED) {
9067
			allowMove = false;
9068
		}	
9069
9070
		// Just move the tooltip, #349
9071
		if (allowMove) {
9072
			tooltip.refresh(tooltipPoints);
9073
9074
		// Full reset
9075
		} else {
9076
9077
			if (hoverPoint) {
9078
				hoverPoint.onMouseOut();
9079
			}
9080
9081
			if (hoverSeries) {
9082
				hoverSeries.onMouseOut();
9083
			}
9084
9085
			if (tooltip) {
9086
				tooltip.hide();
9087
				tooltip.hideCrosshairs();
9088
			}
9089
9090
			pointer.hoverX = null;
9091
9092
		}
9093
	},
9094
9095
	/**
9096
	 * Scale series groups to a certain scale and translation
9097
	 */
9098
	scaleGroups: function (attribs, clip) {
9099
9100
		var chart = this.chart;
9101
9102
		// Scale each series
9103
		each(chart.series, function (series) {
9104
			if (series.xAxis && series.xAxis.zoomEnabled) {
9105
				series.group.attr(attribs);
9106
				if (series.markerGroup) {
9107
					series.markerGroup.attr(attribs);
9108
					series.markerGroup.clip(clip ? chart.clipRect : null);
9109
				}
9110
				if (series.dataLabelsGroup) {
9111
					series.dataLabelsGroup.attr(attribs);
9112
				}
9113
			}
9114
		});
9115
		
9116
		// Clip
9117
		chart.clipRect.attr(clip || chart.clipBox);
9118
	},
9119
9120
	/**
9121
	 * Run translation operations for each direction (horizontal and vertical) independently
9122
	 */
9123
	pinchTranslateDirection: function (horiz, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) {
9124
		var chart = this.chart,
9125
			xy = horiz ? 'x' : 'y',
9126
			XY = horiz ? 'X' : 'Y',
9127
			sChartXY = 'chart' + XY,
9128
			wh = horiz ? 'width' : 'height',
9129
			plotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')],
9130
			selectionWH,
9131
			selectionXY,
9132
			clipXY,
9133
			scale = 1,
9134
			inverted = chart.inverted,
9135
			bounds = chart.bounds[horiz ? 'h' : 'v'],
9136
			singleTouch = pinchDown.length === 1,
9137
			touch0Start = pinchDown[0][sChartXY],
9138
			touch0Now = touches[0][sChartXY],
9139
			touch1Start = !singleTouch && pinchDown[1][sChartXY],
9140
			touch1Now = !singleTouch && touches[1][sChartXY],
9141
			outOfBounds,
9142
			transformScale,
9143
			scaleKey,
9144
			setScale = function () {
9145
				if (!singleTouch && mathAbs(touch0Start - touch1Start) > 20) { // Don't zoom if fingers are too close on this axis
9146
					scale = mathAbs(touch0Now - touch1Now) / mathAbs(touch0Start - touch1Start);	
9147
				}
9148
				
9149
				clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start;
9150
				selectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] / scale;
9151
			};
9152
9153
		// Set the scale, first pass
9154
		setScale();
9155
9156
		selectionXY = clipXY; // the clip position (x or y) is altered if out of bounds, the selection position is not
9157
9158
		// Out of bounds
9159
		if (selectionXY < bounds.min) {
9160
			selectionXY = bounds.min;
9161
			outOfBounds = true;
9162
		} else if (selectionXY + selectionWH > bounds.max) {
9163
			selectionXY = bounds.max - selectionWH;
9164
			outOfBounds = true;
9165
		}
9166
		
9167
		// Is the chart dragged off its bounds, determined by dataMin and dataMax?
9168
		if (outOfBounds) {
9169
9170
			// Modify the touchNow position in order to create an elastic drag movement. This indicates
9171
			// to the user that the chart is responsive but can't be dragged further.
9172
			touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]);
9173
			if (!singleTouch) {
9174
				touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]);
9175
			}
9176
9177
			// Set the scale, second pass to adapt to the modified touchNow positions
9178
			setScale();
9179
9180
		} else {
9181
			lastValidTouch[xy] = [touch0Now, touch1Now];
9182
		}
9183
9184
		
9185
		// Set geometry for clipping, selection and transformation
9186
		if (!inverted) { // TODO: implement clipping for inverted charts
9187
			clip[xy] = clipXY - plotLeftTop;
9188
			clip[wh] = selectionWH;
9189
		}
9190
		scaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY;
9191
		transformScale = inverted ? 1 / scale : scale;
9192
9193
		selectionMarker[wh] = selectionWH;
9194
		selectionMarker[xy] = selectionXY;
9195
		transform[scaleKey] = scale;
9196
		transform['translate' + XY] = (transformScale * plotLeftTop) + (touch0Now - (transformScale * touch0Start));
9197
	},
9198
	
9199
	/**
9200
	 * Handle touch events with two touches
9201
	 */
9202
	pinch: function (e) {
9203
9204
		var self = this,
9205
			chart = self.chart,
9206
			pinchDown = self.pinchDown,
9207
			followTouchMove = chart.tooltip && chart.tooltip.options.followTouchMove,
9208
			touches = e.touches,
9209
			touchesLength = touches.length,
9210
			lastValidTouch = self.lastValidTouch,
9211
			zoomHor = self.zoomHor || self.pinchHor,
9212
			zoomVert = self.zoomVert || self.pinchVert,
9213
			hasZoom = zoomHor || zoomVert,
9214
			selectionMarker = self.selectionMarker,
9215
			transform = {},
9216
			clip = {};
9217
9218
		// On touch devices, only proceed to trigger click if a handler is defined
9219
		if (e.type === 'touchstart') {
9220
			if (followTouchMove || hasZoom) {
9221
				e.preventDefault();
9222
			}
9223
		}
9224
			
9225
		// Normalize each touch
9226
		map(touches, function (e) {
9227
			return self.normalize(e);
9228
		});
9229
			
9230
		// Register the touch start position
9231
		if (e.type === 'touchstart') {
9232
			each(touches, function (e, i) {
9233
				pinchDown[i] = { chartX: e.chartX, chartY: e.chartY };
9234
			});
9235
			lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] && pinchDown[1].chartX];
9236
			lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] && pinchDown[1].chartY];
9237
9238
			// Identify the data bounds in pixels
9239
			each(chart.axes, function (axis) {
9240
				if (axis.zoomEnabled) {
9241
					var bounds = chart.bounds[axis.horiz ? 'h' : 'v'],
9242
						minPixelPadding = axis.minPixelPadding,
9243
						min = axis.toPixels(axis.dataMin),
9244
						max = axis.toPixels(axis.dataMax),
9245
						absMin = mathMin(min, max),
9246
						absMax = mathMax(min, max);
9247
9248
					// Store the bounds for use in the touchmove handler
9249
					bounds.min = mathMin(axis.pos, absMin - minPixelPadding);
9250
					bounds.max = mathMax(axis.pos + axis.len, absMax + minPixelPadding);
9251
				}
9252
			});
9253
		
9254
		// Event type is touchmove, handle panning and pinching
9255
		} else if (pinchDown.length) { // can be 0 when releasing, if touchend fires first
9256
			
9257
9258
			// Set the marker
9259
			if (!selectionMarker) {
9260
				self.selectionMarker = selectionMarker = extend({
9261
					destroy: noop
9262
				}, chart.plotBox);
9263
			}
9264
9265
			
9266
9267
			if (zoomHor) {
9268
				self.pinchTranslateDirection(true, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
9269
			}
9270
			if (zoomVert) {
9271
				self.pinchTranslateDirection(false, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);
9272
			}
9273
9274
			self.hasPinched = hasZoom;
9275
9276
			// Scale and translate the groups to provide visual feedback during pinching
9277
			self.scaleGroups(transform, clip);
9278
			
9279
			// Optionally move the tooltip on touchmove
9280
			if (!hasZoom && followTouchMove && touchesLength === 1) {
9281
				this.runPointActions(self.normalize(e));
9282
			}
9283
		}
9284
	},
9285
9286
	/**
9287
	 * Start a drag operation
9288
	 */
9289
	dragStart: function (e) {
9290
		var chart = this.chart;
9291
9292
		// Record the start position
9293
		chart.mouseIsDown = e.type;
9294
		chart.cancelClick = false;
9295
		chart.mouseDownX = this.mouseDownX = e.chartX;
9296
		this.mouseDownY = e.chartY;
9297
	},
9298
9299
	/**
9300
	 * Perform a drag operation in response to a mousemove event while the mouse is down
9301
	 */
9302
	drag: function (e) {
9303
9304
		var chart = this.chart,
9305
			chartOptions = chart.options.chart,
9306
			chartX = e.chartX,
9307
			chartY = e.chartY,
9308
			zoomHor = this.zoomHor,
9309
			zoomVert = this.zoomVert,
9310
			plotLeft = chart.plotLeft,
9311
			plotTop = chart.plotTop,
9312
			plotWidth = chart.plotWidth,
9313
			plotHeight = chart.plotHeight,
9314
			clickedInside,
9315
			size,
9316
			mouseDownX = this.mouseDownX,
9317
			mouseDownY = this.mouseDownY;
9318
9319
		// If the mouse is outside the plot area, adjust to cooordinates
9320
		// inside to prevent the selection marker from going outside
9321
		if (chartX < plotLeft) {
9322
			chartX = plotLeft;
9323
		} else if (chartX > plotLeft + plotWidth) {
9324
			chartX = plotLeft + plotWidth;
9325
		}
9326
9327
		if (chartY < plotTop) {
9328
			chartY = plotTop;
9329
		} else if (chartY > plotTop + plotHeight) {
9330
			chartY = plotTop + plotHeight;
9331
		}
9332
		
9333
		// determine if the mouse has moved more than 10px
9334
		this.hasDragged = Math.sqrt(
9335
			Math.pow(mouseDownX - chartX, 2) +
9336
			Math.pow(mouseDownY - chartY, 2)
9337
		);
9338
		if (this.hasDragged > 10) {
9339
			clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop);
9340
9341
			// make a selection
9342
			if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside) {
9343
				if (!this.selectionMarker) {
9344
					this.selectionMarker = chart.renderer.rect(
9345
						plotLeft,
9346
						plotTop,
9347
						zoomHor ? 1 : plotWidth,
9348
						zoomVert ? 1 : plotHeight,
9349
						0
9350
					)
9351
					.attr({
9352
						fill: chartOptions.selectionMarkerFill || 'rgba(69,114,167,0.25)',
9353
						zIndex: 7
9354
					})
9355
					.add();
9356
				}
9357
			}
9358
9359
			// adjust the width of the selection marker
9360
			if (this.selectionMarker && zoomHor) {
9361
				size = chartX - mouseDownX;
9362
				this.selectionMarker.attr({
9363
					width: mathAbs(size),
9364
					x: (size > 0 ? 0 : size) + mouseDownX
9365
				});
9366
			}
9367
			// adjust the height of the selection marker
9368
			if (this.selectionMarker && zoomVert) {
9369
				size = chartY - mouseDownY;
9370
				this.selectionMarker.attr({
9371
					height: mathAbs(size),
9372
					y: (size > 0 ? 0 : size) + mouseDownY
9373
				});
9374
			}
9375
9376
			// panning
9377
			if (clickedInside && !this.selectionMarker && chartOptions.panning) {
9378
				chart.pan(chartX);
9379
			}
9380
		}
9381
	},
9382
9383
	/**
9384
	 * On mouse up or touch end across the entire document, drop the selection.
9385
	 */
9386
	drop: function (e) {
9387
		var chart = this.chart,
9388
			hasPinched = this.hasPinched;
9389
9390
		if (this.selectionMarker) {
9391
			var selectionData = {
9392
					xAxis: [],
9393
					yAxis: [],
9394
					originalEvent: e.originalEvent || e
9395
				},
9396
				selectionBox = this.selectionMarker,
9397
				selectionLeft = selectionBox.x,
9398
				selectionTop = selectionBox.y,
9399
				runZoom;
9400
			// a selection has been made
9401
			if (this.hasDragged || hasPinched) {
9402
9403
				// record each axis' min and max
9404
				each(chart.axes, function (axis) {
9405
					if (axis.zoomEnabled) {
9406
						var horiz = axis.horiz,
9407
							selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop)),
9408
							selectionMax = axis.toValue((horiz ? selectionLeft + selectionBox.width : selectionTop + selectionBox.height));
9409
9410
						if (!isNaN(selectionMin) && !isNaN(selectionMax)) { // #859
9411
							selectionData[axis.xOrY + 'Axis'].push({
9412
								axis: axis,
9413
								min: mathMin(selectionMin, selectionMax), // for reversed axes,
9414
								max: mathMax(selectionMin, selectionMax)
9415
							});
9416
							runZoom = true;
9417
						}
9418
					}
9419
				});
9420
				if (runZoom) {
9421
					fireEvent(chart, 'selection', selectionData, function (args) { 
9422
						chart.zoom(extend(args, hasPinched ? { animation: false } : null)); 
9423
					});
9424
				}
9425
9426
			}
9427
			this.selectionMarker = this.selectionMarker.destroy();
9428
9429
			// Reset scaling preview
9430
			if (hasPinched) {
9431
				this.scaleGroups({
9432
					translateX: chart.plotLeft,
9433
					translateY: chart.plotTop,
9434
					scaleX: 1,
9435
					scaleY: 1
9436
				});
9437
			}
9438
		}
9439
9440
		// Reset all
9441
		if (chart) { // it may be destroyed on mouse up - #877
9442
			css(chart.container, { cursor: chart._cursor });
9443
			chart.cancelClick = this.hasDragged > 10; // #370
9444
			chart.mouseIsDown = this.hasDragged = this.hasPinched = false;
9445
			this.pinchDown = [];
9446
		}
9447
	},
9448
9449
	onContainerMouseDown: function (e) {
9450
9451
		e = this.normalize(e);
9452
9453
		// issue #295, dragging not always working in Firefox
9454
		if (e.preventDefault) {
9455
			e.preventDefault();
9456
		}
9457
		
9458
		this.dragStart(e);
9459
	},
9460
9461
	
9462
9463
	onDocumentMouseUp: function (e) {
9464
		this.drop(e);
9465
	},
9466
9467
	/**
9468
	 * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea.
9469
	 * Issue #149 workaround. The mouseleave event does not always fire. 
9470
	 */
9471
	onDocumentMouseMove: function (e) {
9472
		var chart = this.chart,
9473
			chartPosition = this.chartPosition,
9474
			hoverSeries = chart.hoverSeries;
9475
9476
		// Get e.pageX and e.pageY back in MooTools
9477
		e = washMouseEvent(e);
9478
9479
		// If we're outside, hide the tooltip
9480
		if (chartPosition && hoverSeries && hoverSeries.isCartesian &&
9481
			!chart.isInsidePlot(e.pageX - chartPosition.left - chart.plotLeft,
9482
			e.pageY - chartPosition.top - chart.plotTop)) {
9483
				this.reset();
9484
		}
9485
	},
9486
9487
	/**
9488
	 * When mouse leaves the container, hide the tooltip.
9489
	 */
9490
	onContainerMouseLeave: function () {
9491
		this.reset();
9492
		this.chartPosition = null; // also reset the chart position, used in #149 fix
9493
	},
9494
9495
	// The mousemove, touchmove and touchstart event handler
9496
	onContainerMouseMove: function (e) {
9497
9498
		var chart = this.chart;
9499
9500
		// normalize
9501
		e = this.normalize(e);
9502
9503
		// #295
9504
		e.returnValue = false;
9505
		
9506
		
9507
		if (chart.mouseIsDown === 'mousedown') {
9508
			this.drag(e);
9509
		} 
9510
		
9511
		// Show the tooltip and run mouse over events (#977)
9512
		if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop) && !chart.openMenu) {
9513
			this.runPointActions(e);
9514
		}
9515
	},
9516
9517
	/**
9518
	 * Utility to detect whether an element has, or has a parent with, a specific
9519
	 * class name. Used on detection of tracker objects and on deciding whether
9520
	 * hovering the tooltip should cause the active series to mouse out.
9521
	 */
9522
	inClass: function (element, className) {
9523
		var elemClassName;
9524
		while (element) {
9525
			elemClassName = attr(element, 'class');
9526
			if (elemClassName) {
9527
				if (elemClassName.indexOf(className) !== -1) {
9528
					return true;
9529
				} else if (elemClassName.indexOf(PREFIX + 'container') !== -1) {
9530
					return false;
9531
				}
9532
			}
9533
			element = element.parentNode;
9534
		}		
9535
	},
9536
9537
	onTrackerMouseOut: function (e) {
9538
		var series = this.chart.hoverSeries;
9539
		if (series && !series.options.stickyTracking && !this.inClass(e.toElement || e.relatedTarget, PREFIX + 'tooltip')) {
9540
			series.onMouseOut();
9541
		}
9542
	},
9543
9544
	onContainerClick: function (e) {
9545
		var chart = this.chart,
9546
			hoverPoint = chart.hoverPoint, 
9547
			plotLeft = chart.plotLeft,
9548
			plotTop = chart.plotTop,
9549
			inverted = chart.inverted,
9550
			chartPosition,
9551
			plotX,
9552
			plotY;
9553
		
9554
		e = this.normalize(e);
9555
		e.cancelBubble = true; // IE specific
9556
9557
		if (!chart.cancelClick) {
9558
			
9559
			// On tracker click, fire the series and point events. #783, #1583
9560
			if (hoverPoint && this.inClass(e.target, PREFIX + 'tracker')) {
9561
				chartPosition = this.chartPosition;
9562
				plotX = hoverPoint.plotX;
9563
				plotY = hoverPoint.plotY;
9564
9565
				// add page position info
9566
				extend(hoverPoint, {
9567
					pageX: chartPosition.left + plotLeft +
9568
						(inverted ? chart.plotWidth - plotY : plotX),
9569
					pageY: chartPosition.top + plotTop +
9570
						(inverted ? chart.plotHeight - plotX : plotY)
9571
				});
9572
			
9573
				// the series click event
9574
				fireEvent(hoverPoint.series, 'click', extend(e, {
9575
					point: hoverPoint
9576
				}));
9577
9578
				// the point click event
9579
				if (chart.hoverPoint) { // it may be destroyed (#1844)
9580
					hoverPoint.firePointEvent('click', e);
9581
				}
9582
9583
			// When clicking outside a tracker, fire a chart event
9584
			} else {
9585
				extend(e, this.getCoordinates(e));
9586
9587
				// fire a click event in the chart
9588
				if (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
9589
					fireEvent(chart, 'click', e);
9590
				}
9591
			}
9592
9593
9594
		}
9595
	},
9596
9597
	onContainerTouchStart: function (e) {
9598
		var chart = this.chart;
9599
9600
		if (e.touches.length === 1) {
9601
9602
			e = this.normalize(e);
9603
9604
			if (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
9605
9606
				// Prevent the click pseudo event from firing unless it is set in the options
9607
				/*if (!chart.runChartClick) {
9608
					e.preventDefault();
9609
				}*/
9610
			
9611
				// Run mouse events and display tooltip etc
9612
				this.runPointActions(e);
9613
9614
				this.pinch(e);
9615
			}
9616
9617
		} else if (e.touches.length === 2) {
9618
			this.pinch(e);	
9619
		}		
9620
	},
9621
9622
	onContainerTouchMove: function (e) {
9623
		if (e.touches.length === 1 || e.touches.length === 2) {
9624
			this.pinch(e);
9625
		}
9626
	},
9627
9628
	onDocumentTouchEnd: function (e) {
9629
		this.drop(e);
9630
	},
9631
9632
	/**
9633
	 * Set the JS DOM events on the container and document. This method should contain
9634
	 * a one-to-one assignment between methods and their handlers. Any advanced logic should
9635
	 * be moved to the handler reflecting the event's name.
9636
	 */
9637
	setDOMEvents: function () {
9638
9639
		var pointer = this,
9640
			container = pointer.chart.container,
9641
			events;
9642
9643
		this._events = events = [
9644
			[container, 'onmousedown', 'onContainerMouseDown'],
9645
			[container, 'onmousemove', 'onContainerMouseMove'],
9646
			[container, 'onclick', 'onContainerClick'],
9647
			[container, 'mouseleave', 'onContainerMouseLeave'],
9648
			[doc, 'mousemove', 'onDocumentMouseMove'],
9649
			[doc, 'mouseup', 'onDocumentMouseUp']
9650
		];
9651
9652
		if (hasTouch) {
9653
			events.push(
9654
				[container, 'ontouchstart', 'onContainerTouchStart'],
9655
				[container, 'ontouchmove', 'onContainerTouchMove'],
9656
				[doc, 'touchend', 'onDocumentTouchEnd']
9657
			);
9658
		}
9659
9660
		each(events, function (eventConfig) {
9661
9662
			// First, create the callback function that in turn calls the method on Pointer
9663
			pointer['_' + eventConfig[2]] = function (e) {
9664
				pointer[eventConfig[2]](e);
9665
			};
9666
9667
			// Now attach the function, either as a direct property or through addEvent
9668
			if (eventConfig[1].indexOf('on') === 0) {
9669
				eventConfig[0][eventConfig[1]] = pointer['_' + eventConfig[2]];
9670
			} else {
9671
				addEvent(eventConfig[0], eventConfig[1], pointer['_' + eventConfig[2]]);
9672
			}
9673
		});
9674
9675
		
9676
	},
9677
9678
	/**
9679
	 * Destroys the Pointer object and disconnects DOM events.
9680
	 */
9681
	destroy: function () {
9682
		var pointer = this;
9683
9684
		// Release all DOM events
9685
		each(pointer._events, function (eventConfig) {	
9686
			if (eventConfig[1].indexOf('on') === 0) {
9687
				eventConfig[0][eventConfig[1]] = null; // delete breaks oldIE
9688
			} else {		
9689
				removeEvent(eventConfig[0], eventConfig[1], pointer['_' + eventConfig[2]]);
9690
			}
9691
		});
9692
		delete pointer._events;
9693
9694
		// memory and CPU leak
9695
		clearInterval(pointer.tooltipTimeout);
9696
	}
9697
};
9698
/**
9699
 * The overview of the chart's series
9700
 */
9701
function Legend(chart, options) {
9702
	this.init(chart, options);
9703
}
9704
9705
Legend.prototype = {
9706
	
9707
	/**
9708
	 * Initialize the legend
9709
	 */
9710
	init: function (chart, options) {
9711
		
9712
		var legend = this,
9713
			itemStyle = options.itemStyle,
9714
			padding = pick(options.padding, 8),
9715
			itemMarginTop = options.itemMarginTop || 0;
9716
	
9717
		this.options = options;
9718
9719
		if (!options.enabled) {
9720
			return;
9721
		}
9722
	
9723
		legend.baseline = pInt(itemStyle.fontSize) + 3 + itemMarginTop; // used in Series prototype
9724
		legend.itemStyle = itemStyle;
9725
		legend.itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle);
9726
		legend.itemMarginTop = itemMarginTop;
9727
		legend.padding = padding;
9728
		legend.initialItemX = padding;
9729
		legend.initialItemY = padding - 5; // 5 is the number of pixels above the text
9730
		legend.maxItemWidth = 0;
9731
		legend.chart = chart;
9732
		legend.itemHeight = 0;
9733
		legend.lastLineHeight = 0;
9734
9735
		// Render it
9736
		legend.render();
9737
9738
		// move checkboxes
9739
		addEvent(legend.chart, 'endResize', function () { 
9740
			legend.positionCheckboxes();
9741
		});
9742
9743
	},
9744
9745
	/**
9746
	 * Set the colors for the legend item
9747
	 * @param {Object} item A Series or Point instance
9748
	 * @param {Object} visible Dimmed or colored
9749
	 */
9750
	colorizeItem: function (item, visible) {
9751
		var legend = this,
9752
			options = legend.options,
9753
			legendItem = item.legendItem,
9754
			legendLine = item.legendLine,
9755
			legendSymbol = item.legendSymbol,
9756
			hiddenColor = legend.itemHiddenStyle.color,
9757
			textColor = visible ? options.itemStyle.color : hiddenColor,
9758
			symbolColor = visible ? item.color : hiddenColor,
9759
			markerOptions = item.options && item.options.marker,
9760
			symbolAttr = {
9761
				stroke: symbolColor,
9762
				fill: symbolColor
9763
			},
9764
			key,
9765
			val;
9766
9767
		
9768
		if (legendItem) {
9769
			legendItem.css({ fill: textColor, color: textColor }); // color for #1553, oldIE
9770
		}
9771
		if (legendLine) {
9772
			legendLine.attr({ stroke: symbolColor });
9773
		}
9774
		
9775
		if (legendSymbol) {
9776
			
9777
			// Apply marker options
9778
			if (markerOptions) {
9779
				markerOptions = item.convertAttribs(markerOptions);
9780
				for (key in markerOptions) {
9781
					val = markerOptions[key];
9782
					if (val !== UNDEFINED) {
9783
						symbolAttr[key] = val;
9784
					}
9785
				}
9786
			}
9787
9788
			legendSymbol.attr(symbolAttr);
9789
		}
9790
	},
9791
9792
	/**
9793
	 * Position the legend item
9794
	 * @param {Object} item A Series or Point instance
9795
	 */
9796
	positionItem: function (item) {
9797
		var legend = this,
9798
			options = legend.options,
9799
			symbolPadding = options.symbolPadding,
9800
			ltr = !options.rtl,
9801
			legendItemPos = item._legendItemPos,
9802
			itemX = legendItemPos[0],
9803
			itemY = legendItemPos[1],
9804
			checkbox = item.checkbox;
9805
9806
		if (item.legendGroup) {
9807
			item.legendGroup.translate(
9808
				ltr ? itemX : legend.legendWidth - itemX - 2 * symbolPadding - 4,
9809
				itemY
9810
			);
9811
		}
9812
9813
		if (checkbox) {
9814
			checkbox.x = itemX;
9815
			checkbox.y = itemY;
9816
		}
9817
	},
9818
9819
	/**
9820
	 * Destroy a single legend item
9821
	 * @param {Object} item The series or point
9822
	 */
9823
	destroyItem: function (item) {
9824
		var checkbox = item.checkbox;
9825
9826
		// destroy SVG elements
9827
		each(['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'], function (key) {
9828
			if (item[key]) {
9829
				item[key].destroy();
9830
			}
9831
		});
9832
9833
		if (checkbox) {
9834
			discardElement(item.checkbox);
9835
		}
9836
	},
9837
9838
	/**
9839
	 * Destroys the legend.
9840
	 */
9841
	destroy: function () {
9842
		var legend = this,
9843
			legendGroup = legend.group,
9844
			box = legend.box;
9845
9846
		if (box) {
9847
			legend.box = box.destroy();
9848
		}
9849
9850
		if (legendGroup) {
9851
			legend.group = legendGroup.destroy();
9852
		}
9853
	},
9854
9855
	/**
9856
	 * Position the checkboxes after the width is determined
9857
	 */
9858
	positionCheckboxes: function (scrollOffset) {
9859
		var alignAttr = this.group.alignAttr,
9860
			translateY,
9861
			clipHeight = this.clipHeight || this.legendHeight;
9862
9863
		if (alignAttr) {
9864
			translateY = alignAttr.translateY;
9865
			each(this.allItems, function (item) {
9866
				var checkbox = item.checkbox,
9867
					top;
9868
				
9869
				if (checkbox) {
9870
					top = (translateY + checkbox.y + (scrollOffset || 0) + 3);
9871
					css(checkbox, {
9872
						left: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 20) + PX,
9873
						top: top + PX,
9874
						display: top > translateY - 6 && top < translateY + clipHeight - 6 ? '' : NONE
9875
					});
9876
				}
9877
			});
9878
		}
9879
	},
9880
	
9881
	/**
9882
	 * Render the legend title on top of the legend
9883
	 */
9884
	renderTitle: function () {
9885
		var options = this.options,
9886
			padding = this.padding,
9887
			titleOptions = options.title,
9888
			titleHeight = 0;
9889
		
9890
		if (titleOptions.text) {
9891
			if (!this.title) {
9892
				this.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, null, null, null, null, null, 'legend-title')
9893
					.attr({ zIndex: 1 })
9894
					.css(titleOptions.style)
9895
					.add(this.group);
9896
			}
9897
			titleHeight = this.title.getBBox().height;
9898
			this.contentGroup.attr({ translateY: titleHeight });
9899
		}
9900
		this.titleHeight = titleHeight;
9901
	},
9902
9903
	/**
9904
	 * Render a single specific legend item
9905
	 * @param {Object} item A series or point
9906
	 */
9907
	renderItem: function (item) {
9908
		var legend = this,
9909
			chart = legend.chart,
9910
			renderer = chart.renderer,
9911
			options = legend.options,
9912
			horizontal = options.layout === 'horizontal',
9913
			symbolWidth = options.symbolWidth,
9914
			symbolPadding = options.symbolPadding,
9915
			itemStyle = legend.itemStyle,
9916
			itemHiddenStyle = legend.itemHiddenStyle,
9917
			padding = legend.padding,
9918
			ltr = !options.rtl,
9919
			itemHeight,
9920
			widthOption = options.width,
9921
			itemMarginBottom = options.itemMarginBottom || 0,
9922
			itemMarginTop = legend.itemMarginTop,
9923
			initialItemX = legend.initialItemX,
9924
			bBox,
9925
			itemWidth,
9926
			li = item.legendItem,
9927
			series = item.series || item,
9928
			itemOptions = series.options,
9929
			showCheckbox = itemOptions.showCheckbox,
9930
			useHTML = options.useHTML;
9931
9932
		if (!li) { // generate it once, later move it
9933
9934
			// Generate the group box
9935
			// A group to hold the symbol and text. Text is to be appended in Legend class.
9936
			item.legendGroup = renderer.g('legend-item')
9937
				.attr({ zIndex: 1 })
9938
				.add(legend.scrollGroup);
9939
9940
			// Draw the legend symbol inside the group box
9941
			series.drawLegendSymbol(legend, item);
9942
9943
			// Generate the list item text and add it to the group
9944
			item.legendItem = li = renderer.text(
9945
					options.labelFormat ? format(options.labelFormat, item) : options.labelFormatter.call(item),
9946
					ltr ? symbolWidth + symbolPadding : -symbolPadding,
9947
					legend.baseline,
9948
					useHTML
9949
				)
9950
				.css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021)
9951
				.attr({
9952
					align: ltr ? 'left' : 'right',
9953
					zIndex: 2
9954
				})
9955
				.add(item.legendGroup);
9956
9957
			// Set the events on the item group, or in case of useHTML, the item itself (#1249)
9958
			(useHTML ? li : item.legendGroup).on('mouseover', function () {
9959
					item.setState(HOVER_STATE);
9960
					li.css(legend.options.itemHoverStyle);
9961
				})
9962
				.on('mouseout', function () {
9963
					li.css(item.visible ? itemStyle : itemHiddenStyle);
9964
					item.setState();
9965
				})
9966
				.on('click', function (event) {
9967
					var strLegendItemClick = 'legendItemClick',
9968
						fnLegendItemClick = function () {
9969
							item.setVisible();
9970
						};
9971
						
9972
					// Pass over the click/touch event. #4.
9973
					event = {
9974
						browserEvent: event
9975
					};
9976
9977
					// click the name or symbol
9978
					if (item.firePointEvent) { // point
9979
						item.firePointEvent(strLegendItemClick, event, fnLegendItemClick);
9980
					} else {
9981
						fireEvent(item, strLegendItemClick, event, fnLegendItemClick);
9982
					}
9983
				});
9984
9985
			// Colorize the items
9986
			legend.colorizeItem(item, item.visible);
9987
9988
			// add the HTML checkbox on top
9989
			if (itemOptions && showCheckbox) {
9990
				item.checkbox = createElement('input', {
9991
					type: 'checkbox',
9992
					checked: item.selected,
9993
					defaultChecked: item.selected // required by IE7
9994
				}, options.itemCheckboxStyle, chart.container);
9995
9996
				addEvent(item.checkbox, 'click', function (event) {
9997
					var target = event.target;
9998
					fireEvent(item, 'checkboxClick', {
9999
							checked: target.checked
10000
						},
10001
						function () {
10002
							item.select();
10003
						}
10004
					);
10005
				});
10006
			}
10007
		}
10008
10009
		// calculate the positions for the next line
10010
		bBox = li.getBBox();
10011
10012
		itemWidth = item.legendItemWidth =
10013
			options.itemWidth || symbolWidth + symbolPadding + bBox.width + padding +
10014
			(showCheckbox ? 20 : 0);
10015
		legend.itemHeight = itemHeight = bBox.height;
10016
10017
		// if the item exceeds the width, start a new line
10018
		if (horizontal && legend.itemX - initialItemX + itemWidth >
10019
				(widthOption || (chart.chartWidth - 2 * padding - initialItemX))) {
10020
			legend.itemX = initialItemX;
10021
			legend.itemY += itemMarginTop + legend.lastLineHeight + itemMarginBottom;
10022
			legend.lastLineHeight = 0; // reset for next line
10023
		}
10024
10025
		// If the item exceeds the height, start a new column
10026
		/*if (!horizontal && legend.itemY + options.y + itemHeight > chart.chartHeight - spacingTop - spacingBottom) {
10027
			legend.itemY = legend.initialItemY;
10028
			legend.itemX += legend.maxItemWidth;
10029
			legend.maxItemWidth = 0;
10030
		}*/
10031
10032
		// Set the edge positions
10033
		legend.maxItemWidth = mathMax(legend.maxItemWidth, itemWidth);
10034
		legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom;
10035
		legend.lastLineHeight = mathMax(itemHeight, legend.lastLineHeight); // #915
10036
10037
		// cache the position of the newly generated or reordered items
10038
		item._legendItemPos = [legend.itemX, legend.itemY];
10039
10040
		// advance
10041
		if (horizontal) {
10042
			legend.itemX += itemWidth;
10043
10044
		} else {
10045
			legend.itemY += itemMarginTop + itemHeight + itemMarginBottom;
10046
			legend.lastLineHeight = itemHeight;
10047
		}
10048
10049
		// the width of the widest item
10050
		legend.offsetWidth = widthOption || mathMax(
10051
			horizontal ? legend.itemX - initialItemX : itemWidth,
10052
			legend.offsetWidth
10053
		);
10054
	},
10055
10056
	/**
10057
	 * Render the legend. This method can be called both before and after
10058
	 * chart.render. If called after, it will only rearrange items instead
10059
	 * of creating new ones.
10060
	 */
10061
	render: function () {
10062
		var legend = this,
10063
			chart = legend.chart,
10064
			renderer = chart.renderer,
10065
			legendGroup = legend.group,
10066
			allItems,
10067
			display,
10068
			legendWidth,
10069
			legendHeight,
10070
			box = legend.box,
10071
			options = legend.options,
10072
			padding = legend.padding,
10073
			legendBorderWidth = options.borderWidth,
10074
			legendBackgroundColor = options.backgroundColor;
10075
10076
		legend.itemX = legend.initialItemX;
10077
		legend.itemY = legend.initialItemY;
10078
		legend.offsetWidth = 0;
10079
		legend.lastItemY = 0;
10080
10081
		if (!legendGroup) {
10082
			legend.group = legendGroup = renderer.g('legend')
10083
				.attr({ zIndex: 7 }) 
10084
				.add();
10085
			legend.contentGroup = renderer.g()
10086
				.attr({ zIndex: 1 }) // above background
10087
				.add(legendGroup);
10088
			legend.scrollGroup = renderer.g()
10089
				.add(legend.contentGroup);
10090
		}
10091
		
10092
		legend.renderTitle();
10093
10094
		// add each series or point
10095
		allItems = [];
10096
		each(chart.series, function (serie) {
10097
			var seriesOptions = serie.options;
10098
10099
			if (!seriesOptions.showInLegend || defined(seriesOptions.linkedTo)) {
10100
				return;
10101
			}
10102
10103
			// use points or series for the legend item depending on legendType
10104
			allItems = allItems.concat(
10105
					serie.legendItems ||
10106
					(seriesOptions.legendType === 'point' ?
10107
							serie.data :
10108
							serie)
10109
			);
10110
		});
10111
10112
		// sort by legendIndex
10113
		stableSort(allItems, function (a, b) {
10114
			return ((a.options && a.options.legendIndex) || 0) - ((b.options && b.options.legendIndex) || 0);
10115
		});
10116
10117
		// reversed legend
10118
		if (options.reversed) {
10119
			allItems.reverse();
10120
		}
10121
10122
		legend.allItems = allItems;
10123
		legend.display = display = !!allItems.length;
10124
10125
		// render the items
10126
		each(allItems, function (item) {
10127
			legend.renderItem(item); 
10128
		});
10129
10130
		// Draw the border
10131
		legendWidth = options.width || legend.offsetWidth;
10132
		legendHeight = legend.lastItemY + legend.lastLineHeight + legend.titleHeight;
10133
		
10134
		
10135
		legendHeight = legend.handleOverflow(legendHeight);
10136
10137
		if (legendBorderWidth || legendBackgroundColor) {
10138
			legendWidth += padding;
10139
			legendHeight += padding;
10140
10141
			if (!box) {
10142
				legend.box = box = renderer.rect(
10143
					0,
10144
					0,
10145
					legendWidth,
10146
					legendHeight,
10147
					options.borderRadius,
10148
					legendBorderWidth || 0
10149
				).attr({
10150
					stroke: options.borderColor,
10151
					'stroke-width': legendBorderWidth || 0,
10152
					fill: legendBackgroundColor || NONE
10153
				})
10154
				.add(legendGroup)
10155
				.shadow(options.shadow);
10156
				box.isNew = true;
10157
10158
			} else if (legendWidth > 0 && legendHeight > 0) {
10159
				box[box.isNew ? 'attr' : 'animate'](
10160
					box.crisp(null, null, null, legendWidth, legendHeight)
10161
				);
10162
				box.isNew = false;
10163
			}
10164
10165
			// hide the border if no items
10166
			box[display ? 'show' : 'hide']();
10167
		}
10168
		
10169
		legend.legendWidth = legendWidth;
10170
		legend.legendHeight = legendHeight;
10171
10172
		// Now that the legend width and height are established, put the items in the 
10173
		// final position
10174
		each(allItems, function (item) {
10175
			legend.positionItem(item);
10176
		});
10177
10178
		// 1.x compatibility: positioning based on style
10179
		/*var props = ['left', 'right', 'top', 'bottom'],
10180
			prop,
10181
			i = 4;
10182
		while (i--) {
10183
			prop = props[i];
10184
			if (options.style[prop] && options.style[prop] !== 'auto') {
10185
				options[i < 2 ? 'align' : 'verticalAlign'] = prop;
10186
				options[i < 2 ? 'x' : 'y'] = pInt(options.style[prop]) * (i % 2 ? -1 : 1);
10187
			}
10188
		}*/
10189
10190
		if (display) {
10191
			legendGroup.align(extend({
10192
				width: legendWidth,
10193
				height: legendHeight
10194
			}, options), true, 'spacingBox');
10195
		}
10196
10197
		if (!chart.isResizing) {
10198
			this.positionCheckboxes();
10199
		}
10200
	},
10201
	
10202
	/**
10203
	 * Set up the overflow handling by adding navigation with up and down arrows below the
10204
	 * legend.
10205
	 */
10206
	handleOverflow: function (legendHeight) {
10207
		var legend = this,
10208
			chart = this.chart,
10209
			renderer = chart.renderer,
10210
			pageCount,
10211
			options = this.options,
10212
			optionsY = options.y,
10213
			alignTop = options.verticalAlign === 'top',
10214
			spaceHeight = chart.spacingBox.height + (alignTop ? -optionsY : optionsY) - this.padding,
10215
			maxHeight = options.maxHeight,
10216
			clipHeight,
10217
			clipRect = this.clipRect,
10218
			navOptions = options.navigation,
10219
			animation = pick(navOptions.animation, true),
10220
			arrowSize = navOptions.arrowSize || 12,
10221
			nav = this.nav;
10222
			
10223
		// Adjust the height
10224
		if (options.layout === 'horizontal') {
10225
			spaceHeight /= 2;
10226
		}
10227
		if (maxHeight) {
10228
			spaceHeight = mathMin(spaceHeight, maxHeight);
10229
		}
10230
		
10231
		// Reset the legend height and adjust the clipping rectangle
10232
		if (legendHeight > spaceHeight && !options.useHTML) {
10233
10234
			this.clipHeight = clipHeight = spaceHeight - 20 - this.titleHeight;
10235
			this.pageCount = pageCount = mathCeil(legendHeight / clipHeight);
10236
			this.currentPage = pick(this.currentPage, 1);
10237
			this.fullHeight = legendHeight;
10238
			
10239
			// Only apply clipping if needed. Clipping causes blurred legend in PDF export (#1787)
10240
			if (!clipRect) {
10241
				clipRect = legend.clipRect = renderer.clipRect(0, 0, 9999, 0);
10242
				legend.contentGroup.clip(clipRect);
10243
			}
10244
			clipRect.attr({
10245
				height: clipHeight
10246
			});
10247
			
10248
			// Add navigation elements
10249
			if (!nav) {
10250
				this.nav = nav = renderer.g().attr({ zIndex: 1 }).add(this.group);
10251
				this.up = renderer.symbol('triangle', 0, 0, arrowSize, arrowSize)
10252
					.on('click', function () {
10253
						legend.scroll(-1, animation);
10254
					})
10255
					.add(nav);
10256
				this.pager = renderer.text('', 15, 10)
10257
					.css(navOptions.style)
10258
					.add(nav);
10259
				this.down = renderer.symbol('triangle-down', 0, 0, arrowSize, arrowSize)
10260
					.on('click', function () {
10261
						legend.scroll(1, animation);
10262
					})
10263
					.add(nav);
10264
			}
10265
			
10266
			// Set initial position
10267
			legend.scroll(0);
10268
			
10269
			legendHeight = spaceHeight;
10270
			
10271
		} else if (nav) {
10272
			clipRect.attr({
10273
				height: chart.chartHeight
10274
			});
10275
			nav.hide();
10276
			this.scrollGroup.attr({
10277
				translateY: 1
10278
			});
10279
			this.clipHeight = 0; // #1379
10280
		}
10281
		
10282
		return legendHeight;
10283
	},
10284
	
10285
	/**
10286
	 * Scroll the legend by a number of pages
10287
	 * @param {Object} scrollBy
10288
	 * @param {Object} animation
10289
	 */
10290
	scroll: function (scrollBy, animation) {
10291
		var pageCount = this.pageCount,
10292
			currentPage = this.currentPage + scrollBy,
10293
			clipHeight = this.clipHeight,
10294
			navOptions = this.options.navigation,
10295
			activeColor = navOptions.activeColor,
10296
			inactiveColor = navOptions.inactiveColor,
10297
			pager = this.pager,
10298
			padding = this.padding,
10299
			scrollOffset;
10300
		
10301
		// When resizing while looking at the last page
10302
		if (currentPage > pageCount) {
10303
			currentPage = pageCount;
10304
		}
10305
		
10306
		if (currentPage > 0) {
10307
			
10308
			if (animation !== UNDEFINED) {
10309
				setAnimation(animation, this.chart);
10310
			}
10311
			
10312
			this.nav.attr({
10313
				translateX: padding,
10314
				translateY: clipHeight + 7 + this.titleHeight,
10315
				visibility: VISIBLE
10316
			});
10317
			this.up.attr({
10318
					fill: currentPage === 1 ? inactiveColor : activeColor
10319
				})
10320
				.css({
10321
					cursor: currentPage === 1 ? 'default' : 'pointer'
10322
				});
10323
			pager.attr({
10324
				text: currentPage + '/' + this.pageCount
10325
			});
10326
			this.down.attr({
10327
					x: 18 + this.pager.getBBox().width, // adjust to text width
10328
					fill: currentPage === pageCount ? inactiveColor : activeColor
10329
				})
10330
				.css({
10331
					cursor: currentPage === pageCount ? 'default' : 'pointer'
10332
				});
10333
			
10334
			scrollOffset = -mathMin(clipHeight * (currentPage - 1), this.fullHeight - clipHeight + padding) + 1;
10335
			this.scrollGroup.animate({
10336
				translateY: scrollOffset
10337
			});
10338
			pager.attr({
10339
				text: currentPage + '/' + pageCount
10340
			});
10341
			
10342
			
10343
			this.currentPage = currentPage;
10344
			this.positionCheckboxes(scrollOffset);
10345
		}
10346
			
10347
	}
10348
	
10349
};
10350
10351
/**
10352
 * The chart class
10353
 * @param {Object} options
10354
 * @param {Function} callback Function to run when the chart has loaded
10355
 */
10356
function Chart() {
10357
	this.init.apply(this, arguments);
10358
}
10359
10360
Chart.prototype = {
10361
10362
	/**
10363
	 * Initialize the chart
10364
	 */
10365
	init: function (userOptions, callback) {
10366
10367
		// Handle regular options
10368
		var options,
10369
			seriesOptions = userOptions.series; // skip merging data points to increase performance
10370
10371
		userOptions.series = null;
10372
		options = merge(defaultOptions, userOptions); // do the merge
10373
		options.series = userOptions.series = seriesOptions; // set back the series data
10374
10375
		var optionsChart = options.chart,
10376
			optionsMargin = optionsChart.margin,
10377
			margin = isObject(optionsMargin) ?
10378
				optionsMargin :
10379
				[optionsMargin, optionsMargin, optionsMargin, optionsMargin];
10380
10381
		this.optionsMarginTop = pick(optionsChart.marginTop, margin[0]);
10382
		this.optionsMarginRight = pick(optionsChart.marginRight, margin[1]);
10383
		this.optionsMarginBottom = pick(optionsChart.marginBottom, margin[2]);
10384
		this.optionsMarginLeft = pick(optionsChart.marginLeft, margin[3]);
10385
10386
		var chartEvents = optionsChart.events;
10387
10388
		this.runChartClick = chartEvents && !!chartEvents.click;
10389
		this.bounds = { h: {}, v: {} }; // Pixel data bounds for touch zoom
10390
10391
		this.callback = callback;
10392
		this.isResizing = 0;
10393
		this.options = options;
10394
		//chartTitleOptions = UNDEFINED;
10395
		//chartSubtitleOptions = UNDEFINED;
10396
10397
		this.axes = [];
10398
		this.series = [];
10399
		this.hasCartesianSeries = optionsChart.showAxes;
10400
		//this.axisOffset = UNDEFINED;
10401
		//this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes
10402
		//this.inverted = UNDEFINED;
10403
		//this.loadingShown = UNDEFINED;
10404
		//this.container = UNDEFINED;
10405
		//this.chartWidth = UNDEFINED;
10406
		//this.chartHeight = UNDEFINED;
10407
		//this.marginRight = UNDEFINED;
10408
		//this.marginBottom = UNDEFINED;
10409
		//this.containerWidth = UNDEFINED;
10410
		//this.containerHeight = UNDEFINED;
10411
		//this.oldChartWidth = UNDEFINED;
10412
		//this.oldChartHeight = UNDEFINED;
10413
10414
		//this.renderTo = UNDEFINED;
10415
		//this.renderToClone = UNDEFINED;
10416
10417
		//this.spacingBox = UNDEFINED
10418
10419
		//this.legend = UNDEFINED;
10420
10421
		// Elements
10422
		//this.chartBackground = UNDEFINED;
10423
		//this.plotBackground = UNDEFINED;
10424
		//this.plotBGImage = UNDEFINED;
10425
		//this.plotBorder = UNDEFINED;
10426
		//this.loadingDiv = UNDEFINED;
10427
		//this.loadingSpan = UNDEFINED;
10428
10429
		var chart = this,
10430
			eventType;
10431
10432
		// Add the chart to the global lookup
10433
		chart.index = charts.length;
10434
		charts.push(chart);
10435
10436
		// Set up auto resize
10437
		if (optionsChart.reflow !== false) {
10438
			addEvent(chart, 'load', function () {
10439
				chart.initReflow();
10440
			});
10441
		}
10442
10443
		// Chart event handlers
10444
		if (chartEvents) {
10445
			for (eventType in chartEvents) {
10446
				addEvent(chart, eventType, chartEvents[eventType]);
10447
			}
10448
		}
10449
10450
		chart.xAxis = [];
10451
		chart.yAxis = [];
10452
10453
		// Expose methods and variables
10454
		chart.animation = useCanVG ? false : pick(optionsChart.animation, true);
10455
		chart.pointCount = 0;
10456
		chart.counters = new ChartCounters();
10457
10458
		chart.firstRender();
10459
	},
10460
10461
	/**
10462
	 * Initialize an individual series, called internally before render time
10463
	 */
10464
	initSeries: function (options) {
10465
		var chart = this,
10466
			optionsChart = chart.options.chart,
10467
			type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
10468
			series,
10469
			constr = seriesTypes[type];
10470
10471
		// No such series type
10472
		if (!constr) {
10473
			error(17, true);
10474
		}
10475
10476
		series = new constr();
10477
		series.init(this, options);
10478
		return series;
10479
	},
10480
10481
	/**
10482
	 * Add a series dynamically after  time
10483
	 *
10484
	 * @param {Object} options The config options
10485
	 * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true.
10486
	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
10487
	 *    configuration
10488
	 *
10489
	 * @return {Object} series The newly created series object
10490
	 */
10491
	addSeries: function (options, redraw, animation) {
10492
		var series,
10493
			chart = this;
10494
10495
		if (options) {
10496
			redraw = pick(redraw, true); // defaults to true
10497
10498
			fireEvent(chart, 'addSeries', { options: options }, function () {
10499
				series = chart.initSeries(options);
10500
				
10501
				chart.isDirtyLegend = true; // the series array is out of sync with the display
10502
				if (redraw) {
10503
					chart.redraw(animation);
10504
				}
10505
			});
10506
		}
10507
10508
		return series;
10509
	},
10510
10511
	/**
10512
     * Add an axis to the chart
10513
     * @param {Object} options The axis option
10514
     * @param {Boolean} isX Whether it is an X axis or a value axis
10515
     */
10516
	addAxis: function (options, isX, redraw, animation) {
10517
		var key = isX ? 'xAxis' : 'yAxis',
10518
			chartOptions = this.options,
10519
			axis;
10520
10521
		/*jslint unused: false*/
10522
		axis = new Axis(this, merge(options, {
10523
			index: this[key].length
10524
		}));
10525
		/*jslint unused: true*/
10526
10527
		// Push the new axis options to the chart options
10528
		chartOptions[key] = splat(chartOptions[key] || {});
10529
		chartOptions[key].push(options);
10530
10531
		if (pick(redraw, true)) {
10532
			this.redraw(animation);
10533
		}
10534
	},
10535
10536
	/**
10537
	 * Check whether a given point is within the plot area
10538
	 *
10539
	 * @param {Number} plotX Pixel x relative to the plot area
10540
	 * @param {Number} plotY Pixel y relative to the plot area
10541
	 * @param {Boolean} inverted Whether the chart is inverted
10542
	 */
10543
	isInsidePlot: function (plotX, plotY, inverted) {
10544
		var x = inverted ? plotY : plotX,
10545
			y = inverted ? plotX : plotY;
10546
			
10547
		return x >= 0 &&
10548
			x <= this.plotWidth &&
10549
			y >= 0 &&
10550
			y <= this.plotHeight;
10551
	},
10552
10553
	/**
10554
	 * Adjust all axes tick amounts
10555
	 */
10556
	adjustTickAmounts: function () {
10557
		if (this.options.chart.alignTicks !== false) {
10558
			each(this.axes, function (axis) {
10559
				axis.adjustTickAmount();
10560
			});
10561
		}
10562
		this.maxTicks = null;
10563
	},
10564
10565
	/**
10566
	 * Redraw legend, axes or series based on updated data
10567
	 *
10568
	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
10569
	 *    configuration
10570
	 */
10571
	redraw: function (animation) {
10572
		var chart = this,
10573
			axes = chart.axes,
10574
			series = chart.series,
10575
			pointer = chart.pointer,
10576
			legend = chart.legend,
10577
			redrawLegend = chart.isDirtyLegend,
10578
			hasStackedSeries,
10579
			isDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?
10580
			seriesLength = series.length,
10581
			i = seriesLength,
10582
			serie,
10583
			renderer = chart.renderer,
10584
			isHiddenChart = renderer.isHidden(),
10585
			afterRedraw = [];
10586
			
10587
		setAnimation(animation, chart);
10588
		
10589
		if (isHiddenChart) {
10590
			chart.cloneRenderTo();
10591
		}
10592
10593
		// link stacked series
10594
		while (i--) {
10595
			serie = series[i];
10596
			if (serie.isDirty && serie.options.stacking) {
10597
				hasStackedSeries = true;
10598
				break;
10599
			}
10600
		}
10601
		if (hasStackedSeries) { // mark others as dirty
10602
			i = seriesLength;
10603
			while (i--) {
10604
				serie = series[i];
10605
				if (serie.options.stacking) {
10606
					serie.isDirty = true;
10607
				}
10608
			}
10609
		}
10610
10611
		// handle updated data in the series
10612
		each(series, function (serie) {
10613
			if (serie.isDirty) { // prepare the data so axis can read it
10614
				if (serie.options.legendType === 'point') {
10615
					redrawLegend = true;
10616
				}
10617
			}
10618
		});
10619
10620
		// handle added or removed series
10621
		if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed
10622
			// draw legend graphics
10623
			legend.render();
10624
10625
			chart.isDirtyLegend = false;
10626
		}
10627
10628
10629
		if (chart.hasCartesianSeries) {
10630
			if (!chart.isResizing) {
10631
10632
				// reset maxTicks
10633
				chart.maxTicks = null;
10634
10635
				// set axes scales
10636
				each(axes, function (axis) {
10637
					axis.setScale();
10638
				});
10639
			}
10640
			chart.adjustTickAmounts();
10641
			chart.getMargins();
10642
10643
			// redraw axes
10644
			each(axes, function (axis) {
10645
				
10646
				// Fire 'afterSetExtremes' only if extremes are set
10647
				if (axis.isDirtyExtremes) { // #821
10648
					axis.isDirtyExtremes = false;
10649
					afterRedraw.push(function () { // prevent a recursive call to chart.redraw() (#1119)
10650
						fireEvent(axis, 'afterSetExtremes', axis.getExtremes()); // #747, #751
10651
					});
10652
				}
10653
								
10654
				if (axis.isDirty || isDirtyBox || hasStackedSeries) {
10655
					axis.redraw();
10656
					isDirtyBox = true; // #792
10657
				}
10658
			});
10659
10660
10661
		}
10662
		// the plot areas size has changed
10663
		if (isDirtyBox) {
10664
			chart.drawChartBox();
10665
		}
10666
10667
10668
10669
		// redraw affected series
10670
		each(series, function (serie) {
10671
			if (serie.isDirty && serie.visible &&
10672
					(!serie.isCartesian || serie.xAxis)) { // issue #153
10673
				serie.redraw();
10674
			}
10675
		});
10676
10677
		// move tooltip or reset
10678
		if (pointer && pointer.reset) {
10679
			pointer.reset(true);
10680
		}
10681
10682
		// redraw if canvas
10683
		renderer.draw();
10684
10685
		// fire the event
10686
		fireEvent(chart, 'redraw'); // jQuery breaks this when calling it from addEvent. Overwrites chart.redraw
10687
		
10688
		if (isHiddenChart) {
10689
			chart.cloneRenderTo(true);
10690
		}
10691
		
10692
		// Fire callbacks that are put on hold until after the redraw
10693
		each(afterRedraw, function (callback) {
10694
			callback.call();
10695
		});
10696
	},
10697
10698
10699
10700
	/**
10701
	 * Dim the chart and show a loading text or symbol
10702
	 * @param {String} str An optional text to show in the loading label instead of the default one
10703
	 */
10704
	showLoading: function (str) {
10705
		var chart = this,
10706
			options = chart.options,
10707
			loadingDiv = chart.loadingDiv;
10708
10709
		var loadingOptions = options.loading;
10710
10711
		// create the layer at the first call
10712
		if (!loadingDiv) {
10713
			chart.loadingDiv = loadingDiv = createElement(DIV, {
10714
				className: PREFIX + 'loading'
10715
			}, extend(loadingOptions.style, {
10716
				zIndex: 10,
10717
				display: NONE
10718
			}), chart.container);
10719
10720
			chart.loadingSpan = createElement(
10721
				'span',
10722
				null,
10723
				loadingOptions.labelStyle,
10724
				loadingDiv
10725
			);
10726
10727
		}
10728
10729
		// update text
10730
		chart.loadingSpan.innerHTML = str || options.lang.loading;
10731
10732
		// show it
10733
		if (!chart.loadingShown) {
10734
			css(loadingDiv, { 
10735
				opacity: 0, 
10736
				display: '',
10737
				left: chart.plotLeft + PX,
10738
				top: chart.plotTop + PX,
10739
				width: chart.plotWidth + PX,
10740
				height: chart.plotHeight + PX
10741
			});
10742
			animate(loadingDiv, {
10743
				opacity: loadingOptions.style.opacity
10744
			}, {
10745
				duration: loadingOptions.showDuration || 0
10746
			});
10747
			chart.loadingShown = true;
10748
		}
10749
	},
10750
10751
	/**
10752
	 * Hide the loading layer
10753
	 */
10754
	hideLoading: function () {
10755
		var options = this.options,
10756
			loadingDiv = this.loadingDiv;
10757
10758
		if (loadingDiv) {
10759
			animate(loadingDiv, {
10760
				opacity: 0
10761
			}, {
10762
				duration: options.loading.hideDuration || 100,
10763
				complete: function () {
10764
					css(loadingDiv, { display: NONE });
10765
				}
10766
			});
10767
		}
10768
		this.loadingShown = false;
10769
	},
10770
10771
	/**
10772
	 * Get an axis, series or point object by id.
10773
	 * @param id {String} The id as given in the configuration options
10774
	 */
10775
	get: function (id) {
10776
		var chart = this,
10777
			axes = chart.axes,
10778
			series = chart.series;
10779
10780
		var i,
10781
			j,
10782
			points;
10783
10784
		// search axes
10785
		for (i = 0; i < axes.length; i++) {
10786
			if (axes[i].options.id === id) {
10787
				return axes[i];
10788
			}
10789
		}
10790
10791
		// search series
10792
		for (i = 0; i < series.length; i++) {
10793
			if (series[i].options.id === id) {
10794
				return series[i];
10795
			}
10796
		}
10797
10798
		// search points
10799
		for (i = 0; i < series.length; i++) {
10800
			points = series[i].points || [];
10801
			for (j = 0; j < points.length; j++) {
10802
				if (points[j].id === id) {
10803
					return points[j];
10804
				}
10805
			}
10806
		}
10807
		return null;
10808
	},
10809
10810
	/**
10811
	 * Create the Axis instances based on the config options
10812
	 */
10813
	getAxes: function () {
10814
		var chart = this,
10815
			options = this.options,
10816
			xAxisOptions = options.xAxis = splat(options.xAxis || {}),
10817
			yAxisOptions = options.yAxis = splat(options.yAxis || {}),
10818
			optionsArray,
10819
			axis;
10820
10821
		// make sure the options are arrays and add some members
10822
		each(xAxisOptions, function (axis, i) {
10823
			axis.index = i;
10824
			axis.isX = true;
10825
		});
10826
10827
		each(yAxisOptions, function (axis, i) {
10828
			axis.index = i;
10829
		});
10830
10831
		// concatenate all axis options into one array
10832
		optionsArray = xAxisOptions.concat(yAxisOptions);
10833
10834
		each(optionsArray, function (axisOptions) {
10835
			axis = new Axis(chart, axisOptions);
10836
		});
10837
10838
		chart.adjustTickAmounts();
10839
	},
10840
10841
10842
	/**
10843
	 * Get the currently selected points from all series
10844
	 */
10845
	getSelectedPoints: function () {
10846
		var points = [];
10847
		each(this.series, function (serie) {
10848
			points = points.concat(grep(serie.points || [], function (point) {
10849
				return point.selected;
10850
			}));
10851
		});
10852
		return points;
10853
	},
10854
10855
	/**
10856
	 * Get the currently selected series
10857
	 */
10858
	getSelectedSeries: function () {
10859
		return grep(this.series, function (serie) {
10860
			return serie.selected;
10861
		});
10862
	},
10863
10864
	/**
10865
	 * Display the zoom button
10866
	 */
10867
	showResetZoom: function () {
10868
		var chart = this,
10869
			lang = defaultOptions.lang,
10870
			btnOptions = chart.options.chart.resetZoomButton,
10871
			theme = btnOptions.theme,
10872
			states = theme.states,
10873
			alignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox';
10874
			
10875
		this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, function () { chart.zoomOut(); }, theme, states && states.hover)
10876
			.attr({
10877
				align: btnOptions.position.align,
10878
				title: lang.resetZoomTitle
10879
			})
10880
			.add()
10881
			.align(btnOptions.position, false, alignTo);
10882
			
10883
	},
10884
10885
	/**
10886
	 * Zoom out to 1:1
10887
	 */
10888
	zoomOut: function () {
10889
		var chart = this;
10890
		fireEvent(chart, 'selection', { resetSelection: true }, function () { 
10891
			chart.zoom();
10892
		});
10893
	},
10894
10895
	/**
10896
	 * Zoom into a given portion of the chart given by axis coordinates
10897
	 * @param {Object} event
10898
	 */
10899
	zoom: function (event) {
10900
		var chart = this,
10901
			hasZoomed,
10902
			pointer = chart.pointer,
10903
			displayButton = false,
10904
			resetZoomButton;
10905
10906
		// If zoom is called with no arguments, reset the axes
10907
		if (!event || event.resetSelection) {
10908
			each(chart.axes, function (axis) {
10909
				hasZoomed = axis.zoom();
10910
			});
10911
		} else { // else, zoom in on all axes
10912
			each(event.xAxis.concat(event.yAxis), function (axisData) {
10913
				var axis = axisData.axis,
10914
					isXAxis = axis.isXAxis;
10915
10916
				// don't zoom more than minRange
10917
				if (pointer[isXAxis ? 'zoomX' : 'zoomY'] || pointer[isXAxis ? 'pinchX' : 'pinchY']) {
10918
					hasZoomed = axis.zoom(axisData.min, axisData.max);
10919
					if (axis.displayBtn) {
10920
						displayButton = true;
10921
					}
10922
				}
10923
			});
10924
		}
10925
		
10926
		// Show or hide the Reset zoom button
10927
		resetZoomButton = chart.resetZoomButton;
10928
		if (displayButton && !resetZoomButton) {
10929
			chart.showResetZoom();
10930
		} else if (!displayButton && isObject(resetZoomButton)) {
10931
			chart.resetZoomButton = resetZoomButton.destroy();
10932
		}
10933
		
10934
10935
		// Redraw
10936
		if (hasZoomed) {
10937
			chart.redraw(
10938
				pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation
10939
			);
10940
		}
10941
	},
10942
10943
	/**
10944
	 * Pan the chart by dragging the mouse across the pane. This function is called
10945
	 * on mouse move, and the distance to pan is computed from chartX compared to
10946
	 * the first chartX position in the dragging operation.
10947
	 */
10948
	pan: function (chartX) {
10949
		var chart = this,
10950
			xAxis = chart.xAxis[0],
10951
			mouseDownX = chart.mouseDownX,
10952
			halfPointRange = xAxis.pointRange / 2,
10953
			extremes = xAxis.getExtremes(),
10954
			newMin = xAxis.translate(mouseDownX - chartX, true) + halfPointRange,
10955
			newMax = xAxis.translate(mouseDownX + chart.plotWidth - chartX, true) - halfPointRange,
10956
			hoverPoints = chart.hoverPoints;
10957
10958
		// remove active points for shared tooltip
10959
		if (hoverPoints) {
10960
			each(hoverPoints, function (point) {
10961
				point.setState();
10962
			});
10963
		}
10964
10965
		if (xAxis.series.length && newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) {
10966
			xAxis.setExtremes(newMin, newMax, true, false, { trigger: 'pan' });
10967
		}
10968
10969
		chart.mouseDownX = chartX; // set new reference for next run
10970
		css(chart.container, { cursor: 'move' });
10971
	},
10972
10973
	/**
10974
	 * Show the title and subtitle of the chart
10975
	 *
10976
	 * @param titleOptions {Object} New title options
10977
	 * @param subtitleOptions {Object} New subtitle options
10978
	 *
10979
	 */
10980
	setTitle: function (titleOptions, subtitleOptions) {
10981
		var chart = this,
10982
			options = chart.options,
10983
			chartTitleOptions,
10984
			chartSubtitleOptions;
10985
10986
		chartTitleOptions = options.title = merge(options.title, titleOptions);
10987
		chartSubtitleOptions = options.subtitle = merge(options.subtitle, subtitleOptions);
10988
10989
		// add title and subtitle
10990
		each([
10991
			['title', titleOptions, chartTitleOptions],
10992
			['subtitle', subtitleOptions, chartSubtitleOptions]
10993
		], function (arr) {
10994
			var name = arr[0],
10995
				title = chart[name],
10996
				titleOptions = arr[1],
10997
				chartTitleOptions = arr[2];
10998
10999
			if (title && titleOptions) {
11000
				chart[name] = title = title.destroy(); // remove old
11001
			}
11002
			
11003
			if (chartTitleOptions && chartTitleOptions.text && !title) {
11004
				chart[name] = chart.renderer.text(
11005
					chartTitleOptions.text,
11006
					0,
11007
					0,
11008
					chartTitleOptions.useHTML
11009
				)
11010
				.attr({
11011
					align: chartTitleOptions.align,
11012
					'class': PREFIX + name,
11013
					zIndex: chartTitleOptions.zIndex || 4
11014
				})
11015
				.css(chartTitleOptions.style)
11016
				.add()
11017
				.align(chartTitleOptions, false, 'spacingBox');
11018
			}
11019
		});
11020
11021
	},
11022
11023
	/**
11024
	 * Get chart width and height according to options and container size
11025
	 */
11026
	getChartSize: function () {
11027
		var chart = this,
11028
			optionsChart = chart.options.chart,
11029
			renderTo = chart.renderToClone || chart.renderTo;
11030
11031
		// get inner width and height from jQuery (#824)
11032
		chart.containerWidth = adapterRun(renderTo, 'width');
11033
		chart.containerHeight = adapterRun(renderTo, 'height');
11034
		
11035
		chart.chartWidth = mathMax(0, optionsChart.width || chart.containerWidth || 600); // #1393, 1460
11036
		chart.chartHeight = mathMax(0, pick(optionsChart.height,
11037
			// the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:
11038
			chart.containerHeight > 19 ? chart.containerHeight : 400));
11039
	},
11040
11041
	/**
11042
	 * Create a clone of the chart's renderTo div and place it outside the viewport to allow
11043
	 * size computation on chart.render and chart.redraw
11044
	 */
11045
	cloneRenderTo: function (revert) {
11046
		var clone = this.renderToClone,
11047
			container = this.container;
11048
		
11049
		// Destroy the clone and bring the container back to the real renderTo div
11050
		if (revert) {
11051
			if (clone) {
11052
				this.renderTo.appendChild(container);
11053
				discardElement(clone);
11054
				delete this.renderToClone;
11055
			}
11056
		
11057
		// Set up the clone
11058
		} else {
11059
			if (container) {
11060
				this.renderTo.removeChild(container); // do not clone this
11061
			}
11062
			this.renderToClone = clone = this.renderTo.cloneNode(0);
11063
			css(clone, {
11064
				position: ABSOLUTE,
11065
				top: '-9999px',
11066
				display: 'block' // #833
11067
			});
11068
			doc.body.appendChild(clone);
11069
			if (container) {
11070
				clone.appendChild(container);
11071
			}
11072
		}
11073
	},
11074
11075
	/**
11076
	 * Get the containing element, determine the size and create the inner container
11077
	 * div to hold the chart
11078
	 */
11079
	getContainer: function () {
11080
		var chart = this,
11081
			container,
11082
			optionsChart = chart.options.chart,
11083
			chartWidth,
11084
			chartHeight,
11085
			renderTo,
11086
			indexAttrName = 'data-highcharts-chart',
11087
			oldChartIndex,
11088
			containerId;
11089
11090
		chart.renderTo = renderTo = optionsChart.renderTo;
11091
		containerId = PREFIX + idCounter++;
11092
11093
		if (isString(renderTo)) {
11094
			chart.renderTo = renderTo = doc.getElementById(renderTo);
11095
		}
11096
		
11097
		// Display an error if the renderTo is wrong
11098
		if (!renderTo) {
11099
			error(13, true);
11100
		}
11101
		
11102
		// If the container already holds a chart, destroy it
11103
		oldChartIndex = pInt(attr(renderTo, indexAttrName));
11104
		if (!isNaN(oldChartIndex) && charts[oldChartIndex]) {
11105
			charts[oldChartIndex].destroy();
11106
		}		
11107
		
11108
		// Make a reference to the chart from the div
11109
		attr(renderTo, indexAttrName, chart.index);
11110
11111
		// remove previous chart
11112
		renderTo.innerHTML = '';
11113
11114
		// If the container doesn't have an offsetWidth, it has or is a child of a node
11115
		// that has display:none. We need to temporarily move it out to a visible
11116
		// state to determine the size, else the legend and tooltips won't render
11117
		// properly
11118
		if (!renderTo.offsetWidth) {
11119
			chart.cloneRenderTo();
11120
		}
11121
11122
		// get the width and height
11123
		chart.getChartSize();
11124
		chartWidth = chart.chartWidth;
11125
		chartHeight = chart.chartHeight;
11126
11127
		// create the inner container
11128
		chart.container = container = createElement(DIV, {
11129
				className: PREFIX + 'container' +
11130
					(optionsChart.className ? ' ' + optionsChart.className : ''),
11131
				id: containerId
11132
			}, extend({
11133
				position: RELATIVE,
11134
				overflow: HIDDEN, // needed for context menu (avoid scrollbars) and
11135
					// content overflow in IE
11136
				width: chartWidth + PX,
11137
				height: chartHeight + PX,
11138
				textAlign: 'left',
11139
				lineHeight: 'normal', // #427
11140
				zIndex: 0, // #1072
11141
				'-webkit-tap-highlight-color': 'rgba(0,0,0,0)'
11142
			}, optionsChart.style),
11143
			chart.renderToClone || renderTo
11144
		);
11145
11146
		// cache the cursor (#1650)
11147
		chart._cursor = container.style.cursor;
11148
11149
		chart.renderer =
11150
			optionsChart.forExport ? // force SVG, used for SVG export
11151
				new SVGRenderer(container, chartWidth, chartHeight, true) :
11152
				new Renderer(container, chartWidth, chartHeight);
11153
11154
		if (useCanVG) {
11155
			// If we need canvg library, extend and configure the renderer
11156
			// to get the tracker for translating mouse events
11157
			chart.renderer.create(chart, container, chartWidth, chartHeight);
11158
		}
11159
	},
11160
11161
	/**
11162
	 * Calculate margins by rendering axis labels in a preliminary position. Title,
11163
	 * subtitle and legend have already been rendered at this stage, but will be
11164
	 * moved into their final positions
11165
	 */
11166
	getMargins: function () {
11167
		var chart = this,
11168
			optionsChart = chart.options.chart,
11169
			spacingTop = optionsChart.spacingTop,
11170
			spacingRight = optionsChart.spacingRight,
11171
			spacingBottom = optionsChart.spacingBottom,
11172
			spacingLeft = optionsChart.spacingLeft,
11173
			axisOffset,
11174
			legend = chart.legend,
11175
			optionsMarginTop = chart.optionsMarginTop,
11176
			optionsMarginLeft = chart.optionsMarginLeft,
11177
			optionsMarginRight = chart.optionsMarginRight,
11178
			optionsMarginBottom = chart.optionsMarginBottom,
11179
			chartTitleOptions = chart.options.title,
11180
			chartSubtitleOptions = chart.options.subtitle,
11181
			legendOptions = chart.options.legend,
11182
			legendMargin = pick(legendOptions.margin, 10),
11183
			legendX = legendOptions.x,
11184
			legendY = legendOptions.y,
11185
			align = legendOptions.align,
11186
			verticalAlign = legendOptions.verticalAlign,
11187
			titleOffset;
11188
11189
		chart.resetMargins();
11190
		axisOffset = chart.axisOffset;
11191
11192
		// adjust for title and subtitle
11193
		if ((chart.title || chart.subtitle) && !defined(chart.optionsMarginTop)) {
11194
			titleOffset = mathMax(
11195
				(chart.title && !chartTitleOptions.floating && !chartTitleOptions.verticalAlign && chartTitleOptions.y) || 0,
11196
				(chart.subtitle && !chartSubtitleOptions.floating && !chartSubtitleOptions.verticalAlign && chartSubtitleOptions.y) || 0
11197
			);
11198
			if (titleOffset) {
11199
				chart.plotTop = mathMax(chart.plotTop, titleOffset + pick(chartTitleOptions.margin, 15) + spacingTop);
11200
			}
11201
		}
11202
		// adjust for legend
11203
		if (legend.display && !legendOptions.floating) {
11204
			if (align === 'right') { // horizontal alignment handled first
11205
				if (!defined(optionsMarginRight)) {
11206
					chart.marginRight = mathMax(
11207
						chart.marginRight,
11208
						legend.legendWidth - legendX + legendMargin + spacingRight
11209
					);
11210
				}
11211
			} else if (align === 'left') {
11212
				if (!defined(optionsMarginLeft)) {
11213
					chart.plotLeft = mathMax(
11214
						chart.plotLeft,
11215
						legend.legendWidth + legendX + legendMargin + spacingLeft
11216
					);
11217
				}
11218
11219
			} else if (verticalAlign === 'top') {
11220
				if (!defined(optionsMarginTop)) {
11221
					chart.plotTop = mathMax(
11222
						chart.plotTop,
11223
						legend.legendHeight + legendY + legendMargin + spacingTop
11224
					);
11225
				}
11226
11227
			} else if (verticalAlign === 'bottom') {
11228
				if (!defined(optionsMarginBottom)) {
11229
					chart.marginBottom = mathMax(
11230
						chart.marginBottom,
11231
						legend.legendHeight - legendY + legendMargin + spacingBottom
11232
					);
11233
				}
11234
			}
11235
		}
11236
11237
		// adjust for scroller
11238
		if (chart.extraBottomMargin) {
11239
			chart.marginBottom += chart.extraBottomMargin;
11240
		}
11241
		if (chart.extraTopMargin) {
11242
			chart.plotTop += chart.extraTopMargin;
11243
		}
11244
11245
		// pre-render axes to get labels offset width
11246
		if (chart.hasCartesianSeries) {
11247
			each(chart.axes, function (axis) {
11248
				axis.getOffset();
11249
			});
11250
		}
11251
		
11252
		if (!defined(optionsMarginLeft)) {
11253
			chart.plotLeft += axisOffset[3];
11254
		}
11255
		if (!defined(optionsMarginTop)) {
11256
			chart.plotTop += axisOffset[0];
11257
		}
11258
		if (!defined(optionsMarginBottom)) {
11259
			chart.marginBottom += axisOffset[2];
11260
		}
11261
		if (!defined(optionsMarginRight)) {
11262
			chart.marginRight += axisOffset[1];
11263
		}
11264
11265
		chart.setChartSize();
11266
11267
	},
11268
11269
	/**
11270
	 * Add the event handlers necessary for auto resizing
11271
	 *
11272
	 */
11273
	initReflow: function () {
11274
		var chart = this,
11275
			optionsChart = chart.options.chart,
11276
			renderTo = chart.renderTo,
11277
			reflowTimeout;
11278
			
11279
		function reflow(e) {
11280
			var width = optionsChart.width || adapterRun(renderTo, 'width'),
11281
				height = optionsChart.height || adapterRun(renderTo, 'height'),
11282
				target = e ? e.target : win; // #805 - MooTools doesn't supply e
11283
				
11284
			// Width and height checks for display:none. Target is doc in IE8 and Opera,
11285
			// win in Firefox, Chrome and IE9.
11286
			if (!chart.hasUserSize && width && height && (target === win || target === doc)) {
11287
				
11288
				if (width !== chart.containerWidth || height !== chart.containerHeight) {
11289
					clearTimeout(reflowTimeout);
11290
					chart.reflowTimeout = reflowTimeout = setTimeout(function () {
11291
						if (chart.container) { // It may have been destroyed in the meantime (#1257)
11292
							chart.setSize(width, height, false);
11293
							chart.hasUserSize = null;
11294
						}
11295
					}, 100);
11296
				}
11297
				chart.containerWidth = width;
11298
				chart.containerHeight = height;
11299
			}
11300
		}
11301
		addEvent(win, 'resize', reflow);
11302
		addEvent(chart, 'destroy', function () {
11303
			removeEvent(win, 'resize', reflow);
11304
		});
11305
	},
11306
11307
	/**
11308
	 * Resize the chart to a given width and height
11309
	 * @param {Number} width
11310
	 * @param {Number} height
11311
	 * @param {Object|Boolean} animation
11312
	 */
11313
	setSize: function (width, height, animation) {
11314
		var chart = this,
11315
			chartWidth,
11316
			chartHeight,
11317
			fireEndResize;
11318
11319
		// Handle the isResizing counter
11320
		chart.isResizing += 1;
11321
		fireEndResize = function () {
11322
			if (chart) {
11323
				fireEvent(chart, 'endResize', null, function () {
11324
					chart.isResizing -= 1;
11325
				});
11326
			}
11327
		};
11328
11329
		// set the animation for the current process
11330
		setAnimation(animation, chart);
11331
11332
		chart.oldChartHeight = chart.chartHeight;
11333
		chart.oldChartWidth = chart.chartWidth;
11334
		if (defined(width)) {
11335
			chart.chartWidth = chartWidth = mathMax(0, mathRound(width));
11336
			chart.hasUserSize = !!chartWidth;
11337
		}
11338
		if (defined(height)) {
11339
			chart.chartHeight = chartHeight = mathMax(0, mathRound(height));
11340
		}
11341
11342
		css(chart.container, {
11343
			width: chartWidth + PX,
11344
			height: chartHeight + PX
11345
		});
11346
		chart.setChartSize(true);
11347
		chart.renderer.setSize(chartWidth, chartHeight, animation);
11348
11349
		// handle axes
11350
		chart.maxTicks = null;
11351
		each(chart.axes, function (axis) {
11352
			axis.isDirty = true;
11353
			axis.setScale();
11354
		});
11355
11356
		// make sure non-cartesian series are also handled
11357
		each(chart.series, function (serie) {
11358
			serie.isDirty = true;
11359
		});
11360
11361
		chart.isDirtyLegend = true; // force legend redraw
11362
		chart.isDirtyBox = true; // force redraw of plot and chart border
11363
11364
		chart.getMargins();
11365
11366
		chart.redraw(animation);
11367
11368
11369
		chart.oldChartHeight = null;
11370
		fireEvent(chart, 'resize');
11371
11372
		// fire endResize and set isResizing back
11373
		// If animation is disabled, fire without delay
11374
		if (globalAnimation === false) {
11375
			fireEndResize();
11376
		} else { // else set a timeout with the animation duration
11377
			setTimeout(fireEndResize, (globalAnimation && globalAnimation.duration) || 500);
11378
		}
11379
	},
11380
11381
	/**
11382
	 * Set the public chart properties. This is done before and after the pre-render
11383
	 * to determine margin sizes
11384
	 */
11385
	setChartSize: function (skipAxes) {
11386
		var chart = this,
11387
			inverted = chart.inverted,
11388
			renderer = chart.renderer,
11389
			chartWidth = chart.chartWidth,
11390
			chartHeight = chart.chartHeight,
11391
			optionsChart = chart.options.chart,
11392
			spacingTop = optionsChart.spacingTop,
11393
			spacingRight = optionsChart.spacingRight,
11394
			spacingBottom = optionsChart.spacingBottom,
11395
			spacingLeft = optionsChart.spacingLeft,
11396
			clipOffset = chart.clipOffset,
11397
			clipX,
11398
			clipY,
11399
			plotLeft,
11400
			plotTop,
11401
			plotWidth,
11402
			plotHeight,
11403
			plotBorderWidth;
11404
11405
		chart.plotLeft = plotLeft = mathRound(chart.plotLeft);
11406
		chart.plotTop = plotTop = mathRound(chart.plotTop);
11407
		chart.plotWidth = plotWidth = mathMax(0, mathRound(chartWidth - plotLeft - chart.marginRight));
11408
		chart.plotHeight = plotHeight = mathMax(0, mathRound(chartHeight - plotTop - chart.marginBottom));
11409
11410
		chart.plotSizeX = inverted ? plotHeight : plotWidth;
11411
		chart.plotSizeY = inverted ? plotWidth : plotHeight;
11412
		
11413
		chart.plotBorderWidth = plotBorderWidth = optionsChart.plotBorderWidth || 0;
11414
11415
		// Set boxes used for alignment
11416
		chart.spacingBox = renderer.spacingBox = {
11417
			x: spacingLeft,
11418
			y: spacingTop,
11419
			width: chartWidth - spacingLeft - spacingRight,
11420
			height: chartHeight - spacingTop - spacingBottom
11421
		};
11422
		chart.plotBox = renderer.plotBox = {
11423
			x: plotLeft,
11424
			y: plotTop,
11425
			width: plotWidth,
11426
			height: plotHeight
11427
		};
11428
		clipX = mathCeil(mathMax(plotBorderWidth, clipOffset[3]) / 2);
11429
		clipY = mathCeil(mathMax(plotBorderWidth, clipOffset[0]) / 2);
11430
		chart.clipBox = {
11431
			x: clipX, 
11432
			y: clipY, 
11433
			width: mathFloor(chart.plotSizeX - mathMax(plotBorderWidth, clipOffset[1]) / 2 - clipX), 
11434
			height: mathFloor(chart.plotSizeY - mathMax(plotBorderWidth, clipOffset[2]) / 2 - clipY)
11435
		};
11436
11437
		if (!skipAxes) {
11438
			each(chart.axes, function (axis) {
11439
				axis.setAxisSize();
11440
				axis.setAxisTranslation();
11441
			});
11442
		}
11443
	},
11444
11445
	/**
11446
	 * Initial margins before auto size margins are applied
11447
	 */
11448
	resetMargins: function () {
11449
		var chart = this,
11450
			optionsChart = chart.options.chart,
11451
			spacingTop = optionsChart.spacingTop,
11452
			spacingRight = optionsChart.spacingRight,
11453
			spacingBottom = optionsChart.spacingBottom,
11454
			spacingLeft = optionsChart.spacingLeft;
11455
11456
		chart.plotTop = pick(chart.optionsMarginTop, spacingTop);
11457
		chart.marginRight = pick(chart.optionsMarginRight, spacingRight);
11458
		chart.marginBottom = pick(chart.optionsMarginBottom, spacingBottom);
11459
		chart.plotLeft = pick(chart.optionsMarginLeft, spacingLeft);
11460
		chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
11461
		chart.clipOffset = [0, 0, 0, 0];
11462
	},
11463
11464
	/**
11465
	 * Draw the borders and backgrounds for chart and plot area
11466
	 */
11467
	drawChartBox: function () {
11468
		var chart = this,
11469
			optionsChart = chart.options.chart,
11470
			renderer = chart.renderer,
11471
			chartWidth = chart.chartWidth,
11472
			chartHeight = chart.chartHeight,
11473
			chartBackground = chart.chartBackground,
11474
			plotBackground = chart.plotBackground,
11475
			plotBorder = chart.plotBorder,
11476
			plotBGImage = chart.plotBGImage,
11477
			chartBorderWidth = optionsChart.borderWidth || 0,
11478
			chartBackgroundColor = optionsChart.backgroundColor,
11479
			plotBackgroundColor = optionsChart.plotBackgroundColor,
11480
			plotBackgroundImage = optionsChart.plotBackgroundImage,
11481
			plotBorderWidth = optionsChart.plotBorderWidth || 0,
11482
			mgn,
11483
			bgAttr,
11484
			plotLeft = chart.plotLeft,
11485
			plotTop = chart.plotTop,
11486
			plotWidth = chart.plotWidth,
11487
			plotHeight = chart.plotHeight,
11488
			plotBox = chart.plotBox,
11489
			clipRect = chart.clipRect,
11490
			clipBox = chart.clipBox;
11491
11492
		// Chart area
11493
		mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);
11494
11495
		if (chartBorderWidth || chartBackgroundColor) {
11496
			if (!chartBackground) {
11497
				
11498
				bgAttr = {
11499
					fill: chartBackgroundColor || NONE
11500
				};
11501
				if (chartBorderWidth) { // #980
11502
					bgAttr.stroke = optionsChart.borderColor;
11503
					bgAttr['stroke-width'] = chartBorderWidth;
11504
				}
11505
				chart.chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn,
11506
						optionsChart.borderRadius, chartBorderWidth)
11507
					.attr(bgAttr)
11508
					.add()
11509
					.shadow(optionsChart.shadow);
11510
11511
			} else { // resize
11512
				chartBackground.animate(
11513
					chartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn)
11514
				);
11515
			}
11516
		}
11517
11518
11519
		// Plot background
11520
		if (plotBackgroundColor) {
11521
			if (!plotBackground) {
11522
				chart.plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0)
11523
					.attr({
11524
						fill: plotBackgroundColor
11525
					})
11526
					.add()
11527
					.shadow(optionsChart.plotShadow);
11528
			} else {
11529
				plotBackground.animate(plotBox);
11530
			}
11531
		}
11532
		if (plotBackgroundImage) {
11533
			if (!plotBGImage) {
11534
				chart.plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight)
11535
					.add();
11536
			} else {
11537
				plotBGImage.animate(plotBox);
11538
			}
11539
		}
11540
		
11541
		// Plot clip
11542
		if (!clipRect) {
11543
			chart.clipRect = renderer.clipRect(clipBox);
11544
		} else {
11545
			clipRect.animate({
11546
				width: clipBox.width,
11547
				height: clipBox.height
11548
			});
11549
		}
11550
11551
		// Plot area border
11552
		if (plotBorderWidth) {
11553
			if (!plotBorder) {
11554
				chart.plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, plotBorderWidth)
11555
					.attr({
11556
						stroke: optionsChart.plotBorderColor,
11557
						'stroke-width': plotBorderWidth,
11558
						zIndex: 1
11559
					})
11560
					.add();
11561
			} else {
11562
				plotBorder.animate(
11563
					plotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight)
11564
				);
11565
			}
11566
		}
11567
11568
		// reset
11569
		chart.isDirtyBox = false;
11570
	},
11571
11572
	/**
11573
	 * Detect whether a certain chart property is needed based on inspecting its options
11574
	 * and series. This mainly applies to the chart.invert property, and in extensions to 
11575
	 * the chart.angular and chart.polar properties.
11576
	 */
11577
	propFromSeries: function () {
11578
		var chart = this,
11579
			optionsChart = chart.options.chart,
11580
			klass,
11581
			seriesOptions = chart.options.series,
11582
			i,
11583
			value;
11584
			
11585
			
11586
		each(['inverted', 'angular', 'polar'], function (key) {
11587
			
11588
			// The default series type's class
11589
			klass = seriesTypes[optionsChart.type || optionsChart.defaultSeriesType];
11590
			
11591
			// Get the value from available chart-wide properties
11592
			value = (
11593
				chart[key] || // 1. it is set before
11594
				optionsChart[key] || // 2. it is set in the options
11595
				(klass && klass.prototype[key]) // 3. it's default series class requires it
11596
			);
11597
	
11598
			// 4. Check if any the chart's series require it
11599
			i = seriesOptions && seriesOptions.length;
11600
			while (!value && i--) {
11601
				klass = seriesTypes[seriesOptions[i].type];
11602
				if (klass && klass.prototype[key]) {
11603
					value = true;
11604
				}
11605
			}
11606
	
11607
			// Set the chart property
11608
			chart[key] = value;	
11609
		});
11610
		
11611
	},
11612
11613
	/**
11614
	 * Render all graphics for the chart
11615
	 */
11616
	render: function () {
11617
		var chart = this,
11618
			axes = chart.axes,
11619
			renderer = chart.renderer,
11620
			options = chart.options;
11621
11622
		var labels = options.labels,
11623
			credits = options.credits,
11624
			creditsHref;
11625
11626
		// Title
11627
		chart.setTitle();
11628
11629
11630
		// Legend
11631
		chart.legend = new Legend(chart, options.legend);
11632
11633
		// Get margins by pre-rendering axes
11634
		// set axes scales
11635
		each(axes, function (axis) {
11636
			axis.setScale();
11637
		});
11638
		chart.getMargins();
11639
11640
		chart.maxTicks = null; // reset for second pass
11641
		each(axes, function (axis) {
11642
			axis.setTickPositions(true); // update to reflect the new margins
11643
			axis.setMaxTicks();
11644
		});
11645
		chart.adjustTickAmounts();
11646
		chart.getMargins(); // second pass to check for new labels
11647
11648
11649
		// Draw the borders and backgrounds
11650
		chart.drawChartBox();		
11651
11652
11653
		// Axes
11654
		if (chart.hasCartesianSeries) {
11655
			each(axes, function (axis) {
11656
				axis.render();
11657
			});
11658
		}
11659
11660
		// The series
11661
		if (!chart.seriesGroup) {
11662
			chart.seriesGroup = renderer.g('series-group')
11663
				.attr({ zIndex: 3 })
11664
				.add();
11665
		}
11666
		each(chart.series, function (serie) {
11667
			serie.translate();
11668
			serie.setTooltipPoints();
11669
			serie.render();
11670
		});
11671
11672
		// Labels
11673
		if (labels.items) {
11674
			each(labels.items, function (label) {
11675
				var style = extend(labels.style, label.style),
11676
					x = pInt(style.left) + chart.plotLeft,
11677
					y = pInt(style.top) + chart.plotTop + 12;
11678
11679
				// delete to prevent rewriting in IE
11680
				delete style.left;
11681
				delete style.top;
11682
11683
				renderer.text(
11684
					label.html,
11685
					x,
11686
					y
11687
				)
11688
				.attr({ zIndex: 2 })
11689
				.css(style)
11690
				.add();
11691
11692
			});
11693
		}
11694
11695
		// Credits
11696
		if (credits.enabled && !chart.credits) {
11697
			creditsHref = credits.href;
11698
			chart.credits = renderer.text(
11699
				credits.text,
11700
				0,
11701
				0
11702
			)
11703
			.on('click', function () {
11704
				if (creditsHref) {
11705
					location.href = creditsHref;
11706
				}
11707
			})
11708
			.attr({
11709
				align: credits.position.align,
11710
				zIndex: 8
11711
			})
11712
			.css(credits.style)
11713
			.add()
11714
			.align(credits.position);
11715
		}
11716
11717
		// Set flag
11718
		chart.hasRendered = true;
11719
11720
	},
11721
11722
	/**
11723
	 * Clean up memory usage
11724
	 */
11725
	destroy: function () {
11726
		var chart = this,
11727
			axes = chart.axes,
11728
			series = chart.series,
11729
			container = chart.container,
11730
			i,
11731
			parentNode = container && container.parentNode;
11732
			
11733
		// fire the chart.destoy event
11734
		fireEvent(chart, 'destroy');
11735
		
11736
		// Delete the chart from charts lookup array
11737
		charts[chart.index] = UNDEFINED;
11738
		chart.renderTo.removeAttribute('data-highcharts-chart');
11739
11740
		// remove events
11741
		removeEvent(chart);
11742
11743
		// ==== Destroy collections:
11744
		// Destroy axes
11745
		i = axes.length;
11746
		while (i--) {
11747
			axes[i] = axes[i].destroy();
11748
		}
11749
11750
		// Destroy each series
11751
		i = series.length;
11752
		while (i--) {
11753
			series[i] = series[i].destroy();
11754
		}
11755
11756
		// ==== Destroy chart properties:
11757
		each(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage', 
11758
				'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'pointer', 'scroller', 
11759
				'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) {
11760
			var prop = chart[name];
11761
11762
			if (prop && prop.destroy) {
11763
				chart[name] = prop.destroy();
11764
			}
11765
		});
11766
11767
		// remove container and all SVG
11768
		if (container) { // can break in IE when destroyed before finished loading
11769
			container.innerHTML = '';
11770
			removeEvent(container);
11771
			if (parentNode) {
11772
				discardElement(container);
11773
			}
11774
11775
		}
11776
11777
		// clean it all up
11778
		for (i in chart) {
11779
			delete chart[i];
11780
		}
11781
11782
	},
11783
11784
11785
	/**
11786
	 * VML namespaces can't be added until after complete. Listening
11787
	 * for Perini's doScroll hack is not enough.
11788
	 */
11789
	isReadyToRender: function () {
11790
		var chart = this;
11791
11792
		// Note: in spite of JSLint's complaints, win == win.top is required
11793
		/*jslint eqeq: true*/
11794
		if ((!hasSVG && (win == win.top && doc.readyState !== 'complete')) || (useCanVG && !win.canvg)) {
11795
		/*jslint eqeq: false*/
11796
			if (useCanVG) {
11797
				// Delay rendering until canvg library is downloaded and ready
11798
				CanVGController.push(function () { chart.firstRender(); }, chart.options.global.canvasToolsURL);
11799
			} else {
11800
				doc.attachEvent('onreadystatechange', function () {
11801
					doc.detachEvent('onreadystatechange', chart.firstRender);
11802
					if (doc.readyState === 'complete') {
11803
						chart.firstRender();
11804
					}
11805
				});
11806
			}
11807
			return false;
11808
		}
11809
		return true;
11810
	},
11811
11812
	/**
11813
	 * Prepare for first rendering after all data are loaded
11814
	 */
11815
	firstRender: function () {
11816
		var chart = this,
11817
			options = chart.options,
11818
			callback = chart.callback;
11819
11820
		// Check whether the chart is ready to render
11821
		if (!chart.isReadyToRender()) {
11822
			return;
11823
		}
11824
11825
		// Create the container
11826
		chart.getContainer();
11827
11828
		// Run an early event after the container and renderer are established
11829
		fireEvent(chart, 'init');
11830
11831
		
11832
		chart.resetMargins();
11833
		chart.setChartSize();
11834
11835
		// Set the common chart properties (mainly invert) from the given series
11836
		chart.propFromSeries();
11837
11838
		// get axes
11839
		chart.getAxes();
11840
11841
		// Initialize the series
11842
		each(options.series || [], function (serieOptions) {
11843
			chart.initSeries(serieOptions);
11844
		});
11845
11846
		// Run an event after axes and series are initialized, but before render. At this stage,
11847
		// the series data is indexed and cached in the xData and yData arrays, so we can access
11848
		// those before rendering. Used in Highstock. 
11849
		fireEvent(chart, 'beforeRender'); 
11850
11851
		// depends on inverted and on margins being set
11852
		chart.pointer = new Pointer(chart, options);
11853
11854
		chart.render();
11855
11856
		// add canvas
11857
		chart.renderer.draw();
11858
		// run callbacks
11859
		if (callback) {
11860
			callback.apply(chart, [chart]);
11861
		}
11862
		each(chart.callbacks, function (fn) {
11863
			fn.apply(chart, [chart]);
11864
		});
11865
		
11866
		
11867
		// If the chart was rendered outside the top container, put it back in
11868
		chart.cloneRenderTo(true);
11869
11870
		fireEvent(chart, 'load');
11871
11872
	}
11873
}; // end Chart
11874
11875
// Hook for exporting module
11876
Chart.prototype.callbacks = [];
11877
/**
11878
 * The Point object and prototype. Inheritable and used as base for PiePoint
11879
 */
11880
var Point = function () {};
11881
Point.prototype = {
11882
11883
	/**
11884
	 * Initialize the point
11885
	 * @param {Object} series The series object containing this point
11886
	 * @param {Object} options The data in either number, array or object format
11887
	 */
11888
	init: function (series, options, x) {
11889
11890
		var point = this,
11891
			colors;
11892
		point.series = series;
11893
		point.applyOptions(options, x);
11894
		point.pointAttr = {};
11895
11896
		if (series.options.colorByPoint) {
11897
			colors = series.options.colors || series.chart.options.colors;
11898
			point.color = point.color || colors[series.colorCounter++];
11899
			// loop back to zero
11900
			if (series.colorCounter === colors.length) {
11901
				series.colorCounter = 0;
11902
			}
11903
		}
11904
11905
		series.chart.pointCount++;
11906
		return point;
11907
	},
11908
	/**
11909
	 * Apply the options containing the x and y data and possible some extra properties.
11910
	 * This is called on point init or from point.update.
11911
	 *
11912
	 * @param {Object} options
11913
	 */
11914
	applyOptions: function (options, x) {
11915
		var point = this,
11916
			series = point.series,
11917
			pointValKey = series.pointValKey;
11918
11919
		options = Point.prototype.optionsToObject.call(this, options);
11920
11921
		// copy options directly to point
11922
		extend(point, options);
11923
		point.options = point.options ? extend(point.options, options) : options;
11924
			
11925
		// For higher dimension series types. For instance, for ranges, point.y is mapped to point.low.
11926
		if (pointValKey) {
11927
			point.y = point[pointValKey];
11928
		}
11929
		
11930
		// If no x is set by now, get auto incremented value. All points must have an
11931
		// x value, however the y value can be null to create a gap in the series
11932
		if (point.x === UNDEFINED && series) {
11933
			point.x = x === UNDEFINED ? series.autoIncrement() : x;
11934
		}
11935
		
11936
		return point;
11937
	},
11938
11939
	/**
11940
	 * Transform number or array configs into objects
11941
	 */
11942
	optionsToObject: function (options) {
11943
		var ret,
11944
			series = this.series,
11945
			pointArrayMap = series.pointArrayMap || ['y'],
11946
			valueCount = pointArrayMap.length,
11947
			firstItemType,
11948
			i = 0,
11949
			j = 0;
11950
11951
		if (typeof options === 'number' || options === null) {
11952
			ret = { y: options };
11953
11954
		} else if (isArray(options)) {
11955
			ret = {};
11956
			// with leading x value
11957
			if (options.length > valueCount) {
11958
				firstItemType = typeof options[0];
11959
				if (firstItemType === 'string') {
11960
					ret.name = options[0];
11961
				} else if (firstItemType === 'number') {
11962
					ret.x = options[0];
11963
				}
11964
				i++;
11965
			}
11966
			while (j < valueCount) {
11967
				ret[pointArrayMap[j++]] = options[i++];
11968
			}			
11969
		} else if (typeof options === 'object') {
11970
			ret = options;
11971
11972
			// This is the fastest way to detect if there are individual point dataLabels that need 
11973
			// to be considered in drawDataLabels. These can only occur in object configs.
11974
			if (options.dataLabels) {
11975
				series._hasPointLabels = true;
11976
			}
11977
11978
			// Same approach as above for markers
11979
			if (options.marker) {
11980
				series._hasPointMarkers = true;
11981
			}
11982
		}
11983
		return ret;
11984
	},
11985
11986
	/**
11987
	 * Destroy a point to clear memory. Its reference still stays in series.data.
11988
	 */
11989
	destroy: function () {
11990
		var point = this,
11991
			series = point.series,
11992
			chart = series.chart,
11993
			hoverPoints = chart.hoverPoints,
11994
			prop;
11995
11996
		chart.pointCount--;
11997
11998
		if (hoverPoints) {
11999
			point.setState();
12000
			erase(hoverPoints, point);
12001
			if (!hoverPoints.length) {
12002
				chart.hoverPoints = null;
12003
			}
12004
12005
		}
12006
		if (point === chart.hoverPoint) {
12007
			point.onMouseOut();
12008
		}
12009
		
12010
		// remove all events
12011
		if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive
12012
			removeEvent(point);
12013
			point.destroyElements();
12014
		}
12015
12016
		if (point.legendItem) { // pies have legend items
12017
			chart.legend.destroyItem(point);
12018
		}
12019
12020
		for (prop in point) {
12021
			point[prop] = null;
12022
		}
12023
12024
12025
	},
12026
12027
	/**
12028
	 * Destroy SVG elements associated with the point
12029
	 */
12030
	destroyElements: function () {
12031
		var point = this,
12032
			props = ['graphic', 'dataLabel', 'dataLabelUpper', 'group', 'connector', 'shadowGroup'],
12033
			prop,
12034
			i = 6;
12035
		while (i--) {
12036
			prop = props[i];
12037
			if (point[prop]) {
12038
				point[prop] = point[prop].destroy();
12039
			}
12040
		}
12041
	},
12042
12043
	/**
12044
	 * Return the configuration hash needed for the data label and tooltip formatters
12045
	 */
12046
	getLabelConfig: function () {
12047
		var point = this;
12048
		return {
12049
			x: point.category,
12050
			y: point.y,
12051
			key: point.name || point.category,
12052
			series: point.series,
12053
			point: point,
12054
			percentage: point.percentage,
12055
			total: point.total || point.stackTotal
12056
		};
12057
	},
12058
12059
	/**
12060
	 * Toggle the selection status of a point
12061
	 * @param {Boolean} selected Whether to select or unselect the point.
12062
	 * @param {Boolean} accumulate Whether to add to the previous selection. By default,
12063
	 *     this happens if the control key (Cmd on Mac) was pressed during clicking.
12064
	 */
12065
	select: function (selected, accumulate) {
12066
		var point = this,
12067
			series = point.series,
12068
			chart = series.chart;
12069
12070
		selected = pick(selected, !point.selected);
12071
12072
		// fire the event with the defalut handler
12073
		point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () {
12074
			point.selected = point.options.selected = selected;
12075
			series.options.data[inArray(point, series.data)] = point.options;
12076
			
12077
			point.setState(selected && SELECT_STATE);
12078
12079
			// unselect all other points unless Ctrl or Cmd + click
12080
			if (!accumulate) {
12081
				each(chart.getSelectedPoints(), function (loopPoint) {
12082
					if (loopPoint.selected && loopPoint !== point) {
12083
						loopPoint.selected = loopPoint.options.selected = false;
12084
						series.options.data[inArray(loopPoint, series.data)] = loopPoint.options;
12085
						loopPoint.setState(NORMAL_STATE);
12086
						loopPoint.firePointEvent('unselect');
12087
					}
12088
				});
12089
			}
12090
		});
12091
	},
12092
12093
	/**
12094
	 * Runs on mouse over the point
12095
	 */
12096
	onMouseOver: function (e) {
12097
		var point = this,
12098
			series = point.series,
12099
			chart = series.chart,
12100
			tooltip = chart.tooltip,
12101
			hoverPoint = chart.hoverPoint;
12102
12103
		// set normal state to previous series
12104
		if (hoverPoint && hoverPoint !== point) {
12105
			hoverPoint.onMouseOut();
12106
		}
12107
12108
		// trigger the event
12109
		point.firePointEvent('mouseOver');
12110
12111
		// update the tooltip
12112
		if (tooltip && (!tooltip.shared || series.noSharedTooltip)) {
12113
			tooltip.refresh(point, e);
12114
		}
12115
12116
		// hover this
12117
		point.setState(HOVER_STATE);
12118
		chart.hoverPoint = point;
12119
	},
12120
	
12121
	/**
12122
	 * Runs on mouse out from the point
12123
	 */
12124
	onMouseOut: function () {
12125
		var chart = this.series.chart,
12126
			hoverPoints = chart.hoverPoints;
12127
		
12128
		if (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887
12129
			this.firePointEvent('mouseOut');
12130
	
12131
			this.setState();
12132
			chart.hoverPoint = null;
12133
		}
12134
	},
12135
12136
	/**
12137
	 * Extendable method for formatting each point's tooltip line
12138
	 *
12139
	 * @return {String} A string to be concatenated in to the common tooltip text
12140
	 */
12141
	tooltipFormatter: function (pointFormat) {
12142
		
12143
		// Insert options for valueDecimals, valuePrefix, and valueSuffix
12144
		var series = this.series,
12145
			seriesTooltipOptions = series.tooltipOptions,
12146
			valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''),
12147
			valuePrefix = seriesTooltipOptions.valuePrefix || '',
12148
			valueSuffix = seriesTooltipOptions.valueSuffix || '';
12149
			
12150
		// Loop over the point array map and replace unformatted values with sprintf formatting markup
12151
		each(series.pointArrayMap || ['y'], function (key) {
12152
			key = '{point.' + key; // without the closing bracket
12153
			if (valuePrefix || valueSuffix) {
12154
				pointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix);
12155
			}
12156
			pointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}');
12157
		});
12158
		
12159
		return format(pointFormat, {
12160
			point: this,
12161
			series: this.series
12162
		});
12163
	},
12164
12165
	/**
12166
	 * Update the point with new options (typically x/y data) and optionally redraw the series.
12167
	 *
12168
	 * @param {Object} options Point options as defined in the series.data array
12169
	 * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
12170
	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
12171
	 *    configuration
12172
	 *
12173
	 */
12174
	update: function (options, redraw, animation) {
12175
		var point = this,
12176
			series = point.series,
12177
			graphic = point.graphic,
12178
			i,
12179
			data = series.data,
12180
			chart = series.chart;
12181
12182
		redraw = pick(redraw, true);
12183
12184
		// fire the event with a default handler of doing the update
12185
		point.firePointEvent('update', { options: options }, function () {
12186
12187
			point.applyOptions(options);
12188
12189
			// update visuals
12190
			if (isObject(options)) {
12191
				series.getAttribs();
12192
				if (graphic) {
12193
					graphic.attr(point.pointAttr[series.state]);
12194
				}
12195
			}
12196
12197
			// record changes in the parallel arrays
12198
			i = inArray(point, data);
12199
			series.xData[i] = point.x;
12200
			series.yData[i] = series.toYData ? series.toYData(point) : point.y;
12201
			series.zData[i] = point.z;
12202
			series.options.data[i] = point.options;
12203
12204
			// redraw
12205
			series.isDirty = true;
12206
			series.isDirtyData = true;
12207
			if (redraw) {
12208
				chart.redraw(animation);
12209
			}
12210
		});
12211
	},
12212
12213
	/**
12214
	 * Remove a point and optionally redraw the series and if necessary the axes
12215
	 * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
12216
	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
12217
	 *    configuration
12218
	 */
12219
	remove: function (redraw, animation) {
12220
		var point = this,
12221
			series = point.series,
12222
			chart = series.chart,
12223
			i,
12224
			data = series.data;
12225
12226
		setAnimation(animation, chart);
12227
		redraw = pick(redraw, true);
12228
12229
		// fire the event with a default handler of removing the point
12230
		point.firePointEvent('remove', null, function () {
12231
12232
			// splice all the parallel arrays
12233
			i = inArray(point, data);
12234
			data.splice(i, 1);
12235
			series.options.data.splice(i, 1);
12236
			series.xData.splice(i, 1);
12237
			series.yData.splice(i, 1);
12238
			series.zData.splice(i, 1);
12239
12240
			point.destroy();
12241
12242
12243
			// redraw
12244
			series.isDirty = true;
12245
			series.isDirtyData = true;
12246
			if (redraw) {
12247
				chart.redraw();
12248
			}
12249
		});
12250
12251
12252
	},
12253
12254
	/**
12255
	 * Fire an event on the Point object. Must not be renamed to fireEvent, as this
12256
	 * causes a name clash in MooTools
12257
	 * @param {String} eventType
12258
	 * @param {Object} eventArgs Additional event arguments
12259
	 * @param {Function} defaultFunction Default event handler
12260
	 */
12261
	firePointEvent: function (eventType, eventArgs, defaultFunction) {
12262
		var point = this,
12263
			series = this.series,
12264
			seriesOptions = series.options;
12265
12266
		// load event handlers on demand to save time on mouseover/out
12267
		if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {
12268
			this.importEvents();
12269
		}
12270
12271
		// add default handler if in selection mode
12272
		if (eventType === 'click' && seriesOptions.allowPointSelect) {
12273
			defaultFunction = function (event) {
12274
				// Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
12275
				point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
12276
			};
12277
		}
12278
12279
		fireEvent(this, eventType, eventArgs, defaultFunction);
12280
	},
12281
	/**
12282
	 * Import events from the series' and point's options. Only do it on
12283
	 * demand, to save processing time on hovering.
12284
	 */
12285
	importEvents: function () {
12286
		if (!this.hasImportedEvents) {
12287
			var point = this,
12288
				options = merge(point.series.options.point, point.options),
12289
				events = options.events,
12290
				eventType;
12291
12292
			point.events = events;
12293
12294
			for (eventType in events) {
12295
				addEvent(point, eventType, events[eventType]);
12296
			}
12297
			this.hasImportedEvents = true;
12298
12299
		}
12300
	},
12301
12302
	/**
12303
	 * Set the point's state
12304
	 * @param {String} state
12305
	 */
12306
	setState: function (state) {
12307
		var point = this,
12308
			plotX = point.plotX,
12309
			plotY = point.plotY,
12310
			series = point.series,
12311
			stateOptions = series.options.states,
12312
			markerOptions = defaultPlotOptions[series.type].marker && series.options.marker,
12313
			normalDisabled = markerOptions && !markerOptions.enabled,
12314
			markerStateOptions = markerOptions && markerOptions.states[state],
12315
			stateDisabled = markerStateOptions && markerStateOptions.enabled === false,
12316
			stateMarkerGraphic = series.stateMarkerGraphic,
12317
			pointMarker = point.marker || {},
12318
			chart = series.chart,
12319
			radius,
12320
			newSymbol,
12321
			pointAttr = point.pointAttr;
12322
12323
		state = state || NORMAL_STATE; // empty string
12324
12325
		if (
12326
				// already has this state
12327
				state === point.state ||
12328
				// selected points don't respond to hover
12329
				(point.selected && state !== SELECT_STATE) ||
12330
				// series' state options is disabled
12331
				(stateOptions[state] && stateOptions[state].enabled === false) ||
12332
				// point marker's state options is disabled
12333
				(state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled)))
12334
12335
			) {
12336
			return;
12337
		}
12338
12339
		// apply hover styles to the existing point
12340
		if (point.graphic) {
12341
			radius = markerOptions && point.graphic.symbolName && pointAttr[state].r;
12342
			point.graphic.attr(merge(
12343
				pointAttr[state],
12344
				radius ? { // new symbol attributes (#507, #612)
12345
					x: plotX - radius,
12346
					y: plotY - radius,
12347
					width: 2 * radius,
12348
					height: 2 * radius
12349
				} : {}
12350
			));
12351
		} else {
12352
			// if a graphic is not applied to each point in the normal state, create a shared
12353
			// graphic for the hover state
12354
			if (state && markerStateOptions) {
12355
				radius = markerStateOptions.radius;
12356
				newSymbol = pointMarker.symbol || series.symbol;
12357
12358
				// If the point has another symbol than the previous one, throw away the 
12359
				// state marker graphic and force a new one (#1459)
12360
				if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) {				
12361
					stateMarkerGraphic = stateMarkerGraphic.destroy();
12362
				}
12363
12364
				// Add a new state marker graphic
12365
				if (!stateMarkerGraphic) {
12366
					series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
12367
						newSymbol,
12368
						plotX - radius,
12369
						plotY - radius,
12370
						2 * radius,
12371
						2 * radius
12372
					)
12373
					.attr(pointAttr[state])
12374
					.add(series.markerGroup);
12375
					stateMarkerGraphic.currentSymbol = newSymbol;
12376
				
12377
				// Move the existing graphic
12378
				} else {
12379
					stateMarkerGraphic.attr({ // #1054
12380
						x: plotX - radius,
12381
						y: plotY - radius
12382
					});
12383
				}
12384
			}
12385
12386
			if (stateMarkerGraphic) {
12387
				stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY) ? 'show' : 'hide']();
12388
			}
12389
		}
12390
12391
		point.state = state;
12392
	}
12393
};
12394
12395
/**
12396
 * @classDescription The base function which all other series types inherit from. The data in the series is stored
12397
 * in various arrays.
12398
 *
12399
 * - First, series.options.data contains all the original config options for
12400
 * each point whether added by options or methods like series.addPoint.
12401
 * - Next, series.data contains those values converted to points, but in case the series data length
12402
 * exceeds the cropThreshold, or if the data is grouped, series.data doesn't contain all the points. It
12403
 * only contains the points that have been created on demand.
12404
 * - Then there's series.points that contains all currently visible point objects. In case of cropping,
12405
 * the cropped-away points are not part of this array. The series.points array starts at series.cropStart
12406
 * compared to series.data and series.options.data. If however the series data is grouped, these can't
12407
 * be correlated one to one.
12408
 * - series.xData and series.processedXData contain clean x values, equivalent to series.data and series.points.
12409
 * - series.yData and series.processedYData contain clean x values, equivalent to series.data and series.points.
12410
 *
12411
 * @param {Object} chart
12412
 * @param {Object} options
12413
 */
12414
var Series = function () {};
12415
12416
Series.prototype = {
12417
12418
	isCartesian: true,
12419
	type: 'line',
12420
	pointClass: Point,
12421
	sorted: true, // requires the data to be sorted
12422
	requireSorting: true,
12423
	pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
12424
		stroke: 'lineColor',
12425
		'stroke-width': 'lineWidth',
12426
		fill: 'fillColor',
12427
		r: 'radius'
12428
	},
12429
	colorCounter: 0,
12430
	init: function (chart, options) {
12431
		var series = this,
12432
			eventType,
12433
			events,
12434
			linkedTo,
12435
			chartSeries = chart.series;
12436
12437
		series.chart = chart;
12438
		series.options = options = series.setOptions(options); // merge with plotOptions
12439
12440
		// bind the axes
12441
		series.bindAxes();
12442
12443
		// set some variables
12444
		extend(series, {
12445
			name: options.name,
12446
			state: NORMAL_STATE,
12447
			pointAttr: {},
12448
			visible: options.visible !== false, // true by default
12449
			selected: options.selected === true // false by default
12450
		});
12451
		
12452
		// special
12453
		if (useCanVG) {
12454
			options.animation = false;
12455
		}
12456
12457
		// register event listeners
12458
		events = options.events;
12459
		for (eventType in events) {
12460
			addEvent(series, eventType, events[eventType]);
12461
		}
12462
		if (
12463
			(events && events.click) ||
12464
			(options.point && options.point.events && options.point.events.click) ||
12465
			options.allowPointSelect
12466
		) {
12467
			chart.runTrackerClick = true;
12468
		}
12469
12470
		series.getColor();
12471
		series.getSymbol();
12472
12473
		// set the data
12474
		series.setData(options.data, false);
12475
		
12476
		// Mark cartesian
12477
		if (series.isCartesian) {
12478
			chart.hasCartesianSeries = true;
12479
		}
12480
12481
		// Register it in the chart
12482
		chartSeries.push(series);
12483
		series._i = chartSeries.length - 1;
12484
		
12485
		// Sort series according to index option (#248, #1123)
12486
		stableSort(chartSeries, function (a, b) {
12487
			return pick(a.options.index, a._i) - pick(b.options.index, a._i);
12488
		});
12489
		each(chartSeries, function (series, i) {
12490
			series.index = i;
12491
			series.name = series.name || 'Series ' + (i + 1);
12492
		});
12493
12494
		// Linked series
12495
		linkedTo = options.linkedTo;
12496
		series.linkedSeries = [];
12497
		if (isString(linkedTo)) {
12498
			if (linkedTo === ':previous') {
12499
				linkedTo = chartSeries[series.index - 1];
12500
			} else {
12501
				linkedTo = chart.get(linkedTo);
12502
			}
12503
			if (linkedTo) {
12504
				linkedTo.linkedSeries.push(series);
12505
				series.linkedParent = linkedTo;
12506
			}
12507
		}
12508
	},
12509
	
12510
	/**
12511
	 * Set the xAxis and yAxis properties of cartesian series, and register the series
12512
	 * in the axis.series array
12513
	 */
12514
	bindAxes: function () {
12515
		var series = this,
12516
			seriesOptions = series.options,
12517
			chart = series.chart,
12518
			axisOptions;
12519
			
12520
		if (series.isCartesian) {
12521
			
12522
			each(['xAxis', 'yAxis'], function (AXIS) { // repeat for xAxis and yAxis
12523
				
12524
				each(chart[AXIS], function (axis) { // loop through the chart's axis objects
12525
					
12526
					axisOptions = axis.options;
12527
					
12528
					// apply if the series xAxis or yAxis option mathches the number of the 
12529
					// axis, or if undefined, use the first axis
12530
					if ((seriesOptions[AXIS] === axisOptions.index) ||
12531
							(seriesOptions[AXIS] !== UNDEFINED && seriesOptions[AXIS] === axisOptions.id) ||
12532
							(seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) {
12533
						
12534
						// register this series in the axis.series lookup
12535
						axis.series.push(series);
12536
						
12537
						// set this series.xAxis or series.yAxis reference
12538
						series[AXIS] = axis;
12539
						
12540
						// mark dirty for redraw
12541
						axis.isDirty = true;
12542
					}
12543
				});
12544
12545
				// The series needs an X and an Y axis
12546
				if (!series[AXIS]) {
12547
					error(18, true);
12548
				}
12549
12550
			});
12551
		}
12552
	},
12553
12554
12555
	/**
12556
	 * Return an auto incremented x value based on the pointStart and pointInterval options.
12557
	 * This is only used if an x value is not given for the point that calls autoIncrement.
12558
	 */
12559
	autoIncrement: function () {
12560
		var series = this,
12561
			options = series.options,
12562
			xIncrement = series.xIncrement;
12563
12564
		xIncrement = pick(xIncrement, options.pointStart, 0);
12565
12566
		series.pointInterval = pick(series.pointInterval, options.pointInterval, 1);
12567
12568
		series.xIncrement = xIncrement + series.pointInterval;
12569
		return xIncrement;
12570
	},
12571
12572
	/**
12573
	 * Divide the series data into segments divided by null values.
12574
	 */
12575
	getSegments: function () {
12576
		var series = this,
12577
			lastNull = -1,
12578
			segments = [],
12579
			i,
12580
			points = series.points,
12581
			pointsLength = points.length;
12582
12583
		if (pointsLength) { // no action required for []
12584
			
12585
			// if connect nulls, just remove null points
12586
			if (series.options.connectNulls) {
12587
				i = pointsLength;
12588
				while (i--) {
12589
					if (points[i].y === null) {
12590
						points.splice(i, 1);
12591
					}
12592
				}
12593
				if (points.length) {
12594
					segments = [points];
12595
				}
12596
				
12597
			// else, split on null points
12598
			} else {
12599
				each(points, function (point, i) {
12600
					if (point.y === null) {
12601
						if (i > lastNull + 1) {
12602
							segments.push(points.slice(lastNull + 1, i));
12603
						}
12604
						lastNull = i;
12605
					} else if (i === pointsLength - 1) { // last value
12606
						segments.push(points.slice(lastNull + 1, i + 1));
12607
					}
12608
				});
12609
			}
12610
		}
12611
		
12612
		// register it
12613
		series.segments = segments;
12614
	},
12615
	/**
12616
	 * Set the series options by merging from the options tree
12617
	 * @param {Object} itemOptions
12618
	 */
12619
	setOptions: function (itemOptions) {
12620
		var chart = this.chart,
12621
			chartOptions = chart.options,
12622
			plotOptions = chartOptions.plotOptions,
12623
			typeOptions = plotOptions[this.type],
12624
			options;
12625
12626
		this.userOptions = itemOptions;
12627
12628
		options = merge(
12629
			typeOptions,
12630
			plotOptions.series,
12631
			itemOptions
12632
		);
12633
		
12634
		// the tooltip options are merged between global and series specific options
12635
		this.tooltipOptions = merge(chartOptions.tooltip, options.tooltip);
12636
		
12637
		// Delte marker object if not allowed (#1125)
12638
		if (typeOptions.marker === null) {
12639
			delete options.marker;
12640
		}
12641
		
12642
		return options;
12643
12644
	},
12645
	/**
12646
	 * Get the series' color
12647
	 */
12648
	getColor: function () {
12649
		var options = this.options,
12650
			userOptions = this.userOptions,
12651
			defaultColors = this.chart.options.colors,
12652
			counters = this.chart.counters,
12653
			color,
12654
			colorIndex;
12655
12656
		color = options.color || defaultPlotOptions[this.type].color;
12657
12658
		if (!color && !options.colorByPoint) {
12659
			if (defined(userOptions._colorIndex)) { // after Series.update()
12660
				colorIndex = userOptions._colorIndex;
12661
			} else {
12662
				userOptions._colorIndex = counters.color;
12663
				colorIndex = counters.color++;
12664
			}
12665
			color = defaultColors[colorIndex];
12666
		}
12667
		
12668
		this.color = color;
12669
		counters.wrapColor(defaultColors.length);
12670
	},
12671
	/**
12672
	 * Get the series' symbol
12673
	 */
12674
	getSymbol: function () {
12675
		var series = this,
12676
			userOptions = series.userOptions,
12677
			seriesMarkerOption = series.options.marker,
12678
			chart = series.chart,
12679
			defaultSymbols = chart.options.symbols,
12680
			counters = chart.counters,
12681
			symbolIndex;
12682
12683
		series.symbol = seriesMarkerOption.symbol;
12684
		if (!series.symbol) {
12685
			if (defined(userOptions._symbolIndex)) { // after Series.update()
12686
				symbolIndex = userOptions._symbolIndex;
12687
			} else {
12688
				userOptions._symbolIndex = counters.symbol;
12689
				symbolIndex = counters.symbol++;
12690
			}
12691
			series.symbol = defaultSymbols[symbolIndex];
12692
		}
12693
12694
		// don't substract radius in image symbols (#604)
12695
		if (/^url/.test(series.symbol)) {
12696
			seriesMarkerOption.radius = 0;
12697
		}
12698
		counters.wrapSymbol(defaultSymbols.length);
12699
	},
12700
12701
	/**
12702
	 * Get the series' symbol in the legend. This method should be overridable to create custom 
12703
	 * symbols through Highcharts.seriesTypes[type].prototype.drawLegendSymbols.
12704
	 * 
12705
	 * @param {Object} legend The legend object
12706
	 */
12707
	drawLegendSymbol: function (legend) {
12708
		
12709
		var options = this.options,
12710
			markerOptions = options.marker,
12711
			radius,
12712
			legendOptions = legend.options,
12713
			legendSymbol,
12714
			symbolWidth = legendOptions.symbolWidth,
12715
			renderer = this.chart.renderer,
12716
			legendItemGroup = this.legendGroup,
12717
			baseline = legend.baseline,
12718
			attr;
12719
			
12720
		// Draw the line
12721
		if (options.lineWidth) {
12722
			attr = {
12723
				'stroke-width': options.lineWidth
12724
			};
12725
			if (options.dashStyle) {
12726
				attr.dashstyle = options.dashStyle;
12727
			}
12728
			this.legendLine = renderer.path([
12729
				M,
12730
				0,
12731
				baseline - 4,
12732
				L,
12733
				symbolWidth,
12734
				baseline - 4
12735
			])
12736
			.attr(attr)
12737
			.add(legendItemGroup);
12738
		}
12739
		
12740
		// Draw the marker
12741
		if (markerOptions && markerOptions.enabled) {
12742
			radius = markerOptions.radius;
12743
			this.legendSymbol = legendSymbol = renderer.symbol(
12744
				this.symbol,
12745
				(symbolWidth / 2) - radius,
12746
				baseline - 4 - radius,
12747
				2 * radius,
12748
				2 * radius
12749
			)
12750
			.add(legendItemGroup);
12751
		}
12752
	},
12753
12754
	/**
12755
	 * Add a point dynamically after chart load time
12756
	 * @param {Object} options Point options as given in series.data
12757
	 * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
12758
	 * @param {Boolean} shift If shift is true, a point is shifted off the start
12759
	 *    of the series as one is appended to the end.
12760
	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
12761
	 *    configuration
12762
	 */
12763
	addPoint: function (options, redraw, shift, animation) {
12764
		var series = this,
12765
			seriesOptions = series.options,
12766
			data = series.data,
12767
			graph = series.graph,
12768
			area = series.area,
12769
			chart = series.chart,
12770
			xData = series.xData,
12771
			yData = series.yData,
12772
			zData = series.zData,
12773
			names = series.names,
12774
			currentShift = (graph && graph.shift) || 0,
12775
			dataOptions = seriesOptions.data,
12776
			point;
12777
12778
		setAnimation(animation, chart);
12779
12780
		// Make graph animate sideways
12781
		if (graph && shift) { 
12782
			graph.shift = currentShift + 1;
12783
		}
12784
		if (area) {
12785
			if (shift) { // #780
12786
				area.shift = currentShift + 1;
12787
			}
12788
			area.isArea = true; // needed in animation, both with and without shift
12789
		}
12790
		
12791
		// Optional redraw, defaults to true
12792
		redraw = pick(redraw, true);
12793
12794
		// Get options and push the point to xData, yData and series.options. In series.generatePoints
12795
		// the Point instance will be created on demand and pushed to the series.data array.
12796
		point = { series: series };
12797
		series.pointClass.prototype.applyOptions.apply(point, [options]);
12798
		xData.push(point.x);
12799
		yData.push(series.toYData ? series.toYData(point) : point.y);
12800
		zData.push(point.z);
12801
		if (names) {
12802
			names[point.x] = point.name;
12803
		}
12804
		dataOptions.push(options);
12805
12806
		// Generate points to be added to the legend (#1329) 
12807
		if (seriesOptions.legendType === 'point') {
12808
			series.generatePoints();
12809
		}
12810
12811
		// Shift the first point off the parallel arrays
12812
		// todo: consider series.removePoint(i) method
12813
		if (shift) {
12814
			if (data[0] && data[0].remove) {
12815
				data[0].remove(false);
12816
			} else {
12817
				data.shift();
12818
				xData.shift();
12819
				yData.shift();
12820
				zData.shift();
12821
				dataOptions.shift();
12822
			}
12823
		}
12824
		series.getAttribs();
12825
12826
		// redraw
12827
		series.isDirty = true;
12828
		series.isDirtyData = true;
12829
		if (redraw) {
12830
			chart.redraw();
12831
		}
12832
	},
12833
12834
	/**
12835
	 * Replace the series data with a new set of data
12836
	 * @param {Object} data
12837
	 * @param {Object} redraw
12838
	 */
12839
	setData: function (data, redraw) {
12840
		var series = this,
12841
			oldData = series.points,
12842
			options = series.options,
12843
			chart = series.chart,
12844
			firstPoint = null,
12845
			xAxis = series.xAxis,
12846
			names = xAxis && xAxis.categories && !xAxis.categories.length ? [] : null,
12847
			i;
12848
12849
		// reset properties
12850
		series.xIncrement = null;
12851
		series.pointRange = xAxis && xAxis.categories ? 1 : options.pointRange;
12852
12853
		series.colorCounter = 0; // for series with colorByPoint (#1547)
12854
		
12855
		// parallel arrays
12856
		var xData = [],
12857
			yData = [],
12858
			zData = [],
12859
			dataLength = data ? data.length : [],
12860
			turboThreshold = options.turboThreshold || 1000,
12861
			pt,
12862
			pointArrayMap = series.pointArrayMap,
12863
			valueCount = pointArrayMap && pointArrayMap.length,
12864
			hasToYData = !!series.toYData;
12865
12866
		// In turbo mode, only one- or twodimensional arrays of numbers are allowed. The
12867
		// first value is tested, and we assume that all the rest are defined the same
12868
		// way. Although the 'for' loops are similar, they are repeated inside each
12869
		// if-else conditional for max performance.
12870
		if (dataLength > turboThreshold) {
12871
			
12872
			// find the first non-null point
12873
			i = 0;
12874
			while (firstPoint === null && i < dataLength) {
12875
				firstPoint = data[i];
12876
				i++;
12877
			}
12878
		
12879
		
12880
			if (isNumber(firstPoint)) { // assume all points are numbers
12881
				var x = pick(options.pointStart, 0),
12882
					pointInterval = pick(options.pointInterval, 1);
12883
12884
				for (i = 0; i < dataLength; i++) {
12885
					xData[i] = x;
12886
					yData[i] = data[i];
12887
					x += pointInterval;
12888
				}
12889
				series.xIncrement = x;
12890
			} else if (isArray(firstPoint)) { // assume all points are arrays
12891
				if (valueCount) { // [x, low, high] or [x, o, h, l, c]
12892
					for (i = 0; i < dataLength; i++) {
12893
						pt = data[i];
12894
						xData[i] = pt[0];
12895
						yData[i] = pt.slice(1, valueCount + 1);
12896
					}
12897
				} else { // [x, y]
12898
					for (i = 0; i < dataLength; i++) {
12899
						pt = data[i];
12900
						xData[i] = pt[0];
12901
						yData[i] = pt[1];
12902
					}
12903
				}
12904
			} /* else {
12905
				error(12); // Highcharts expects configs to be numbers or arrays in turbo mode
12906
			}*/
12907
		} else {
12908
			for (i = 0; i < dataLength; i++) {
12909
				if (data[i] !== UNDEFINED) { // stray commas in oldIE
12910
					pt = { series: series };
12911
					series.pointClass.prototype.applyOptions.apply(pt, [data[i]]);
12912
					xData[i] = pt.x;
12913
					yData[i] = hasToYData ? series.toYData(pt) : pt.y;
12914
					zData[i] = pt.z;
12915
					if (names && pt.name) {
12916
						names[i] = pt.name;
12917
					}
12918
				}
12919
			}
12920
		}
12921
		
12922
		// Unsorted data is not supported by the line tooltip as well as data grouping and 
12923
		// navigation in Stock charts (#725)
12924
		if (series.requireSorting && xData.length > 1 && xData[1] < xData[0]) {
12925
			error(15);
12926
		}
12927
12928
		// Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON		
12929
		if (isString(yData[0])) {
12930
			error(14, true);
12931
		} 
12932
12933
		series.data = [];
12934
		series.options.data = data;
12935
		series.xData = xData;
12936
		series.yData = yData;
12937
		series.zData = zData;
12938
		series.names = names;
12939
12940
		// destroy old points
12941
		i = (oldData && oldData.length) || 0;
12942
		while (i--) {
12943
			if (oldData[i] && oldData[i].destroy) {
12944
				oldData[i].destroy();
12945
			}
12946
		}
12947
12948
		// reset minRange (#878)
12949
		if (xAxis) {
12950
			xAxis.minRange = xAxis.userMinRange;
12951
		}
12952
12953
		// redraw
12954
		series.isDirty = series.isDirtyData = chart.isDirtyBox = true;
12955
		if (pick(redraw, true)) {
12956
			chart.redraw(false);
12957
		}
12958
	},
12959
12960
	/**
12961
	 * Remove a series and optionally redraw the chart
12962
	 *
12963
	 * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call
12964
	 * @param {Boolean|Object} animation Whether to apply animation, and optionally animation
12965
	 *    configuration
12966
	 */
12967
12968
	remove: function (redraw, animation) {
12969
		var series = this,
12970
			chart = series.chart;
12971
		redraw = pick(redraw, true);
12972
12973
		if (!series.isRemoving) {  /* prevent triggering native event in jQuery
12974
				(calling the remove function from the remove event) */
12975
			series.isRemoving = true;
12976
12977
			// fire the event with a default handler of removing the point
12978
			fireEvent(series, 'remove', null, function () {
12979
12980
12981
				// destroy elements
12982
				series.destroy();
12983
12984
12985
				// redraw
12986
				chart.isDirtyLegend = chart.isDirtyBox = true;
12987
				if (redraw) {
12988
					chart.redraw(animation);
12989
				}
12990
			});
12991
12992
		}
12993
		series.isRemoving = false;
12994
	},
12995
12996
	/**
12997
	 * Process the data by cropping away unused data points if the series is longer
12998
	 * than the crop threshold. This saves computing time for lage series.
12999
	 */
13000
	processData: function (force) {
13001
		var series = this,
13002
			processedXData = series.xData, // copied during slice operation below
13003
			processedYData = series.yData,
13004
			dataLength = processedXData.length,
13005
			cropStart = 0,
13006
			cropEnd = dataLength,
13007
			cropped,
13008
			distance,
13009
			closestPointRange,
13010
			xAxis = series.xAxis,
13011
			i, // loop variable
13012
			options = series.options,
13013
			cropThreshold = options.cropThreshold,
13014
			isCartesian = series.isCartesian;
13015
13016
		// If the series data or axes haven't changed, don't go through this. Return false to pass
13017
		// the message on to override methods like in data grouping. 
13018
		if (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) {
13019
			return false;
13020
		}
13021
13022
		// optionally filter out points outside the plot area
13023
		if (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) {
13024
			var extremes = xAxis.getExtremes(),
13025
				min = extremes.min,
13026
				max = extremes.max;
13027
13028
			// it's outside current extremes
13029
			if (processedXData[dataLength - 1] < min || processedXData[0] > max) {
13030
				processedXData = [];
13031
				processedYData = [];
13032
			
13033
			// only crop if it's actually spilling out
13034
			} else if (processedXData[0] < min || processedXData[dataLength - 1] > max) {
13035
13036
				// iterate up to find slice start
13037
				for (i = 0; i < dataLength; i++) {
13038
					if (processedXData[i] >= min) {
13039
						cropStart = mathMax(0, i - 1);
13040
						break;
13041
					}
13042
				}
13043
				// proceed to find slice end
13044
				for (; i < dataLength; i++) {
13045
					if (processedXData[i] > max) {
13046
						cropEnd = i + 1;
13047
						break;
13048
					}
13049
					
13050
				}
13051
				processedXData = processedXData.slice(cropStart, cropEnd);
13052
				processedYData = processedYData.slice(cropStart, cropEnd);
13053
				cropped = true;
13054
			}
13055
		}
13056
		
13057
		
13058
		// Find the closest distance between processed points
13059
		for (i = processedXData.length - 1; i > 0; i--) {
13060
			distance = processedXData[i] - processedXData[i - 1];
13061
			if (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) {
13062
				closestPointRange = distance;
13063
			}
13064
		}
13065
		
13066
		// Record the properties
13067
		series.cropped = cropped; // undefined or true
13068
		series.cropStart = cropStart;
13069
		series.processedXData = processedXData;
13070
		series.processedYData = processedYData;
13071
		
13072
		if (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC
13073
			series.pointRange = closestPointRange || 1;
13074
		}
13075
		series.closestPointRange = closestPointRange;
13076
		
13077
	},
13078
13079
	/**
13080
	 * Generate the data point after the data has been processed by cropping away
13081
	 * unused points and optionally grouped in Highcharts Stock.
13082
	 */
13083
	generatePoints: function () {
13084
		var series = this,
13085
			options = series.options,
13086
			dataOptions = options.data,
13087
			data = series.data,
13088
			dataLength,
13089
			processedXData = series.processedXData,
13090
			processedYData = series.processedYData,
13091
			pointClass = series.pointClass,
13092
			processedDataLength = processedXData.length,
13093
			cropStart = series.cropStart || 0,
13094
			cursor,
13095
			hasGroupedData = series.hasGroupedData,
13096
			point,
13097
			points = [],
13098
			i;
13099
13100
		if (!data && !hasGroupedData) {
13101
			var arr = [];
13102
			arr.length = dataOptions.length;
13103
			data = series.data = arr;
13104
		}
13105
13106
		for (i = 0; i < processedDataLength; i++) {
13107
			cursor = cropStart + i;
13108
			if (!hasGroupedData) {
13109
				if (data[cursor]) {
13110
					point = data[cursor];
13111
				} else if (dataOptions[cursor] !== UNDEFINED) { // #970
13112
					data[cursor] = point = (new pointClass()).init(series, dataOptions[cursor], processedXData[i]);
13113
				}
13114
				points[i] = point;
13115
			} else {
13116
				// splat the y data in case of ohlc data array
13117
				points[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i])));
13118
			}
13119
		}
13120
13121
		// Hide cropped-away points - this only runs when the number of points is above cropThreshold, or when
13122
		// swithching view from non-grouped data to grouped data (#637)	
13123
		if (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) {
13124
			for (i = 0; i < dataLength; i++) {
13125
				if (i === cropStart && !hasGroupedData) { // when has grouped data, clear all points
13126
					i += processedDataLength;
13127
				}
13128
				if (data[i]) {
13129
					data[i].destroyElements();
13130
					data[i].plotX = UNDEFINED; // #1003
13131
				}
13132
			}
13133
		}
13134
13135
		series.data = data;
13136
		series.points = points;
13137
	},
13138
13139
	/**
13140
	 * Translate data points from raw data values to chart specific positioning data
13141
	 * needed later in drawPoints, drawGraph and drawTracker.
13142
	 */
13143
	translate: function () {
13144
		if (!this.processedXData) { // hidden series
13145
			this.processData();
13146
		}
13147
		this.generatePoints();
13148
		var series = this,
13149
			options = series.options,
13150
			stacking = options.stacking,
13151
			xAxis = series.xAxis,
13152
			categories = xAxis.categories,
13153
			yAxis = series.yAxis,
13154
			points = series.points,
13155
			dataLength = points.length,
13156
			hasModifyValue = !!series.modifyValue,
13157
			isBottomSeries,
13158
			allStackSeries,
13159
			i,
13160
			placeBetween = options.pointPlacement === 'between',
13161
			threshold = options.threshold;
13162
			//nextSeriesDown;
13163
			
13164
		// Is it the last visible series? (#809, #1722).
13165
		// TODO: After merging in the 'stacking' branch, this logic should probably be moved to Chart.getStacks
13166
		allStackSeries = yAxis.series.sort(function (a, b) {
13167
			return a.index - b.index;
13168
		});
13169
		i = allStackSeries.length;
13170
		while (i--) {
13171
			if (allStackSeries[i].visible) {
13172
				if (allStackSeries[i] === series) { // #809
13173
					isBottomSeries = true;
13174
				}
13175
				break;
13176
			}
13177
		}
13178
		
13179
		// Translate each point
13180
		for (i = 0; i < dataLength; i++) {
13181
			var point = points[i],
13182
				xValue = point.x,
13183
				yValue = point.y,
13184
				yBottom = point.low,
13185
				stack = yAxis.stacks[(yValue < threshold ? '-' : '') + series.stackKey],
13186
				pointStack,
13187
				pointStackTotal;
13188
13189
			// Discard disallowed y values for log axes
13190
			if (yAxis.isLog && yValue <= 0) {
13191
				point.y = yValue = null;
13192
			}
13193
			
13194
			// Get the plotX translation
13195
			point.plotX = xAxis.translate(xValue, 0, 0, 0, 1, placeBetween); // Math.round fixes #591
13196
13197
			// Calculate the bottom y value for stacked series
13198
			if (stacking && series.visible && stack && stack[xValue]) {
13199
				pointStack = stack[xValue];
13200
				pointStackTotal = pointStack.total;
13201
				pointStack.cum = yBottom = pointStack.cum - yValue; // start from top
13202
				yValue = yBottom + yValue;
13203
				
13204
				if (isBottomSeries) {
13205
					yBottom = pick(threshold, yAxis.min);
13206
				}
13207
				
13208
				if (yAxis.isLog && yBottom <= 0) { // #1200, #1232
13209
					yBottom = null;
13210
				}
13211
				
13212
				if (stacking === 'percent') {
13213
					yBottom = pointStackTotal ? yBottom * 100 / pointStackTotal : 0;
13214
					yValue = pointStackTotal ? yValue * 100 / pointStackTotal : 0;
13215
				}
13216
13217
				point.percentage = pointStackTotal ? point.y * 100 / pointStackTotal : 0;
13218
				point.total = point.stackTotal = pointStackTotal;
13219
				point.stackY = yValue;
13220
			}
13221
13222
			// Set translated yBottom or remove it
13223
			point.yBottom = defined(yBottom) ? 
13224
				yAxis.translate(yBottom, 0, 1, 0, 1) :
13225
				null;
13226
			
13227
			// general hook, used for Highstock compare mode
13228
			if (hasModifyValue) {
13229
				yValue = series.modifyValue(yValue, point);
13230
			}
13231
13232
			// Set the the plotY value, reset it for redraws
13233
			point.plotY = (typeof yValue === 'number' && yValue !== Infinity) ? 
13234
				mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10 : // Math.round fixes #591
13235
				UNDEFINED;
13236
			
13237
			// Set client related positions for mouse tracking
13238
			point.clientX = placeBetween ? xAxis.translate(xValue, 0, 0, 0, 1) : point.plotX; // #1514
13239
				
13240
			point.negative = point.y < (threshold || 0);
13241
13242
			// some API data
13243
			point.category = categories && categories[point.x] !== UNDEFINED ?
13244
				categories[point.x] : point.x;
13245
13246
13247
		}
13248
13249
		// now that we have the cropped data, build the segments
13250
		series.getSegments();
13251
	},
13252
	/**
13253
	 * Memoize tooltip texts and positions
13254
	 */
13255
	setTooltipPoints: function (renew) {
13256
		var series = this,
13257
			points = [],
13258
			pointsLength,
13259
			low,
13260
			high,
13261
			xAxis = series.xAxis,
13262
			axisLength = xAxis ? (xAxis.tooltipLen || xAxis.len) : series.chart.plotSizeX, // tooltipLen and tooltipPosName used in polar
13263
			point,
13264
			i,
13265
			tooltipPoints = []; // a lookup array for each pixel in the x dimension
13266
13267
		// don't waste resources if tracker is disabled
13268
		if (series.options.enableMouseTracking === false) {
13269
			return;
13270
		}
13271
13272
		// renew
13273
		if (renew) {
13274
			series.tooltipPoints = null;
13275
		}
13276
13277
		// concat segments to overcome null values
13278
		each(series.segments || series.points, function (segment) {
13279
			points = points.concat(segment);
13280
		});
13281
13282
		// Reverse the points in case the X axis is reversed
13283
		if (xAxis && xAxis.reversed) {
13284
			points = points.reverse();
13285
		}
13286
13287
		// Assign each pixel position to the nearest point
13288
		pointsLength = points.length;
13289
		for (i = 0; i < pointsLength; i++) {
13290
			point = points[i];
13291
			// Set this range's low to the last range's high plus one
13292
			low = points[i - 1] ? high + 1 : 0;
13293
			// Now find the new high
13294
			high = points[i + 1] ?
13295
				mathMax(0, mathFloor((point.clientX + (points[i + 1] ? points[i + 1].clientX : axisLength)) / 2)) :
13296
				axisLength;
13297
13298
			while (low >= 0 && low <= high) {
13299
				tooltipPoints[low++] = point;
13300
			}
13301
		}
13302
		series.tooltipPoints = tooltipPoints;
13303
	},
13304
13305
	/**
13306
	 * Format the header of the tooltip
13307
	 */
13308
	tooltipHeaderFormatter: function (point) {
13309
		var series = this,
13310
			tooltipOptions = series.tooltipOptions,
13311
			xDateFormat = tooltipOptions.xDateFormat,
13312
			dateTimeLabelFormats = tooltipOptions.dateTimeLabelFormats,
13313
			xAxis = series.xAxis,
13314
			isDateTime = xAxis && xAxis.options.type === 'datetime',
13315
			headerFormat = tooltipOptions.headerFormat,
13316
			closestPointRange = xAxis && xAxis.closestPointRange,
13317
			n;
13318
			
13319
		// Guess the best date format based on the closest point distance (#568)
13320
		if (isDateTime && !xDateFormat) {
13321
			if (closestPointRange) {
13322
				for (n in timeUnits) {
13323
					if (timeUnits[n] >= closestPointRange) {
13324
						xDateFormat = dateTimeLabelFormats[n];
13325
						break;
13326
					}
13327
				}
13328
			} else {
13329
				xDateFormat = dateTimeLabelFormats.day;
13330
			}
13331
		}
13332
		
13333
		// Insert the header date format if any
13334
		if (isDateTime && xDateFormat && isNumber(point.key)) {
13335
			headerFormat = headerFormat.replace('{point.key}', '{point.key:' + xDateFormat + '}');
13336
		}
13337
		
13338
		return format(headerFormat, {
13339
			point: point,
13340
			series: series
13341
		});
13342
	},
13343
13344
	/**
13345
	 * Series mouse over handler
13346
	 */
13347
	onMouseOver: function () {
13348
		var series = this,
13349
			chart = series.chart,
13350
			hoverSeries = chart.hoverSeries;
13351
13352
		// set normal state to previous series
13353
		if (hoverSeries && hoverSeries !== series) {
13354
			hoverSeries.onMouseOut();
13355
		}
13356
13357
		// trigger the event, but to save processing time,
13358
		// only if defined
13359
		if (series.options.events.mouseOver) {
13360
			fireEvent(series, 'mouseOver');
13361
		}
13362
13363
		// hover this
13364
		series.setState(HOVER_STATE);
13365
		chart.hoverSeries = series;
13366
	},
13367
13368
	/**
13369
	 * Series mouse out handler
13370
	 */
13371
	onMouseOut: function () {
13372
		// trigger the event only if listeners exist
13373
		var series = this,
13374
			options = series.options,
13375
			chart = series.chart,
13376
			tooltip = chart.tooltip,
13377
			hoverPoint = chart.hoverPoint;
13378
13379
		// trigger mouse out on the point, which must be in this series
13380
		if (hoverPoint) {
13381
			hoverPoint.onMouseOut();
13382
		}
13383
13384
		// fire the mouse out event
13385
		if (series && options.events.mouseOut) {
13386
			fireEvent(series, 'mouseOut');
13387
		}
13388
13389
13390
		// hide the tooltip
13391
		if (tooltip && !options.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) {
13392
			tooltip.hide();
13393
		}
13394
13395
		// set normal state
13396
		series.setState();
13397
		chart.hoverSeries = null;
13398
	},
13399
13400
	/**
13401
	 * Animate in the series
13402
	 */
13403
	animate: function (init) {
13404
		var series = this,
13405
			chart = series.chart,
13406
			renderer = chart.renderer,
13407
			clipRect,
13408
			markerClipRect,
13409
			animation = series.options.animation,
13410
			clipBox = chart.clipBox,
13411
			inverted = chart.inverted,
13412
			sharedClipKey;
13413
13414
		// Animation option is set to true
13415
		if (animation && !isObject(animation)) {
13416
			animation = defaultPlotOptions[series.type].animation;
13417
		}
13418
		sharedClipKey = '_sharedClip' + animation.duration + animation.easing;
13419
13420
		// Initialize the animation. Set up the clipping rectangle.
13421
		if (init) { 
13422
			
13423
			// If a clipping rectangle with the same properties is currently present in the chart, use that. 
13424
			clipRect = chart[sharedClipKey];
13425
			markerClipRect = chart[sharedClipKey + 'm'];
13426
			if (!clipRect) {
13427
				chart[sharedClipKey] = clipRect = renderer.clipRect(
13428
					extend(clipBox, { width: 0 })
13429
				);
13430
				
13431
				chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect(
13432
					-99, // include the width of the first marker
13433
					inverted ? -chart.plotLeft : -chart.plotTop, 
13434
					99,
13435
					inverted ? chart.chartWidth : chart.chartHeight
13436
				);
13437
			}
13438
			series.group.clip(clipRect);
13439
			series.markerGroup.clip(markerClipRect);
13440
			series.sharedClipKey = sharedClipKey;
13441
13442
		// Run the animation
13443
		} else { 
13444
			clipRect = chart[sharedClipKey];
13445
			if (clipRect) {
13446
				clipRect.animate({
13447
					width: chart.plotSizeX
13448
				}, animation);
13449
				chart[sharedClipKey + 'm'].animate({
13450
					width: chart.plotSizeX + 99
13451
				}, animation);
13452
			}
13453
13454
			// Delete this function to allow it only once
13455
			series.animate = null;
13456
			
13457
			// Call the afterAnimate function on animation complete (but don't overwrite the animation.complete option
13458
			// which should be available to the user).
13459
			series.animationTimeout = setTimeout(function () {
13460
				series.afterAnimate();
13461
			}, animation.duration);
13462
		}
13463
	},
13464
	
13465
	/**
13466
	 * This runs after animation to land on the final plot clipping
13467
	 */
13468
	afterAnimate: function () {
13469
		var chart = this.chart,
13470
			sharedClipKey = this.sharedClipKey,
13471
			group = this.group;
13472
			
13473
		if (group && this.options.clip !== false) {
13474
			group.clip(chart.clipRect);
13475
			this.markerGroup.clip(); // no clip
13476
		}
13477
		
13478
		// Remove the shared clipping rectancgle when all series are shown		
13479
		setTimeout(function () {
13480
			if (sharedClipKey && chart[sharedClipKey]) {
13481
				chart[sharedClipKey] = chart[sharedClipKey].destroy();
13482
				chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();
13483
			}
13484
		}, 100);
13485
	},
13486
13487
	/**
13488
	 * Draw the markers
13489
	 */
13490
	drawPoints: function () {
13491
		var series = this,
13492
			pointAttr,
13493
			points = series.points,
13494
			chart = series.chart,
13495
			plotX,
13496
			plotY,
13497
			i,
13498
			point,
13499
			radius,
13500
			symbol,
13501
			isImage,
13502
			graphic,
13503
			options = series.options,
13504
			seriesMarkerOptions = options.marker,
13505
			pointMarkerOptions,
13506
			enabled,
13507
			isInside,
13508
			markerGroup = series.markerGroup;
13509
13510
		if (seriesMarkerOptions.enabled || series._hasPointMarkers) {
13511
			
13512
			i = points.length;
13513
			while (i--) {
13514
				point = points[i];
13515
				plotX = point.plotX;
13516
				plotY = point.plotY;
13517
				graphic = point.graphic;
13518
				pointMarkerOptions = point.marker || {};
13519
				enabled = (seriesMarkerOptions.enabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled;
13520
				isInside = chart.isInsidePlot(mathRound(plotX), plotY, chart.inverted); // #1858
13521
				
13522
				// only draw the point if y is defined
13523
				if (enabled && plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
13524
13525
					// shortcuts
13526
					pointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE];
13527
					radius = pointAttr.r;
13528
					symbol = pick(pointMarkerOptions.symbol, series.symbol);
13529
					isImage = symbol.indexOf('url') === 0;
13530
13531
					if (graphic) { // update
13532
						graphic
13533
							.attr({ // Since the marker group isn't clipped, each individual marker must be toggled
13534
								visibility: isInside ? (hasSVG ? 'inherit' : VISIBLE) : HIDDEN
13535
							})
13536
							.animate(extend({
13537
								x: plotX - radius,
13538
								y: plotY - radius
13539
							}, graphic.symbolName ? { // don't apply to image symbols #507
13540
								width: 2 * radius,
13541
								height: 2 * radius
13542
							} : {}));
13543
					} else if (isInside && (radius > 0 || isImage)) {
13544
						point.graphic = graphic = chart.renderer.symbol(
13545
							symbol,
13546
							plotX - radius,
13547
							plotY - radius,
13548
							2 * radius,
13549
							2 * radius
13550
						)
13551
						.attr(pointAttr)
13552
						.add(markerGroup);
13553
					}
13554
					
13555
				} else if (graphic) {
13556
					point.graphic = graphic.destroy(); // #1269
13557
				}
13558
			}
13559
		}
13560
13561
	},
13562
13563
	/**
13564
	 * Convert state properties from API naming conventions to SVG attributes
13565
	 *
13566
	 * @param {Object} options API options object
13567
	 * @param {Object} base1 SVG attribute object to inherit from
13568
	 * @param {Object} base2 Second level SVG attribute object to inherit from
13569
	 */
13570
	convertAttribs: function (options, base1, base2, base3) {
13571
		var conversion = this.pointAttrToOptions,
13572
			attr,
13573
			option,
13574
			obj = {};
13575
13576
		options = options || {};
13577
		base1 = base1 || {};
13578
		base2 = base2 || {};
13579
		base3 = base3 || {};
13580
13581
		for (attr in conversion) {
13582
			option = conversion[attr];
13583
			obj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]);
13584
		}
13585
		return obj;
13586
	},
13587
13588
	/**
13589
	 * Get the state attributes. Each series type has its own set of attributes
13590
	 * that are allowed to change on a point's state change. Series wide attributes are stored for
13591
	 * all series, and additionally point specific attributes are stored for all
13592
	 * points with individual marker options. If such options are not defined for the point,
13593
	 * a reference to the series wide attributes is stored in point.pointAttr.
13594
	 */
13595
	getAttribs: function () {
13596
		var series = this,
13597
			seriesOptions = series.options,
13598
			normalOptions = defaultPlotOptions[series.type].marker ? seriesOptions.marker : seriesOptions,
13599
			stateOptions = normalOptions.states,
13600
			stateOptionsHover = stateOptions[HOVER_STATE],
13601
			pointStateOptionsHover,
13602
			seriesColor = series.color,
13603
			normalDefaults = {
13604
				stroke: seriesColor,
13605
				fill: seriesColor
13606
			},
13607
			points = series.points || [], // #927
13608
			i,
13609
			point,
13610
			seriesPointAttr = [],
13611
			pointAttr,
13612
			pointAttrToOptions = series.pointAttrToOptions,
13613
			hasPointSpecificOptions,
13614
			negativeColor = seriesOptions.negativeColor,
13615
			key;
13616
13617
		// series type specific modifications
13618
		if (seriesOptions.marker) { // line, spline, area, areaspline, scatter
13619
13620
			// if no hover radius is given, default to normal radius + 2
13621
			stateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2;
13622
			stateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1;
13623
			
13624
		} else { // column, bar, pie
13625
13626
			// if no hover color is given, brighten the normal color
13627
			stateOptionsHover.color = stateOptionsHover.color ||
13628
				Color(stateOptionsHover.color || seriesColor)
13629
					.brighten(stateOptionsHover.brightness).get();
13630
		}
13631
13632
		// general point attributes for the series normal state
13633
		seriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults);
13634
13635
		// HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius
13636
		each([HOVER_STATE, SELECT_STATE], function (state) {
13637
			seriesPointAttr[state] =
13638
					series.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]);
13639
		});
13640
13641
		// set it
13642
		series.pointAttr = seriesPointAttr;
13643
13644
13645
		// Generate the point-specific attribute collections if specific point
13646
		// options are given. If not, create a referance to the series wide point
13647
		// attributes
13648
		i = points.length;
13649
		while (i--) {
13650
			point = points[i];
13651
			normalOptions = (point.options && point.options.marker) || point.options;
13652
			if (normalOptions && normalOptions.enabled === false) {
13653
				normalOptions.radius = 0;
13654
			}
13655
			
13656
			if (point.negative && negativeColor) {
13657
				point.color = point.fillColor = negativeColor;
13658
			}
13659
			
13660
			hasPointSpecificOptions = seriesOptions.colorByPoint || point.color; // #868
13661
			
13662
			// check if the point has specific visual options
13663
			if (point.options) {
13664
				for (key in pointAttrToOptions) {
13665
					if (defined(normalOptions[pointAttrToOptions[key]])) {
13666
						hasPointSpecificOptions = true;
13667
					}
13668
				}
13669
			}
13670
13671
			// a specific marker config object is defined for the individual point:
13672
			// create it's own attribute collection
13673
			if (hasPointSpecificOptions) {
13674
				normalOptions = normalOptions || {};
13675
				pointAttr = [];
13676
				stateOptions = normalOptions.states || {}; // reassign for individual point
13677
				pointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {};
13678
13679
				// Handle colors for column and pies
13680
				if (!seriesOptions.marker) { // column, bar, point
13681
					// if no hover color is given, brighten the normal color
13682
					pointStateOptionsHover.color =
13683
						Color(pointStateOptionsHover.color || point.color)
13684
							.brighten(pointStateOptionsHover.brightness ||
13685
								stateOptionsHover.brightness).get();
13686
13687
				}
13688
13689
				// normal point state inherits series wide normal state
13690
				pointAttr[NORMAL_STATE] = series.convertAttribs(extend({
13691
					color: point.color // #868
13692
				}, normalOptions), seriesPointAttr[NORMAL_STATE]);
13693
13694
				// inherit from point normal and series hover
13695
				pointAttr[HOVER_STATE] = series.convertAttribs(
13696
					stateOptions[HOVER_STATE],
13697
					seriesPointAttr[HOVER_STATE],
13698
					pointAttr[NORMAL_STATE]
13699
				);
13700
				
13701
				// inherit from point normal and series hover
13702
				pointAttr[SELECT_STATE] = series.convertAttribs(
13703
					stateOptions[SELECT_STATE],
13704
					seriesPointAttr[SELECT_STATE],
13705
					pointAttr[NORMAL_STATE]
13706
				);
13707
13708
				// Force the fill to negativeColor on markers
13709
				if (point.negative && seriesOptions.marker && negativeColor) {
13710
					pointAttr[NORMAL_STATE].fill = pointAttr[HOVER_STATE].fill = pointAttr[SELECT_STATE].fill = 
13711
						series.convertAttribs({ fillColor: negativeColor }).fill;
13712
				}
13713
13714
13715
			// no marker config object is created: copy a reference to the series-wide
13716
			// attribute collection
13717
			} else {
13718
				pointAttr = seriesPointAttr;
13719
			}
13720
13721
			point.pointAttr = pointAttr;
13722
13723
		}
13724
13725
	},
13726
	/**
13727
	 * Update the series with a new set of options
13728
	 */
13729
	update: function (newOptions, redraw) {
13730
		var chart = this.chart,
13731
			// must use user options when changing type because this.options is merged
13732
			// in with type specific plotOptions
13733
			oldOptions = this.userOptions,
13734
			oldType = this.type;
13735
13736
		// Do the merge, with some forced options
13737
		newOptions = merge(oldOptions, {
13738
			animation: false,
13739
			index: this.index,
13740
			pointStart: this.xData[0] // when updating after addPoint
13741
		}, newOptions);
13742
13743
		// Destroy the series and reinsert methods from the type prototype
13744
		this.remove(false);
13745
		extend(this, seriesTypes[newOptions.type || oldType].prototype);
13746
		
13747
13748
		this.init(chart, newOptions);
13749
		if (pick(redraw, true)) {
13750
			chart.redraw(false);
13751
		}
13752
	},
13753
13754
	/**
13755
	 * Clear DOM objects and free up memory
13756
	 */
13757
	destroy: function () {
13758
		var series = this,
13759
			chart = series.chart,
13760
			issue134 = /AppleWebKit\/533/.test(userAgent),
13761
			destroy,
13762
			i,
13763
			data = series.data || [],
13764
			point,
13765
			prop,
13766
			axis;
13767
13768
		// add event hook
13769
		fireEvent(series, 'destroy');
13770
13771
		// remove all events
13772
		removeEvent(series);
13773
		
13774
		// erase from axes
13775
		each(['xAxis', 'yAxis'], function (AXIS) {
13776
			axis = series[AXIS];
13777
			if (axis) {
13778
				erase(axis.series, series);
13779
				axis.isDirty = axis.forceRedraw = true;
13780
			}
13781
		});
13782
13783
		// remove legend items
13784
		if (series.legendItem) {
13785
			series.chart.legend.destroyItem(series);
13786
		}
13787
13788
		// destroy all points with their elements
13789
		i = data.length;
13790
		while (i--) {
13791
			point = data[i];
13792
			if (point && point.destroy) {
13793
				point.destroy();
13794
			}
13795
		}
13796
		series.points = null;
13797
13798
		// Clear the animation timeout if we are destroying the series during initial animation
13799
		clearTimeout(series.animationTimeout);
13800
13801
		// destroy all SVGElements associated to the series
13802
		each(['area', 'graph', 'dataLabelsGroup', 'group', 'markerGroup', 'tracker',
13803
				'graphNeg', 'areaNeg', 'posClip', 'negClip'], function (prop) {
13804
			if (series[prop]) {
13805
13806
				// issue 134 workaround
13807
				destroy = issue134 && prop === 'group' ?
13808
					'hide' :
13809
					'destroy';
13810
13811
				series[prop][destroy]();
13812
			}
13813
		});
13814
13815
		// remove from hoverSeries
13816
		if (chart.hoverSeries === series) {
13817
			chart.hoverSeries = null;
13818
		}
13819
		erase(chart.series, series);
13820
13821
		// clear all members
13822
		for (prop in series) {
13823
			delete series[prop];
13824
		}
13825
	},
13826
13827
	/**
13828
	 * Draw the data labels
13829
	 */
13830
	drawDataLabels: function () {
13831
		
13832
		var series = this,
13833
			seriesOptions = series.options,
13834
			options = seriesOptions.dataLabels,
13835
			points = series.points,
13836
			pointOptions,
13837
			generalOptions,
13838
			str,
13839
			dataLabelsGroup;
13840
		
13841
		if (options.enabled || series._hasPointLabels) {
13842
						
13843
			// Process default alignment of data labels for columns
13844
			if (series.dlProcessOptions) {
13845
				series.dlProcessOptions(options);
13846
			}
13847
13848
			// Create a separate group for the data labels to avoid rotation
13849
			dataLabelsGroup = series.plotGroup(
13850
				'dataLabelsGroup', 
13851
				'data-labels', 
13852
				series.visible ? VISIBLE : HIDDEN, 
13853
				options.zIndex || 6
13854
			);
13855
			
13856
			// Make the labels for each point
13857
			generalOptions = options;
13858
			each(points, function (point) {
13859
				
13860
				var enabled,
13861
					dataLabel = point.dataLabel,
13862
					labelConfig,
13863
					attr,
13864
					name,
13865
					rotation,
13866
					connector = point.connector,
13867
					isNew = true;
13868
				
13869
				// Determine if each data label is enabled
13870
				pointOptions = point.options && point.options.dataLabels;
13871
				enabled = generalOptions.enabled || (pointOptions && pointOptions.enabled);
13872
				
13873
				
13874
				// If the point is outside the plot area, destroy it. #678, #820
13875
				if (dataLabel && !enabled) {
13876
					point.dataLabel = dataLabel.destroy();
13877
				
13878
				// Individual labels are disabled if the are explicitly disabled 
13879
				// in the point options, or if they fall outside the plot area.
13880
				} else if (enabled) {
13881
					
13882
					rotation = options.rotation;
13883
					
13884
					// Create individual options structure that can be extended without 
13885
					// affecting others
13886
					options = merge(generalOptions, pointOptions);
13887
				
13888
					// Get the string
13889
					labelConfig = point.getLabelConfig();
13890
					str = options.format ?
13891
						format(options.format, labelConfig) : 
13892
						options.formatter.call(labelConfig, options);
13893
					
13894
					// Determine the color
13895
					options.style.color = pick(options.color, options.style.color, series.color, 'black');
13896
	
13897
					
13898
					// update existing label
13899
					if (dataLabel) {
13900
						
13901
						if (defined(str)) {
13902
							dataLabel
13903
								.attr({
13904
									text: str
13905
								});
13906
							isNew = false;
13907
						
13908
						} else { // #1437 - the label is shown conditionally
13909
							point.dataLabel = dataLabel = dataLabel.destroy();
13910
							if (connector) {
13911
								point.connector = connector.destroy();
13912
							}
13913
						}
13914
						
13915
					// create new label
13916
					} else if (defined(str)) {
13917
						attr = {
13918
							//align: align,
13919
							fill: options.backgroundColor,
13920
							stroke: options.borderColor,
13921
							'stroke-width': options.borderWidth,
13922
							r: options.borderRadius || 0,
13923
							rotation: rotation,
13924
							padding: options.padding,
13925
							zIndex: 1
13926
						};
13927
						// Remove unused attributes (#947)
13928
						for (name in attr) {
13929
							if (attr[name] === UNDEFINED) {
13930
								delete attr[name];
13931
							}
13932
						}
13933
						
13934
						dataLabel = point.dataLabel = series.chart.renderer[rotation ? 'text' : 'label']( // labels don't support rotation
13935
							str,
13936
							0,
13937
							-999,
13938
							null,
13939
							null,
13940
							null,
13941
							options.useHTML
13942
						)
13943
						.attr(attr)
13944
						.css(options.style)
13945
						.add(dataLabelsGroup)
13946
						.shadow(options.shadow);
13947
						
13948
					}
13949
					
13950
					if (dataLabel) {
13951
						// Now the data label is created and placed at 0,0, so we need to align it
13952
						series.alignDataLabel(point, dataLabel, options, null, isNew);
13953
					}
13954
				}
13955
			});
13956
		}
13957
	},
13958
	
13959
	/**
13960
	 * Align each individual data label
13961
	 */
13962
	alignDataLabel: function (point, dataLabel, options, alignTo, isNew) {
13963
		var chart = this.chart,
13964
			inverted = chart.inverted,
13965
			plotX = pick(point.plotX, -999),
13966
			plotY = pick(point.plotY, -999),
13967
			bBox = dataLabel.getBBox(),
13968
			alignAttr; // the final position;
13969
				
13970
		// The alignment box is a singular point
13971
		alignTo = extend({
13972
			x: inverted ? chart.plotWidth - plotY : plotX,
13973
			y: mathRound(inverted ? chart.plotHeight - plotX : plotY),
13974
			width: 0,
13975
			height: 0
13976
		}, alignTo);
13977
		
13978
		// Add the text size for alignment calculation
13979
		extend(options, {
13980
			width: bBox.width,
13981
			height: bBox.height
13982
		});
13983
		
13984
		// Allow a hook for changing alignment in the last moment, then do the alignment
13985
		if (options.rotation) { // Fancy box alignment isn't supported for rotated text
13986
			alignAttr = {
13987
				align: options.align,
13988
				x: alignTo.x + options.x + alignTo.width / 2,
13989
				y: alignTo.y + options.y + alignTo.height / 2
13990
			};
13991
			dataLabel[isNew ? 'attr' : 'animate'](alignAttr);
13992
		} else {
13993
			dataLabel.align(options, null, alignTo);
13994
			alignAttr = dataLabel.alignAttr;
13995
		}
13996
		
13997
		// Show or hide based on the final aligned position
13998
		dataLabel.attr({
13999
			visibility: options.crop === false || /*chart.isInsidePlot(alignAttr.x, alignAttr.y) || */chart.isInsidePlot(plotX, plotY, inverted) ?
14000
				(chart.renderer.isSVG ? 'inherit' : VISIBLE) : 
14001
				HIDDEN
14002
		});
14003
				
14004
	},
14005
	
14006
	/**
14007
	 * Return the graph path of a segment
14008
	 */
14009
	getSegmentPath: function (segment) {		
14010
		var series = this,
14011
			segmentPath = [],
14012
			step = series.options.step;
14013
			
14014
		// build the segment line
14015
		each(segment, function (point, i) {
14016
			
14017
			var plotX = point.plotX,
14018
				plotY = point.plotY,
14019
				lastPoint;
14020
14021
			if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
14022
				segmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));
14023
14024
			} else {
14025
14026
				// moveToPixel or lineTo
14027
				segmentPath.push(i ? L : M);
14028
14029
				// step line?
14030
				if (step && i) {
14031
					lastPoint = segment[i - 1];
14032
					if (step === 'right') {
14033
						segmentPath.push(
14034
							lastPoint.plotX,
14035
							plotY
14036
						);
14037
						
14038
					} else if (step === 'center') {
14039
						segmentPath.push(
14040
							(lastPoint.plotX + plotX) / 2,
14041
							lastPoint.plotY,
14042
							(lastPoint.plotX + plotX) / 2,
14043
							plotY
14044
						);
14045
						
14046
					} else {
14047
						segmentPath.push(
14048
							plotX,
14049
							lastPoint.plotY
14050
						);
14051
					}
14052
				}
14053
14054
				// normal line to next point
14055
				segmentPath.push(
14056
					point.plotX,
14057
					point.plotY
14058
				);
14059
			}
14060
		});
14061
		
14062
		return segmentPath;
14063
	},
14064
14065
	/**
14066
	 * Get the graph path
14067
	 */
14068
	getGraphPath: function () {
14069
		var series = this,
14070
			graphPath = [],
14071
			segmentPath,
14072
			singlePoints = []; // used in drawTracker
14073
14074
		// Divide into segments and build graph and area paths
14075
		each(series.segments, function (segment) {
14076
			
14077
			segmentPath = series.getSegmentPath(segment);
14078
			
14079
			// add the segment to the graph, or a single point for tracking
14080
			if (segment.length > 1) {
14081
				graphPath = graphPath.concat(segmentPath);
14082
			} else {
14083
				singlePoints.push(segment[0]);
14084
			}
14085
		});
14086
14087
		// Record it for use in drawGraph and drawTracker, and return graphPath
14088
		series.singlePoints = singlePoints;
14089
		series.graphPath = graphPath;
14090
		
14091
		return graphPath;
14092
		
14093
	},
14094
	
14095
	/**
14096
	 * Draw the actual graph
14097
	 */
14098
	drawGraph: function () {
14099
		var series = this,
14100
			options = this.options,
14101
			props = [['graph', options.lineColor || this.color]],
14102
			lineWidth = options.lineWidth,
14103
			dashStyle =  options.dashStyle,
14104
			graphPath = this.getGraphPath(),
14105
			negativeColor = options.negativeColor;
14106
			
14107
		if (negativeColor) {
14108
			props.push(['graphNeg', negativeColor]);
14109
		}
14110
		
14111
		// draw the graph
14112
		each(props, function (prop, i) {
14113
			var graphKey = prop[0],
14114
				graph = series[graphKey],
14115
				attribs;
14116
			
14117
			if (graph) {
14118
				stop(graph); // cancel running animations, #459
14119
				graph.animate({ d: graphPath });
14120
	
14121
			} else if (lineWidth && graphPath.length) { // #1487
14122
				attribs = {
14123
					stroke: prop[1],
14124
					'stroke-width': lineWidth,
14125
					zIndex: 1 // #1069
14126
				};
14127
				if (dashStyle) {
14128
					attribs.dashstyle = dashStyle;
14129
				}
14130
14131
				series[graphKey] = series.chart.renderer.path(graphPath)
14132
					.attr(attribs)
14133
					.add(series.group)
14134
					.shadow(!i && options.shadow);
14135
			}
14136
		});
14137
	},
14138
	
14139
	/**
14140
	 * Clip the graphs into the positive and negative coloured graphs
14141
	 */
14142
	clipNeg: function () {
14143
		var options = this.options,
14144
			chart = this.chart,
14145
			renderer = chart.renderer,
14146
			negativeColor = options.negativeColor,
14147
			translatedThreshold,
14148
			posAttr,
14149
			negAttr,
14150
			graph = this.graph,
14151
			area = this.area,
14152
			posClip = this.posClip,
14153
			negClip = this.negClip,
14154
			chartWidth = chart.chartWidth,
14155
			chartHeight = chart.chartHeight,
14156
			chartSizeMax = mathMax(chartWidth, chartHeight),
14157
			above,
14158
			below;
14159
		
14160
		if (negativeColor && (graph || area)) {
14161
			translatedThreshold = mathCeil(this.yAxis.len - this.yAxis.translate(options.threshold || 0));
14162
			above = {
14163
				x: 0,
14164
				y: 0,
14165
				width: chartSizeMax,
14166
				height: translatedThreshold
14167
			};
14168
			below = {
14169
				x: 0,
14170
				y: translatedThreshold,
14171
				width: chartSizeMax,
14172
				height: chartSizeMax - translatedThreshold
14173
			};
14174
			
14175
			if (chart.inverted && renderer.isVML) {
14176
				above = {
14177
					x: chart.plotWidth - translatedThreshold - chart.plotLeft,
14178
					y: 0,
14179
					width: chartWidth,
14180
					height: chartHeight
14181
				};
14182
				below = {
14183
					x: translatedThreshold + chart.plotLeft - chartWidth,
14184
					y: 0,
14185
					width: chart.plotLeft + translatedThreshold,
14186
					height: chartWidth
14187
				};
14188
			}
14189
			
14190
			if (this.yAxis.reversed) {
14191
				posAttr = below;
14192
				negAttr = above;
14193
			} else {
14194
				posAttr = above;
14195
				negAttr = below;
14196
			}
14197
		
14198
			if (posClip) { // update
14199
				posClip.animate(posAttr);
14200
				negClip.animate(negAttr);
14201
			} else {
14202
				
14203
				this.posClip = posClip = renderer.clipRect(posAttr);
14204
				this.negClip = negClip = renderer.clipRect(negAttr);
14205
				
14206
				if (graph) {
14207
					graph.clip(posClip);
14208
					this.graphNeg.clip(negClip);	
14209
				}
14210
				
14211
				if (area) {
14212
					area.clip(posClip);
14213
					this.areaNeg.clip(negClip);
14214
				} 
14215
			} 
14216
		}	
14217
	},
14218
14219
	/**
14220
	 * Initialize and perform group inversion on series.group and series.markerGroup
14221
	 */
14222
	invertGroups: function () {
14223
		var series = this,
14224
			chart = series.chart;
14225
14226
		// Pie, go away (#1736)
14227
		if (!series.xAxis) {
14228
			return;
14229
		}
14230
		
14231
		// A fixed size is needed for inversion to work
14232
		function setInvert() {			
14233
			var size = {
14234
				width: series.yAxis.len,
14235
				height: series.xAxis.len
14236
			};
14237
			
14238
			each(['group', 'markerGroup'], function (groupName) {
14239
				if (series[groupName]) {
14240
					series[groupName].attr(size).invert();
14241
				}
14242
			});
14243
		}
14244
14245
		addEvent(chart, 'resize', setInvert); // do it on resize
14246
		addEvent(series, 'destroy', function () {
14247
			removeEvent(chart, 'resize', setInvert);
14248
		});
14249
14250
		// Do it now
14251
		setInvert(); // do it now
14252
		
14253
		// On subsequent render and redraw, just do setInvert without setting up events again
14254
		series.invertGroups = setInvert;
14255
	},
14256
	
14257
	/**
14258
	 * General abstraction for creating plot groups like series.group, series.dataLabelsGroup and 
14259
	 * series.markerGroup. On subsequent calls, the group will only be adjusted to the updated plot size.
14260
	 */
14261
	plotGroup: function (prop, name, visibility, zIndex, parent) {
14262
		var group = this[prop],
14263
			isNew = !group,
14264
			chart = this.chart,
14265
			xAxis = this.xAxis,
14266
			yAxis = this.yAxis;
14267
		
14268
		// Generate it on first call
14269
		if (isNew) {	
14270
			this[prop] = group = chart.renderer.g(name)
14271
				.attr({
14272
					visibility: visibility,
14273
					zIndex: zIndex || 0.1 // IE8 needs this
14274
				})
14275
				.add(parent);
14276
		}
14277
		// Place it on first and subsequent (redraw) calls
14278
		group[isNew ? 'attr' : 'animate']({
14279
			translateX: xAxis ? xAxis.left : chart.plotLeft, 
14280
			translateY: yAxis ? yAxis.top : chart.plotTop,
14281
			scaleX: 1, // #1623
14282
			scaleY: 1
14283
		});
14284
		return group;
14285
		
14286
	},
14287
	
14288
	/**
14289
	 * Render the graph and markers
14290
	 */
14291
	render: function () {
14292
		var series = this,
14293
			chart = series.chart,
14294
			group,
14295
			options = series.options,
14296
			animation = options.animation,
14297
			doAnimation = animation && !!series.animate && 
14298
				chart.renderer.isSVG, // this animation doesn't work in IE8 quirks when the group div is hidden,
14299
				// and looks bad in other oldIE
14300
			visibility = series.visible ? VISIBLE : HIDDEN,
14301
			zIndex = options.zIndex,
14302
			hasRendered = series.hasRendered,
14303
			chartSeriesGroup = chart.seriesGroup;
14304
		
14305
		// the group
14306
		group = series.plotGroup(
14307
			'group', 
14308
			'series', 
14309
			visibility, 
14310
			zIndex, 
14311
			chartSeriesGroup
14312
		);
14313
		
14314
		series.markerGroup = series.plotGroup(
14315
			'markerGroup', 
14316
			'markers', 
14317
			visibility, 
14318
			zIndex, 
14319
			chartSeriesGroup
14320
		);
14321
		
14322
		// initiate the animation
14323
		if (doAnimation) {
14324
			series.animate(true);
14325
		}
14326
14327
		// cache attributes for shapes
14328
		series.getAttribs();
14329
14330
		// SVGRenderer needs to know this before drawing elements (#1089, #1795)
14331
		group.inverted = series.isCartesian ? chart.inverted : false;
14332
		
14333
		// draw the graph if any
14334
		if (series.drawGraph) {
14335
			series.drawGraph();
14336
			series.clipNeg();
14337
		}
14338
14339
		// draw the data labels (inn pies they go before the points)
14340
		series.drawDataLabels();
14341
		
14342
		// draw the points
14343
		series.drawPoints();
14344
14345
14346
		// draw the mouse tracking area
14347
		if (series.options.enableMouseTracking !== false) {
14348
			series.drawTracker();
14349
		}
14350
		
14351
		// Handle inverted series and tracker groups
14352
		if (chart.inverted) {
14353
			series.invertGroups();
14354
		}
14355
		
14356
		// Initial clipping, must be defined after inverting groups for VML
14357
		if (options.clip !== false && !series.sharedClipKey && !hasRendered) {
14358
			group.clip(chart.clipRect);
14359
		}
14360
14361
		// Run the animation
14362
		if (doAnimation) {
14363
			series.animate();
14364
		} else if (!hasRendered) {
14365
			series.afterAnimate();
14366
		}
14367
14368
		series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
14369
		// (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see
14370
		series.hasRendered = true;
14371
	},
14372
	
14373
	/**
14374
	 * Redraw the series after an update in the axes.
14375
	 */
14376
	redraw: function () {
14377
		var series = this,
14378
			chart = series.chart,
14379
			wasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after
14380
			group = series.group,
14381
			xAxis = series.xAxis,
14382
			yAxis = series.yAxis;
14383
14384
		// reposition on resize
14385
		if (group) {
14386
			if (chart.inverted) {
14387
				group.attr({
14388
					width: chart.plotWidth,
14389
					height: chart.plotHeight
14390
				});
14391
			}
14392
14393
			group.animate({
14394
				translateX: pick(xAxis && xAxis.left, chart.plotLeft),
14395
				translateY: pick(yAxis && yAxis.top, chart.plotTop)
14396
			});
14397
		}
14398
14399
		series.translate();
14400
		series.setTooltipPoints(true);
14401
14402
		series.render();
14403
		if (wasDirtyData) {
14404
			fireEvent(series, 'updatedData');
14405
		}
14406
	},
14407
14408
	/**
14409
	 * Set the state of the graph
14410
	 */
14411
	setState: function (state) {
14412
		var series = this,
14413
			options = series.options,
14414
			graph = series.graph,
14415
			graphNeg = series.graphNeg,
14416
			stateOptions = options.states,
14417
			lineWidth = options.lineWidth,
14418
			attribs;
14419
14420
		state = state || NORMAL_STATE;
14421
14422
		if (series.state !== state) {
14423
			series.state = state;
14424
14425
			if (stateOptions[state] && stateOptions[state].enabled === false) {
14426
				return;
14427
			}
14428
14429
			if (state) {
14430
				lineWidth = stateOptions[state].lineWidth || lineWidth + 1;
14431
			}
14432
14433
			if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
14434
				attribs = {
14435
					'stroke-width': lineWidth
14436
				};
14437
				// use attr because animate will cause any other animation on the graph to stop
14438
				graph.attr(attribs);
14439
				if (graphNeg) {
14440
					graphNeg.attr(attribs);
14441
				}
14442
			}
14443
		}
14444
	},
14445
14446
	/**
14447
	 * Set the visibility of the graph
14448
	 *
14449
	 * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED,
14450
	 *        the visibility is toggled.
14451
	 */
14452
	setVisible: function (vis, redraw) {
14453
		var series = this,
14454
			chart = series.chart,
14455
			legendItem = series.legendItem,
14456
			showOrHide,
14457
			ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
14458
			oldVisibility = series.visible;
14459
14460
		// if called without an argument, toggle visibility
14461
		series.visible = vis = series.userOptions.visible = vis === UNDEFINED ? !oldVisibility : vis;
14462
		showOrHide = vis ? 'show' : 'hide';
14463
14464
		// show or hide elements
14465
		each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker'], function (key) {
14466
			if (series[key]) {
14467
				series[key][showOrHide]();
14468
			}
14469
		});
14470
14471
		
14472
		// hide tooltip (#1361)
14473
		if (chart.hoverSeries === series) {
14474
			series.onMouseOut();
14475
		}
14476
14477
14478
		if (legendItem) {
14479
			chart.legend.colorizeItem(series, vis);
14480
		}
14481
14482
14483
		// rescale or adapt to resized chart
14484
		series.isDirty = true;
14485
		// in a stack, all other series are affected
14486
		if (series.options.stacking) {
14487
			each(chart.series, function (otherSeries) {
14488
				if (otherSeries.options.stacking && otherSeries.visible) {
14489
					otherSeries.isDirty = true;
14490
				}
14491
			});
14492
		}
14493
14494
		// show or hide linked series
14495
		each(series.linkedSeries, function (otherSeries) {
14496
			otherSeries.setVisible(vis, false);
14497
		});
14498
14499
		if (ignoreHiddenSeries) {
14500
			chart.isDirtyBox = true;
14501
		}
14502
		if (redraw !== false) {
14503
			chart.redraw();
14504
		}
14505
14506
		fireEvent(series, showOrHide);
14507
	},
14508
14509
	/**
14510
	 * Show the graph
14511
	 */
14512
	show: function () {
14513
		this.setVisible(true);
14514
	},
14515
14516
	/**
14517
	 * Hide the graph
14518
	 */
14519
	hide: function () {
14520
		this.setVisible(false);
14521
	},
14522
14523
14524
	/**
14525
	 * Set the selected state of the graph
14526
	 *
14527
	 * @param selected {Boolean} True to select the series, false to unselect. If
14528
	 *        UNDEFINED, the selection state is toggled.
14529
	 */
14530
	select: function (selected) {
14531
		var series = this;
14532
		// if called without an argument, toggle
14533
		series.selected = selected = (selected === UNDEFINED) ? !series.selected : selected;
14534
14535
		if (series.checkbox) {
14536
			series.checkbox.checked = selected;
14537
		}
14538
14539
		fireEvent(series, selected ? 'select' : 'unselect');
14540
	},
14541
14542
	/**
14543
	 * Draw the tracker object that sits above all data labels and markers to
14544
	 * track mouse events on the graph or points. For the line type charts
14545
	 * the tracker uses the same graphPath, but with a greater stroke width
14546
	 * for better control.
14547
	 */
14548
	drawTracker: function () {
14549
		var series = this,
14550
			options = series.options,
14551
			trackByArea = options.trackByArea,
14552
			trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath),
14553
			trackerPathLength = trackerPath.length,
14554
			chart = series.chart,
14555
			pointer = chart.pointer,
14556
			renderer = chart.renderer,
14557
			snap = chart.options.tooltip.snap,
14558
			tracker = series.tracker,
14559
			cursor = options.cursor,
14560
			css = cursor && { cursor: cursor },
14561
			singlePoints = series.singlePoints,
14562
			singlePoint,
14563
			i,
14564
			onMouseOver = function () {
14565
				if (chart.hoverSeries !== series) {
14566
					series.onMouseOver();
14567
				}
14568
			};
14569
14570
		// Extend end points. A better way would be to use round linecaps,
14571
		// but those are not clickable in VML.
14572
		if (trackerPathLength && !trackByArea) {
14573
			i = trackerPathLength + 1;
14574
			while (i--) {
14575
				if (trackerPath[i] === M) { // extend left side
14576
					trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L);
14577
				}
14578
				if ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side
14579
					trackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]);
14580
				}
14581
			}
14582
		}
14583
14584
		// handle single points
14585
		for (i = 0; i < singlePoints.length; i++) {
14586
			singlePoint = singlePoints[i];
14587
			trackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,
14588
				L, singlePoint.plotX + snap, singlePoint.plotY);
14589
		}
14590
		
14591
		
14592
14593
		// draw the tracker
14594
		if (tracker) {
14595
			tracker.attr({ d: trackerPath });
14596
14597
		} else { // create
14598
				
14599
			series.tracker = tracker = renderer.path(trackerPath)
14600
				.attr({
14601
					'class': PREFIX + 'tracker',
14602
					'stroke-linejoin': 'round', // #1225
14603
					visibility: series.visible ? VISIBLE : HIDDEN,
14604
					stroke: TRACKER_FILL,
14605
					fill: trackByArea ? TRACKER_FILL : NONE,
14606
					'stroke-width' : options.lineWidth + (trackByArea ? 0 : 2 * snap),
14607
					zIndex: 2
14608
				})
14609
				.addClass(PREFIX + 'tracker')
14610
				.on('mouseover', onMouseOver)
14611
				.on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })
14612
				.css(css)
14613
				.add(series.markerGroup);
14614
				
14615
			if (hasTouch) {
14616
				tracker.on('touchstart', onMouseOver);
14617
			} 
14618
		}
14619
14620
	}
14621
14622
}; // end Series prototype
14623
14624
14625
/**
14626
 * LineSeries object
14627
 */
14628
var LineSeries = extendClass(Series);
14629
seriesTypes.line = LineSeries;
14630
14631
/**
14632
 * Set the default options for area
14633
 */
14634
defaultPlotOptions.area = merge(defaultSeriesOptions, {
14635
	threshold: 0
14636
	// trackByArea: false,
14637
	// lineColor: null, // overrides color, but lets fillColor be unaltered
14638
	// fillOpacity: 0.75,
14639
	// fillColor: null
14640
});
14641
14642
/**
14643
 * AreaSeries object
14644
 */
14645
var AreaSeries = extendClass(Series, {
14646
	type: 'area',
14647
	
14648
	/**
14649
	 * For stacks, don't split segments on null values. Instead, draw null values with 
14650
	 * no marker. Also insert dummy points for any X position that exists in other series
14651
	 * in the stack.
14652
	 */ 
14653
	getSegments: function () {
14654
		var segments = [],
14655
			segment = [],
14656
			keys = [],
14657
			xAxis = this.xAxis,
14658
			yAxis = this.yAxis,
14659
			stack = yAxis.stacks[this.stackKey],
14660
			pointMap = {},
14661
			plotX,
14662
			plotY,
14663
			points = this.points,
14664
			i,
14665
			x;
14666
14667
		if (this.options.stacking && !this.cropped) { // cropped causes artefacts in Stock, and perf issue
14668
			// Create a map where we can quickly look up the points by their X value.
14669
			for (i = 0; i < points.length; i++) {
14670
				pointMap[points[i].x] = points[i];
14671
			}
14672
14673
			// Sort the keys (#1651)
14674
			for (x in stack) {
14675
				keys.push(+x);
14676
			}
14677
			keys.sort(function (a, b) {
14678
				return a - b;
14679
			});
14680
14681
			each(keys, function (x) {
14682
				// The point exists, push it to the segment
14683
				if (pointMap[x]) {
14684
					segment.push(pointMap[x]);
14685
14686
				// There is no point for this X value in this series, so we 
14687
				// insert a dummy point in order for the areas to be drawn
14688
				// correctly.
14689
				} else {
14690
					plotX = xAxis.translate(x);
14691
					plotY = yAxis.toPixels(stack[x].cum, true);
14692
					segment.push({ 
14693
						y: null, 
14694
						plotX: plotX,
14695
						clientX: plotX, 
14696
						plotY: plotY, 
14697
						yBottom: plotY,
14698
						onMouseOver: noop
14699
					});
14700
				}
14701
			});
14702
14703
			if (segment.length) {
14704
				segments.push(segment);
14705
			}
14706
14707
		} else {
14708
			Series.prototype.getSegments.call(this);
14709
			segments = this.segments;
14710
		}
14711
14712
		this.segments = segments;
14713
	},
14714
	
14715
	/**
14716
	 * Extend the base Series getSegmentPath method by adding the path for the area.
14717
	 * This path is pushed to the series.areaPath property.
14718
	 */
14719
	getSegmentPath: function (segment) {
14720
		
14721
		var segmentPath = Series.prototype.getSegmentPath.call(this, segment), // call base method
14722
			areaSegmentPath = [].concat(segmentPath), // work on a copy for the area path
14723
			i,
14724
			options = this.options,
14725
			segLength = segmentPath.length;
14726
		
14727
		if (segLength === 3) { // for animation from 1 to two points
14728
			areaSegmentPath.push(L, segmentPath[1], segmentPath[2]);
14729
		}
14730
		if (options.stacking && !this.closedStacks) {
14731
			
14732
			// Follow stack back. Todo: implement areaspline. A general solution could be to 
14733
			// reverse the entire graphPath of the previous series, though may be hard with
14734
			// splines and with series with different extremes
14735
			for (i = segment.length - 1; i >= 0; i--) {
14736
			
14737
				// step line?
14738
				if (i < segment.length - 1 && options.step) {
14739
					areaSegmentPath.push(segment[i + 1].plotX, segment[i].yBottom);
14740
				}
14741
				
14742
				areaSegmentPath.push(segment[i].plotX, segment[i].yBottom);
14743
			}
14744
14745
		} else { // follow zero line back
14746
			this.closeSegment(areaSegmentPath, segment);
14747
		}
14748
		this.areaPath = this.areaPath.concat(areaSegmentPath);
14749
		
14750
		return segmentPath;
14751
	},
14752
	
14753
	/**
14754
	 * Extendable method to close the segment path of an area. This is overridden in polar 
14755
	 * charts.
14756
	 */
14757
	closeSegment: function (path, segment) {
14758
		var translatedThreshold = this.yAxis.getThreshold(this.options.threshold);
14759
		path.push(
14760
			L,
14761
			segment[segment.length - 1].plotX,
14762
			translatedThreshold,
14763
			L,
14764
			segment[0].plotX,
14765
			translatedThreshold
14766
		);
14767
	},
14768
	
14769
	/**
14770
	 * Draw the graph and the underlying area. This method calls the Series base
14771
	 * function and adds the area. The areaPath is calculated in the getSegmentPath
14772
	 * method called from Series.prototype.drawGraph.
14773
	 */
14774
	drawGraph: function () {
14775
		
14776
		// Define or reset areaPath
14777
		this.areaPath = [];
14778
		
14779
		// Call the base method
14780
		Series.prototype.drawGraph.apply(this);
14781
		
14782
		// Define local variables
14783
		var series = this,
14784
			areaPath = this.areaPath,
14785
			options = this.options,
14786
			negativeColor = options.negativeColor,
14787
			props = [['area', this.color, options.fillColor]]; // area name, main color, fill color
14788
		
14789
		if (negativeColor) {
14790
			props.push(['areaNeg', options.negativeColor, options.negativeFillColor]);
14791
		}
14792
		
14793
		each(props, function (prop) {
14794
			var areaKey = prop[0],
14795
				area = series[areaKey];
14796
				
14797
			// Create or update the area
14798
			if (area) { // update
14799
				area.animate({ d: areaPath });
14800
	
14801
			} else { // create
14802
				series[areaKey] = series.chart.renderer.path(areaPath)
14803
					.attr({
14804
						fill: pick(
14805
							prop[2],
14806
							Color(prop[1]).setOpacity(options.fillOpacity || 0.75).get()
14807
						),
14808
						zIndex: 0 // #1069
14809
					}).add(series.group);
14810
			}
14811
		});
14812
	},
14813
	
14814
	/**
14815
	 * Get the series' symbol in the legend
14816
	 * 
14817
	 * @param {Object} legend The legend object
14818
	 * @param {Object} item The series (this) or point
14819
	 */
14820
	drawLegendSymbol: function (legend, item) {
14821
		
14822
		item.legendSymbol = this.chart.renderer.rect(
14823
			0,
14824
			legend.baseline - 11,
14825
			legend.options.symbolWidth,
14826
			12,
14827
			2
14828
		).attr({
14829
			zIndex: 3
14830
		}).add(item.legendGroup);		
14831
		
14832
	}
14833
});
14834
14835
seriesTypes.area = AreaSeries;/**
14836
 * Set the default options for spline
14837
 */
14838
defaultPlotOptions.spline = merge(defaultSeriesOptions);
14839
14840
/**
14841
 * SplineSeries object
14842
 */
14843
var SplineSeries = extendClass(Series, {
14844
	type: 'spline',
14845
14846
	/**
14847
	 * Get the spline segment from a given point's previous neighbour to the given point
14848
	 */
14849
	getPointSpline: function (segment, point, i) {
14850
		var smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc
14851
			denom = smoothing + 1,
14852
			plotX = point.plotX,
14853
			plotY = point.plotY,
14854
			lastPoint = segment[i - 1],
14855
			nextPoint = segment[i + 1],
14856
			leftContX,
14857
			leftContY,
14858
			rightContX,
14859
			rightContY,
14860
			ret;
14861
14862
		// find control points
14863
		if (lastPoint && nextPoint) {
14864
		
14865
			var lastX = lastPoint.plotX,
14866
				lastY = lastPoint.plotY,
14867
				nextX = nextPoint.plotX,
14868
				nextY = nextPoint.plotY,
14869
				correction;
14870
14871
			leftContX = (smoothing * plotX + lastX) / denom;
14872
			leftContY = (smoothing * plotY + lastY) / denom;
14873
			rightContX = (smoothing * plotX + nextX) / denom;
14874
			rightContY = (smoothing * plotY + nextY) / denom;
14875
14876
			// have the two control points make a straight line through main point
14877
			correction = ((rightContY - leftContY) * (rightContX - plotX)) /
14878
				(rightContX - leftContX) + plotY - rightContY;
14879
14880
			leftContY += correction;
14881
			rightContY += correction;
14882
14883
			// to prevent false extremes, check that control points are between
14884
			// neighbouring points' y values
14885
			if (leftContY > lastY && leftContY > plotY) {
14886
				leftContY = mathMax(lastY, plotY);
14887
				rightContY = 2 * plotY - leftContY; // mirror of left control point
14888
			} else if (leftContY < lastY && leftContY < plotY) {
14889
				leftContY = mathMin(lastY, plotY);
14890
				rightContY = 2 * plotY - leftContY;
14891
			}
14892
			if (rightContY > nextY && rightContY > plotY) {
14893
				rightContY = mathMax(nextY, plotY);
14894
				leftContY = 2 * plotY - rightContY;
14895
			} else if (rightContY < nextY && rightContY < plotY) {
14896
				rightContY = mathMin(nextY, plotY);
14897
				leftContY = 2 * plotY - rightContY;
14898
			}
14899
14900
			// record for drawing in next point
14901
			point.rightContX = rightContX;
14902
			point.rightContY = rightContY;
14903
14904
		}
14905
		
14906
		// Visualize control points for debugging
14907
		/*
14908
		if (leftContX) {
14909
			this.chart.renderer.circle(leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 2)
14910
				.attr({
14911
					stroke: 'red',
14912
					'stroke-width': 1,
14913
					fill: 'none'
14914
				})
14915
				.add();
14916
			this.chart.renderer.path(['M', leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop,
14917
				'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
14918
				.attr({
14919
					stroke: 'red',
14920
					'stroke-width': 1
14921
				})
14922
				.add();
14923
			this.chart.renderer.circle(rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 2)
14924
				.attr({
14925
					stroke: 'green',
14926
					'stroke-width': 1,
14927
					fill: 'none'
14928
				})
14929
				.add();
14930
			this.chart.renderer.path(['M', rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop,
14931
				'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
14932
				.attr({
14933
					stroke: 'green',
14934
					'stroke-width': 1
14935
				})
14936
				.add();
14937
		}
14938
		*/
14939
14940
		// moveToPixel or lineTo
14941
		if (!i) {
14942
			ret = [M, plotX, plotY];
14943
		} else { // curve from last point to this
14944
			ret = [
14945
				'C',
14946
				lastPoint.rightContX || lastPoint.plotX,
14947
				lastPoint.rightContY || lastPoint.plotY,
14948
				leftContX || plotX,
14949
				leftContY || plotY,
14950
				plotX,
14951
				plotY
14952
			];
14953
			lastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later
14954
		}
14955
		return ret;
14956
	}
14957
});
14958
seriesTypes.spline = SplineSeries;
14959
14960
/**
14961
 * Set the default options for areaspline
14962
 */
14963
defaultPlotOptions.areaspline = merge(defaultPlotOptions.area);
14964
14965
/**
14966
 * AreaSplineSeries object
14967
 */
14968
var areaProto = AreaSeries.prototype,
14969
	AreaSplineSeries = extendClass(SplineSeries, {
14970
		type: 'areaspline',
14971
		closedStacks: true, // instead of following the previous graph back, follow the threshold back
14972
		
14973
		// Mix in methods from the area series
14974
		getSegmentPath: areaProto.getSegmentPath,
14975
		closeSegment: areaProto.closeSegment,
14976
		drawGraph: areaProto.drawGraph
14977
	});
14978
seriesTypes.areaspline = AreaSplineSeries;
14979
14980
/**
14981
 * Set the default options for column
14982
 */
14983
defaultPlotOptions.column = merge(defaultSeriesOptions, {
14984
	borderColor: '#FFFFFF',
14985
	borderWidth: 1,
14986
	borderRadius: 0,
14987
	//colorByPoint: undefined,
14988
	groupPadding: 0.2,
14989
	//grouping: true,
14990
	marker: null, // point options are specified in the base options
14991
	pointPadding: 0.1,
14992
	//pointWidth: null,
14993
	minPointLength: 0,
14994
	cropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes
14995
	pointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories
14996
	states: {
14997
		hover: {
14998
			brightness: 0.1,
14999
			shadow: false
15000
		},
15001
		select: {
15002
			color: '#C0C0C0',
15003
			borderColor: '#000000',
15004
			shadow: false
15005
		}
15006
	},
15007
	dataLabels: {
15008
		align: null, // auto
15009
		verticalAlign: null, // auto
15010
		y: null
15011
	},
15012
	stickyTracking: false,
15013
	threshold: 0
15014
});
15015
15016
/**
15017
 * ColumnSeries object
15018
 */
15019
var ColumnSeries = extendClass(Series, {
15020
	type: 'column',
15021
	tooltipOutsidePlot: true,
15022
	requireSorting: false,
15023
	pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
15024
		stroke: 'borderColor',
15025
		'stroke-width': 'borderWidth',
15026
		fill: 'color',
15027
		r: 'borderRadius'
15028
	},
15029
	trackerGroups: ['group', 'dataLabelsGroup'],
15030
	
15031
	/**
15032
	 * Initialize the series
15033
	 */
15034
	init: function () {
15035
		Series.prototype.init.apply(this, arguments);
15036
15037
		var series = this,
15038
			chart = series.chart;
15039
15040
		// if the series is added dynamically, force redraw of other
15041
		// series affected by a new column
15042
		if (chart.hasRendered) {
15043
			each(chart.series, function (otherSeries) {
15044
				if (otherSeries.type === series.type) {
15045
					otherSeries.isDirty = true;
15046
				}
15047
			});
15048
		}
15049
	},
15050
15051
	/**
15052
	 * Return the width and x offset of the columns adjusted for grouping, groupPadding, pointPadding,
15053
	 * pointWidth etc. 
15054
	 */
15055
	getColumnMetrics: function () {
15056
15057
		var series = this,
15058
			chart = series.chart,
15059
			options = series.options,
15060
			xAxis = this.xAxis,
15061
			reversedXAxis = xAxis.reversed,
15062
			stackKey,
15063
			stackGroups = {},
15064
			columnIndex,
15065
			columnCount = 0;
15066
15067
		// Get the total number of column type series.
15068
		// This is called on every series. Consider moving this logic to a
15069
		// chart.orderStacks() function and call it on init, addSeries and removeSeries
15070
		if (options.grouping === false) {
15071
			columnCount = 1;
15072
		} else {
15073
			each(chart.series, function (otherSeries) {
15074
				var otherOptions = otherSeries.options;
15075
				if (otherSeries.type === series.type && otherSeries.visible &&
15076
						series.options.group === otherOptions.group) { // used in Stock charts navigator series
15077
					if (otherOptions.stacking) {
15078
						stackKey = otherSeries.stackKey;
15079
						if (stackGroups[stackKey] === UNDEFINED) {
15080
							stackGroups[stackKey] = columnCount++;
15081
						}
15082
						columnIndex = stackGroups[stackKey];
15083
					} else if (otherOptions.grouping !== false) { // #1162
15084
						columnIndex = columnCount++;
15085
					}
15086
					otherSeries.columnIndex = columnIndex;
15087
				}
15088
			});
15089
		}
15090
15091
		var categoryWidth = mathMin(
15092
				mathAbs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || 1), 
15093
				xAxis.len // #1535
15094
			),
15095
			groupPadding = categoryWidth * options.groupPadding,
15096
			groupWidth = categoryWidth - 2 * groupPadding,
15097
			pointOffsetWidth = groupWidth / columnCount,
15098
			optionPointWidth = options.pointWidth,
15099
			pointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 :
15100
				pointOffsetWidth * options.pointPadding,
15101
			pointWidth = pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), // exact point width, used in polar charts
15102
			colIndex = (reversedXAxis ? 
15103
				columnCount - (series.columnIndex || 0) : // #1251
15104
				series.columnIndex) || 0,
15105
			pointXOffset = pointPadding + (groupPadding + colIndex *
15106
				pointOffsetWidth - (categoryWidth / 2)) *
15107
				(reversedXAxis ? -1 : 1);
15108
15109
		// Save it for reading in linked series (Error bars particularly)
15110
		return (series.columnMetrics = { 
15111
			width: pointWidth, 
15112
			offset: pointXOffset 
15113
		});
15114
			
15115
	},
15116
15117
	/**
15118
	 * Translate each point to the plot area coordinate system and find shape positions
15119
	 */
15120
	translate: function () {
15121
		var series = this,
15122
			chart = series.chart,
15123
			options = series.options,
15124
			stacking = options.stacking,
15125
			borderWidth = options.borderWidth,
15126
			yAxis = series.yAxis,
15127
			threshold = options.threshold,
15128
			translatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold),
15129
			minPointLength = pick(options.minPointLength, 5),
15130
			metrics = series.getColumnMetrics(),
15131
			pointWidth = metrics.width,
15132
			barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width
15133
			pointXOffset = metrics.offset;
15134
15135
		Series.prototype.translate.apply(series);
15136
15137
		// record the new values
15138
		each(series.points, function (point) {
15139
			var plotY = mathMin(mathMax(-999, point.plotY), yAxis.len + 999), // Don't draw too far outside plot area (#1303)
15140
				yBottom = pick(point.yBottom, translatedThreshold),
15141
				barX = point.plotX + pointXOffset,
15142
				barY = mathCeil(mathMin(plotY, yBottom)),
15143
				barH = mathCeil(mathMax(plotY, yBottom) - barY),
15144
				stack = yAxis.stacks[(point.y < 0 ? '-' : '') + series.stackKey],
15145
				shapeArgs;
15146
15147
			// Record the offset'ed position and width of the bar to be able to align the stacking total correctly
15148
			if (stacking && series.visible && stack && stack[point.x]) {
15149
				stack[point.x].setOffset(pointXOffset, barW);
15150
			}
15151
15152
			// handle options.minPointLength
15153
			if (mathAbs(barH) < minPointLength) {
15154
				if (minPointLength) {
15155
					barH = minPointLength;
15156
					barY =
15157
						mathAbs(barY - translatedThreshold) > minPointLength ? // stacked
15158
							yBottom - minPointLength : // keep position
15159
							translatedThreshold - (yAxis.translate(point.y, 0, 1, 0, 1) <= translatedThreshold ? minPointLength : 0); // use exact yAxis.translation (#1485)
15160
				}
15161
			}
15162
15163
			point.barX = barX;
15164
			point.pointWidth = pointWidth;
15165
15166
			// create shape type and shape args that are reused in drawPoints and drawTracker
15167
			point.shapeType = 'rect';
15168
			point.shapeArgs = shapeArgs = chart.renderer.Element.prototype.crisp.call(0, borderWidth, barX, barY, barW, barH); 
15169
			
15170
			if (borderWidth % 2) { // correct for shorting in crisp method, visible in stacked columns with 1px border
15171
				shapeArgs.y -= 1;
15172
				shapeArgs.height += 1;
15173
			}
15174
15175
		});
15176
15177
	},
15178
15179
	getSymbol: noop,
15180
	
15181
	/**
15182
	 * Use a solid rectangle like the area series types
15183
	 */
15184
	drawLegendSymbol: AreaSeries.prototype.drawLegendSymbol,
15185
	
15186
	
15187
	/**
15188
	 * Columns have no graph
15189
	 */
15190
	drawGraph: noop,
15191
15192
	/**
15193
	 * Draw the columns. For bars, the series.group is rotated, so the same coordinates
15194
	 * apply for columns and bars. This method is inherited by scatter series.
15195
	 *
15196
	 */
15197
	drawPoints: function () {
15198
		var series = this,
15199
			options = series.options,
15200
			renderer = series.chart.renderer,
15201
			shapeArgs;
15202
15203
15204
		// draw the columns
15205
		each(series.points, function (point) {
15206
			var plotY = point.plotY,
15207
				graphic = point.graphic;
15208
15209
			if (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {
15210
				shapeArgs = point.shapeArgs;
15211
				
15212
				if (graphic) { // update
15213
					stop(graphic);
15214
					graphic.animate(merge(shapeArgs));
15215
15216
				} else {
15217
					point.graphic = graphic = renderer[point.shapeType](shapeArgs)
15218
						.attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE])
15219
						.add(series.group)
15220
						.shadow(options.shadow, null, options.stacking && !options.borderRadius);
15221
				}
15222
15223
			} else if (graphic) {
15224
				point.graphic = graphic.destroy(); // #1269
15225
			}
15226
		});
15227
	},
15228
15229
	/**
15230
	 * Add tracking event listener to the series group, so the point graphics
15231
	 * themselves act as trackers
15232
	 */
15233
	drawTracker: function () {
15234
		var series = this,
15235
			pointer = series.chart.pointer,
15236
			cursor = series.options.cursor,
15237
			css = cursor && { cursor: cursor },
15238
			onMouseOver = function (e) {
15239
				var target = e.target,
15240
					point;
15241
15242
				series.onMouseOver();
15243
15244
				while (target && !point) {
15245
					point = target.point;
15246
					target = target.parentNode;
15247
				}
15248
				if (point !== UNDEFINED) { // undefined on graph in scatterchart
15249
					point.onMouseOver(e);
15250
				}
15251
			};
15252
15253
		// Add reference to the point
15254
		each(series.points, function (point) {
15255
			if (point.graphic) {
15256
				point.graphic.element.point = point;
15257
			}
15258
			if (point.dataLabel) {
15259
				point.dataLabel.element.point = point;
15260
			}
15261
		});
15262
15263
		// Add the event listeners, we need to do this only once
15264
		if (!series._hasTracking) {
15265
			each(series.trackerGroups, function (key) {
15266
				if (series[key]) { // we don't always have dataLabelsGroup
15267
					series[key]
15268
						.addClass(PREFIX + 'tracker')
15269
						.on('mouseover', onMouseOver)
15270
						.on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })
15271
						.css(css);
15272
					if (hasTouch) {
15273
						series[key].on('touchstart', onMouseOver);
15274
					}
15275
				}
15276
			});
15277
			
15278
		} else {
15279
			series._hasTracking = true;
15280
		}
15281
	},
15282
	
15283
	/** 
15284
	 * Override the basic data label alignment by adjusting for the position of the column
15285
	 */
15286
	alignDataLabel: function (point, dataLabel, options,  alignTo, isNew) {
15287
		var chart = this.chart,
15288
			inverted = chart.inverted,
15289
			dlBox = point.dlBox || point.shapeArgs, // data label box for alignment
15290
			below = point.below || (point.plotY > pick(this.translatedThreshold, chart.plotSizeY)),
15291
			inside = pick(options.inside, !!this.options.stacking); // draw it inside the box?
15292
		
15293
		// Align to the column itself, or the top of it
15294
		if (dlBox) { // Area range uses this method but not alignTo
15295
			alignTo = merge(dlBox);
15296
			if (inverted) {
15297
				alignTo = {
15298
					x: chart.plotWidth - alignTo.y - alignTo.height,
15299
					y: chart.plotHeight - alignTo.x - alignTo.width,
15300
					width: alignTo.height,
15301
					height: alignTo.width
15302
				};
15303
			}
15304
				
15305
			// Compute the alignment box
15306
			if (!inside) {
15307
				if (inverted) {
15308
					alignTo.x += below ? 0 : alignTo.width;
15309
					alignTo.width = 0;
15310
				} else {
15311
					alignTo.y += below ? alignTo.height : 0;
15312
					alignTo.height = 0;
15313
				}
15314
			}
15315
		}
15316
		
15317
		// When alignment is undefined (typically columns and bars), display the individual 
15318
		// point below or above the point depending on the threshold
15319
		options.align = pick(
15320
			options.align, 
15321
			!inverted || inside ? 'center' : below ? 'right' : 'left'
15322
		);
15323
		options.verticalAlign = pick(
15324
			options.verticalAlign, 
15325
			inverted || inside ? 'middle' : below ? 'top' : 'bottom'
15326
		);
15327
		
15328
		// Call the parent method
15329
		Series.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);
15330
	},
15331
15332
15333
	/**
15334
	 * Animate the column heights one by one from zero
15335
	 * @param {Boolean} init Whether to initialize the animation or run it
15336
	 */
15337
	animate: function (init) {
15338
		var series = this,
15339
			yAxis = this.yAxis,
15340
			options = series.options,
15341
			inverted = this.chart.inverted,
15342
			attr = {},
15343
			translatedThreshold;
15344
15345
		if (hasSVG) { // VML is too slow anyway
15346
			if (init) {
15347
				attr.scaleY = 0.001;
15348
				translatedThreshold = mathMin(yAxis.pos + yAxis.len, mathMax(yAxis.pos, yAxis.toPixels(options.threshold)));
15349
				if (inverted) {
15350
					attr.translateX = translatedThreshold - yAxis.len;
15351
				} else {
15352
					attr.translateY = translatedThreshold;
15353
				}
15354
				series.group.attr(attr);
15355
15356
			} else { // run the animation
15357
				
15358
				attr.scaleY = 1;
15359
				attr[inverted ? 'translateX' : 'translateY'] = yAxis.pos;
15360
				series.group.animate(attr, series.options.animation);
15361
15362
				// delete this function to allow it only once
15363
				series.animate = null;
15364
			}
15365
		}
15366
	},
15367
	
15368
	/**
15369
	 * Remove this series from the chart
15370
	 */
15371
	remove: function () {
15372
		var series = this,
15373
			chart = series.chart;
15374
15375
		// column and bar series affects other series of the same type
15376
		// as they are either stacked or grouped
15377
		if (chart.hasRendered) {
15378
			each(chart.series, function (otherSeries) {
15379
				if (otherSeries.type === series.type) {
15380
					otherSeries.isDirty = true;
15381
				}
15382
			});
15383
		}
15384
15385
		Series.prototype.remove.apply(series, arguments);
15386
	}
15387
});
15388
seriesTypes.column = ColumnSeries;
15389
/**
15390
 * Set the default options for bar
15391
 */
15392
defaultPlotOptions.bar = merge(defaultPlotOptions.column);
15393
/**
15394
 * The Bar series class
15395
 */
15396
var BarSeries = extendClass(ColumnSeries, {
15397
	type: 'bar',
15398
	inverted: true
15399
});
15400
seriesTypes.bar = BarSeries;
15401
15402
/**
15403
 * Set the default options for scatter
15404
 */
15405
defaultPlotOptions.scatter = merge(defaultSeriesOptions, {
15406
	lineWidth: 0,
15407
	tooltip: {
15408
		headerFormat: '<span style="font-size: 10px; color:{series.color}">{series.name}</span><br/>',
15409
		pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>',
15410
		followPointer: true
15411
	},
15412
	stickyTracking: false
15413
});
15414
15415
/**
15416
 * The scatter series class
15417
 */
15418
var ScatterSeries = extendClass(Series, {
15419
	type: 'scatter',
15420
	sorted: false,
15421
	requireSorting: false,
15422
	noSharedTooltip: true,
15423
	trackerGroups: ['markerGroup'],
15424
15425
	drawTracker: ColumnSeries.prototype.drawTracker,
15426
	
15427
	setTooltipPoints: noop
15428
});
15429
seriesTypes.scatter = ScatterSeries;
15430
15431
/**
15432
 * Set the default options for pie
15433
 */
15434
defaultPlotOptions.pie = merge(defaultSeriesOptions, {
15435
	borderColor: '#FFFFFF',
15436
	borderWidth: 1,
15437
	center: [null, null],
15438
	clip: false,
15439
	colorByPoint: true, // always true for pies
15440
	dataLabels: {
15441
		// align: null,
15442
		// connectorWidth: 1,
15443
		// connectorColor: point.color,
15444
		// connectorPadding: 5,
15445
		distance: 30,
15446
		enabled: true,
15447
		formatter: function () {
15448
			return this.point.name;
15449
		}
15450
		// softConnector: true,
15451
		//y: 0
15452
	},
15453
	ignoreHiddenPoint: true,
15454
	//innerSize: 0,
15455
	legendType: 'point',
15456
	marker: null, // point options are specified in the base options
15457
	size: null,
15458
	showInLegend: false,
15459
	slicedOffset: 10,
15460
	states: {
15461
		hover: {
15462
			brightness: 0.1,
15463
			shadow: false
15464
		}
15465
	},
15466
	stickyTracking: false,
15467
	tooltip: {
15468
		followPointer: true
15469
	}
15470
});
15471
15472
/**
15473
 * Extended point object for pies
15474
 */
15475
var PiePoint = extendClass(Point, {
15476
	/**
15477
	 * Initiate the pie slice
15478
	 */
15479
	init: function () {
15480
15481
		Point.prototype.init.apply(this, arguments);
15482
15483
		var point = this,
15484
			toggleSlice;
15485
15486
		// Disallow negative values (#1530)
15487
		if (point.y < 0) {
15488
			point.y = null;
15489
		}
15490
15491
		//visible: options.visible !== false,
15492
		extend(point, {
15493
			visible: point.visible !== false,
15494
			name: pick(point.name, 'Slice')
15495
		});
15496
15497
		// add event listener for select
15498
		toggleSlice = function () {
15499
			point.slice();
15500
		};
15501
		addEvent(point, 'select', toggleSlice);
15502
		addEvent(point, 'unselect', toggleSlice);
15503
15504
		return point;
15505
	},
15506
15507
	/**
15508
	 * Toggle the visibility of the pie slice
15509
	 * @param {Boolean} vis Whether to show the slice or not. If undefined, the
15510
	 *    visibility is toggled
15511
	 */
15512
	setVisible: function (vis) {
15513
		var point = this,
15514
			series = point.series,
15515
			chart = series.chart,
15516
			method;
15517
15518
		// if called without an argument, toggle visibility
15519
		point.visible = point.options.visible = vis = vis === UNDEFINED ? !point.visible : vis;
15520
		series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
15521
		
15522
		method = vis ? 'show' : 'hide';
15523
15524
		// Show and hide associated elements
15525
		each(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function (key) {
15526
			if (point[key]) {
15527
				point[key][method]();
15528
			}
15529
		});
15530
15531
		if (point.legendItem) {
15532
			chart.legend.colorizeItem(point, vis);
15533
		}
15534
		
15535
		// Handle ignore hidden slices
15536
		if (!series.isDirty && series.options.ignoreHiddenPoint) {
15537
			series.isDirty = true;
15538
			chart.redraw();
15539
		}
15540
	},
15541
15542
	/**
15543
	 * Set or toggle whether the slice is cut out from the pie
15544
	 * @param {Boolean} sliced When undefined, the slice state is toggled
15545
	 * @param {Boolean} redraw Whether to redraw the chart. True by default.
15546
	 */
15547
	slice: function (sliced, redraw, animation) {
15548
		var point = this,
15549
			series = point.series,
15550
			chart = series.chart,
15551
			translation;
15552
15553
		setAnimation(animation, chart);
15554
15555
		// redraw is true by default
15556
		redraw = pick(redraw, true);
15557
15558
		// if called without an argument, toggle
15559
		point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced;
15560
		series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
15561
15562
		translation = sliced ? point.slicedTranslation : {
15563
			translateX: 0,
15564
			translateY: 0
15565
		};
15566
15567
		point.graphic.animate(translation);
15568
		
15569
		if (point.shadowGroup) {
15570
			point.shadowGroup.animate(translation);
15571
		}
15572
15573
	}
15574
});
15575
15576
/**
15577
 * The Pie series class
15578
 */
15579
var PieSeries = {
15580
	type: 'pie',
15581
	isCartesian: false,
15582
	pointClass: PiePoint,
15583
	requireSorting: false,
15584
	noSharedTooltip: true,
15585
	trackerGroups: ['group', 'dataLabelsGroup'],
15586
	pointAttrToOptions: { // mapping between SVG attributes and the corresponding options
15587
		stroke: 'borderColor',
15588
		'stroke-width': 'borderWidth',
15589
		fill: 'color'
15590
	},
15591
15592
	/**
15593
	 * Pies have one color each point
15594
	 */
15595
	getColor: noop,
15596
15597
	/**
15598
	 * Animate the pies in
15599
	 */
15600
	animate: function (init) {
15601
		var series = this,
15602
			points = series.points,
15603
			startAngleRad = series.startAngleRad;
15604
15605
		if (!init) {
15606
			each(points, function (point) {
15607
				var graphic = point.graphic,
15608
					args = point.shapeArgs;
15609
15610
				if (graphic) {
15611
					// start values
15612
					graphic.attr({
15613
						r: series.center[3] / 2, // animate from inner radius (#779)
15614
						start: startAngleRad,
15615
						end: startAngleRad
15616
					});
15617
15618
					// animate
15619
					graphic.animate({
15620
						r: args.r,
15621
						start: args.start,
15622
						end: args.end
15623
					}, series.options.animation);
15624
				}
15625
			});
15626
15627
			// delete this function to allow it only once
15628
			series.animate = null;
15629
		}
15630
	},
15631
15632
	/**
15633
	 * Extend the basic setData method by running processData and generatePoints immediately,
15634
	 * in order to access the points from the legend.
15635
	 */
15636
	setData: function (data, redraw) {
15637
		Series.prototype.setData.call(this, data, false);
15638
		this.processData();
15639
		this.generatePoints();
15640
		if (pick(redraw, true)) {
15641
			this.chart.redraw();
15642
		} 
15643
	},
15644
	
15645
	/**
15646
	 * Get the center of the pie based on the size and center options relative to the  
15647
	 * plot area. Borrowed by the polar and gauge series types.
15648
	 */
15649
	getCenter: function () {
15650
		
15651
		var options = this.options,
15652
			chart = this.chart,
15653
			slicingRoom = 2 * (options.slicedOffset || 0),
15654
			handleSlicingRoom,
15655
			plotWidth = chart.plotWidth - 2 * slicingRoom,
15656
			plotHeight = chart.plotHeight - 2 * slicingRoom,
15657
			centerOption = options.center,
15658
			positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0],
15659
			smallestSize = mathMin(plotWidth, plotHeight),
15660
			isPercent;
15661
		
15662
		return map(positions, function (length, i) {
15663
			isPercent = /%$/.test(length);
15664
			handleSlicingRoom = i < 2 || (i === 2 && isPercent);
15665
			return (isPercent ?
15666
				// i == 0: centerX, relative to width
15667
				// i == 1: centerY, relative to height
15668
				// i == 2: size, relative to smallestSize
15669
				// i == 4: innerSize, relative to smallestSize
15670
				[plotWidth, plotHeight, smallestSize, smallestSize][i] *
15671
					pInt(length) / 100 :
15672
				length) + (handleSlicingRoom ? slicingRoom : 0);
15673
		});
15674
	},
15675
	
15676
	/**
15677
	 * Do translation for pie slices
15678
	 */
15679
	translate: function (positions) {
15680
		this.generatePoints();
15681
		
15682
		var total = 0,
15683
			series = this,
15684
			cumulative = 0,
15685
			precision = 1000, // issue #172
15686
			options = series.options,
15687
			slicedOffset = options.slicedOffset,
15688
			connectorOffset = slicedOffset + options.borderWidth,
15689
			start,
15690
			end,
15691
			angle,
15692
			startAngleRad = series.startAngleRad = mathPI / 180 * ((options.startAngle || 0) % 360 - 90),
15693
			points = series.points,
15694
			circ = 2 * mathPI,
15695
			fraction,
15696
			radiusX, // the x component of the radius vector for a given point
15697
			radiusY,
15698
			labelDistance = options.dataLabels.distance,
15699
			ignoreHiddenPoint = options.ignoreHiddenPoint,
15700
			i,
15701
			len = points.length,
15702
			point;
15703
15704
		// Get positions - either an integer or a percentage string must be given.
15705
		// If positions are passed as a parameter, we're in a recursive loop for adjusting
15706
		// space for data labels.
15707
		if (!positions) {
15708
			series.center = positions = series.getCenter();
15709
		}
15710
15711
		// utility for getting the x value from a given y, used for anticollision logic in data labels
15712
		series.getX = function (y, left) {
15713
15714
			angle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance));
15715
15716
			return positions[0] +
15717
				(left ? -1 : 1) *
15718
				(mathCos(angle) * (positions[2] / 2 + labelDistance));
15719
		};
15720
15721
		// get the total sum
15722
		for (i = 0; i < len; i++) {
15723
			point = points[i];
15724
			total += (ignoreHiddenPoint && !point.visible) ? 0 : point.y;
15725
		}
15726
15727
		// Calculate the geometry for each point
15728
		for (i = 0; i < len; i++) {
15729
			
15730
			point = points[i];
15731
			
15732
			// set start and end angle
15733
			fraction = total ? point.y / total : 0;
15734
			start = mathRound((startAngleRad + (cumulative * circ)) * precision) / precision;
15735
			if (!ignoreHiddenPoint || point.visible) {
15736
				cumulative += fraction;
15737
			}
15738
			end = mathRound((startAngleRad + (cumulative * circ)) * precision) / precision;
15739
15740
			// set the shape
15741
			point.shapeType = 'arc';
15742
			point.shapeArgs = {
15743
				x: positions[0],
15744
				y: positions[1],
15745
				r: positions[2] / 2,
15746
				innerR: positions[3] / 2,
15747
				start: start,
15748
				end: end
15749
			};
15750
15751
			// center for the sliced out slice
15752
			angle = (end + start) / 2;
15753
			if (angle > 0.75 * circ) {
15754
				angle -= 2 * mathPI;
15755
			}
15756
			point.slicedTranslation = {
15757
				translateX: mathRound(mathCos(angle) * slicedOffset),
15758
				translateY: mathRound(mathSin(angle) * slicedOffset)
15759
			};
15760
15761
			// set the anchor point for tooltips
15762
			radiusX = mathCos(angle) * positions[2] / 2;
15763
			radiusY = mathSin(angle) * positions[2] / 2;
15764
			point.tooltipPos = [
15765
				positions[0] + radiusX * 0.7,
15766
				positions[1] + radiusY * 0.7
15767
			];
15768
			
15769
			point.half = angle < circ / 4 ? 0 : 1;
15770
			point.angle = angle;
15771
15772
			// set the anchor point for data labels
15773
			connectorOffset = mathMin(connectorOffset, labelDistance / 2); // #1678
15774
			point.labelPos = [
15775
				positions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector
15776
				positions[1] + radiusY + mathSin(angle) * labelDistance, // a/a
15777
				positions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie
15778
				positions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a
15779
				positions[0] + radiusX, // landing point for connector
15780
				positions[1] + radiusY, // a/a
15781
				labelDistance < 0 ? // alignment
15782
					'center' :
15783
					point.half ? 'right' : 'left', // alignment
15784
				angle // center angle
15785
			];
15786
			
15787
			// API properties
15788
			point.percentage = fraction * 100;
15789
			point.total = total;
15790
15791
		}
15792
15793
15794
		this.setTooltipPoints();
15795
	},
15796
15797
	drawGraph: null,
15798
15799
	/**
15800
	 * Draw the data points
15801
	 */
15802
	drawPoints: function () {
15803
		var series = this,
15804
			chart = series.chart,
15805
			renderer = chart.renderer,
15806
			groupTranslation,
15807
			//center,
15808
			graphic,
15809
			//group,
15810
			shadow = series.options.shadow,
15811
			shadowGroup,
15812
			shapeArgs;
15813
15814
		if (shadow && !series.shadowGroup) {
15815
			series.shadowGroup = renderer.g('shadow')
15816
				.add(series.group);
15817
		}
15818
15819
		// draw the slices
15820
		each(series.points, function (point) {
15821
			graphic = point.graphic;
15822
			shapeArgs = point.shapeArgs;
15823
			shadowGroup = point.shadowGroup;
15824
15825
			// put the shadow behind all points
15826
			if (shadow && !shadowGroup) {
15827
				shadowGroup = point.shadowGroup = renderer.g('shadow')
15828
					.add(series.shadowGroup);
15829
			}
15830
15831
			// if the point is sliced, use special translation, else use plot area traslation
15832
			groupTranslation = point.sliced ? point.slicedTranslation : {
15833
				translateX: 0,
15834
				translateY: 0
15835
			};
15836
15837
			//group.translate(groupTranslation[0], groupTranslation[1]);
15838
			if (shadowGroup) {
15839
				shadowGroup.attr(groupTranslation);
15840
			}
15841
15842
			// draw the slice
15843
			if (graphic) {
15844
				graphic.animate(extend(shapeArgs, groupTranslation));
15845
			} else {
15846
				point.graphic = graphic = renderer.arc(shapeArgs)
15847
					.setRadialReference(series.center)
15848
					.attr(
15849
						point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE]
15850
					)
15851
					.attr({ 'stroke-linejoin': 'round' })
15852
					.attr(groupTranslation)
15853
					.add(series.group)
15854
					.shadow(shadow, shadowGroup);	
15855
			}
15856
15857
			// detect point specific visibility
15858
			if (point.visible === false) {
15859
				point.setVisible(false);
15860
			}
15861
15862
		});
15863
15864
	},
15865
15866
	/**
15867
	 * Override the base drawDataLabels method by pie specific functionality
15868
	 */
15869
	drawDataLabels: function () {
15870
		var series = this,
15871
			data = series.data,
15872
			point,
15873
			chart = series.chart,
15874
			options = series.options.dataLabels,
15875
			connectorPadding = pick(options.connectorPadding, 10),
15876
			connectorWidth = pick(options.connectorWidth, 1),
15877
			plotWidth = chart.plotWidth,
15878
			plotHeight = chart.plotHeight,
15879
			connector,
15880
			connectorPath,
15881
			softConnector = pick(options.softConnector, true),
15882
			distanceOption = options.distance,
15883
			seriesCenter = series.center,
15884
			radius = seriesCenter[2] / 2,
15885
			centerY = seriesCenter[1],
15886
			outside = distanceOption > 0,
15887
			dataLabel,
15888
			dataLabelWidth,
15889
			labelPos,
15890
			labelHeight,
15891
			halves = [// divide the points into right and left halves for anti collision
15892
				[], // right
15893
				[]  // left
15894
			],
15895
			x,
15896
			y,
15897
			visibility,
15898
			rankArr,
15899
			i,
15900
			j,
15901
			overflow = [0, 0, 0, 0], // top, right, bottom, left
15902
			sort = function (a, b) {
15903
				return b.y - a.y;
15904
			},
15905
			sortByAngle = function (points, sign) {
15906
				points.sort(function (a, b) {
15907
					return a.angle !== undefined && (b.angle - a.angle) * sign;
15908
				});
15909
			};
15910
15911
		// get out if not enabled
15912
		if (!options.enabled && !series._hasPointLabels) {
15913
			return;
15914
		}
15915
15916
		// run parent method
15917
		Series.prototype.drawDataLabels.apply(series);
15918
15919
		// arrange points for detection collision
15920
		each(data, function (point) {
15921
			if (point.dataLabel) { // it may have been cancelled in the base method (#407)
15922
				halves[point.half].push(point);
15923
			}
15924
		});
15925
15926
		// assume equal label heights
15927
		i = 0;
15928
		while (!labelHeight && data[i]) { // #1569
15929
			labelHeight = data[i] && data[i].dataLabel && (data[i].dataLabel.getBBox().height || 21); // 21 is for #968
15930
			i++;
15931
		}
15932
15933
		/* Loop over the points in each half, starting from the top and bottom
15934
		 * of the pie to detect overlapping labels.
15935
		 */
15936
		i = 2;
15937
		while (i--) {
15938
15939
			var slots = [],
15940
				slotsLength,
15941
				usedSlots = [],
15942
				points = halves[i],
15943
				pos,
15944
				length = points.length,
15945
				slotIndex;
15946
				
15947
			// Sort by angle
15948
			sortByAngle(points, i - 0.5);
15949
15950
			// Only do anti-collision when we are outside the pie and have connectors (#856)
15951
			if (distanceOption > 0) {
15952
				
15953
				// build the slots
15954
				for (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) {
15955
					slots.push(pos);
15956
					
15957
					// visualize the slot
15958
					/*
15959
					var slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0),
15960
						slotY = pos + chart.plotTop;
15961
					if (!isNaN(slotX)) {
15962
						chart.renderer.rect(slotX, slotY - 7, 100, labelHeight, 1)
15963
							.attr({
15964
								'stroke-width': 1,
15965
								stroke: 'silver'
15966
							})
15967
							.add();
15968
						chart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4)
15969
							.attr({
15970
								fill: 'silver'
15971
							}).add();
15972
					}
15973
					*/
15974
				}
15975
				slotsLength = slots.length;
15976
	
15977
				// if there are more values than available slots, remove lowest values
15978
				if (length > slotsLength) {
15979
					// create an array for sorting and ranking the points within each quarter
15980
					rankArr = [].concat(points);
15981
					rankArr.sort(sort);
15982
					j = length;
15983
					while (j--) {
15984
						rankArr[j].rank = j;
15985
					}
15986
					j = length;
15987
					while (j--) {
15988
						if (points[j].rank >= slotsLength) {
15989
							points.splice(j, 1);
15990
						}
15991
					}
15992
					length = points.length;
15993
				}
15994
	
15995
				// The label goes to the nearest open slot, but not closer to the edge than
15996
				// the label's index.
15997
				for (j = 0; j < length; j++) {
15998
	
15999
					point = points[j];
16000
					labelPos = point.labelPos;
16001
	
16002
					var closest = 9999,
16003
						distance,
16004
						slotI;
16005
	
16006
					// find the closest slot index
16007
					for (slotI = 0; slotI < slotsLength; slotI++) {
16008
						distance = mathAbs(slots[slotI] - labelPos[1]);
16009
						if (distance < closest) {
16010
							closest = distance;
16011
							slotIndex = slotI;
16012
						}
16013
					}
16014
	
16015
					// if that slot index is closer to the edges of the slots, move it
16016
					// to the closest appropriate slot
16017
					if (slotIndex < j && slots[j] !== null) { // cluster at the top
16018
						slotIndex = j;
16019
					} else if (slotsLength  < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom
16020
						slotIndex = slotsLength - length + j;
16021
						while (slots[slotIndex] === null) { // make sure it is not taken
16022
							slotIndex++;
16023
						}
16024
					} else {
16025
						// Slot is taken, find next free slot below. In the next run, the next slice will find the
16026
						// slot above these, because it is the closest one
16027
						while (slots[slotIndex] === null) { // make sure it is not taken
16028
							slotIndex++;
16029
						}
16030
					}
16031
	
16032
					usedSlots.push({ i: slotIndex, y: slots[slotIndex] });
16033
					slots[slotIndex] = null; // mark as taken
16034
				}
16035
				// sort them in order to fill in from the top
16036
				usedSlots.sort(sort);
16037
			}
16038
16039
			// now the used slots are sorted, fill them up sequentially
16040
			for (j = 0; j < length; j++) {
16041
				
16042
				var slot, naturalY;
16043
16044
				point = points[j];
16045
				labelPos = point.labelPos;
16046
				dataLabel = point.dataLabel;
16047
				visibility = point.visible === false ? HIDDEN : VISIBLE;
16048
				naturalY = labelPos[1];
16049
				
16050
				if (distanceOption > 0) {
16051
					slot = usedSlots.pop();
16052
					slotIndex = slot.i;
16053
16054
					// if the slot next to currrent slot is free, the y value is allowed
16055
					// to fall back to the natural position
16056
					y = slot.y;
16057
					if ((naturalY > y && slots[slotIndex + 1] !== null) ||
16058
							(naturalY < y &&  slots[slotIndex - 1] !== null)) {
16059
						y = naturalY;
16060
					}
16061
					
16062
				} else {
16063
					y = naturalY;
16064
				}
16065
16066
				// get the x - use the natural x position for first and last slot, to prevent the top
16067
				// and botton slice connectors from touching each other on either side
16068
				x = options.justify ? 
16069
					seriesCenter[0] + (i ? -1 : 1) * (radius + distanceOption) :
16070
					series.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i);
16071
				
16072
			
16073
				// Record the placement and visibility
16074
				dataLabel._attr = {
16075
					visibility: visibility,
16076
					align: labelPos[6]
16077
				};
16078
				dataLabel._pos = {
16079
					x: x + options.x +
16080
						({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0),
16081
					y: y + options.y - 10 // 10 is for the baseline (label vs text)
16082
				};
16083
				dataLabel.connX = x;
16084
				dataLabel.connY = y;
16085
				
16086
						
16087
				// Detect overflowing data labels
16088
				if (this.options.size === null) {
16089
					dataLabelWidth = dataLabel.width;
16090
					// Overflow left
16091
					if (x - dataLabelWidth < connectorPadding) {
16092
						overflow[3] = mathMax(mathRound(dataLabelWidth - x + connectorPadding), overflow[3]);
16093
						
16094
					// Overflow right
16095
					} else if (x + dataLabelWidth > plotWidth - connectorPadding) {
16096
						overflow[1] = mathMax(mathRound(x + dataLabelWidth - plotWidth + connectorPadding), overflow[1]);
16097
					}
16098
					
16099
					// Overflow top
16100
					if (y - labelHeight / 2 < 0) {
16101
						overflow[0] = mathMax(mathRound(-y + labelHeight / 2), overflow[0]);
16102
						
16103
					// Overflow left
16104
					} else if (y + labelHeight / 2 > plotHeight) {
16105
						overflow[2] = mathMax(mathRound(y + labelHeight / 2 - plotHeight), overflow[2]);
16106
					}
16107
				}
16108
			} // for each point
16109
		} // for each half
16110
		
16111
		// Do not apply the final placement and draw the connectors until we have verified
16112
		// that labels are not spilling over. 
16113
		if (arrayMax(overflow) === 0 || this.verifyDataLabelOverflow(overflow)) {
16114
			
16115
			// Place the labels in the final position
16116
			this.placeDataLabels();
16117
			
16118
			// Draw the connectors
16119
			if (outside && connectorWidth) {
16120
				each(this.points, function (point) {
16121
					connector = point.connector;
16122
					labelPos = point.labelPos;
16123
					dataLabel = point.dataLabel;
16124
					
16125
					if (dataLabel && dataLabel._pos) {
16126
						visibility = dataLabel._attr.visibility;
16127
						x = dataLabel.connX;
16128
						y = dataLabel.connY;
16129
						connectorPath = softConnector ? [
16130
							M,
16131
							x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
16132
							'C',
16133
							x, y, // first break, next to the label
16134
							2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],
16135
							labelPos[2], labelPos[3], // second break
16136
							L,
16137
							labelPos[4], labelPos[5] // base
16138
						] : [
16139
							M,
16140
							x + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label
16141
							L,
16142
							labelPos[2], labelPos[3], // second break
16143
							L,
16144
							labelPos[4], labelPos[5] // base
16145
						];
16146
		
16147
						if (connector) {
16148
							connector.animate({ d: connectorPath });
16149
							connector.attr('visibility', visibility);
16150
		
16151
						} else {
16152
							point.connector = connector = series.chart.renderer.path(connectorPath).attr({
16153
								'stroke-width': connectorWidth,
16154
								stroke: options.connectorColor || point.color || '#606060',
16155
								visibility: visibility
16156
							})
16157
							.add(series.group);
16158
						}
16159
					} else if (connector) {
16160
						point.connector = connector.destroy();
16161
					}
16162
				});
16163
			}			
16164
		}
16165
	},
16166
	
16167
	/**
16168
	 * Verify whether the data labels are allowed to draw, or we should run more translation and data
16169
	 * label positioning to keep them inside the plot area. Returns true when data labels are ready 
16170
	 * to draw.
16171
	 */
16172
	verifyDataLabelOverflow: function (overflow) {
16173
		
16174
		var center = this.center,
16175
			options = this.options,
16176
			centerOption = options.center,
16177
			minSize = options.minSize || 80,
16178
			newSize = minSize,
16179
			ret;
16180
			
16181
		// Handle horizontal size and center
16182
		if (centerOption[0] !== null) { // Fixed center
16183
			newSize = mathMax(center[2] - mathMax(overflow[1], overflow[3]), minSize);
16184
			
16185
		} else { // Auto center
16186
			newSize = mathMax(
16187
				center[2] - overflow[1] - overflow[3], // horizontal overflow					
16188
				minSize
16189
			);
16190
			center[0] += (overflow[3] - overflow[1]) / 2; // horizontal center
16191
		}
16192
		
16193
		// Handle vertical size and center
16194
		if (centerOption[1] !== null) { // Fixed center
16195
			newSize = mathMax(mathMin(newSize, center[2] - mathMax(overflow[0], overflow[2])), minSize);
16196
			
16197
		} else { // Auto center
16198
			newSize = mathMax(
16199
				mathMin(
16200
					newSize,		
16201
					center[2] - overflow[0] - overflow[2] // vertical overflow
16202
				),
16203
				minSize
16204
			);
16205
			center[1] += (overflow[0] - overflow[2]) / 2; // vertical center
16206
		}
16207
		
16208
		// If the size must be decreased, we need to run translate and drawDataLabels again
16209
		if (newSize < center[2]) {
16210
			center[2] = newSize;
16211
			this.translate(center);
16212
			each(this.points, function (point) {
16213
				if (point.dataLabel) {
16214
					point.dataLabel._pos = null; // reset
16215
				}
16216
			});
16217
			this.drawDataLabels();
16218
			
16219
		// Else, return true to indicate that the pie and its labels is within the plot area
16220
		} else {
16221
			ret = true;
16222
		}
16223
		return ret;
16224
	},
16225
	
16226
	/**
16227
	 * Perform the final placement of the data labels after we have verified that they
16228
	 * fall within the plot area.
16229
	 */
16230
	placeDataLabels: function () {
16231
		each(this.points, function (point) {
16232
			var dataLabel = point.dataLabel,
16233
				_pos;
16234
			
16235
			if (dataLabel) {
16236
				_pos = dataLabel._pos;
16237
				if (_pos) {
16238
					dataLabel.attr(dataLabel._attr);			
16239
					dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos);
16240
					dataLabel.moved = true;
16241
				} else if (dataLabel) {
16242
					dataLabel.attr({ y: -999 });
16243
				}
16244
			}
16245
		});
16246
	},
16247
	
16248
	alignDataLabel: noop,
16249
16250
	/**
16251
	 * Draw point specific tracker objects. Inherit directly from column series.
16252
	 */
16253
	drawTracker: ColumnSeries.prototype.drawTracker,
16254
16255
	/**
16256
	 * Use a simple symbol from column prototype
16257
	 */
16258
	drawLegendSymbol: AreaSeries.prototype.drawLegendSymbol,
16259
16260
	/**
16261
	 * Pies don't have point marker symbols
16262
	 */
16263
	getSymbol: noop
16264
16265
};
16266
PieSeries = extendClass(Series, PieSeries);
16267
seriesTypes.pie = PieSeries;
16268
16269
16270
// global variables
16271
extend(Highcharts, {
16272
	
16273
	// Constructors
16274
	Axis: Axis,
16275
	Chart: Chart,
16276
	Color: Color,
16277
	Legend: Legend,
16278
	Pointer: Pointer,
16279
	Point: Point,
16280
	Tick: Tick,
16281
	Tooltip: Tooltip,
16282
	Renderer: Renderer,
16283
	Series: Series,
16284
	SVGElement: SVGElement,
16285
	SVGRenderer: SVGRenderer,
16286
	
16287
	// Various
16288
	arrayMin: arrayMin,
16289
	arrayMax: arrayMax,
16290
	charts: charts,
16291
	dateFormat: dateFormat,
16292
	format: format,
16293
	pathAnim: pathAnim,
16294
	getOptions: getOptions,
16295
	hasBidiBug: hasBidiBug,
16296
	isTouchDevice: isTouchDevice,
16297
	numberFormat: numberFormat,
16298
	seriesTypes: seriesTypes,
16299
	setOptions: setOptions,
16300
	addEvent: addEvent,
16301
	removeEvent: removeEvent,
16302
	createElement: createElement,
16303
	discardElement: discardElement,
16304
	css: css,
16305
	each: each,
16306
	extend: extend,
16307
	map: map,
16308
	merge: merge,
16309
	pick: pick,
16310
	splat: splat,
16311
	extendClass: extendClass,
16312
	pInt: pInt,
16313
	wrap: wrap,
16314
	svg: hasSVG,
16315
	canvas: useCanVG,
16316
	vml: !hasSVG && !useCanVG,
16317
	product: PRODUCT,
16318
	version: VERSION
16319
});
16320
}());
16321